From b663fcce964461342d4609a5419ef6ba17d54604 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 31 Jul 2018 18:40:52 +0200 Subject: [PATCH 001/932] Resolves #1482 error CS1003 Syntax error, ',' expected ButtonType.cshtml --- .../Views/Shared/EditorTemplates/ButtonType.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/ButtonType.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/ButtonType.cshtml index 63ec6e1640..35f2bab6af 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/ButtonType.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/ButtonType.cshtml @@ -24,7 +24,7 @@ new SelectListItem { Text = T("Common.Buttons.Success").ToString(), Value = "btn-success", Selected = (Value == "btn-success") }, new SelectListItem { Text = T("Common.Buttons.Warning").ToString(), Value = "btn-warning", Selected = (Value == "btn-warning") }, new SelectListItem { Text = T("Common.Buttons.Danger").ToString(), Value = "btn-danger", Selected = (Value == "btn-danger") }, - new SelectListItem { Text = T("Common.Buttons.Link").ToString(), Value = "btn-link", Selected = (Value == "btn-link") } + new SelectListItem { Text = T("Common.Buttons.Link").ToString(), Value = "btn-link", Selected = (Value == "btn-link") }, new SelectListItem { Text = T("Common.Buttons.Light").ToString(), Value = "btn-light", Selected = (Value == "btn-light") }, new SelectListItem { Text = T("Common.Buttons.Dark").ToString(), Value = "btn-dark", Selected = (Value == "btn-dark") }, }; From 2103ef5cf6d91d2cbc54db66dffb7318f85ae953 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 31 Jul 2018 19:49:43 +0200 Subject: [PATCH 002/932] Downloads: minor refactoring --- .../Migrations/MigrationsConfiguration.cs | 5 ++ .../Controllers/ProductController.cs | 83 +++++++++++-------- 2 files changed, 52 insertions(+), 36 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 17ba508c58..f0f1280a98 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -353,6 +353,11 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Catalog.Products.Download.VersionDelete", "Delete this file version of the download.", "Diese Dateiversion des Downloads löschen."); builder.AddOrUpdate("Admin.Catalog.Products.Download.AddChangelog", "Edit changelog", "Changelog bearbeiten"); builder.AddOrUpdate("Customer.Downloads.NoChangelogAvailable", "No changelog information available.", "Keine Changelog-Informationen verfügbar."); + + builder.AddOrUpdate("Admin.Catalog.Products.Download.SemanticVersion.NotValid", + "The specified version information is not valid. Please enter the version number in the correct format (e.g.: 1.0.0.0, 2.0 or 3.1.5).", + "Die angegebenen Versionsinformationen sind nicht gültig. Bitte geben Sie die Versionsnummer in korrektem Format an (z.B.: 1.0.0.0, 2.0 oder 3.1.5)."); + } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 6ce3883bb9..7314a307b4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -239,38 +239,6 @@ protected void UpdateProductGeneralInfo(Product product, ProductModel model) p.IsGiftCard = m.IsGiftCard; p.GiftCardTypeId = m.GiftCardTypeId; - p.IsDownload = m.IsDownload; - //p.DownloadId = m.DownloadId ?? 0; - //p.UnlimitedDownloads = m.UnlimitedDownloads; - p.MaxNumberOfDownloads = m.MaxNumberOfDownloads; - p.DownloadExpirationDays = m.DownloadExpirationDays; - p.DownloadActivationTypeId = m.DownloadActivationTypeId; - p.HasUserAgreement = m.HasUserAgreement; - p.UserAgreementText = m.UserAgreementText; - p.HasSampleDownload = m.HasSampleDownload; - p.SampleDownloadId = m.SampleDownloadId == 0 ? (int?)null : m.SampleDownloadId; - - if (m.NewVersionDownloadId != 0 && m.NewVersionDownloadId != null) - { - // set version info & product id for new download - var newVersion = _downloadService.GetDownloadById((int)m.NewVersionDownloadId); - newVersion.FileVersion = m.NewVersion != null ? m.NewVersion : String.Empty; - newVersion.EntityId = p.Id; - newVersion.IsTransient = false; - _downloadService.UpdateDownload(newVersion, newVersion.MediaStorage.Data); - } - else if(m.DownloadFileVersion.HasValue()) - { - var download = _downloadService.GetDownloadsByEntityId(p.Id, "Product") - .OrderByDescending(x => x.FileVersion) - .FirstOrDefault(); - - download.FileVersion = new SemanticVersion(m.DownloadFileVersion).ToString(); - download.EntityId = p.Id; - download.IsTransient = false; - _downloadService.UpdateDownload(download); - } - p.IsRecurring = m.IsRecurring; p.RecurringCycleLength = m.RecurringCycleLength; p.RecurringCyclePeriodId = m.RecurringCyclePeriodId; @@ -296,7 +264,46 @@ protected void UpdateProductGeneralInfo(Product product, ProductModel model) p.SpecialPriceEndDateTimeUtc = p.SpecialPriceEndDateTimeUtc.ToEndOfTheDay(); } - [NonAction] + [NonAction] + protected void UpdateProductDownloads(Product product, ProductModel model) + { + var p = product; + var m = model; + + p.IsDownload = m.IsDownload; + //p.DownloadId = m.DownloadId ?? 0; + //p.UnlimitedDownloads = m.UnlimitedDownloads; + p.MaxNumberOfDownloads = m.MaxNumberOfDownloads; + p.DownloadExpirationDays = m.DownloadExpirationDays; + p.DownloadActivationTypeId = m.DownloadActivationTypeId; + p.HasUserAgreement = m.HasUserAgreement; + p.UserAgreementText = m.UserAgreementText; + p.HasSampleDownload = m.HasSampleDownload; + p.SampleDownloadId = m.SampleDownloadId == 0 ? (int?)null : m.SampleDownloadId; + + if (m.NewVersionDownloadId != 0 && m.NewVersionDownloadId != null) + { + // set version info & product id for new download + var newVersion = _downloadService.GetDownloadById((int)m.NewVersionDownloadId); + newVersion.FileVersion = m.NewVersion != null ? m.NewVersion : String.Empty; + newVersion.EntityId = p.Id; + newVersion.IsTransient = false; + _downloadService.UpdateDownload(newVersion, newVersion.MediaStorage.Data); + } + else if (m.DownloadFileVersion.HasValue()) + { + var download = _downloadService.GetDownloadsByEntityId(p.Id, "Product") + .OrderByDescending(x => x.FileVersion) + .FirstOrDefault(); + + download.FileVersion = new SemanticVersion(m.DownloadFileVersion).ToString(); + download.EntityId = p.Id; + download.IsTransient = false; + _downloadService.UpdateDownload(download); + } + } + + [NonAction] protected void UpdateProductTags(Product product, params string[] rawProductTags) { Guard.NotNull(product, nameof(product)); @@ -835,6 +842,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set model.AllowCustomerReviews = true; model.Published = true; model.VisibleIndividually = true; + model.HasPreviewPicture = false; } } @@ -1150,7 +1158,7 @@ public ActionResult Create(ProductModel model, bool continueEditing, FormCollect } catch { - ModelState.AddModelError("FileVersion", "Fileversion no good"); + ModelState.AddModelError("FileVersion", T("Admin.Catalog.Products.Download.SemanticVersion.NotValid")); } } @@ -1276,7 +1284,7 @@ public ActionResult Edit(ProductModel model, bool continueEditing, FormCollectio } catch { - ModelState.AddModelError("FileVersion", "Fileversion no good"); + ModelState.AddModelError("FileVersion", T("Admin.Catalog.Products.Download.SemanticVersion.NotValid")); } } @@ -1331,7 +1339,10 @@ protected void MapModelToProduct(ProductModel model, Product product, FormCollec case "discounts": UpdateProductDiscounts(product, model); break; - case "seo": + case "downloads": + UpdateProductDownloads(product, model); + break; + case "seo": UpdateProductSeo(product, model); break; case "acl": From 987fb87ea6e1cbd5f57c8d3e3f87eabf50873542 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 31 Jul 2018 20:32:53 +0200 Subject: [PATCH 003/932] Added option to display preview pictures in product lists --- .../SmartStore.Core/Domain/Catalog/Product.cs | 10 +- ...11708428_ProductPreviewPicture.Designer.cs | 29 ++++ .../201807311708428_ProductPreviewPicture.cs | 18 +++ ...201807311708428_ProductPreviewPicture.resx | 126 ++++++++++++++++++ .../Migrations/MigrationsConfiguration.cs | 6 + .../SmartStore.Data/SmartStore.Data.csproj | 7 + .../Controllers/ProductController.cs | 14 +- .../Models/Catalog/ProductModel.cs | 8 +- .../Product/_CreateOrUpdate.Pictures.cshtml | 18 ++- .../Controllers/CatalogHelper.cs | 12 +- .../Controllers/ProductController.cs | 7 +- 11 files changed, 244 insertions(+), 11 deletions(-) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.resx diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs index 5496d3fd8f..5321b0f81f 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs @@ -901,10 +901,16 @@ public bool BasePriceHasValue [DataMember] public int? MainPictureId { get; set; } - /// - /// Gets or sets the product type + /// + /// Gets or sets a value that indictaes whether the product has a preview picture /// [DataMember] + public bool HasPreviewPicture { get; set; } + + /// + /// Gets or sets the product type + /// + [DataMember] public ProductType ProductType { get diff --git a/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.Designer.cs new file mode 100644 index 0000000000..0bcd55246f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class ProductPreviewPicture : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ProductPreviewPicture)); + + string IMigrationMetadata.Id + { + get { return "201807311708428_ProductPreviewPicture"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.cs b/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.cs new file mode 100644 index 0000000000..91c06bfb44 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class ProductPreviewPicture : DbMigration + { + public override void Up() + { + AddColumn("dbo.Product", "HasPreviewPicture", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.Product", "HasPreviewPicture"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.resx b/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.resx new file mode 100644 index 0000000000..b90b2b768c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index f0f1280a98..07c734e324 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -357,6 +357,12 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Catalog.Products.Download.SemanticVersion.NotValid", "The specified version information is not valid. Please enter the version number in the correct format (e.g.: 1.0.0.0, 2.0 or 3.1.5).", "Die angegebenen Versionsinformationen sind nicht gültig. Bitte geben Sie die Versionsnummer in korrektem Format an (z.B.: 1.0.0.0, 2.0 oder 3.1.5)."); + + builder.AddOrUpdate("Admin.Catalog.Products.Fields.HasPreviewPicture", + "Has preview picture", + "Hat Vorschaubild", + "If this option is activated, the first image is used as a preview image in product lists and is no longer displayed in the product detail view.", + "Wenn diese Option aktiviert ist wird das erste Bild als Vorschaubild in Produktlisten genutzt und in der Produktdetailanischt nicht mehr angezeigt."); } } diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index f45a8a69b6..5d2543ad4d 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -611,6 +611,10 @@ 201807201157391_DownloadVersions.cs + + + 201807311708428_ProductPreviewPicture.cs + @@ -1099,6 +1103,9 @@ 201807201157391_DownloadVersions.cs + + 201807311708428_ProductPreviewPicture.cs + diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 7314a307b4..2e61a31dad 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -481,7 +481,16 @@ protected void UpdateProductSeo(Product product, ProductModel model) } } - [NonAction] + [NonAction] + protected void UpdateProductPictures(Product product, ProductModel model) + { + var p = product; + var m = model; + + p.HasPreviewPicture = m.HasPreviewPicture; + } + + [NonAction] protected void UpdateProductDiscounts(Product product, ProductModel model) { var allDiscounts = _discountService.GetAllDiscounts(DiscountType.AssignedToSkus, null, true); @@ -1342,6 +1351,9 @@ protected void MapModelToProduct(ProductModel model, Product product, FormCollec case "downloads": UpdateProductDownloads(product, model); break; + case "pictures": + UpdateProductPictures(product, model); + break; case "seo": UpdateProductSeo(product, model); break; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs index 1695c42f94..a412c4e65d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs @@ -373,11 +373,13 @@ public ProductModel() public int NumberOfAvailableProductAttributes { get; set; } //pictures + [SmartResourceDisplayName("Admin.Catalog.Products.Fields.HasPreviewPicture")] + public bool HasPreviewPicture { get; set; } public ProductPictureModel AddPictureModel { get; set; } public IList ProductPictureModels { get; set; } - - //discounts - public List AvailableDiscounts { get; set; } + + //discounts + public List AvailableDiscounts { get; set; } public int[] SelectedDiscountIds { get; set; } //add specification attribute model diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml index 54c8701440..7b3d405bb5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml @@ -35,7 +35,23 @@ }); - +
+ + + + + + + From 43b5b53b0a6fd36a99cf4600b695d0c2ffc549b6 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 4 Sep 2018 16:50:23 +0200 Subject: [PATCH 077/932] Search: Sorting of forum search hits --- .../Domain/Forums/ForumSettings.cs | 6 ++ .../Search/Settings/ForumSearchSettings.cs | 12 +--- .../Migrations/MigrationsConfiguration.cs | 6 ++ .../Search/Forum/LinqForumSearchService.cs | 43 +++++++++------ .../Modelling/ForumSearchQueryFactory.cs | 16 +++--- .../Controllers/SettingController.cs | 2 +- .../Settings/ForumSearchSettingsModel.cs | 4 +- .../Models/Settings/ForumSettingsModel.cs | 3 + .../Administration/Views/Setting/Forum.cshtml | 9 +++ .../Views/Setting/Search.cshtml | 6 +- .../Controllers/BoardsController.cs | 27 +++++++++ .../Models/Boards/ForumSearchResultModel.cs | 5 ++ .../SmartStore.Web/Views/Boards/Search.cshtml | 55 +++++++++++++++++-- 13 files changed, 150 insertions(+), 44 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs index 455b0c1fbf..d87de185f7 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs @@ -13,6 +13,7 @@ public ForumSettings() StrippedTopicMaxLength = 45; TopicsPageSize = 10; PostsPageSize = 10; + AllowSorting = true; SearchResultsPageSize = 10; LatestCustomerPostsPageSize = 10; ShowCustomersPostCount = true; @@ -88,6 +89,11 @@ public ForumSettings() /// public int PostsPageSize { get; set; } + /// + /// Gets or sets a value indicating whether sorting is enabled. + /// + public bool AllowSorting { get; set; } + /// /// Gets or sets the number of links to display for pagination of posts in topics /// diff --git a/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs index c8e7e17af5..b213e2e56b 100644 --- a/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs +++ b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs @@ -10,13 +10,12 @@ public ForumSearchSettings() { SearchMode = SearchMode.Contains; SearchFields = new List { "username", "text" }; - TopicSortOrder = ForumTopicSorting.Relevance; + DefaultSortOrder = ForumTopicSorting.Relevance; InstantSearchEnabled = true; InstantSearchNumberOfHits = 10; InstantSearchTermMinLength = 2; FilterMinHitCount = 1; FilterMaxChoicesCount = 20; - DefaultSortOrder = ForumTopicSorting.Relevance; ForumDisplayOrder = 1; CustomerDisplayOrder = 2; @@ -34,9 +33,9 @@ public ForumSearchSettings() public List SearchFields { get; set; } /// - /// Gets or sets the topic sort order in search results. + /// Gets or sets the default sort order in search results. /// - public ForumTopicSorting TopicSortOrder { get; set; } + public ForumTopicSorting DefaultSortOrder { get; set; } /// /// Gets or sets a value indicating whether instant-search is enabled. @@ -63,11 +62,6 @@ public ForumSearchSettings() /// public int FilterMaxChoicesCount { get; set; } - /// - /// Gets or sets the default sort order in search results - /// - public ForumTopicSorting DefaultSortOrder { get; set; } - #region Common facet settings public bool ForumDisabled { get; set; } diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 1751ea62f1..8476b1c612 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -427,6 +427,12 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies the number of hits displayed in instant search.", "Legt die Anzahl der angezeigten Suchtreffer in der Instantsuche fest."); + builder.AddOrUpdate("Admin.Configuration.Settings.Forums.AllowSorting", + "Allow sorting", + "Sortierung zulassen", + "Specifies whether forum posts can be sorted.", + "Legt fest, ob Forenbeiträge sortiert werden können."); + builder.Delete( "Admin.Configuration.Settings.Search.DefaultSortOrderMode", "Admin.Configuration.Settings.Search.InstantSearchNumberOfProducts", diff --git a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs index 3bc7f8166c..24adb122f0 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -48,7 +48,7 @@ protected virtual IQueryable GetTopicQuery(ForumSearchQuery sea // Post query. var ordered = false; var t = searchQuery.Term; - var f = _customerSettings.CustomerNameFormat; + var cnf = _customerSettings.CustomerNameFormat; var fields = searchQuery.Fields; var filters = new List(); var query = baseQuery ?? _forumPostRepository.TableUntracked.Expand(x => x.ForumTopic); @@ -62,9 +62,9 @@ protected virtual IQueryable GetTopicQuery(ForumSearchQuery sea (fields.Contains("subject") && x.ForumTopic.Subject.StartsWith(t)) || (fields.Contains("text") && x.Text.StartsWith(t)) || (fields.Contains("username") && ( - f == CustomerNameFormat.ShowEmails ? x.Customer.Email.StartsWith(t) : - f == CustomerNameFormat.ShowUsernames ? x.Customer.Username.StartsWith(t) : - f == CustomerNameFormat.ShowFirstName ? x.Customer.FirstName.StartsWith(t) : + cnf == CustomerNameFormat.ShowEmails ? x.Customer.Email.StartsWith(t) : + cnf == CustomerNameFormat.ShowUsernames ? x.Customer.Username.StartsWith(t) : + cnf == CustomerNameFormat.ShowFirstName ? x.Customer.FirstName.StartsWith(t) : x.Customer.FullName.StartsWith(t)) )); } @@ -74,9 +74,9 @@ protected virtual IQueryable GetTopicQuery(ForumSearchQuery sea (fields.Contains("subject") && x.ForumTopic.Subject.Contains(t)) || (fields.Contains("text") && x.Text.Contains(t)) || (fields.Contains("username") && ( - f == CustomerNameFormat.ShowEmails ? x.Customer.Email.Contains(t) : - f == CustomerNameFormat.ShowUsernames ? x.Customer.Username.Contains(t) : - f == CustomerNameFormat.ShowFirstName ? x.Customer.FirstName.Contains(t) : + cnf == CustomerNameFormat.ShowEmails ? x.Customer.Email.Contains(t) : + cnf == CustomerNameFormat.ShowUsernames ? x.Customer.Username.Contains(t) : + cnf == CustomerNameFormat.ShowFirstName ? x.Customer.FirstName.Contains(t) : x.Customer.FullName.Contains(t)) )); } @@ -164,12 +164,6 @@ from sm in gsm.DefaultIfEmpty() } } - var sortCreatedOn = searchQuery.Sorting.FirstOrDefault(x => x.FieldName == "createdon"); - if (sortCreatedOn != null) - { - query = OrderBy(ref ordered, query, x => x.CreatedOnUtc, sortCreatedOn.Descending); - } - // Topic query. // Unified order: we only sort by ForumPost.Id to save an additional database query in ForumSearchService.Search. var topicQuery = @@ -190,19 +184,34 @@ group fp by fp.TopicId into grp } else if (sort.FieldName == "username") { - topicQuery = OrderBy(ref ordered, topicQuery, x => x.Topic.Customer.Username, sort.Descending); + switch (cnf) + { + case CustomerNameFormat.ShowEmails: + topicQuery = OrderBy(ref ordered, topicQuery, x => x.Topic.Customer.Email, sort.Descending); + break; + case CustomerNameFormat.ShowUsernames: + topicQuery = OrderBy(ref ordered, topicQuery, x => x.Topic.Customer.Username, sort.Descending); + break; + case CustomerNameFormat.ShowFirstName: + topicQuery = OrderBy(ref ordered, topicQuery, x => x.Topic.Customer.FirstName, sort.Descending); + break; + default: + topicQuery = OrderBy(ref ordered, topicQuery, x => x.Topic.Customer.FullName, sort.Descending); + break; + } } else if (sort.FieldName == "createdon") { - // Skip, already processed. We want to sort by ForumPost.CreatedOnUtc, not ForumTopic.CreatedOnUtc. + // We want to sort by ForumPost.CreatedOnUtc, not ForumTopic.CreatedOnUtc. + topicQuery = OrderBy(ref ordered, topicQuery, x => x.Topic.LastPostTime, sort.Descending); } - else if (sort.FieldName == "posts") + else if (sort.FieldName == "numposts") { topicQuery = OrderBy(ref ordered, topicQuery, x => x.Topic.NumPosts, sort.Descending); } } - if (!ordered && !searchQuery.Sorting.Any(x => x.FieldName == "createdon")) + if (!ordered) { topicQuery = topicQuery .OrderByDescending(x => x.Topic.TopicTypeId) diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs index 156fcdfb2d..c51f22b5df 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs @@ -109,17 +109,19 @@ protected virtual void ConvertPagingSorting(ForumSearchQuery query, RouteData ro { var index = Math.Max(1, GetValueFor("i") ?? 1); var size = _forumSettings.SearchResultsPageSize; - query.Slice((index - 1) * size, size); - var orderBy = GetValueFor("o"); - if (orderBy == null || orderBy == ForumTopicSorting.Initial) + if (_forumSettings.AllowSorting) { - orderBy = _searchSettings.DefaultSortOrder; - } + var orderBy = GetValueFor("o"); + if (orderBy == null || orderBy == ForumTopicSorting.Initial) + { + orderBy = _searchSettings.DefaultSortOrder; + } - query.SortBy(orderBy.Value); - query.CustomData["CurrentSortOrder"] = orderBy.Value; + query.SortBy(orderBy.Value); + query.CustomData["CurrentSortOrder"] = orderBy.Value; + } } private void AddFacet( diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index 64a538abce..fbb9e9cfd3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -1401,7 +1401,7 @@ public ActionResult Search() model.IsMegaSearchInstalled = megaSearchDescriptor != null; model.AvailableSortOrderModes = settings.DefaultSortOrder.ToSelectList(); - model.ForumSearchSettings.AvailableTopicSortOrders = fsettings.TopicSortOrder.ToSelectList(); + model.ForumSearchSettings.AvailableDefaultSortOrders = fsettings.DefaultSortOrder.ToSelectList(); if (megaSearchDescriptor == null) { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSearchSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSearchSettingsModel.cs index ec306c0f62..72797cc87c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSearchSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSearchSettingsModel.cs @@ -29,8 +29,8 @@ public ForumSearchSettingsModel() public List AvailableSearchFields { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.Search.DefaultSortOrder")] - public ForumTopicSorting TopicSortOrder { get; set; } - public SelectList AvailableTopicSortOrders { get; set; } + public ForumTopicSorting DefaultSortOrder { get; set; } + public SelectList AvailableDefaultSortOrders { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.Search.InstantSearchEnabled")] public bool InstantSearchEnabled { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs index d7e497b5bf..a8dbd25af2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs @@ -35,6 +35,9 @@ public class ForumSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.PostsPageSize")] public int PostsPageSize { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.AllowSorting")] + public bool AllowSorting { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.ForumEditor")] public EditorType ForumEditor { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml index c2b7ded458..e5c56ce992 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml @@ -124,6 +124,15 @@ @Html.ValidationMessageFor(model => model.SearchResultsPageSize) + + + + diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 12c49f00d7..5681d8d48f 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -1564,10 +1564,37 @@ public ActionResult Search(ForumSearchQuery query/*, bool? adv*/) } ForumSearchResult result = null; + var language = Services.WorkContext.WorkingLanguage; var model = new ForumSearchResultModel(query); model.PostsPageSize = _forumSettings.PostsPageSize; + model.AllowSorting = _forumSettings.AllowSorting; model.Term = query.Term; + // Sorting. + if (model.AllowSorting) + { + model.CurrentSortOrder = query?.CustomData.Get("CurrentSortOrder").Convert(); + + model.AvailableSortOptions = Services.Cache.Get("pres:forumsortoptions-{0}".FormatInvariant(language.Id), () => + { + var dict = new Dictionary(); + foreach (ForumTopicSorting val in Enum.GetValues(typeof(ForumTopicSorting))) + { + if (val == ForumTopicSorting.Initial) + continue; + + dict[(int)val] = val.GetLocalizedEnum(Services.Localization, Services.WorkContext); + } + + return dict; + }); + + if (model.CurrentSortOrderName.IsEmpty()) + { + model.CurrentSortOrderName = model.AvailableSortOptions.Get(model.CurrentSortOrder ?? 1) ?? model.AvailableSortOptions.First().Value; + } + } + if (query.Term.HasValue() && query.Term.Length < _searchSettings.InstantSearchTermMinLength) { model.SearchResult = new ForumSearchResult(query); diff --git a/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs index f7e9440bcb..b038ce2599 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs @@ -28,6 +28,11 @@ public ForumSearchResultModel(ForumSearchQuery query) public int PostsPageSize { get; set; } public string Error { get; set; } + public bool AllowSorting { get; set; } + public int? CurrentSortOrder { get; set; } + public string CurrentSortOrderName { get; set; } + public IDictionary AvailableSortOptions { get; set; } + public IList ForumTopics { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml index 78fbd7a209..a7dc66bb74 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml @@ -24,7 +24,7 @@ suggestions = Model.SearchResult.SpellCheckerSuggestions; } - var pager = "TODO: pager etc."; + //"TODO: pager //var pager = Html.SmartStore().Pager(Model.SearchResult.Hits.PageIndex, Model.SearchResult.Hits.PageSize, Model.TotalCount) // .Name("paginator") // .Alignment(PagerAlignment.Right) @@ -50,7 +50,7 @@
-
+
@if (Model.SearchResult.Facets.Any()) {
@@ -60,7 +60,7 @@ @using (Html.BeginRouteForm("BoardSearch", FormMethod.Get)) { -
+
@@ -100,7 +100,35 @@ }
- @pager + @if (Model.TotalCount > 0) + { +
+ @if (Model.AllowSorting) + { +
+
+ + + +
+
+ } +
+ } +
@if (Model.TotalCount > 0) @@ -215,6 +243,23 @@ }
- @pager +
+ + \ No newline at end of file From cc76bb0bbb953e85a72fda0b2208a9a8695b5f27 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 5 Sep 2018 12:06:00 +0200 Subject: [PATCH 078/932] Search: Paging of forum search hits --- .../Domain/Forums/ForumSettings.cs | 19 +++-- .../Migrations/MigrationsConfiguration.cs | 13 +++ .../Modelling/ForumSearchQueryFactory.cs | 32 ++++++- .../Infrastructure/AutoMapperAdminProfile.cs | 1 - .../Models/Settings/ForumSettingsModel.cs | 12 ++- .../Administration/Views/Setting/Forum.cshtml | 59 +++++++++---- .../Controllers/BoardsController.cs | 22 ++++- .../Controllers/CatalogHelper.MapProduct.cs | 4 +- .../Models/Boards/ForumSearchResultModel.cs | 22 +++-- .../Models/Catalog/IListActions.cs | 10 +-- .../Models/Catalog/ProductSummaryModel.cs | 7 +- .../Models/Common/PagedListModel.cs | 20 +++++ .../SmartStore.Web/SmartStore.Web.csproj | 1 + .../SmartStore.Web/Views/Boards/Search.cshtml | 31 ++++--- .../Catalog/Partials/HomepageProducts.cshtml | 2 +- .../Shared/Partials/Product.List.Pager.cshtml | 8 +- .../Views/Shared/Partials/Product.List.cshtml | 84 +++++++++---------- 17 files changed, 240 insertions(+), 107 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Models/Common/PagedListModel.cs diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs index d87de185f7..116811da15 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs @@ -14,8 +14,10 @@ public ForumSettings() TopicsPageSize = 10; PostsPageSize = 10; AllowSorting = true; - SearchResultsPageSize = 10; - LatestCustomerPostsPageSize = 10; + SearchResultsPageSize = 12; + DefaultPageSizeOptions = "12,24,36,48,72,120"; + AllowCustomersToSelectPageSize = true; + LatestCustomerPostsPageSize = 10; ShowCustomersPostCount = true; ForumEditor = EditorType.BBCodeEditor; SignaturesEnabled = true; @@ -95,14 +97,19 @@ public ForumSettings() public bool AllowSorting { get; set; } /// - /// Gets or sets the number of links to display for pagination of posts in topics + /// Gets or sets the page size for search result /// - public int TopicPostsPageLinkDisplayCount { get; set; } + public int SearchResultsPageSize { get; set; } /// - /// Gets or sets the page size for search result + /// Gets or sets the available customer selectable default page size options /// - public int SearchResultsPageSize { get; set; } + public string DefaultPageSizeOptions { get; set; } + + /// + /// Gets or sets a value indicating whether customers can select page size in product listings + /// + public bool AllowCustomersToSelectPageSize { get; set; } /// /// Gets or sets the page size for latest customer posts diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 8476b1c612..6f1619e22f 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -433,6 +433,19 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies whether forum posts can be sorted.", "Legt fest, ob Forenbeiträge sortiert werden können."); + builder.AddOrUpdate("Admin.Common.DefaultPageSizeOptions", + "Page size options", + "Auswahlmöglichkeiten für Seitengröße", + "Comma-separated page size options that a customer can select in lists.", + "Kommagetrennte Liste mit Optionen für Seitengröße, die ein Kunde in Listen wählen kann."); + + builder.AddOrUpdate("Admin.Common.AllowCustomersToSelectPageSize", + "Allow customers to select page size", + "Kunde kann Listengröße ändern", + "Whether customers are allowed to select the page size from a predefined list of options.", + "Kunden können die Listengröße mit Hilfe einer vorgegebenen Optionsliste ändern."); + + builder.Delete( "Admin.Configuration.Settings.Search.DefaultSortOrderMode", "Admin.Configuration.Settings.Search.InstantSearchNumberOfProducts", diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs index c51f22b5df..a16cc31743 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs @@ -108,7 +108,7 @@ public ForumSearchQuery CreateFromQuery() protected virtual void ConvertPagingSorting(ForumSearchQuery query, RouteData routeData, string origin) { var index = Math.Max(1, GetValueFor("i") ?? 1); - var size = _forumSettings.SearchResultsPageSize; + var size = GetPageSize(query, routeData, origin); query.Slice((index - 1) * size, size); if (_forumSettings.AllowSorting) @@ -124,6 +124,36 @@ protected virtual void ConvertPagingSorting(ForumSearchQuery query, RouteData ro } } + protected virtual int GetPageSize(ForumSearchQuery query, RouteData routeData, string origin) + { + var sessionKey = "PageSize:" + origin; + + if (_forumSettings.AllowCustomersToSelectPageSize) + { + var selectedSize = GetValueFor("s"); + if (selectedSize.HasValue) + { + if (_httpContext.Session != null) + { + _httpContext.Session[sessionKey] = selectedSize.Value; + } + + return selectedSize.Value; + } + } + + if (_httpContext.Session != null) + { + var sessionSize = _httpContext.Session[sessionKey].Convert(); + if (sessionSize.HasValue) + { + return sessionSize.Value; + } + } + + return _forumSettings.SearchResultsPageSize; + } + private void AddFacet( ForumSearchQuery query, FacetGroupKind kind, diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs index 8d8256a982..7906ddcbea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs @@ -634,7 +634,6 @@ public AutoMapperAdminProfile() .ForMember(dest => dest.TopicSubjectMaxLength, mo => mo.Ignore()) .ForMember(dest => dest.StrippedTopicMaxLength, mo => mo.Ignore()) .ForMember(dest => dest.PostMaxLength, mo => mo.Ignore()) - .ForMember(dest => dest.TopicPostsPageLinkDisplayCount, mo => mo.Ignore()) .ForMember(dest => dest.LatestCustomerPostsPageSize, mo => mo.Ignore()) .ForMember(dest => dest.PrivateMessagesPageSize, mo => mo.Ignore()) .ForMember(dest => dest.ForumSubscriptionsPageSize, mo => mo.Ignore()) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs index a8dbd25af2..afebec56cc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs @@ -38,6 +38,15 @@ public class ForumSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.AllowSorting")] public bool AllowSorting { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.SearchResultsPageSize")] + public int SearchResultsPageSize { get; set; } + + [SmartResourceDisplayName("Admin.Common.DefaultPageSizeOptions")] + public string DefaultPageSizeOptions { get; set; } + + [SmartResourceDisplayName("Admin.Common.AllowCustomersToSelectPageSize")] + public bool AllowCustomersToSelectPageSize { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.ForumEditor")] public EditorType ForumEditor { get; set; } @@ -64,8 +73,5 @@ public class ForumSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.ForumFeedCount")] public int ForumFeedCount { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.SearchResultsPageSize")] - public int SearchResultsPageSize { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml index e5c56ce992..7b745dbb93 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml @@ -34,6 +34,15 @@
+
+
@T("Common.Options")
+
+
+ @Html.SmartLabelFor(model => model.HasPreviewPicture) + + @Html.EditorFor(model => model.HasPreviewPicture) + @Html.ValidationMessageFor(model => model.HasPreviewPicture) +
diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index b62bbf7782..81f43911c3 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -387,7 +387,13 @@ public ProductDetailsModel PrepareProductDetailsPageModel( // pictures var pictures = _pictureService.GetPicturesByProductId(product.Id); - PrepareProductDetailsPictureModel(model.DetailsPictureModel, pictures, model.Name, combinationPictureIds, isAssociatedProduct, productBundleItem, combination); + + if (product.HasPreviewPicture && pictures.Count > 1) + { + pictures.RemoveAt(0); + } + + PrepareProductDetailsPictureModel(model.DetailsPictureModel, pictures, model.Name, combinationPictureIds, isAssociatedProduct, productBundleItem, combination); return model; } @@ -575,8 +581,8 @@ public void PrepareProductDetailsPictureModel( } if (defaultPicture == null) - { - model.GalleryStartIndex = 0; + { + model.GalleryStartIndex = 0; defaultPicture = pictures.First(); } } diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index 386f57403c..8ccf44f84f 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -562,7 +562,12 @@ public ActionResult UpdateProductDetails(int productId, string itemType, int bun // Update image gallery. var pictures = _pictureService.GetPicturesByProductId(productId); - if (pictures.Count <= _catalogSettings.DisplayAllImagesNumber) + if (product.HasPreviewPicture && pictures.Count > 1) + { + pictures.RemoveAt(0); + } + + if (pictures.Count <= _catalogSettings.DisplayAllImagesNumber) { // All pictures rendered... only index is required. var picture = m.GetAssignedPicture(_pictureService, pictures); From 38f1b3b57c2bc2320387c852bac925124387cbad Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 31 Jul 2018 20:34:20 +0200 Subject: [PATCH 004/932] Updated changelog --- changelog.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 467f5b739a..ebc0604c09 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -# Release Notes +# Release Notes ## SmartStore.NET 3.2 @@ -12,6 +12,8 @@ * #1459 Add option to only submit one category name per product * Allow to specify export categories per product * Wallet: Allow customer to choose whether refund should be submitted to his wallet. +* Added option to display preview pictures in product lists +* Added option to add multiple file versions to product download section ### Improvements * (Perf) Significantly increased query performance for products with a lot of category assignments (> 10). From 1364f2d4a6eabb6778e51759e5c84267d22b6e38 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 1 Aug 2018 10:19:43 +0200 Subject: [PATCH 005/932] Added missing area to download editor template --- .../Administration/Views/Shared/EditorTemplates/Download.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml index 131f0cfbba..97cf495390 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml @@ -134,7 +134,7 @@ }
@(Html.SmartStore().FileUploader() - .UploadUrl(Url.Action("AsyncUpload", "Download", new { minimalMode = MinimalMode, fieldName = FieldName, entityId = EntityId, entityName = EntityName, })) + .UploadUrl(Url.Action("AsyncUpload", "Download", new { minimalMode = MinimalMode, fieldName = FieldName, entityId = EntityId, entityName = EntityName, area = "Admin" })) .AcceptedFileTypes("") .IconCssClass(MinimalMode ? "fa fa-upload" : "") .ButtonOutlineStyle(hasFile) From 68cf87b9cc7cbf0ad5ef9c8be308b4d73dc201c4 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 3 Aug 2018 11:18:02 +0200 Subject: [PATCH 006/932] Minor bugfix for no-picture display in product lists --- .../Themes/Flex/Content/_artlist.scss | 10 +++++++-- .../Shared/Partials/Product.List.Item.cshtml | 21 ++++++++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_artlist.scss b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_artlist.scss index 538f86c8f2..59bedcbb44 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_artlist.scss +++ b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_artlist.scss @@ -216,12 +216,18 @@ $art-border-color: #ddd; position: absolute; z-index: 1; left: 0; - top: 2rem; + top: 0rem; width: 100%; text-align: center; color: $gray-500; } - img { +} + +.art-picture.no-default-picture { + &:before { + top: 2rem; + } + img { display: none; } } diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Partials/Product.List.Item.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Partials/Product.List.Item.cshtml index 02d3eb4eb9..2fbc89b99a 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Partials/Product.List.Item.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Partials/Product.List.Item.cshtml @@ -4,21 +4,26 @@ @using SmartStore.Web.Models.Catalog; @{ - var artDetailUrl = Url.RouteUrl("Product", new RouteValueDictionary { { "SeName", Model.SeName } }); - var list = Model.Parent; - var picture = Model.Picture; + var artDetailUrl = Url.RouteUrl("Product", new RouteValueDictionary { { "SeName", Model.SeName } }); + var list = Model.Parent; + var picture = Model.Picture; - var showButtons = list.ShowButtons && (!Model.Price.DisableWishlistButton || list.CompareEnabled || (!Model.Price.DisableBuyButton && list.BuyEnabled)); - var showVariants = (Model.ColorAttributes != null && Model.ColorAttributes.Any()) || (Model.Attributes != null && Model.Attributes.Any()); - var showDrop = showButtons || showVariants; - var showAttrs = list.ViewMode == ProductSummaryViewMode.List && (list.ShowSku || list.ShowWeight || list.ShowDimensions); + var showButtons = list.ShowButtons && (!Model.Price.DisableWishlistButton || list.CompareEnabled || (!Model.Price.DisableBuyButton && list.BuyEnabled)); + var showVariants = (Model.ColorAttributes != null && Model.ColorAttributes.Any()) || (Model.Attributes != null && Model.Attributes.Any()); + var showDrop = showButtons || showVariants; + var showAttrs = list.ViewMode == ProductSummaryViewMode.List && (list.ShowSku || list.ShowWeight || list.ShowDimensions); + var addPictureClass = picture.PictureId == 0 ? " no-picture" : ""; + if (!picture.ImageUrl.HasValue()) + { + addPictureClass += " no-default-picture"; + } }
@if (picture.Title.HasValue()) { - + @picture.AlternateText } From 17641b57149672cc7c1b4043bb292dddb5771bae Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 3 Aug 2018 15:22:27 +0200 Subject: [PATCH 007/932] Added free badge for free products to product list view --- .../Migrations/MigrationsConfiguration.cs | 7 ++++++- .../Controllers/CatalogHelper.MapProduct.cs | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 07c734e324..d78eded090 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -363,7 +363,12 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Hat Vorschaubild", "If this option is activated, the first image is used as a preview image in product lists and is no longer displayed in the product detail view.", "Wenn diese Option aktiviert ist wird das erste Bild als Vorschaubild in Produktlisten genutzt und in der Produktdetailanischt nicht mehr angezeigt."); - + + builder.AddOrUpdate("Products.Free", "Free", "Kostenlos"); + + builder.AddOrUpdate("Admin.Catalog.Products.Fields.ProductTags.Hint", + "Product tags are keywords that this product can also be identified by. Enter a list of the tags to be associated with this product. The more products associated with a particular tag, the larger it will show on the tag cloud.", + "Eine Liste von Schlüsselwörtern, die das Produkt taxonomisch charakterisieren. Je mehr Produkte einem Schlüsselwort (Tag) zugeordnet sind, desto mehr visuelles Gewicht erhält das Tag."); } } } diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.MapProduct.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.MapProduct.cs index b4b57b5de8..e2dc1b6939 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.MapProduct.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.MapProduct.cs @@ -541,6 +541,16 @@ private void MapProductSummaryItem(Product product, MapProductSummaryItemContext }); } + // free product badge + if (item.Price.PriceValue == 0) + { + item.Badges.Add(new ProductSummaryModel.Badge + { + Label = T("Products.Free"), + Style = BadgeStyle.Success + }); + } + model.Items.Add(item); } From 331f4c3aadd4d2c3eaf27dccecf5232be86e7baa Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 5 Aug 2018 20:03:39 +0200 Subject: [PATCH 008/932] Resolves #1478 "Unable to update database to match the current model because there are pending changes and automatic migration is disabled" --- .../201808051119578_Merge4.Designer.cs | 29 ++++ .../Migrations/201808051119578_Merge4.cs | 16 +++ .../Migrations/201808051119578_Merge4.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 7 + 4 files changed, 178 insertions(+) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.resx diff --git a/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.Designer.cs new file mode 100644 index 0000000000..9002b8b1aa --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class Merge4 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Merge4)); + + string IMigrationMetadata.Id + { + get { return "201808051119578_Merge4"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.cs b/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.cs new file mode 100644 index 0000000000..0777552123 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.cs @@ -0,0 +1,16 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class Merge4 : DbMigration + { + public override void Up() + { + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.resx b/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.resx new file mode 100644 index 0000000000..198ea117b6 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index f45a8a69b6..a48d051bd0 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -611,6 +611,10 @@ 201807201157391_DownloadVersions.cs + + + 201808051119578_Merge4.cs + @@ -1099,6 +1103,9 @@ 201807201157391_DownloadVersions.cs + + 201808051119578_Merge4.cs + From 6aface390c5994b3b9c90f03428158e554d8b867 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 5 Aug 2018 20:21:43 +0200 Subject: [PATCH 009/932] Resolves #1478 "Unable to update database to match the current model because there are pending changes and automatic migration is disabled" --- .../201808051818238_Merge4.Designer.cs | 29 ++++ .../Migrations/201808051818238_Merge4.cs | 16 +++ .../Migrations/201808051818238_Merge4.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 7 + 4 files changed, 178 insertions(+) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.resx diff --git a/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.Designer.cs new file mode 100644 index 0000000000..c28cdd698c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class Merge4 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Merge4)); + + string IMigrationMetadata.Id + { + get { return "201808051818238_Merge4"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.cs b/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.cs new file mode 100644 index 0000000000..0777552123 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.cs @@ -0,0 +1,16 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class Merge4 : DbMigration + { + public override void Up() + { + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.resx b/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.resx new file mode 100644 index 0000000000..b90b2b768c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index 5d2543ad4d..e0f3e29138 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -615,6 +615,10 @@ 201807311708428_ProductPreviewPicture.cs + + + 201808051818238_Merge4.cs + @@ -1106,6 +1110,9 @@ 201807311708428_ProductPreviewPicture.cs + + 201808051818238_Merge4.cs + From 8c31fbe3fb7e3e1e2d30efbe436c87032dcea6a9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 6 Aug 2018 11:05:51 +0200 Subject: [PATCH 010/932] Fixes in a multi-store, message templates may have loaded the wrong disclaimer and conditions-of-use text --- changelog.md | 3 ++- .../SmartStore.Services/Messages/MessageModelProvider.Utils.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index ebc0604c09..c0b71535ec 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -# Release Notes +# Release Notes ## SmartStore.NET 3.2 @@ -43,6 +43,7 @@ * Redirecting within checkout may have displayed an incorrect URL in the browser. * Server cannot modify cookies after HTTP headers have been sent. * Wrong base price on product and cart page when a special price is active. +* In a multi-store, message templates may have loaded the wrong disclaimer and conditions-of-use text. ## SmartStore.NET 3.1.5 diff --git a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.Utils.cs b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.Utils.cs index aa2d28ceb6..9e5a8856e2 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.Utils.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.Utils.cs @@ -76,7 +76,7 @@ private object GetTopic(string topicSystemName, MessageContext ctx) var topicService = _services.Resolve(); // Load by store - var topic = topicService.GetTopicBySystemName(topicSystemName, 0, false); + var topic = topicService.GetTopicBySystemName(topicSystemName, ctx.StoreId ?? 0, false); string body = topic?.GetLocalized(x => x.Body, ctx.Language); if (body.HasValue()) From 5b9d567c1ab0b93f1337337f853fc52f013bd4c4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 6 Aug 2018 14:16:01 +0200 Subject: [PATCH 011/932] Delete related export profiles if the export plugin is uninstalled --- .../GoogleMerchantCenterFeedPlugin.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs index 3808bca487..a930d8907c 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs @@ -2,8 +2,10 @@ using System.Web.Routing; using SmartStore.Core.Plugins; using SmartStore.GoogleMerchantCenter.Data.Migrations; +using SmartStore.GoogleMerchantCenter.Providers; using SmartStore.GoogleMerchantCenter.Services; using SmartStore.Services; +using SmartStore.Services.DataExchange.Export; namespace SmartStore.GoogleMerchantCenter { @@ -11,13 +13,16 @@ public class GoogleMerchantCenterFeedPlugin : BasePlugin, IConfigurable { private readonly IGoogleFeedService _googleFeedService; private readonly ICommonServices _services; + private readonly IExportProfileService _exportProfileService; public GoogleMerchantCenterFeedPlugin( IGoogleFeedService googleFeedService, - ICommonServices services) + ICommonServices services, + IExportProfileService exportProfileService) { _googleFeedService = googleFeedService; _services = services; + _exportProfileService = exportProfileService; } public static string SystemName @@ -35,7 +40,7 @@ public void GetConfigurationRoute(out string actionName, out string controllerNa { actionName = "Configure"; controllerName = "FeedGoogleMerchantCenter"; - routeValues = new RouteValueDictionary() { { "Namespaces", "SmartStore.GoogleMerchantCenter.Controllers" }, { "area", SystemName } }; + routeValues = new RouteValueDictionary { { "Namespaces", "SmartStore.GoogleMerchantCenter.Controllers" }, { "area", SystemName } }; } /// @@ -55,7 +60,11 @@ public override void Uninstall() { _services.Localization.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); - var migrator = new DbMigrator(new Configuration()); + // Delete existing export profiles. + var profiles = _exportProfileService.GetExportProfilesBySystemName(GmcXmlExportProvider.SystemName); + profiles.Each(x => _exportProfileService.DeleteExportProfile(x, true)); + + var migrator = new DbMigrator(new Configuration()); migrator.Update(DbMigrator.InitialDatabase); base.Uninstall(); From 6908891bcaac97fccfe1d81c301a10e318d57b4d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 6 Aug 2018 19:52:35 +0200 Subject: [PATCH 012/932] Resolves #1486 DownloadId missing in several exports. CSV export generates: The field count of the current row does not match the previous row's field count. --- .../DataExchange/Export/DataExporter.cs | 10 +++- .../Export/DynamicEntityHelper.cs | 25 +++++++- .../DataExchange/Export/ExportXmlHelper.cs | 30 +++++++++- .../Export/ProductExportContext.cs | 59 ++++++++++++++----- .../Media/DownloadService.cs | 19 +++++- .../Media/IDownloadService.cs | 17 ++++-- 6 files changed, 132 insertions(+), 28 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 44f90c0bd4..8831a488a0 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -82,7 +82,8 @@ public partial class DataExporter : IDataExporter private readonly Lazy _deliveryTimeService; private readonly Lazy _quantityUnitService; private readonly Lazy _catalogSearchService; - private readonly Lazy _productUrlHelper; + private readonly Lazy _downloadService; + private readonly Lazy _productUrlHelper; private readonly Lazy>_customerRepository; private readonly Lazy> _subscriptionRepository; @@ -129,7 +130,8 @@ public DataExporter( Lazy deliveryTimeService, Lazy quantityUnitService, Lazy catalogSearchService, - Lazy productUrlHelper, + Lazy downloadService, + Lazy productUrlHelper, Lazy> customerRepository, Lazy> subscriptionRepository, Lazy> orderRepository, @@ -173,6 +175,7 @@ public DataExporter( _deliveryTimeService = deliveryTimeService; _quantityUnitService = quantityUnitService; _catalogSearchService = catalogSearchService; + _downloadService = downloadService; _productUrlHelper = productUrlHelper; _customerRepository = customerRepository; @@ -687,7 +690,8 @@ public virtual ProductExportContext CreateProductExportContext( x => _productService.Value.GetBundleItemsByProductIds(x, showHidden), x => _pictureService.Value.GetPicturesByProductIds(x, maxPicturesPerProduct, true), x => _productService.Value.GetProductPicturesByProductIds(x), - x => _productService.Value.GetProductTagsByProductIds(x) + x => _productService.Value.GetProductTagsByProductIds(x), + x => _downloadService.Value.GetDownloadsByEntityIds(x, nameof(Product)) ); return context; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index 2083a76933..8bc039898c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -569,6 +569,7 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, string seNam dynamic result = new DynamicEntity(product); result.AppliedDiscounts = null; + result.Downloads = null; result.TierPrices = null; result.ProductAttributes = null; result.ProductAttributeCombinations = null; @@ -601,7 +602,7 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, string seNam ToDeliveryTime(ctx, result, product.DeliveryTimeId); ToQuantityUnit(ctx, result, product.QuantityUnitId); - result._Localized = GetLocalized(ctx, product, + result._Localized = GetLocalized(ctx, product, x => x.Name, x => x.ShortDescription, x => x.FullDescription, @@ -821,7 +822,16 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, bool isParen .ToList(); } - dynObject.ProductTags = productTags + if (product.IsDownload) + { + var downloads = ctx.ProductExportContext.Downloads.GetOrLoad(product.Id); + + dynObject.Downloads = downloads + .Select(x => ToDynamic(ctx, x)) + .ToList(); + } + + dynObject.ProductTags = productTags .Select(x => { dynamic dyn = new DynamicEntity(x); @@ -1051,7 +1061,16 @@ private dynamic ToDynamic(DataExporterContext ctx, Discount discount) return result; } - private dynamic ToDynamic(DataExporterContext ctx, ProductSpecificationAttribute psa) + private dynamic ToDynamic(DataExporterContext ctx, Download download) + { + if (download == null) + return null; + + dynamic result = new DynamicEntity(download); + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, ProductSpecificationAttribute psa) { if (psa == null) return null; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs index 546d685a7a..c28f89bff8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs @@ -502,7 +502,6 @@ public void WriteProduct(dynamic product, string node) _writer.Write("RequiredProductIds", entity.RequiredProductIds); _writer.Write("AutomaticallyAddRequiredProducts", entity.AutomaticallyAddRequiredProducts.ToString()); _writer.Write("IsDownload", entity.IsDownload.ToString()); - //_writer.Write("DownloadId", entity.DownloadId.ToString()); _writer.Write("UnlimitedDownloads", entity.UnlimitedDownloads.ToString()); _writer.Write("MaxNumberOfDownloads", entity.MaxNumberOfDownloads.ToString()); _writer.Write("DownloadExpirationDays", entity.DownloadExpirationDays.HasValue ? entity.DownloadExpirationDays.Value.ToString() : ""); @@ -607,6 +606,35 @@ public void WriteProduct(dynamic product, string node) _writer.WriteEndElement(); // AppliedDiscounts } + if (product.Downloads != null) + { + _writer.WriteStartElement("Downloads"); + foreach (dynamic download in product.Downloads) + { + Download downloadEntity = download.Entity; + + _writer.WriteStartElement("Download"); + _writer.Write("Id", downloadEntity.Id.ToString()); + _writer.Write("DownloadGuid", downloadEntity.DownloadGuid.ToString()); + _writer.Write("UseDownloadUrl", downloadEntity.UseDownloadUrl.ToString()); + _writer.Write("DownloadUrl", downloadEntity.DownloadUrl); + _writer.Write("ContentType", downloadEntity.ContentType); + _writer.Write("Filename", downloadEntity.Filename); + _writer.Write("Extension", downloadEntity.Extension); + _writer.Write("IsNew", downloadEntity.IsNew.ToString()); + _writer.Write("IsTransient", downloadEntity.IsTransient.ToString()); + _writer.Write("UpdatedOnUtc", downloadEntity.UpdatedOnUtc.ToString(_culture)); + _writer.Write("MediaStorageId", downloadEntity.MediaStorageId.HasValue ? downloadEntity.MediaStorageId.Value.ToString() : ""); + _writer.Write("EntityId", downloadEntity.EntityId.ToString()); + _writer.Write("EntityName", downloadEntity.EntityName); + _writer.Write("FileVersion", downloadEntity.FileVersion); + _writer.Write("Changelog", downloadEntity.Changelog); + + _writer.WriteEndElement(); // Download + } + _writer.WriteEndElement(); // Downloads + } + if (product.TierPrices != null) { _writer.WriteStartElement("TierPrices"); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ProductExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ProductExportContext.cs index 1cdfb837be..be7584b70f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ProductExportContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ProductExportContext.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Discounts; @@ -9,22 +8,24 @@ namespace SmartStore.Services.DataExchange.Export { - /// - /// Cargo data to reduce database round trips during work with product batches (export, list model creation etc.) - /// - public class ProductExportContext : PriceCalculationContext + /// + /// Cargo data to reduce database round trips during work with product batches (export, list model creation etc.) + /// + public class ProductExportContext : PriceCalculationContext { private Func> _funcProductPictures; private Func> _funcProductTags; private Func> _funcSpecificationAttributes; private Func> _funcPictures; + private Func> _funcDownloads; - private LazyMultimap _productPictures; + private LazyMultimap _productPictures; private LazyMultimap _productTags; private LazyMultimap _specificationAttributes; private LazyMultimap _pictures; + private LazyMultimap _downloads; - public ProductExportContext( + public ProductExportContext( IEnumerable products, Func> attributes, Func> attributeCombinations, @@ -36,7 +37,8 @@ public ProductExportContext( Func> productBundleItems, Func> pictures, Func> productPictures, - Func> productTags) + Func> productTags, + Func> downloads) : base(products, attributes, attributeCombinations, @@ -50,18 +52,31 @@ public ProductExportContext( _funcProductPictures = productPictures; _funcProductTags = productTags; _funcSpecificationAttributes = specificationAttributes; + _funcDownloads = downloads; } public new void Clear() { - if (_productPictures != null) - _productPictures.Clear(); - if (_productTags != null) - _productTags.Clear(); - if (_specificationAttributes != null) - _specificationAttributes.Clear(); - if (_pictures != null) - _pictures.Clear(); + if (_productPictures != null) + { + _productPictures.Clear(); + } + if (_productTags != null) + { + _productTags.Clear(); + } + if (_specificationAttributes != null) + { + _specificationAttributes.Clear(); + } + if (_pictures != null) + { + _pictures.Clear(); + } + if (_downloads != null) + { + _downloads.Clear(); + } base.Clear(); } @@ -113,5 +128,17 @@ public LazyMultimap SpecificationAttributes return _specificationAttributes; } } + + public LazyMultimap Downloads + { + get + { + if (_downloads == null) + { + _downloads = new LazyMultimap(keys => _funcDownloads(keys), _productIds); + } + return _downloads; + } + } } } diff --git a/src/Libraries/SmartStore.Services/Media/DownloadService.cs b/src/Libraries/SmartStore.Services/Media/DownloadService.cs index 29ce065832..79809b45d6 100644 --- a/src/Libraries/SmartStore.Services/Media/DownloadService.cs +++ b/src/Libraries/SmartStore.Services/Media/DownloadService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Media; @@ -13,7 +14,7 @@ namespace SmartStore.Services.Media { - public partial class DownloadService : IDownloadService + public partial class DownloadService : IDownloadService { private readonly IRepository _downloadRepository; private readonly IEventPublisher _eventPubisher; @@ -84,6 +85,22 @@ public virtual IList GetDownloadsByEntityId(int entityId, string entit return downloads.ToList(); } + public virtual Multimap GetDownloadsByEntityIds(int[] entityIds, string entityName) + { + Guard.NotNull(entityIds, nameof(entityIds)); + Guard.NotEmpty(entityName, nameof(entityName)); + + var query = _downloadRepository.TableUntracked + .Where(x => entityIds.Contains(x.EntityId) && x.EntityName == entityName); + + var map = query + .OrderBy(x => x.FileVersion) + .ToList() + .ToMultimap(x => x.EntityId, x => x); + + return map; + } + public virtual Download GetDownloadByGuid(Guid downloadGuid) { if (downloadGuid == Guid.Empty) diff --git a/src/Libraries/SmartStore.Services/Media/IDownloadService.cs b/src/Libraries/SmartStore.Services/Media/IDownloadService.cs index b39a78ad5f..d5282fd3d8 100644 --- a/src/Libraries/SmartStore.Services/Media/IDownloadService.cs +++ b/src/Libraries/SmartStore.Services/Media/IDownloadService.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using SmartStore.Collections; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; namespace SmartStore.Services.Media { - public partial interface IDownloadService + public partial interface IDownloadService { /// /// Gets a download @@ -22,13 +23,21 @@ public partial interface IDownloadService IList GetDownloadsByIds(int[] downloadIds); /// - /// Gets downloads by product id + /// Gets downloads by entity identifier /// - /// EntityId - /// EntityName + /// Entity identifier + /// Entity name /// List of download entities IList GetDownloadsByEntityId(int entityId, string entityName); + /// + /// Gets downloads by entity identifiers + /// + /// Entity identifiers + /// Entity name + /// Multimap of download entities + Multimap GetDownloadsByEntityIds(int[] entityIds, string entityName); + /// /// Gets a download by GUID /// From 35ffaeb8f49094fb58ac6e77d0085581d053378b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 6 Aug 2018 20:09:09 +0200 Subject: [PATCH 013/932] Obsolete entity properties should not be decorated with DataMember because they are no longer data members --- src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs | 1 - src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs | 1 - src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs index bbe46d5043..5a895b03a8 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs @@ -124,7 +124,6 @@ public partial class Category : BaseEntity, ICategoryNode, IAuditable, ISoftDele /// /// Gets or sets the available price ranges /// - [DataMember] [Obsolete("Price ranges are calculated automatically since version 3")] [StringLength(400)] public string PriceRanges { get; set; } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs index 844335b79b..823f5e57fb 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs @@ -88,7 +88,6 @@ public partial class Manufacturer : BaseEntity, IAuditable, ISoftDeletable, ILoc /// /// Gets or sets the available price ranges /// - [DataMember] [Obsolete("Price ranges are calculated automatically since version 3")] [StringLength(400)] public string PriceRanges { get; set; } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs index 5321b0f81f..a4bc4c5251 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs @@ -297,8 +297,7 @@ public string Gtin /// /// Gets or sets the download identifier /// - [Obsolete] - [DataMember] + [Obsolete("Since version 3.2 more than one download can be assigned to a product. See property Download.EntityId and Download.EntityName.")] public int DownloadId { get; set; } /// From b6bb67d201472b5a7fd0fbf6359e97a7e239adc3 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 7 Aug 2018 11:50:32 +0200 Subject: [PATCH 014/932] Resolves #1487 The installation of the sample downloads failed --- .../Setup/SeedData/InvariantSeedData.cs | 196 ++++++++---------- .../Installation/InstallDataSeeder.cs | 2 + 2 files changed, 90 insertions(+), 108 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs index a0344e48f1..549e9daeac 100644 --- a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs +++ b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs @@ -9,7 +9,6 @@ using SmartStore.Core.Domain; using SmartStore.Core.Domain.Blogs; using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Cms; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Directory; @@ -31,12 +30,11 @@ using SmartStore.Core.Domain.Tax; using SmartStore.Core.Domain.Themes; using SmartStore.Core.Domain.Topics; -using SmartStore.Data; using SmartStore.Utilities; namespace SmartStore.Data.Setup { - public abstract class InvariantSeedData + public abstract class InvariantSeedData { private SmartObjectContext _ctx; private string _sampleImagesPath; @@ -10149,9 +10147,6 @@ public IList Products() // Pictures var sampleImagesPath = this._sampleImagesPath; - // Downloads - var sampleDownloadsPath = this._sampleDownloadsPath; - // Templates var productTemplate = _ctx.Set().First(x => x.ViewPath == "Product"); @@ -12802,33 +12797,18 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsDownload = true, - HasSampleDownload = true, - SampleDownload = new Download - { - DownloadGuid = Guid.NewGuid(), - ContentType = "application/pdf", - MediaStorage = new MediaStorage - { - Data = File.ReadAllBytes(sampleDownloadsPath + "Stone_of_the_wise_preview.pdf") - }, - Extension = ".pdf", - Filename = "Stone_of_the_wise_preview", - IsNew = true, - UpdatedOnUtc = DateTime.UtcNow - } + HasSampleDownload = true }; - productBooksStoneOfTheWise.ProductCategories.Add(new ProductCategory() { Category = categoryDigitalProducts, DisplayOrder = 1 }); + productBooksStoneOfTheWise.ProductCategories.Add(new ProductCategory { Category = categoryDigitalProducts, DisplayOrder = 1 }); - //pictures - productBooksStoneOfTheWise.ProductPictures.Add(new ProductPicture() + productBooksStoneOfTheWise.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "stone_of_wisdom.jpg"), "image/jpeg", GetSeName(productBooksStoneOfTheWise.Name)), DisplayOrder = 1, }); - //attributes - productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12836,7 +12816,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12844,7 +12824,7 @@ public IList Products() // Category -> cars SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() }); - productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12853,7 +12833,6 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - #endregion product Books Stone of the Wise @@ -12880,38 +12859,18 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsDownload = true, - HasSampleDownload = true, - SampleDownload = new Download - { - DownloadGuid = Guid.NewGuid(), - ContentType = "audio/mp3", - MediaStorage = new MediaStorage - { - Data = File.ReadAllBytes(sampleDownloadsPath + "vivaldi-four-seasons-spring.mp3") - }, - Extension = ".mp3", - Filename = "vivaldi-four-seasons-spring", - IsNew = true, - UpdatedOnUtc = DateTime.UtcNow - } + HasSampleDownload = true }; - productInstantDownloadVivaldi.ProductCategories.Add(new ProductCategory() { Category = categoryDigitalProducts, DisplayOrder = 1 }); + productInstantDownloadVivaldi.ProductCategories.Add(new ProductCategory { Category = categoryDigitalProducts, DisplayOrder = 1 }); - #region pictures - - //pictures - productInstantDownloadVivaldi.ProductPictures.Add(new ProductPicture() + productInstantDownloadVivaldi.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "vivaldi.jpg"), "image/jpeg", GetSeName(productInstantDownloadVivaldi.Name)), DisplayOrder = 1, }); - #endregion pictures - - #region SpecificationAttributes - //attributes - productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12919,7 +12878,7 @@ public IList Products() // mp3 quality > 320 kbit/S SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 18).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12928,8 +12887,6 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 19).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() }); - #endregion SpecificationAttributes - #endregion Antonio Vivildi: then spring #region Beethoven für Elise @@ -12956,38 +12913,18 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsDownload = true, - HasSampleDownload = true, - SampleDownload = new Download() - { - DownloadGuid = Guid.NewGuid(), - ContentType = "audio/mp3", - MediaStorage = new MediaStorage - { - Data = File.ReadAllBytes(sampleDownloadsPath + "beethoven-fur-elise.mp3") - }, - Extension = ".mp3", - Filename = "beethoven-fur-elise.mp3", - IsNew = true, - UpdatedOnUtc = DateTime.UtcNow - } + HasSampleDownload = true }; - productInstantDownloadBeethoven.ProductCategories.Add(new ProductCategory() { Category = categoryDigitalProducts, DisplayOrder = 1 }); - - #region pictures + productInstantDownloadBeethoven.ProductCategories.Add(new ProductCategory { Category = categoryDigitalProducts, DisplayOrder = 1 }); - //pictures - productInstantDownloadBeethoven.ProductPictures.Add(new ProductPicture() + productInstantDownloadBeethoven.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "Beethoven.jpg"), "image/jpeg", GetSeName(productInstantDownloadBeethoven.Name)), DisplayOrder = 1, }); - #endregion pictures - - #region SpecificationAttributes - //attributes - productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12995,7 +12932,7 @@ public IList Products() // mp3 quality > 320 kbit/S SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 18).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13004,8 +12941,6 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 19).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() }); - #endregion SpecificationAttributes - #endregion Beethoven für Elise #endregion Instant Download Music @@ -14206,7 +14141,76 @@ public IList ProductBundleItems() return entities; } - public void AssignGroupedProducts(IList savedProducts) + public void AddDownloads(IList savedProducts) + { + // Sample downloads. + var sampleDownloadSkus = new List { "P-1017", "P-1016", "P-6001" }; + var sampleDownloadProducts = savedProducts + .Where(x => sampleDownloadSkus.Contains(x.Sku)) + .ToDictionary(x => x.Sku); + + foreach (var product in sampleDownloadProducts.Values) + { + if (product.Sku.IsCaseInsensitiveEqual("P-1017")) + { + product.SampleDownload = new Download + { + EntityId = product.Id, + EntityName = nameof(Product), + DownloadGuid = Guid.NewGuid(), + ContentType = "audio/mp3", + MediaStorage = new MediaStorage + { + Data = File.ReadAllBytes(_sampleDownloadsPath + "beethoven-fur-elise.mp3") + }, + Extension = ".mp3", + Filename = "beethoven-fur-elise.mp3", + IsNew = true, + UpdatedOnUtc = DateTime.UtcNow + }; + } + else if (product.Sku.IsCaseInsensitiveEqual("P-1016")) + { + product.SampleDownload = new Download + { + EntityId = product.Id, + EntityName = nameof(Product), + DownloadGuid = Guid.NewGuid(), + ContentType = "audio/mp3", + MediaStorage = new MediaStorage + { + Data = File.ReadAllBytes(_sampleDownloadsPath + "vivaldi-four-seasons-spring.mp3") + }, + Extension = ".mp3", + Filename = "vivaldi-four-seasons-spring", + IsNew = true, + UpdatedOnUtc = DateTime.UtcNow + }; + } + else if (product.Sku.IsCaseInsensitiveEqual("P-6001")) + { + product.SampleDownload = new Download + { + EntityId = product.Id, + EntityName = nameof(Product), + DownloadGuid = Guid.NewGuid(), + ContentType = "application/pdf", + MediaStorage = new MediaStorage + { + Data = File.ReadAllBytes(_sampleDownloadsPath + "Stone_of_the_wise_preview.pdf") + }, + Extension = ".pdf", + Filename = "Stone_of_the_wise_preview", + IsNew = true, + UpdatedOnUtc = DateTime.UtcNow + }; + } + } + + _ctx.SaveChanges(); + } + + public void AssignGroupedProducts(IList savedProducts) { int productGamingAccessoriesId = savedProducts.First(x => x.Sku == "Sony-GroupAccessories").Id; var gamingAccessoriesSkus = new List() { "Sony-PS399004", "PD-Minecraft4ps4", "Sony-PS410037", "Sony-PS410040" }; @@ -14222,10 +14226,9 @@ public void AssignGroupedProducts(IList savedProducts) //_ctx.Entry(x).State = System.Data.Entity.EntityState.Modified; }); - _ctx.SaveChanges(); + _ctx.SaveChanges(); } - #region ForumGroups public IList ForumGroups() { var forumGroupGeneral = new ForumGroup @@ -14243,9 +14246,7 @@ public IList ForumGroups() this.Alter(entities); return entities; } - #endregion ForumGroups - #region Forums public IList Forums() { var newProductsForum = new Forum @@ -14280,9 +14281,6 @@ public IList Forums() this.Alter(entities); return entities; } - #endregion Forums - - #region Discounts public IList Discounts() { @@ -14318,9 +14316,6 @@ public IList Discounts() return entities; } - #endregion Discounts - - #region Deliverytimes public IList DeliveryTimes() { var entities = new List() @@ -14348,10 +14343,6 @@ public IList DeliveryTimes() return entities; } - #endregion Deliverytimes - - #region QuantityUnits - public IList QuantityUnits() { var entities = new List() @@ -14386,9 +14377,6 @@ public IList QuantityUnits() return entities; } - #endregion - - #region BlogPost public IList BlogPosts() { var defaultLanguage = _ctx.Set().FirstOrDefault(); @@ -14419,9 +14407,7 @@ public IList BlogPosts() this.Alter(entities); return entities; } - #endregion BlogPost - #region NewsItems public IList NewsItems() { var defaultLanguage = _ctx.Set().FirstOrDefault(); @@ -14456,9 +14442,7 @@ public IList NewsItems() this.Alter(entities); return entities; } - #endregion NewsItems - #region PollAnswer public IList PollAnswers() { var pollAnswer1 = new PollAnswer() @@ -14510,9 +14494,7 @@ public IList PollAnswers() this.Alter(entities); return entities; } - #endregion PollAnswer - #region Polls public IList Polls() { var defaultLanguage = _ctx.Set().FirstOrDefault(); @@ -14602,8 +14584,6 @@ public IList Polls() this.Alter(entities); return entities; } - #endregion Polls - #region Alterations diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs index 0a8b13558d..315e4a4cc6 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs @@ -296,6 +296,8 @@ private void PopulateProducts() var products = _data.Products(); SaveRange(products); + _data.AddDownloads(products); + // Fix MainPictureId DataMigrator.FixProductMainPictureIds(_ctx); From 89487c95e4383e051644bab29b1a63380191c5b8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 7 Aug 2018 12:21:51 +0200 Subject: [PATCH 015/932] Invariant seed data cleanup --- .../Setup/SeedData/InvariantSeedData.cs | 1330 +++++------------ 1 file changed, 411 insertions(+), 919 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs index 549e9daeac..2f76c557bb 100644 --- a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs +++ b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs @@ -10144,25 +10144,20 @@ public IList Products() { #region definitions - // Pictures - var sampleImagesPath = this._sampleImagesPath; - - // Templates + var sampleImagesPath = _sampleImagesPath; var productTemplate = _ctx.Set().First(x => x.ViewPath == "Product"); - var firstDeliveryTime = _ctx.Set().First(sa => sa.DisplayOrder == 0); - var specialPriceEndDate = DateTime.UtcNow.AddMonths(1); #endregion definitions #region category golf - var categoryGolf = this._ctx.Set().First(c => c.Alias == "Golf"); + var categoryGolf = _ctx.Set().First(c => c.Alias == "Golf"); #region product Titleist SM6 Tour Chrome - var productTitleistSM6TourChrome = new Product() + var productTitleistSM6TourChrome = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10188,15 +10183,15 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productTitleistSM6TourChrome.ProductCategories.Add(new ProductCategory() { Category = categoryGolf, DisplayOrder = 1 }); + productTitleistSM6TourChrome.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); - productTitleistSM6TourChrome.ProductPictures.Add(new ProductPicture() + productTitleistSM6TourChrome.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_titleist_sm6_tour_chrome.jpg"), "image/png", GetSeName(productTitleistSM6TourChrome.Name)), DisplayOrder = 1, }); - productTitleistSM6TourChrome.ProductManufacturers.Add(new ProductManufacturer() + productTitleistSM6TourChrome.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Titleist").Single(), DisplayOrder = 1, @@ -10206,7 +10201,7 @@ public IList Products() #region product Titleist Pro V1x - var productTitleistProV1x = new Product() + var productTitleistProV1x = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10231,15 +10226,15 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productTitleistProV1x.ProductCategories.Add(new ProductCategory() { Category = categoryGolf, DisplayOrder = 1 }); + productTitleistProV1x.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); - productTitleistProV1x.ProductPictures.Add(new ProductPicture() + productTitleistProV1x.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_titleist-pro-v1x.jpg"), "image/png", GetSeName(productTitleistProV1x.Name)), DisplayOrder = 1, }); - productTitleistProV1x.ProductManufacturers.Add(new ProductManufacturer() + productTitleistProV1x.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Titleist").Single(), DisplayOrder = 1, @@ -10249,7 +10244,7 @@ public IList Products() #region product Supreme Golfball - var productSupremeGolfball = new Product() + var productSupremeGolfball = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10274,21 +10269,21 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productSupremeGolfball.ProductCategories.Add(new ProductCategory() { Category = categoryGolf, DisplayOrder = 1 }); + productSupremeGolfball.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); - productSupremeGolfball.ProductPictures.Add(new ProductPicture() + productSupremeGolfball.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_supremeGolfball_1.jpg"), "image/png", GetSeName(productSupremeGolfball.Name)), DisplayOrder = 1, }); - productSupremeGolfball.ProductPictures.Add(new ProductPicture() + productSupremeGolfball.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_supremeGolfball_2.jpg"), "image/png", GetSeName(productSupremeGolfball.Name)), DisplayOrder = 1, }); - productSupremeGolfball.ProductManufacturers.Add(new ProductManufacturer() + productSupremeGolfball.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Titleist").Single(), DisplayOrder = 1, @@ -10298,7 +10293,7 @@ public IList Products() #region product GBB Epic Sub Zero Driver - var productGBBEpicSubZeroDriver = new Product() + var productGBBEpicSubZeroDriver = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10323,15 +10318,15 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productGBBEpicSubZeroDriver.ProductCategories.Add(new ProductCategory() { Category = categoryGolf, DisplayOrder = 1 }); + productGBBEpicSubZeroDriver.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); - productGBBEpicSubZeroDriver.ProductPictures.Add(new ProductPicture() + productGBBEpicSubZeroDriver.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gbb-epic-sub-zero-driver.jpg"), "image/png", GetSeName(productGBBEpicSubZeroDriver.Name)), DisplayOrder = 1, }); - productGBBEpicSubZeroDriver.ProductManufacturers.Add(new ProductManufacturer() + productGBBEpicSubZeroDriver.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Titleist").Single(), DisplayOrder = 1, @@ -10343,11 +10338,11 @@ public IList Products() #region category Soccer - var categorySoccer = this._ctx.Set().First(c => c.Alias == "Soccer"); + var categorySoccer = _ctx.Set().First(c => c.Alias == "Soccer"); #region product Nike Strike Football - var productNikeStrikeFootball = new Product() + var productNikeStrikeFootball = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10370,25 +10365,25 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single(), + HasTierPrices = true }; - productNikeStrikeFootball.ProductCategories.Add(new ProductCategory() { Category = categorySoccer, DisplayOrder = 1 }); + productNikeStrikeFootball.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); - productNikeStrikeFootball.ProductPictures.Add(new ProductPicture() + productNikeStrikeFootball.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_nike-strike-football.jpg"), "image/png", GetSeName(productNikeStrikeFootball.Name)), DisplayOrder = 1, }); - productNikeStrikeFootball.ProductManufacturers.Add(new ProductManufacturer() + productNikeStrikeFootball.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Nike").Single(), DisplayOrder = 1, }); - //attributes - productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10396,7 +10391,7 @@ public IList Products() // Manufacturer -> Nike SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder ==20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 20).Single() }); - productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10405,30 +10400,15 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 12).Single() }); - #region tierPrieces - productNikeStrikeFootball.TierPrices.Add(new TierPrice() - { - Quantity = 6, - Price = 26.90M - }); - productNikeStrikeFootball.TierPrices.Add(new TierPrice() - { - Quantity = 12, - Price = 24.90M - }); - productNikeStrikeFootball.TierPrices.Add(new TierPrice() - { - Quantity = 24, - Price = 22.90M - }); - productNikeStrikeFootball.HasTierPrices = true; - #endregion tierPrieces + productNikeStrikeFootball.TierPrices.Add(new TierPrice { Quantity = 6, Price = 26.90M }); + productNikeStrikeFootball.TierPrices.Add(new TierPrice { Quantity = 12, Price = 24.90M }); + productNikeStrikeFootball.TierPrices.Add(new TierPrice { Quantity = 24, Price = 22.90M }); #endregion product Nike Strike Football #region product Evopower 5.3 Trainer HS Ball - var productNikeEvoPowerBall = new Product() + var productNikeEvoPowerBall = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10453,22 +10433,21 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productNikeEvoPowerBall.ProductCategories.Add(new ProductCategory() { Category = categorySoccer, DisplayOrder = 1 }); + productNikeEvoPowerBall.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); - productNikeEvoPowerBall.ProductPictures.Add(new ProductPicture() + productNikeEvoPowerBall.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_nike-vopower-53-trainer-hs-ball.jpg"), "image/png", GetSeName(productNikeEvoPowerBall.Name)), DisplayOrder = 1, }); - productNikeEvoPowerBall.ProductManufacturers.Add(new ProductManufacturer() + productNikeEvoPowerBall.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Nike").Single(), DisplayOrder = 1, }); - //attributes - productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10476,7 +10455,7 @@ public IList Products() // Manufacturer -> Nike SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 20).Single() }); - productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10489,7 +10468,7 @@ public IList Products() #region product Torfabrik official game ball - var productTorfabrikOfficialGameBall = new Product() + var productTorfabrikOfficialGameBall = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10514,46 +10493,46 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productTorfabrikOfficialGameBall.ProductCategories.Add(new ProductCategory() { Category = categorySoccer, DisplayOrder = 1 }); + productTorfabrikOfficialGameBall.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture() + productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_white.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "white"), DisplayOrder = 1, }); - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture() + productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_red.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "red"), DisplayOrder = 1, }); - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture() + productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_yellow.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "yellow"), DisplayOrder = 1, }); - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture() + productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_blue.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "blue"), DisplayOrder = 1, }); - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture() + productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_green.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "green"), DisplayOrder = 1, }); - productTorfabrikOfficialGameBall.ProductManufacturers.Add(new ProductManufacturer() + productTorfabrikOfficialGameBall.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Adidas").Single(), DisplayOrder = 1, }); - //attributes - productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + + productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10561,7 +10540,7 @@ public IList Products() // Manufacturer -> Adidas SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 19).Single() }); - productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10570,12 +10549,11 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 5).Single() }); - #endregion Torfabrik official game ball #region product Adidas TANGO SALA BALL - var productAdidasTangoSalaBall = new Product() + var productAdidasTangoSalaBall = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10600,58 +10578,57 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productAdidasTangoSalaBall.ProductCategories.Add(new ProductCategory() { Category = categorySoccer, DisplayOrder = 1 }); + productAdidasTangoSalaBall.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() + productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-white.png"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-white"), DisplayOrder = 1, }); - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() + productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-yellow.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-yellow"), DisplayOrder = 1, }); - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() + productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-red.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-red"), DisplayOrder = 1, }); - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() + productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-green.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-green"), DisplayOrder = 1, }); - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() + productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-gray.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-gray"), DisplayOrder = 1, }); - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() + productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-brown.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-brown"), DisplayOrder = 1, }); - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() + productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-blue.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-blue"), DisplayOrder = 1, }); - productAdidasTangoSalaBall.ProductManufacturers.Add(new ProductManufacturer() + productAdidasTangoSalaBall.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Adidas").Single(), DisplayOrder = 1, }); - //attributes - productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10659,7 +10636,7 @@ public IList Products() // Manufacturer -> Adidas SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 19).Single() }); - productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10674,11 +10651,11 @@ public IList Products() #region category Basketball - var categoryBasketball = this._ctx.Set().First(c => c.Alias == "Basketball"); + var categoryBasketball = _ctx.Set().First(c => c.Alias == "Basketball"); #region Wilson Evolution High School Game Basketball - var productEvolutionHighSchoolGameBasketball = new Product() + var productEvolutionHighSchoolGameBasketball = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10701,49 +10678,33 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single(), + HasTierPrices = true }; - productEvolutionHighSchoolGameBasketball.ProductCategories.Add(new ProductCategory() { Category = categoryBasketball, DisplayOrder = 1 }); + productEvolutionHighSchoolGameBasketball.ProductCategories.Add(new ProductCategory { Category = categoryBasketball, DisplayOrder = 1 }); - productEvolutionHighSchoolGameBasketball.ProductPictures.Add(new ProductPicture() + productEvolutionHighSchoolGameBasketball.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_evolution-high-school-game-basketball.jpg"), "image/png", GetSeName(productEvolutionHighSchoolGameBasketball.Name)), DisplayOrder = 1, }); - productEvolutionHighSchoolGameBasketball.ProductManufacturers.Add(new ProductManufacturer() + productEvolutionHighSchoolGameBasketball.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Adidas").Single(), DisplayOrder = 1, }); - #region tierPrieces - productEvolutionHighSchoolGameBasketball.TierPrices.Add(new TierPrice() - { - Quantity = 6, - Price = 24.90M - }); - productEvolutionHighSchoolGameBasketball.TierPrices.Add(new TierPrice() - { - Quantity = 12, - Price = 22.90M - }); - productEvolutionHighSchoolGameBasketball.TierPrices.Add(new TierPrice() - { - Quantity = 24, - Price = 20.90M - }); - productEvolutionHighSchoolGameBasketball.HasTierPrices = true; - #endregion tierPrieces - + productEvolutionHighSchoolGameBasketball.TierPrices.Add(new TierPrice { Quantity = 6, Price = 24.90M }); + productEvolutionHighSchoolGameBasketball.TierPrices.Add(new TierPrice { Quantity = 12, Price = 22.90M }); + productEvolutionHighSchoolGameBasketball.TierPrices.Add(new TierPrice { Quantity = 24, Price = 20.90M }); #endregion Wilson Evolution High School Game Basketball - #region All Court Basketball - var productAllCourtBasketball = new Product() + var productAllCourtBasketball = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10768,15 +10729,15 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productAllCourtBasketball.ProductCategories.Add(new ProductCategory() { Category = categoryBasketball, DisplayOrder = 1 }); + productAllCourtBasketball.ProductCategories.Add(new ProductCategory { Category = categoryBasketball, DisplayOrder = 1 }); - productAllCourtBasketball.ProductPictures.Add(new ProductPicture() + productAllCourtBasketball.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_all-court-basketball.png"), "image/png", GetSeName(productAllCourtBasketball.Name)), DisplayOrder = 1, }); - productAllCourtBasketball.ProductManufacturers.Add(new ProductManufacturer() + productAllCourtBasketball.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Adidas").Single(), DisplayOrder = 1, @@ -10788,11 +10749,11 @@ public IList Products() #region category sunglasses - var categorySunglasses = this._ctx.Set().First(c => c.Alias == "Sunglasses"); + var categorySunglasses = _ctx.Set().First(c => c.Alias == "Sunglasses"); #region product Top bar - var productRayBanTopBar = new Product() + var productRayBanTopBar = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10817,27 +10778,27 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productRayBanTopBar.ProductCategories.Add(new ProductCategory() { Category = categorySunglasses, DisplayOrder = 1 }); + productRayBanTopBar.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); - productRayBanTopBar.ProductPictures.Add(new ProductPicture() + productRayBanTopBar.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_RayBanTopBar_1.jpg"), "image/png", GetSeName(productRayBanTopBar.Name)), DisplayOrder = 1, }); - productRayBanTopBar.ProductPictures.Add(new ProductPicture() + productRayBanTopBar.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_RayBanTopBar_2.jpg"), "image/png", GetSeName(productRayBanTopBar.Name)), DisplayOrder = 1, }); - productRayBanTopBar.ProductPictures.Add(new ProductPicture() + productRayBanTopBar.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_RayBanTopBar_3.jpg"), "image/png", GetSeName(productRayBanTopBar.Name)), DisplayOrder = 1, }); - productRayBanTopBar.ProductManufacturers.Add(new ProductManufacturer() + productRayBanTopBar.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Ray-Ban").Single(), DisplayOrder = 1, @@ -10847,7 +10808,7 @@ public IList Products() #region product ORIGINAL WAYFARER AT COLLECTION - var productOriginalWayfarer = new Product() + var productOriginalWayfarer = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10872,45 +10833,45 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productOriginalWayfarer.ProductCategories.Add(new ProductCategory() { Category = categorySunglasses, DisplayOrder = 1 }); + productOriginalWayfarer.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); - productOriginalWayfarer.ProductPictures.Add(new ProductPicture() + productOriginalWayfarer.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_1.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_blue-gray-classic-black"), DisplayOrder = 1, }); - productOriginalWayfarer.ProductPictures.Add(new ProductPicture() + productOriginalWayfarer.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_2.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_blue-gray-classic-black"), DisplayOrder = 1, }); - productOriginalWayfarer.ProductPictures.Add(new ProductPicture() + productOriginalWayfarer.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_3.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_gray-course-black"), DisplayOrder = 1, }); - productOriginalWayfarer.ProductPictures.Add(new ProductPicture() + productOriginalWayfarer.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_4.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_brown-course-havana"), DisplayOrder = 1, }); - productOriginalWayfarer.ProductPictures.Add(new ProductPicture() + productOriginalWayfarer.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_5.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_green-classic-havana-black"), DisplayOrder = 1, }); - productOriginalWayfarer.ProductPictures.Add(new ProductPicture() + productOriginalWayfarer.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_6.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_blue-gray-classic-black"), DisplayOrder = 1, }); - productOriginalWayfarer.ProductManufacturers.Add(new ProductManufacturer() + productOriginalWayfarer.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Ray-Ban").Single(), DisplayOrder = 1, @@ -10920,7 +10881,7 @@ public IList Products() #region product Radar EV Prizm Sports Sunglasses - var productRadarEVPrizmSportsSunglasses = new Product() + var productRadarEVPrizmSportsSunglasses = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10945,15 +10906,15 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productRadarEVPrizmSportsSunglasses.ProductCategories.Add(new ProductCategory() { Category = categorySunglasses, DisplayOrder = 1 }); + productRadarEVPrizmSportsSunglasses.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); - productRadarEVPrizmSportsSunglasses.ProductPictures.Add(new ProductPicture() + productRadarEVPrizmSportsSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_radar_ev_prizm.jpg"), "image/png", GetSeName(productRadarEVPrizmSportsSunglasses.Name)), DisplayOrder = 1, }); - productRadarEVPrizmSportsSunglasses.ProductManufacturers.Add(new ProductManufacturer() + productRadarEVPrizmSportsSunglasses.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Oakley").Single(), DisplayOrder = 1, @@ -10963,7 +10924,7 @@ public IList Products() #region product Custom Flak Sunglasses - var productCustomFlakSunglasses = new Product() + var productCustomFlakSunglasses = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10988,255 +10949,255 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productCustomFlakSunglasses.ProductCategories.Add(new ProductCategory() { Category = categorySunglasses, DisplayOrder = 1 }); + productCustomFlakSunglasses.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlakSunglasses.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlakSunglasses.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "productCustomFlakSunglasses_black_white.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "productCustomFlakSunglasses_black_white.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_gray.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_clear.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_jadeiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_positiverediridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_rubyiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_sapphireiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_violetiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_24kiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_fireiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_24kiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_clear.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_fireiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_gray.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_jadeiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_positiverediridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_rubyiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_sapphireiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_violetiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_24kiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_clear.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_fireiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_gray.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_jadeiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_rubyiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_sapphireiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_violetiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_positiverediridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_24kiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_clear.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_fireiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_gray.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_jadeiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_positiverediridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_rubyiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_sapphireiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_violetiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_24kiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_clear.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_fireiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_gray.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_jadeiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_positiverediridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_rubyiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_sapphireiridium.jpg"), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_violetiridium.jpg"), DisplayOrder = 1, @@ -11244,7 +11205,7 @@ public IList Products() - productCustomFlakSunglasses.ProductManufacturers.Add(new ProductManufacturer() + productCustomFlakSunglasses.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Oakley").Single(), DisplayOrder = 1, @@ -11258,11 +11219,11 @@ public IList Products() #region category apple - var categoryApple = this._ctx.Set().First(c => c.Alias == "Apple"); + var categoryApple = _ctx.Set().First(c => c.Alias == "Apple"); #region product iphone plus - var productIphoneplus = new Product() + var productIphoneplus = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11289,52 +11250,51 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productIphoneplus.ProductCategories.Add(new ProductCategory() { Category = categoryApple, DisplayOrder = 1 }); + productIphoneplus.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); - productIphoneplus.ProductPictures.Add(new ProductPicture() + productIphoneplus.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_all_colors.jpg"), "image/png", GetSeName(productIphoneplus.Name)), DisplayOrder = 1, }); - productIphoneplus.ProductPictures.Add(new ProductPicture() + productIphoneplus.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphoneplus_1.jpg"), "image/png", GetSeName(productIphoneplus.Name)), DisplayOrder = 2, }); - productIphoneplus.ProductPictures.Add(new ProductPicture() + productIphoneplus.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_red.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-red"), DisplayOrder = 2, }); - productIphoneplus.ProductPictures.Add(new ProductPicture() + productIphoneplus.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_silver.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-silver"), DisplayOrder = 2, }); - productIphoneplus.ProductPictures.Add(new ProductPicture() + productIphoneplus.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_black.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-black"), DisplayOrder = 2, }); - productIphoneplus.ProductPictures.Add(new ProductPicture() + productIphoneplus.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_rose.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-rose"), DisplayOrder = 2, }); - productIphoneplus.ProductPictures.Add(new ProductPicture() + productIphoneplus.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_gold.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-gold"), DisplayOrder = 2, }); - //attributes - productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11343,7 +11303,7 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11351,7 +11311,7 @@ public IList Products() // storage capacity -> 64gb SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 27).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11359,7 +11319,7 @@ public IList Products() // storage capacity -> 128gb SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 27).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11372,7 +11332,7 @@ public IList Products() #region product Watch Series 2 - var productWatchSeries2 = new Product() + var productWatchSeries2 = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = false, @@ -11398,28 +11358,28 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productWatchSeries2.ProductCategories.Add(new ProductCategory() { Category = categoryApple, DisplayOrder = 1 }); + productWatchSeries2.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); - productWatchSeries2.ProductPictures.Add(new ProductPicture() + productWatchSeries2.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_watchseries2_1.jpg"), "image/png", GetSeName(productWatchSeries2.Name)), DisplayOrder = 1, }); - productWatchSeries2.ProductPictures.Add(new ProductPicture() + productWatchSeries2.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_watchseries2_2.jpg"), "image/png", GetSeName(productWatchSeries2.Name)), DisplayOrder = 2, }); - productWatchSeries2.ProductManufacturers.Add(new ProductManufacturer() + productWatchSeries2.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), DisplayOrder = 1, }); //attributes - productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11428,7 +11388,7 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() }); - productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11437,7 +11397,7 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 27).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11446,12 +11406,11 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 5).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() }); - #endregion product Watch Series 2 #region product Airpods - var productAirpods = new Product() + var productAirpods = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11476,45 +11435,45 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productAirpods.ProductCategories.Add(new ProductCategory() { Category = categoryApple, DisplayOrder = 1 }); + productAirpods.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); - productAirpods.ProductPictures.Add(new ProductPicture() + productAirpods.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_white.jpg"), "image/png", GetSeName(productAirpods.Name) + "-white"), DisplayOrder = 1, }); - productAirpods.ProductPictures.Add(new ProductPicture() + productAirpods.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_turquoise.jpg"), "image/png", GetSeName(productAirpods.Name) + "-turquoise"), DisplayOrder = 2, }); - productAirpods.ProductPictures.Add(new ProductPicture() + productAirpods.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_lightblue.jpg"), "image/png", GetSeName(productAirpods.Name) + "-lightblue"), DisplayOrder = 3, }); - productAirpods.ProductPictures.Add(new ProductPicture() + productAirpods.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_rose.jpg"), "image/png", GetSeName(productAirpods.Name) + "-rose"), DisplayOrder = 4, }); - productAirpods.ProductPictures.Add(new ProductPicture() + productAirpods.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_gold.jpg"), "image/png", GetSeName(productAirpods.Name) + "-gold"), DisplayOrder = 5, }); - productAirpods.ProductPictures.Add(new ProductPicture() + productAirpods.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_mint.jpg"), "image/png", GetSeName(productAirpods.Name) + "-mint"), DisplayOrder = 6, }); - productAirpods.ProductManufacturers.Add(new ProductManufacturer() + productAirpods.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), DisplayOrder = 7, @@ -11524,7 +11483,7 @@ public IList Products() #region product Ultimate Apple Pro Hipster Bundle - var productAppleProHipsterBundle = new Product() + var productAppleProHipsterBundle = new Product { ProductType = ProductType.BundledProduct, VisibleIndividually = true, @@ -11553,39 +11512,39 @@ public IList Products() BundlePerItemShoppingCart = true }; - productAppleProHipsterBundle.ProductCategories.Add(new ProductCategory() { Category = categoryApple, DisplayOrder = 1 }); + productAppleProHipsterBundle.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() + productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ultimate-apple-pro-hipster-bundle.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), DisplayOrder = 1, }); - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() + productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_white.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), DisplayOrder = 2, }); - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() + productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_watchseries2_2.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), DisplayOrder = 2, }); - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() + productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphoneplus_2.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), DisplayOrder = 2, }); - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() + productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_apple.png"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), DisplayOrder = 2, }); - productAppleProHipsterBundle.ProductManufacturers.Add(new ProductManufacturer() + productAppleProHipsterBundle.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), DisplayOrder = 1, @@ -11595,7 +11554,7 @@ public IList Products() #region product 9,7 iPad - var product97ipad = new Product() + var product97ipad = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11625,84 +11584,82 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - product97ipad.ProductCategories.Add(new ProductCategory() { Category = categoryApple, DisplayOrder = 1 }); + product97ipad.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); - #region pictures - product97ipad.ProductPictures.Add(new ProductPicture() + product97ipad.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ipad_1.jpg"), "image/png", GetSeName(product97ipad.Name)), DisplayOrder = 1, }); - product97ipad.ProductPictures.Add(new ProductPicture() + product97ipad.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ipad_2.jpg"), "image/png", GetSeName(product97ipad.Name)), DisplayOrder = 2, }); - product97ipad.ProductPictures.Add(new ProductPicture() + product97ipad.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-yellow.jpg"), "image/png", GetSeName(product97ipad.Name) + "-yellow"), DisplayOrder = 2, }); - product97ipad.ProductPictures.Add(new ProductPicture() + product97ipad.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-turquoise.jpg"), "image/png", GetSeName(product97ipad.Name) + "-turquoise"), DisplayOrder = 2, }); - product97ipad.ProductPictures.Add(new ProductPicture() + product97ipad.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-lightblue.jpg"), "image/png", GetSeName(product97ipad.Name) + "-lightblue"), DisplayOrder = 2, }); - product97ipad.ProductPictures.Add(new ProductPicture() + product97ipad.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-purple.jpg"), "image/png", GetSeName(product97ipad.Name) + "-purple"), DisplayOrder = 2, }); - product97ipad.ProductPictures.Add(new ProductPicture() + product97ipad.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-mint.jpg"), "image/png", GetSeName(product97ipad.Name) + "-mint"), DisplayOrder = 2, }); - product97ipad.ProductPictures.Add(new ProductPicture() + product97ipad.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-rose.jpg"), "image/png", GetSeName(product97ipad.Name) + "-rose"), DisplayOrder = 2, }); - product97ipad.ProductPictures.Add(new ProductPicture() + product97ipad.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-spacegray.jpg"), "image/png", GetSeName(product97ipad.Name) + "-spacegray"), DisplayOrder = 2, }); - product97ipad.ProductPictures.Add(new ProductPicture() + product97ipad.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-gold.jpg"), "image/png", GetSeName(product97ipad.Name) + "-gold"), DisplayOrder = 2, }); - product97ipad.ProductPictures.Add(new ProductPicture() + product97ipad.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-silver.jpg"), "image/png", GetSeName(product97ipad.Name) + "-silver"), DisplayOrder = 2, }); - #endregion pictures - product97ipad.ProductManufacturers.Add(new ProductManufacturer() + + product97ipad.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), DisplayOrder = 1, }); - #region attributes - //attributes - product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + + product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11711,7 +11668,7 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11719,7 +11676,7 @@ public IList Products() // storage capacity -> 64gb SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 27).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11727,7 +11684,7 @@ public IList Products() // storage capacity -> 128gb SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 27).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11735,20 +11692,18 @@ public IList Products() // operating system -> ios SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 5).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() }); - #endregion attributes #endregion product 9,7 iPad - #endregion category apple #region category Gift Cards - var categoryGiftCards = this._ctx.Set().First(c => c.Alias == "Gift Cards"); + var categoryGiftCards = _ctx.Set().First(c => c.Alias == "Gift Cards"); #region product10GiftCard - var product10GiftCard = new Product() + var product10GiftCard = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11773,24 +11728,18 @@ public IList Products() }; - product10GiftCard.ProductCategories.Add(new ProductCategory() { Category = categoryGiftCards, DisplayOrder = 1 }); + product10GiftCard.ProductCategories.Add(new ProductCategory { Category = categoryGiftCards, DisplayOrder = 1 }); - //var productTag = _productTagRepository.Table.Where(pt => pt.Name == "gift").FirstOrDefault(); - //productTag.ProductCount++; - //productTag.Products.Add(product5GiftCard); - //_productTagRepository.Update(productTag); - - product10GiftCard.ProductPictures.Add(new ProductPicture() + product10GiftCard.ProductPictures.Add(new ProductPicture { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_10.png"), "image/png", GetSeName(product10GiftCard.Name)), - //DisplayOrder = 1, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_10.png"), "image/png", GetSeName(product10GiftCard.Name)) }); #endregion product10GiftCard #region product25GiftCard - var product25GiftCard = new Product() + var product25GiftCard = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11815,19 +11764,18 @@ public IList Products() DisplayOrder = 2 }; - product25GiftCard.ProductCategories.Add(new ProductCategory() { Category = categoryGiftCards, DisplayOrder = 1 }); + product25GiftCard.ProductCategories.Add(new ProductCategory { Category = categoryGiftCards, DisplayOrder = 1 }); - product25GiftCard.ProductPictures.Add(new ProductPicture() + product25GiftCard.ProductPictures.Add(new ProductPicture { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_25.png"), "image/png", GetSeName(product25GiftCard.Name)), - //DisplayOrder = 2, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_25.png"), "image/png", GetSeName(product25GiftCard.Name)) }); #endregion product25GiftCard #region product50GiftCard - var product50GiftCard = new Product() + var product50GiftCard = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11852,19 +11800,18 @@ public IList Products() DisplayOrder = 3 }; - product50GiftCard.ProductCategories.Add(new ProductCategory() { Category = categoryGiftCards, DisplayOrder = 1 }); + product50GiftCard.ProductCategories.Add(new ProductCategory { Category = categoryGiftCards, DisplayOrder = 1 }); - product50GiftCard.ProductPictures.Add(new ProductPicture() + product50GiftCard.ProductPictures.Add(new ProductPicture { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_50.png"), "image/png", GetSeName(product50GiftCard.Name)), - //DisplayOrder = 3, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_50.png"), "image/png", GetSeName(product50GiftCard.Name)) }); #endregion product50GiftCard #region product100GiftCard - var product100GiftCard = new Product() + var product100GiftCard = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11889,12 +11836,11 @@ public IList Products() DisplayOrder = 4, }; - product100GiftCard.ProductCategories.Add(new ProductCategory() { Category = categoryGiftCards, DisplayOrder = 1 }); + product100GiftCard.ProductCategories.Add(new ProductCategory { Category = categoryGiftCards, DisplayOrder = 1 }); - product100GiftCard.ProductPictures.Add(new ProductPicture() + product100GiftCard.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_100.png"), "image/png", GetSeName(product100GiftCard.Name)), - //DisplayOrder = 4, }); #endregion product100GiftCard @@ -11903,13 +11849,13 @@ public IList Products() #region category books - var categorySpiegelBestseller = this._ctx.Set().First(c => c.Alias == "SPIEGEL-Bestseller"); - var categoryCookAndEnjoy = this._ctx.Set().First(c => c.Alias == "Cook and enjoy"); - var categoryBooks = this._ctx.Set().First(c => c.Alias == "Books"); + var categorySpiegelBestseller = _ctx.Set().First(c => c.Alias == "SPIEGEL-Bestseller"); + var categoryCookAndEnjoy = _ctx.Set().First(c => c.Alias == "Cook and enjoy"); + var categoryBooks = _ctx.Set().First(c => c.Alias == "Books"); #region productBooksUberMan - var productBooksUberMan = new Product() + var productBooksUberMan = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11931,42 +11877,41 @@ public IList Products() IsShipEnabled = true }; - productBooksUberMan.ProductCategories.Add(new ProductCategory() { Category = categorySpiegelBestseller, DisplayOrder = 1 }); + productBooksUberMan.ProductCategories.Add(new ProductCategory { Category = categorySpiegelBestseller, DisplayOrder = 1 }); - //pictures - productBooksUberMan.ProductPictures.Add(new ProductPicture() + productBooksUberMan.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000932_uberman-der-roman.jpeg"), "image/jpeg", GetSeName(productBooksUberMan.Name)), DisplayOrder = 1, }); - //attributes - productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 3, SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 3, SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 7).Single() }); - productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 3, SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); + #endregion productBooksUberMan #region productBooksGefangeneDesHimmels - var productBooksGefangeneDesHimmels = new Product() + var productBooksGefangeneDesHimmels = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11989,17 +11934,15 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() }; - productBooksGefangeneDesHimmels.ProductCategories.Add(new ProductCategory() { Category = categorySpiegelBestseller, DisplayOrder = 1 }); + productBooksGefangeneDesHimmels.ProductCategories.Add(new ProductCategory { Category = categorySpiegelBestseller, DisplayOrder = 1 }); - //pictures - productBooksGefangeneDesHimmels.ProductPictures.Add(new ProductPicture() + productBooksGefangeneDesHimmels.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000935_der-gefangene-des-himmels-roman_300.jpeg"), "image/jpeg", GetSeName(productBooksGefangeneDesHimmels.Name)), DisplayOrder = 1, }); - //attributes - productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12008,7 +11951,7 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12016,7 +11959,7 @@ public IList Products() // Category -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 7).Single() }); - productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12029,7 +11972,7 @@ public IList Products() #region productBooksBestGrillingRecipes - var productBooksBestGrillingRecipes = new Product() + var productBooksBestGrillingRecipes = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12052,17 +11995,15 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() }; - productBooksBestGrillingRecipes.ProductCategories.Add(new ProductCategory() { Category = categoryCookAndEnjoy, DisplayOrder = 1 }); + productBooksBestGrillingRecipes.ProductCategories.Add(new ProductCategory { Category = categoryCookAndEnjoy, DisplayOrder = 1 }); - //pictures - productBooksBestGrillingRecipes.ProductPictures.Add(new ProductPicture() + productBooksBestGrillingRecipes.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_bestgrillingrecipes.jpg"), "image/jpeg", GetSeName(productBooksBestGrillingRecipes.Name)), DisplayOrder = 1, }); - //attributes - productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12070,7 +12011,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12078,7 +12019,7 @@ public IList Products() // Category -> cook & bake SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 8).Single() }); - productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12091,7 +12032,7 @@ public IList Products() #region productBooksCookingForTwo - var productBooksCookingForTwo = new Product() + var productBooksCookingForTwo = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12114,17 +12055,15 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 1).Single() }; - productBooksCookingForTwo.ProductCategories.Add(new ProductCategory() { Category = categoryCookAndEnjoy, DisplayOrder = 1 }); + productBooksCookingForTwo.ProductCategories.Add(new ProductCategory { Category = categoryCookAndEnjoy, DisplayOrder = 1 }); - //pictures - productBooksCookingForTwo.ProductPictures.Add(new ProductPicture() + productBooksCookingForTwo.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_cookingfortwo.jpg"), "image/jpeg", GetSeName(productBooksCookingForTwo.Name)), DisplayOrder = 1, }); - //attributes - productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12132,7 +12071,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12140,7 +12079,7 @@ public IList Products() // Category -> cook & bake SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 8).Single() }); - productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12153,7 +12092,7 @@ public IList Products() #region productBooksAutosDerSuperlative - var productBooksAutosDerSuperlative = new Product() + var productBooksAutosDerSuperlative = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12176,17 +12115,15 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productBooksAutosDerSuperlative.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); + productBooksAutosDerSuperlative.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); - //pictures - productBooksAutosDerSuperlative.ProductPictures.Add(new ProductPicture() + productBooksAutosDerSuperlative.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000944_autos-der-superlative-die-starksten-die-ersten-die-schonsten-die-schnellsten.jpeg"), "image/jpeg", GetSeName(productBooksAutosDerSuperlative.Name)), DisplayOrder = 1, }); - //attributes - productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12194,7 +12131,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12202,7 +12139,7 @@ public IList Products() // Category -> cars SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() }); - productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12211,12 +12148,11 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - #endregion productBooksAutosDerSuperlative #region productBooksBildatlasMotorraeder - var productBooksBildatlasMotorraeder = new Product() + var productBooksBildatlasMotorraeder = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12239,17 +12175,15 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() }; - productBooksBildatlasMotorraeder.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); + productBooksBildatlasMotorraeder.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); - //pictures - productBooksBildatlasMotorraeder.ProductPictures.Add(new ProductPicture() + productBooksBildatlasMotorraeder.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000942_bildatlas-motorrader-mit-mehr-als-350-brillanten-abbildungen.jpeg"), "image/jpeg", GetSeName(productBooksBildatlasMotorraeder.Name)), DisplayOrder = 1, }); - //attributes - productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12257,7 +12191,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12265,7 +12199,7 @@ public IList Products() // Category -> non-fiction SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() }); - productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12278,7 +12212,7 @@ public IList Products() #region productBooksAutoBuch - var productBooksAutoBuch = new Product() + var productBooksAutoBuch = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12301,17 +12235,15 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() }; - productBooksAutoBuch.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); + productBooksAutoBuch.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); - //pictures - productBooksAutoBuch.ProductPictures.Add(new ProductPicture() + productBooksAutoBuch.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000947_das-auto-buch-die-grose-chronik-mit-uber-1200-modellen_300.jpeg"), "image/jpeg", GetSeName(productBooksAutoBuch.Name)), DisplayOrder = 1, }); - //attributes - productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12319,7 +12251,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12327,7 +12259,7 @@ public IList Products() // Category -> non-fiction SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() }); - productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12340,7 +12272,7 @@ public IList Products() #region productBooksFastCars - var productBooksFastCars = new Product() + var productBooksFastCars = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12363,17 +12295,17 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() }; - productBooksFastCars.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); + productBooksFastCars.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); //pictures - productBooksFastCars.ProductPictures.Add(new ProductPicture() + productBooksFastCars.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000946_fast-cars-bildkalender-2013_300.jpeg"), "image/jpeg", GetSeName(productBooksFastCars.Name)), DisplayOrder = 1, }); //attributes - productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12381,7 +12313,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12389,7 +12321,7 @@ public IList Products() // Category -> cars SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() }); - productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12402,7 +12334,7 @@ public IList Products() #region productBooksMotorradAbenteuer - var productBooksMotorradAbenteuer = new Product() + var productBooksMotorradAbenteuer = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12425,17 +12357,17 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 1).Single() }; - productBooksMotorradAbenteuer.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); + productBooksMotorradAbenteuer.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); //pictures - productBooksMotorradAbenteuer.ProductPictures.Add(new ProductPicture() + productBooksMotorradAbenteuer.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000943_motorrad-abenteuer-fahrtechnik-fur-reise-enduros.jpeg"), "image/jpeg", GetSeName(productBooksMotorradAbenteuer.Name)), DisplayOrder = 1, }); //attributes - productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12443,7 +12375,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12451,7 +12383,7 @@ public IList Products() // Category -> cars SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() }); - productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12464,319 +12396,13 @@ public IList Products() #endregion category books - #region computer - - // var categoryComputer = this._ctx.Set().First(c => c.Alias == "Computers"); - // var categoryNotebooks = this._ctx.Set().First(c => c.Alias == "Notebooks"); - // var categoryDesktops = this._ctx.Set().First(c => c.Alias == "Desktops"); - - //#region productComputerDellInspiron23 - - //var productComputerDellInspiron23 = new Product() - //{ - // ProductType = ProductType.SimpleProduct, - // VisibleIndividually = true, - // Name = "Dell Inspiron One 23", - // ShortDescription = "This 58 cm (23'')-All-in-One PC with Full HD, Windows 8 and powerful Intel ® Core ™ processor third generation allows practical interaction with a touch screen.", - // FullDescription = "

Ultra high performance all-in-one i7 PC with Windows 8, Intel ® Core ™ processor, huge 2TB hard drive and Blu-Ray drive.

Intel® Core™ i7-3770S Processor ( 3,1 GHz, 6 MB Cache)
Windows 8 64bit , english
8 GB1 DDR3 SDRAM at 1600 MHz
2 TB-Serial ATA-Harddisk (7.200 rot/min)
1GB AMD Radeon HD 7650

", - // Sku = "P-1012", - // ProductTemplateId = productTemplateSimple.Id, - // AllowCustomerReviews = true, - // Published = true, - // MetaTitle = "Dell Inspiron One 23", - // Price = 589.00M, - // ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 10000, - // StockQuantity = 10000, - // NotifyAdminForQuantityBelow = 1, - // AllowBackInStockSubscriptions = false, - // IsShipEnabled = true, - // DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - //}; - - // productComputerDellInspiron23.ProductCategories.Add(new ProductCategory() { Category = categoryComputer, DisplayOrder = 1 }); - // productComputerDellInspiron23.ProductCategories.Add(new ProductCategory() { Category = categoryDesktops, DisplayOrder = 1 }); - - //#region pictures - - ////pictures - //productComputerDellInspiron23.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_dellinspiron23.png"), "image/png", GetSeName(productComputerDellInspiron23.Name)), - // DisplayOrder = 1, - //}); - //productComputerDellInspiron23.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000954_dell-inspiron-one-23.jpeg"), "image/jpeg", GetSeName(productComputerDellInspiron23.Name)), - // DisplayOrder = 2, - //}); - //productComputerDellInspiron23.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000956_dell-inspiron-one-23.jpeg"), "image/jpeg", GetSeName(productComputerDellInspiron23.Name)), - // DisplayOrder = 3, - //}); - - //#endregion pictures - - //#region manufacturer - - ////manufacturer - //productComputerDellInspiron23.ProductManufacturers.Add(new ProductManufacturer() - //{ - // Manufacturer = _ctx.Set().Where(c => c.Name == "Dell").Single(), - // DisplayOrder = 1, - //}); - - // #endregion manufacturer - - // #region SpecificationAttributes - // //attributes - // productComputerDellInspiron23.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - // { - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 1, - // // CPU -> Intel - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 1).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() - // }); - // productComputerDellInspiron23.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 2, - // // RAM -> 4 GB - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 4).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - //}); - //productComputerDellInspiron23.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 3, - // // Harddisk-Typ / HDD - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 16).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - //}); - //productComputerDellInspiron23.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 4, - // // Harddisk-Capacity / 750 GB - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 3).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() - //}); - //productComputerDellInspiron23.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 5, - // // OS / Windows 7 32 Bit - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 5).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - //}); - //#endregion SpecificationAttributes - - //#endregion productComputerDellInspiron23 - - //#region productComputerDellOptiplex3010 - - //var productComputerDellOptiplex3010 = new Product() - //{ - // ProductType = ProductType.SimpleProduct, - // VisibleIndividually = true, - // Name = "Dell Optiplex 3010 DT Base", - // ShortDescription = "SPECIAL OFFER: Extra 50 € discount on all Dell OptiPlex desktops from a value of € 549. Online Coupon:? W8DWQ0ZRKTM1, valid until 04/12/2013.", - // FullDescription = "

Also included in this system include To change these selections, the

1 Year Basic Service - On-Site NBD - No Upgrade Selected
No asset tag required

The following options are default selections included with your order.
German (QWERTY) Dell KB212-B Multimedia USB Keyboard Black
X11301001
WINDOWS LIVE
OptiPlex ™ order - Germany
OptiPlex ™ Intel ® Core ™ i3 sticker
Optical software is not required, operating system software sufficiently

", - // Sku = "P-1013", - // ProductTemplateId = productTemplateSimple.Id, - // AllowCustomerReviews = true, - // Published = true, - // MetaTitle = "Dell Optiplex 3010 DT Base", - // Price = 419.00M, - // ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 10000, - // StockQuantity = 10000, - // NotifyAdminForQuantityBelow = 1, - // AllowBackInStockSubscriptions = false, - // IsShipEnabled = true, - // DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - //}; - - // productComputerDellOptiplex3010.ProductCategories.Add(new ProductCategory() { Category = categoryComputer, DisplayOrder = 1 }); - // productComputerDellOptiplex3010.ProductCategories.Add(new ProductCategory() { Category = categoryDesktops, DisplayOrder = 1 }); - - //#region pictures - - ////pictures - //productComputerDellOptiplex3010.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_dellinspiron23.png"), "image/png", GetSeName(productComputerDellOptiplex3010.Name)), - // DisplayOrder = 1, - //}); - //productComputerDellOptiplex3010.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000954_dell-inspiron-one-23.jpeg"), "image/jpeg", GetSeName(productComputerDellOptiplex3010.Name)), - // DisplayOrder = 2, - //}); - //productComputerDellOptiplex3010.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000956_dell-inspiron-one-23.jpeg"), "image/jpeg", GetSeName(productComputerDellOptiplex3010.Name)), - // DisplayOrder = 3, - //}); - - //#endregion pictures - - //#region manufacturer - - ////manufacturer - //productComputerDellOptiplex3010.ProductManufacturers.Add(new ProductManufacturer() - //{ - // Manufacturer = _ctx.Set().Where(c => c.Name == "Dell").Single(), - // DisplayOrder = 1, - //}); - - //#endregion manufacturer - - //#region SpecificationAttributes - ////attributes - //productComputerDellOptiplex3010.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 1, - // // CPU -> Intel - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 1).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() - //}); - //productComputerDellOptiplex3010.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 2, - // // RAM -> 4 GB - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 4).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() - //}); - //productComputerDellOptiplex3010.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 3, - // // Harddisk-Typ / HDD - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 16).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() - //}); - //productComputerDellOptiplex3010.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 4, - // // Harddisk-Capacity / 750 GB - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 3).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() - //}); - //productComputerDellOptiplex3010.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 5, - // // OS / Windows 7 32 Bit - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 5).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 4).Single() - //}); - //#endregion SpecificationAttributes - - //#endregion productComputerDellOptiplex3010 - - //#region productComputerAcerAspireOne - //var productComputerAcerAspireOne = new Product() - //{ - // ProductType = ProductType.SimpleProduct, - // VisibleIndividually = true, - // Name = "Acer Aspire One 8.9\" Mini-Notebook Case - (Black)", - // ShortDescription = "Acer Aspire One 8.9\" Mini-Notebook and 6 Cell Battery model (AOA150-1447)", - // FullDescription = "

Acer Aspire One 8.9" Memory Foam Pouch is the perfect fit for Acer Aspire One 8.9". This pouch is made out of premium quality shock absorbing memory form and it provides extra protection even though case is very light and slim. This pouch is water resistant and has internal supporting bands for Acer Aspire One 8.9". Made In Korea.

", - // Sku = "P-1014", - // ProductTemplateId = productTemplateSimple.Id, - // AllowCustomerReviews = true, - // Published = true, - // MetaTitle = "Acer Aspire One 8.9", - // ShowOnHomePage = true, - // Price = 210.6M, - // IsShipEnabled = true, - // Weight = 2, - // Length = 2, - // Width = 2, - // Height = 3, - // ManageInventoryMethod = ManageInventoryMethod.ManageStock, - // StockQuantity = 10000, - // NotifyAdminForQuantityBelow = 1, - // AllowBackInStockSubscriptions = false, - // DisplayStockAvailability = true, - // LowStockActivity = LowStockActivity.DisableBuyButton, - // BackorderMode = BackorderMode.NoBackorders, - // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 10000, - // DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - //}; - - // productComputerAcerAspireOne.ProductCategories.Add(new ProductCategory() { Category = categoryComputer, DisplayOrder = 1 }); - // productComputerAcerAspireOne.ProductCategories.Add(new ProductCategory() { Category = categoryNotebooks, DisplayOrder = 1 }); - - //#region manufacturer - - ////manufacturer - //productComputerAcerAspireOne.ProductManufacturers.Add(new ProductManufacturer() - //{ - // Manufacturer = _ctx.Set().Where(c => c.Name == "Acer").Single(), - // DisplayOrder = 1, - //}); - - //#endregion manufacturer - - //#region tierPrieces - //productComputerAcerAspireOne.TierPrices.Add(new TierPrice() - //{ - // Quantity = 2, - // Price = 205 - //}); - //productComputerAcerAspireOne.TierPrices.Add(new TierPrice() - //{ - // Quantity = 5, - // Price = 189 - //}); - //productComputerAcerAspireOne.TierPrices.Add(new TierPrice() - //{ - // Quantity = 10, - // Price = 155 - //}); - //productComputerAcerAspireOne.HasTierPrices = true; - //#endregion tierPrieces - - //#region pictures - //productComputerAcerAspireOne.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_aceraspiresl1500.png"), "image/png", GetSeName(productComputerAcerAspireOne.Name)), - // DisplayOrder = 1, - //}); - //productComputerAcerAspireOne.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "01-12Hand_Aspire1.jpg"), "image/jpeg", GetSeName(productComputerAcerAspireOne.Name)), - // DisplayOrder = 2, - //}); - //productComputerAcerAspireOne.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "58_00007561.jpg"), "image/jpeg", GetSeName(productComputerAcerAspireOne.Name)), - // DisplayOrder = 3, - //}); - - //#endregion tierPrieces - - //#endregion productComputerAcerAspireOne - - #endregion computer - #region Instant Download Music / Digital Products - var categoryDigitalProducts = this._ctx.Set().First(c => c.Alias == "Digital Products"); + var categoryDigitalProducts = _ctx.Set().First(c => c.Alias == "Digital Products"); #region product Books Stone of the Wise - var productBooksStoneOfTheWise = new Product() + var productBooksStoneOfTheWise = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12891,7 +12517,7 @@ public IList Products() #region Beethoven für Elise - var productInstantDownloadBeethoven = new Product() + var productInstantDownloadBeethoven = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12947,12 +12573,11 @@ public IList Products() #region watches - var categoryWatches = this._ctx.Set().First(c => c.Alias == "Watches"); - + var categoryWatches = _ctx.Set().First(c => c.Alias == "Watches"); #region productTRANSOCEANCHRONOGRAPH - var productTRANSOCEANCHRONOGRAPH = new Product() + var productTRANSOCEANCHRONOGRAPH = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12977,33 +12602,21 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productTRANSOCEANCHRONOGRAPH.ProductCategories.Add(new ProductCategory() { Category = categoryWatches, DisplayOrder = 1 }); + productTRANSOCEANCHRONOGRAPH.ProductCategories.Add(new ProductCategory { Category = categoryWatches, DisplayOrder = 1 }); - #region pictures - - //pictures - productTRANSOCEANCHRONOGRAPH.ProductPictures.Add(new ProductPicture() + productTRANSOCEANCHRONOGRAPH.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_transocean-chronograph.jpg"), "image/png", GetSeName(productTRANSOCEANCHRONOGRAPH.Name)), DisplayOrder = 1, }); - #endregion pictures - - #region manufacturer - - //manufacturer - productTRANSOCEANCHRONOGRAPH.ProductManufacturers.Add(new ProductManufacturer() + productTRANSOCEANCHRONOGRAPH.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Breitling").Single(), DisplayOrder = 1, }); - #endregion manufacturer - - #region SpecificationAttributes - //attributes - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13011,7 +12624,7 @@ public IList Products() // offer > promotion SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13019,7 +12632,7 @@ public IList Products() // manufacturer > Breitling SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 18).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13027,7 +12640,7 @@ public IList Products() // housing > steel SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13035,7 +12648,7 @@ public IList Products() // material -> leather SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 5).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13043,7 +12656,7 @@ public IList Products() // Gender -> gentlemen SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 7).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13051,7 +12664,7 @@ public IList Products() // movement -> mechanical, self winding SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 9).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13059,7 +12672,7 @@ public IList Products() // diameter -> 44mm SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 24).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13067,13 +12680,12 @@ public IList Products() // closure -> folding clasp SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 25).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - #endregion SpecificationAttributes #endregion productTRANSOCEANCHRONOGRAPH #region productTissotT-TouchExpertSolar - var productTissotTTouchExpertSolar = new Product() + var productTissotTTouchExpertSolar = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13098,39 +12710,27 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productTissotTTouchExpertSolar.ProductCategories.Add(new ProductCategory() { Category = categoryWatches, DisplayOrder = 1 }); - - #region pictures + productTissotTTouchExpertSolar.ProductCategories.Add(new ProductCategory { Category = categoryWatches, DisplayOrder = 1 }); - //pictures - productTissotTTouchExpertSolar.ProductPictures.Add(new ProductPicture() + productTissotTTouchExpertSolar.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_tissot-t-touch-expert-solar.jpg"), "image/png", GetSeName(productTissotTTouchExpertSolar.Name)), DisplayOrder = 1, }); - productTissotTTouchExpertSolar.ProductPictures.Add(new ProductPicture() + productTissotTTouchExpertSolar.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_tissot-t-touch-expert-solar-t091_2.jpg"), "image/png", GetSeName(productTissotTTouchExpertSolar.Name)), DisplayOrder = 1, }); - #endregion pictures - - #region manufacturer - - //manufacturer - productTissotTTouchExpertSolar.ProductManufacturers.Add(new ProductManufacturer() + productTissotTTouchExpertSolar.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Tissot").Single(), DisplayOrder = 1, }); - #endregion manufacturer - - #region SpecificationAttributes - //attributes - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13138,7 +12738,7 @@ public IList Products() // offer > best price SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 8).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13146,7 +12746,7 @@ public IList Products() // manufacturer > Tissot SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 17).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13154,7 +12754,7 @@ public IList Products() // housing > steel SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13162,7 +12762,7 @@ public IList Products() // material -> silicone SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 7).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13170,7 +12770,7 @@ public IList Products() // Gender -> gentlemen SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 7).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13178,7 +12778,7 @@ public IList Products() // movement -> Automatic, self-winding SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 9).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13186,7 +12786,7 @@ public IList Products() // diameter -> 44mm SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 24).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13194,13 +12794,12 @@ public IList Products() // closure -> thorn close SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 25).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - #endregion SpecificationAttributes #endregion productTissotT-TouchExpertSolar #region productSeikoSRPA49K1 - var productSeikoSRPA49K1 = new Product() + var productSeikoSRPA49K1 = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13225,33 +12824,21 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productSeikoSRPA49K1.ProductCategories.Add(new ProductCategory() { Category = categoryWatches, DisplayOrder = 1 }); - - #region pictures + productSeikoSRPA49K1.ProductCategories.Add(new ProductCategory { Category = categoryWatches, DisplayOrder = 1 }); - //pictures - productSeikoSRPA49K1.ProductPictures.Add(new ProductPicture() + productSeikoSRPA49K1.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_SeikoSRPA49K1.jpg"), "image/png", GetSeName(productSeikoSRPA49K1.Name)), DisplayOrder = 1, }); - #endregion pictures - - #region manufacturer - - //manufacturer - productSeikoSRPA49K1.ProductManufacturers.Add(new ProductManufacturer() + productSeikoSRPA49K1.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Seiko").Single(), DisplayOrder = 1, }); - #endregion manufacturer - - #region SpecificationAttributes - //attributes - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13259,7 +12846,7 @@ public IList Products() // housing > steel SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13267,7 +12854,7 @@ public IList Products() // material -> stainless steel SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13275,7 +12862,7 @@ public IList Products() // manufacturer > Seiko SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 16).Single() }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13283,7 +12870,7 @@ public IList Products() // Gender -> gentlemen SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 7).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13291,7 +12878,7 @@ public IList Products() // movement -> quarz SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 9).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13299,7 +12886,7 @@ public IList Products() // closure -> folding clasp SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 25).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13307,14 +12894,13 @@ public IList Products() // diameter -> 44mm SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 24).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - #endregion SpecificationAttributes #endregion productSeikoSRPA49K1 #region productWatchesCertinaDSPodiumBigSize - var productWatchesCertinaDSPodiumBigSize = new Product() + var productWatchesCertinaDSPodiumBigSize = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13338,33 +12924,21 @@ public IList Products() DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() }; - productWatchesCertinaDSPodiumBigSize.ProductCategories.Add(new ProductCategory() { Category = categoryWatches, DisplayOrder = 1 }); - - #region pictures + productWatchesCertinaDSPodiumBigSize.ProductCategories.Add(new ProductCategory { Category = categoryWatches, DisplayOrder = 1 }); - //pictures - productWatchesCertinaDSPodiumBigSize.ProductPictures.Add(new ProductPicture() + productWatchesCertinaDSPodiumBigSize.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_certina_ds_podium_big.png"), "image/png", GetSeName(productWatchesCertinaDSPodiumBigSize.Name)), DisplayOrder = 1, }); - #endregion pictures - - #region manufacturer - - //manufacturer - productWatchesCertinaDSPodiumBigSize.ProductManufacturers.Add(new ProductManufacturer() + productWatchesCertinaDSPodiumBigSize.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Certina").Single(), DisplayOrder = 1, }); - #endregion manufacturer - - #region SpecificationAttributes - //attributes - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13372,7 +12946,7 @@ public IList Products() // housing > steel SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13380,7 +12954,7 @@ public IList Products() // material -> leather SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 5).Single() }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13388,7 +12962,7 @@ public IList Products() // manufacturer > Certina SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 14).Single() }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13396,7 +12970,7 @@ public IList Products() // Gender -> gentlemen SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 7).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13404,7 +12978,7 @@ public IList Products() // movement -> quarz SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 9).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13412,7 +12986,7 @@ public IList Products() // closure -> folding clasp SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 25).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13420,7 +12994,6 @@ public IList Products() // diameter -> 40mm SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 24).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - #endregion SpecificationAttributes #endregion productWatchesCertinaDSPodiumBigSize @@ -13431,14 +13004,14 @@ public IList Products() var manuSony = _ctx.Set().First(c => c.Name == "Sony"); var manuEASports = _ctx.Set().First(c => c.Name == "EA Sports"); var manuUbisoft = _ctx.Set().First(c => c.Name == "Ubisoft"); - var categoryGaming = this._ctx.Set().First(c => c.Alias == "Gaming"); - var categoryGamingAccessories = this._ctx.Set().First(c => c.Alias == "Gaming Accessories"); - var categoryGamingGames = this._ctx.Set().First(c => c.Alias == "Games"); + var categoryGaming = _ctx.Set().First(c => c.Alias == "Gaming"); + var categoryGamingAccessories = _ctx.Set().First(c => c.Alias == "Gaming Accessories"); + var categoryGamingGames = _ctx.Set().First(c => c.Alias == "Games"); var manuWarnerHomme = _ctx.Set().First(c => c.Name == "Warner Home Video Games"); #region bundlePs3AssassinCreed - var productPs3 = new Product() + var productPs3 = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13463,22 +13036,21 @@ public IList Products() DeliveryTime = firstDeliveryTime }; - productPs3.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs3.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 4 }); + productPs3.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productPs3.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 4 }); - productPs3.ProductPictures.Add(new ProductPicture() + productPs3.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ps4_w_controller.jpg"), "image/png", GetSeName(productPs3.Name) + "-controller"), DisplayOrder = 1 }); - productPs3.ProductPictures.Add(new ProductPicture() + productPs3.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ps4_wo_controller.jpg"), "image/jpeg", GetSeName(productPs3.Name) + "-single"), DisplayOrder = 2 }); - - var productDualshock4Controller = new Product() + var productDualshock4Controller = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13502,17 +13074,17 @@ public IList Products() DeliveryTime = firstDeliveryTime }; - productDualshock4Controller.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productDualshock4Controller.ProductCategories.Add(new ProductCategory() { Category = categoryGamingAccessories, DisplayOrder = 1 }); + productDualshock4Controller.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productDualshock4Controller.ProductCategories.Add(new ProductCategory { Category = categoryGamingAccessories, DisplayOrder = 1 }); - productDualshock4Controller.ProductPictures.Add(new ProductPicture() + productDualshock4Controller.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_dualshock4.jpg"), "image/png", GetSeName(productDualshock4Controller.Name)), DisplayOrder = 1 }); - var productMinecraft = new Product() + var productMinecraft = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13538,17 +13110,17 @@ public IList Products() DeliveryTime = firstDeliveryTime }; - productMinecraft.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productMinecraft.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 4 }); + productMinecraft.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productMinecraft.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 4 }); - productMinecraft.ProductPictures.Add(new ProductPicture() + productMinecraft.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_minecraft.jpg"), "image/jpeg", GetSeName("Minecraft - Playstation 4 Edition")), DisplayOrder = 1 }); - var productBundlePs3AssassinCreed = new Product() + var productBundlePs3AssassinCreed = new Product { ProductType = ProductType.BundledProduct, VisibleIndividually = true, @@ -13576,10 +13148,10 @@ public IList Products() BundlePerItemShoppingCart = true }; - productBundlePs3AssassinCreed.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productBundlePs3AssassinCreed.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 1 }); + productBundlePs3AssassinCreed.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productBundlePs3AssassinCreed.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 1 }); - productBundlePs3AssassinCreed.ProductPictures.Add(new ProductPicture() + productBundlePs3AssassinCreed.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "ps4_bundle_minecraft.jpg"), "image/png", GetSeName(productBundlePs3AssassinCreed.Name)), DisplayOrder = 1 @@ -13589,7 +13161,7 @@ public IList Products() #region bundlePs4 - var productPs4 = new Product() + var productPs4 = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13614,55 +13186,22 @@ public IList Products() DeliveryTime = firstDeliveryTime }; - productPs4.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs4.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 5 }); + productPs4.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productPs4.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 5 }); - productPs4.ProductPictures.Add(new ProductPicture() + productPs4.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4.png"), "image/png", GetSeName(productPs4.Name)), DisplayOrder = 1 }); - productPs4.ProductPictures.Add(new ProductPicture() + productPs4.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_dualshock4_wirelesscontroller.png"), "image/png", GetSeName(productPs4.Name)), DisplayOrder = 1 }); - //var productDualshock4Controller = new Product() - //{ - // ProductType = ProductType.SimpleProduct, - // VisibleIndividually = true, - // Sku = "Sony-PS410037", - // Name = "DUALSHOCK 4 Wireless Controller", - // ShortDescription = "Combining classic controls with innovative new ways to play, the DUALSHOCK®4 wireless controller is an evolutionary controller for a new era of gaming.", - // FullDescription = "

Keys / Switches : PS button, SHARE button, OPTIONS button, Directional buttons (Up/Down/Left/Right), Action buttons (Triangle, Circle, Cross, Square), R1/L1/R2/L2/R3/L3, Right stick, Left stick, Touch Pad Button. The DualShock 4 is currently available in Jet Black, Magma Red, and Wave Blue.

The DualShock 4 features the following buttons: PS button, SHARE button, OPTIONS button, directional buttons, action buttons (triangle, circle, cross, square), shoulder buttons (R1/L1), triggers (R2/L2), analog stick click buttons (L3/R3) and a touch pad click button.[25] These mark several changes from the DualShock 3 and other previous PlayStation controllers. The START and SELECT buttons have been merged into a single OPTIONS button.[25][27] A dedicated SHARE button will allow players to upload video from their gameplay experiences.[25] The joysticks and triggers have been redesigned based on developer input.[25] with the ridged surface of the joysticks now featuring an outer ring surrounding the concave dome caps.

The DualShock 4 is backward compatible with the PlayStation 3, but only via a microUSB cable. Backward compatibility is not supported via Bluetooth.

", - // ProductTemplateId = productTemplateSimple.Id, - // AllowCustomerReviews = true, - // Published = true, - // MetaTitle = "DUALSHOCK 4 Wireless Controller", - // Price = 59.99M, - // ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 10, - // StockQuantity = 10000, - // NotifyAdminForQuantityBelow = 1, - // AllowBackInStockSubscriptions = false, - // IsShipEnabled = true, - // DeliveryTime = firstDeliveryTime - //}; - - //productDualshock4Controller.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - //productDualshock4Controller.ProductCategories.Add(new ProductCategory() { Category = categoryGamingAccessories, DisplayOrder = 2 }); - - //productDualshock4Controller.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_dualshock4_wirelesscontroller.png"), "image/png", GetSeName(productDualshock4Controller.Name)), - // DisplayOrder = 1 - //}); - - - var productPs4Camera = new Product() + var productPs4Camera = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13685,17 +13224,17 @@ public IList Products() DeliveryTime = firstDeliveryTime }; - productPs4Camera.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs4Camera.ProductCategories.Add(new ProductCategory() { Category = categoryGamingAccessories, DisplayOrder = 3 }); + productPs4Camera.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productPs4Camera.ProductCategories.Add(new ProductCategory { Category = categoryGamingAccessories, DisplayOrder = 3 }); - productPs4Camera.ProductPictures.Add(new ProductPicture() + productPs4Camera.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4_camera.png"), "image/png", GetSeName(productPs4Camera.Name)), DisplayOrder = 1 }); - var productBundlePs4 = new Product() + var productBundlePs4 = new Product { ProductType = ProductType.BundledProduct, VisibleIndividually = true, @@ -13721,10 +13260,10 @@ public IList Products() BundleTitleText = "Bundle includes" }; - productBundlePs4.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productBundlePs4.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 2 }); + productBundlePs4.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productBundlePs4.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 2 }); - productBundlePs4.ProductPictures.Add(new ProductPicture() + productBundlePs4.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4_bundle.png"), "image/png", GetSeName(productBundlePs4.Name)), DisplayOrder = 1 @@ -13734,7 +13273,7 @@ public IList Products() #region groupAccessories - var productGroupAccessories = new Product() + var productGroupAccessories = new Product { ProductType = ProductType.GroupedProduct, VisibleIndividually = true, @@ -13757,10 +13296,10 @@ public IList Products() ShowOnHomePage = true }; - productGroupAccessories.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productGroupAccessories.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 3 }); + productGroupAccessories.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productGroupAccessories.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 3 }); - productGroupAccessories.ProductPictures.Add(new ProductPicture() + productGroupAccessories.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gaming_accessories.png"), "image/png", GetSeName(productGroupAccessories.Name)), DisplayOrder = 1 @@ -13770,39 +13309,7 @@ public IList Products() #region Ps3PlusOneGame - //var productWatchDogs = new Product() - //{ - // ProductType = ProductType.SimpleProduct, - // VisibleIndividually = true, - // Sku = "Ubi-watchdogs", - // Name = "Watch Dogs", - // ShortDescription = "Hack and control the city – Use the city systems as weapons: traffic lights, security cameras, movable bridges, gas pipes, electricity grid and more.", - // FullDescription = "

In today's hyper-connected world, Chicago has the country’s most advanced computer system – one which controls almost every piece of city technology and holds key information on all of the city's residents.

You play as Aiden Pearce, a brilliant hacker but also a former thug, whose criminal past lead to a violent family tragedy. Now on the hunt for those who hurt your family, you'll be able to monitor and hack all who surround you while manipulating the city's systems to stop traffic lights, download personal information, turn off the electrical grid and more.

Use the city of Chicago as your ultimate weapon and exact your own style of revenge.

Monitor the masses – Everyone leaves a digital shadow - access all data on anyone and use it to your advantage.

State of the art graphics

", - // ProductTemplateId = productTemplateSimple.Id, - // AllowCustomerReviews = true, - // Published = true, - // MetaTitle = "Watch Dogs", - // Price = 49.90M, - // ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 10000, - // StockQuantity = 10000, - // NotifyAdminForQuantityBelow = 1, - // AllowBackInStockSubscriptions = false, - // IsShipEnabled = true, - // DeliveryTime = firstDeliveryTime - //}; - - //productWatchDogs.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuUbisoft, DisplayOrder = 1 }); - //productWatchDogs.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 1 }); - - //productWatchDogs.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "ubisoft-watchdogs.jpg"), "image/jpeg", GetSeName(productWatchDogs.Name)), - // DisplayOrder = 1 - //}); - - var productPrinceOfPersia = new Product() + var productPrinceOfPersia = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13825,18 +13332,20 @@ public IList Products() DeliveryTime = firstDeliveryTime }; - productPrinceOfPersia.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuUbisoft, DisplayOrder = 1 }); - productPrinceOfPersia.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 2 }); + productPrinceOfPersia.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuUbisoft, DisplayOrder = 1 }); + productPrinceOfPersia.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 2 }); - productPrinceOfPersia.ProductPictures.Add(new ProductPicture() + productPrinceOfPersia.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_princeofpersia.jpg"), "image/jpeg", GetSeName(productPrinceOfPersia.Name)), DisplayOrder = 1 }); + #endregion Ps3PlusOneGame #region Horizon Zero Down - var productHorizonZeroDown = new Product() + + var productHorizonZeroDown = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13861,18 +13370,20 @@ public IList Products() DeliveryTime = firstDeliveryTime }; - productHorizonZeroDown.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productHorizonZeroDown.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 2 }); + productHorizonZeroDown.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productHorizonZeroDown.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 2 }); - productHorizonZeroDown.ProductPictures.Add(new ProductPicture() + productHorizonZeroDown.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_horizon.jpg"), "image/jpeg", GetSeName(productHorizonZeroDown.Name)), DisplayOrder = 1 }); + #endregion Horizon Zero Down #region Fifa 17 - var productFifa17 = new Product() + + var productFifa17 = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13897,23 +13408,23 @@ public IList Products() DeliveryTime = firstDeliveryTime }; - productFifa17.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuEASports, DisplayOrder = 1 }); - productFifa17.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 2 }); + productFifa17.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuEASports, DisplayOrder = 1 }); + productFifa17.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 2 }); - productFifa17.ProductPictures.Add(new ProductPicture() + productFifa17.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_fifa17.jpg"), "image/jpeg", GetSeName(productFifa17.Name)), DisplayOrder = 1 }); + #endregion Fifa 17 #region Lego Worlds - //productDriverSanFrancisco - var productLegoWorlds = new Product() + + var productLegoWorlds = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, - //Sku = "Ubi-driversanfrancisco", Sku = "Gaming-Lego-001", Name = "LEGO Worlds - PlayStation 4", ShortDescription = "Experience a galaxy of Worlds made entirely from LEGO bricks.", @@ -13934,67 +13445,34 @@ public IList Products() DeliveryTime = firstDeliveryTime }; - productLegoWorlds.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuWarnerHomme, DisplayOrder = 1 }); - productLegoWorlds.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 3 }); + productLegoWorlds.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuWarnerHomme, DisplayOrder = 1 }); + productLegoWorlds.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 3 }); - productLegoWorlds.ProductPictures.Add(new ProductPicture() + productLegoWorlds.ProductPictures.Add(new ProductPicture { Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_legoworlds.jpg"), "image/jpeg", GetSeName(productLegoWorlds.Name)), DisplayOrder = 1 }); - #endregion Lego Worlds - - #region Ps3PlusOneGame - //var productPs3OneGame = new Product() - //{ - // ProductType = ProductType.SimpleProduct, - // VisibleIndividually = true, - // Sku = "Sony-PS310111", - // Name = "PlayStation 3 plus game cheaper", - // ShortDescription = "Our special offer: PlayStation 3 plus one game of your choise cheaper.", - // FullDescription = productPs3.FullDescription, - // ProductTemplateId = productTemplate.Id, - // AllowCustomerReviews = true, - // Published = true, - // MetaTitle = "PlayStation 3 plus game cheaper", - // Price = 160.00M, - // ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 3, - // StockQuantity = 10000, - // NotifyAdminForQuantityBelow = 1, - // AllowBackInStockSubscriptions = false, - // IsShipEnabled = true, - // DeliveryTime = firstDeliveryTime - //}; - - //productPs3OneGame.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - //productPs3OneGame.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 6 }); - - //productPs3OneGame.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps3_plus_game.png"), "image/png", GetSeName(productPs3OneGame.Name)), - // DisplayOrder = 1 - //}); - - #endregion Ps3PlusOneGame + #endregion Lego Worlds #endregion gaming var entities = new List { - productTRANSOCEANCHRONOGRAPH,productTissotTTouchExpertSolar,productSeikoSRPA49K1,productTitleistSM6TourChrome,productTitleistProV1x,productGBBEpicSubZeroDriver,productSupremeGolfball,productBooksStoneOfTheWise,productNikeStrikeFootball,productNikeEvoPowerBall, - productTorfabrikOfficialGameBall,productAdidasTangoSalaBall,productAllCourtBasketball,productEvolutionHighSchoolGameBasketball,productRayBanTopBar, - productOriginalWayfarer,productCustomFlakSunglasses,productRadarEVPrizmSportsSunglasses,productAppleProHipsterBundle,product97ipad,productAirpods, + productTRANSOCEANCHRONOGRAPH, productTissotTTouchExpertSolar, productSeikoSRPA49K1, + productTitleistSM6TourChrome, productTitleistProV1x, productGBBEpicSubZeroDriver, + productSupremeGolfball, productBooksStoneOfTheWise, productNikeStrikeFootball, productNikeEvoPowerBall, + productTorfabrikOfficialGameBall, productAdidasTangoSalaBall, productAllCourtBasketball, productEvolutionHighSchoolGameBasketball, productRayBanTopBar, + productOriginalWayfarer, productCustomFlakSunglasses, productRadarEVPrizmSportsSunglasses, productAppleProHipsterBundle, product97ipad, productAirpods, productIphoneplus,productWatchSeries2,product10GiftCard, product25GiftCard, product50GiftCard,product100GiftCard, productBooksUberMan, productBooksGefangeneDesHimmels, - productBooksBestGrillingRecipes, productBooksCookingForTwo, productBooksAutosDerSuperlative, productBooksBildatlasMotorraeder, productBooksAutoBuch, productBooksFastCars, - productBooksMotorradAbenteuer, - productInstantDownloadVivaldi, productInstantDownloadBeethoven, productWatchesCertinaDSPodiumBigSize, + productBooksBestGrillingRecipes, productBooksCookingForTwo, productBooksAutosDerSuperlative, productBooksBildatlasMotorraeder, + productBooksAutoBuch, productBooksFastCars, productBooksMotorradAbenteuer, + productInstantDownloadVivaldi, productInstantDownloadBeethoven, productWatchesCertinaDSPodiumBigSize, productPs3, productMinecraft, productBundlePs3AssassinCreed, productPs4, productDualshock4Controller, productPs4Camera, productBundlePs4, productGroupAccessories, - productPrinceOfPersia, productLegoWorlds,productHorizonZeroDown,productFifa17 + productPrinceOfPersia, productLegoWorlds, productHorizonZeroDown, productFifa17 }; entities.AddRange(GetFashionProducts()); @@ -14870,6 +14348,20 @@ protected Picture CreatePicture(byte[] pictureBinary, string mimeType, string se return picture; } + protected void AddProductPictures( + Product product, + string imageName, + int displayOrder = 1) + { + var mimeType = imageName.EndsWith(".png") ? "image/png" : "image/jpg"; + + product.ProductPictures.Add(new ProductPicture + { + Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + imageName), mimeType, GetSeName(product.Name)), + DisplayOrder = displayOrder + }); + } + protected string GetSeName(string name) { return SeoHelper.GetSeName(name, true, false); From 739f10dc8f89c427823d5d5ad4959b1fb051d4fd Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 7 Aug 2018 15:10:16 +0200 Subject: [PATCH 016/932] Fixes issues with product pictures of installation sample data --- .../Setup/SeedData/InvariantSeedData.cs | 1179 ++++------------- 1 file changed, 259 insertions(+), 920 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs index 2f76c557bb..e61e2c513b 100644 --- a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs +++ b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs @@ -7516,7 +7516,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = true, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_blue-gray-classic-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-blue-gray-classic-black-1").Id.ToString() }); #endregion blue-gray-classic-black @@ -7535,7 +7535,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = true, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_gray-course-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-gray-course-black").Id.ToString() }); #endregion gray-course-black @@ -7554,7 +7554,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = true, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_gray-course-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-brown-course-havana").Id.ToString() }); #endregion brown-course-havana @@ -7573,7 +7573,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = true, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-green-classic-havana-black").Id.ToString() }); #endregion green-classic-havana-black @@ -7594,7 +7594,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-blue-gray-classic-black-1").Id.ToString() }); #endregion green-classic-havana-black @@ -7613,7 +7613,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-blue-gray-classic-black-1").Id.ToString() }); #endregion green-classic-rayban-black @@ -7633,7 +7633,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = true, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-gray-course-black").Id.ToString() }); #endregion gray-course-havana-black @@ -7652,7 +7652,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-gray-course-black").Id.ToString() }); #endregion gray-course-rayban-black @@ -7671,7 +7671,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-green-classic-havana-black").Id.ToString() }); #endregion green-classic-rayban-black @@ -7690,7 +7690,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-green-classic-havana-black").Id.ToString() }); #endregion gray-course-rayban-black @@ -7710,7 +7710,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-brown-course-havana").Id.ToString() }); #endregion brown-course-havana-black @@ -7729,7 +7729,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-brown-course-havana").Id.ToString() }); #endregion brown-course-rayban-black @@ -10144,9 +10144,10 @@ public IList Products() { #region definitions - var sampleImagesPath = _sampleImagesPath; var productTemplate = _ctx.Set().First(x => x.ViewPath == "Product"); var firstDeliveryTime = _ctx.Set().First(sa => sa.DisplayOrder == 0); + var secondDeliveryTime = _ctx.Set().First(sa => sa.DisplayOrder == 1); + var thirdDeliveryTime = _ctx.Set().First(sa => sa.DisplayOrder == 2); var specialPriceEndDate = DateTime.UtcNow.AddMonths(1); #endregion definitions @@ -10180,16 +10181,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productTitleistSM6TourChrome.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); + AddProductPicture(productTitleistSM6TourChrome, "product_titleist_sm6_tour_chrome.jpg"); - productTitleistSM6TourChrome.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_titleist_sm6_tour_chrome.jpg"), "image/png", GetSeName(productTitleistSM6TourChrome.Name)), - DisplayOrder = 1, - }); + productTitleistSM6TourChrome.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); productTitleistSM6TourChrome.ProductManufacturers.Add(new ProductManufacturer { @@ -10223,16 +10220,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productTitleistProV1x.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); + AddProductPicture(productTitleistProV1x, "product_titleist-pro-v1x.jpg"); - productTitleistProV1x.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_titleist-pro-v1x.jpg"), "image/png", GetSeName(productTitleistProV1x.Name)), - DisplayOrder = 1, - }); + productTitleistProV1x.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); productTitleistProV1x.ProductManufacturers.Add(new ProductManufacturer { @@ -10266,22 +10259,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productSupremeGolfball.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); + AddProductPicture(productSupremeGolfball, "product_supremeGolfball_1.jpg", "golfball-1"); + AddProductPicture(productSupremeGolfball, "product_supremeGolfball_2.jpg", "golfball-2"); - productSupremeGolfball.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_supremeGolfball_1.jpg"), "image/png", GetSeName(productSupremeGolfball.Name)), - DisplayOrder = 1, - }); - - productSupremeGolfball.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_supremeGolfball_2.jpg"), "image/png", GetSeName(productSupremeGolfball.Name)), - DisplayOrder = 1, - }); + productSupremeGolfball.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); productSupremeGolfball.ProductManufacturers.Add(new ProductManufacturer { @@ -10315,16 +10299,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productGBBEpicSubZeroDriver.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); + AddProductPicture(productGBBEpicSubZeroDriver, "product_gbb-epic-sub-zero-driver.jpg"); - productGBBEpicSubZeroDriver.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gbb-epic-sub-zero-driver.jpg"), "image/png", GetSeName(productGBBEpicSubZeroDriver.Name)), - DisplayOrder = 1, - }); + productGBBEpicSubZeroDriver.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); productGBBEpicSubZeroDriver.ProductManufacturers.Add(new ProductManufacturer { @@ -10365,17 +10345,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single(), + DeliveryTimeId = thirdDeliveryTime.Id, HasTierPrices = true }; - productNikeStrikeFootball.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); + AddProductPicture(productNikeStrikeFootball, "products_nike-strike-football.jpg"); - productNikeStrikeFootball.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_nike-strike-football.jpg"), "image/png", GetSeName(productNikeStrikeFootball.Name)), - DisplayOrder = 1, - }); + productNikeStrikeFootball.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); productNikeStrikeFootball.ProductManufacturers.Add(new ProductManufacturer { @@ -10430,16 +10406,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productNikeEvoPowerBall.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); + AddProductPicture(productNikeEvoPowerBall, "product_nike-vopower-53-trainer-hs-ball.jpg"); - productNikeEvoPowerBall.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_nike-vopower-53-trainer-hs-ball.jpg"), "image/png", GetSeName(productNikeEvoPowerBall.Name)), - DisplayOrder = 1, - }); + productNikeEvoPowerBall.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); productNikeEvoPowerBall.ProductManufacturers.Add(new ProductManufacturer { @@ -10490,40 +10462,16 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productTorfabrikOfficialGameBall.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); - - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_white.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "white"), - DisplayOrder = 1, - }); - - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_red.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "red"), - DisplayOrder = 1, - }); - - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_yellow.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "yellow"), - DisplayOrder = 1, - }); - - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_blue.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "blue"), - DisplayOrder = 1, - }); + AddProductPicture(productTorfabrikOfficialGameBall, "product_torfabrik-offizieller-spielball_white.png", "official-game-ball-white"); + AddProductPicture(productTorfabrikOfficialGameBall, "product_torfabrik-offizieller-spielball_red.png", "official-game-ball-red"); + AddProductPicture(productTorfabrikOfficialGameBall, "product_torfabrik-offizieller-spielball_yellow.png", "official-game-ball-yellow"); + AddProductPicture(productTorfabrikOfficialGameBall, "product_torfabrik-offizieller-spielball_blue.png", "official-game-ball-blue"); + AddProductPicture(productTorfabrikOfficialGameBall, "product_torfabrik-offizieller-spielball_green.png", "official-game-ball-green"); - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_green.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "green"), - DisplayOrder = 1, - }); + productTorfabrikOfficialGameBall.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); productTorfabrikOfficialGameBall.ProductManufacturers.Add(new ProductManufacturer { @@ -10531,7 +10479,6 @@ public IList Products() DisplayOrder = 1, }); - productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, @@ -10575,52 +10522,18 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productAdidasTangoSalaBall.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-white.png", "adidas-tango-pasadena-ball-white"); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-yellow.jpg", "adidas-tango-pasadena-ball-yellow"); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-red.jpg", "adidas-tango-pasadena-ball-red"); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-green.jpg", "adidas-tango-pasadena-ball-green"); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-gray.jpg", "adidas-tango-pasadena-ball-gray"); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-brown.jpg", "adidas-tango-pasadena-ball-brown"); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-blue.jpg", "adidas-tango-pasadena-ball-blue"); - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-white.png"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-white"), - DisplayOrder = 1, - }); - - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-yellow.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-yellow"), - DisplayOrder = 1, - }); - - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-red.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-red"), - DisplayOrder = 1, - }); - - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-green.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-green"), - DisplayOrder = 1, - }); - - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-gray.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-gray"), - DisplayOrder = 1, - }); - - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-brown.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-brown"), - DisplayOrder = 1, - }); - - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-blue.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-blue"), - DisplayOrder = 1, - }); + productAdidasTangoSalaBall.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); productAdidasTangoSalaBall.ProductManufacturers.Add(new ProductManufacturer { @@ -10678,17 +10591,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single(), + DeliveryTimeId = thirdDeliveryTime.Id, HasTierPrices = true }; - productEvolutionHighSchoolGameBasketball.ProductCategories.Add(new ProductCategory { Category = categoryBasketball, DisplayOrder = 1 }); + AddProductPicture(productEvolutionHighSchoolGameBasketball, "product_evolution-high-school-game-basketball.jpg"); - productEvolutionHighSchoolGameBasketball.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_evolution-high-school-game-basketball.jpg"), "image/png", GetSeName(productEvolutionHighSchoolGameBasketball.Name)), - DisplayOrder = 1, - }); + productEvolutionHighSchoolGameBasketball.ProductCategories.Add(new ProductCategory { Category = categoryBasketball, DisplayOrder = 1 }); productEvolutionHighSchoolGameBasketball.ProductManufacturers.Add(new ProductManufacturer { @@ -10726,16 +10635,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productAllCourtBasketball.ProductCategories.Add(new ProductCategory { Category = categoryBasketball, DisplayOrder = 1 }); + AddProductPicture(productAllCourtBasketball, "product_all-court-basketball.png"); - productAllCourtBasketball.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_all-court-basketball.png"), "image/png", GetSeName(productAllCourtBasketball.Name)), - DisplayOrder = 1, - }); + productAllCourtBasketball.ProductCategories.Add(new ProductCategory { Category = categoryBasketball, DisplayOrder = 1 }); productAllCourtBasketball.ProductManufacturers.Add(new ProductManufacturer { @@ -10775,28 +10680,14 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productRayBanTopBar.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); - - productRayBanTopBar.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_RayBanTopBar_1.jpg"), "image/png", GetSeName(productRayBanTopBar.Name)), - DisplayOrder = 1, - }); + AddProductPicture(productRayBanTopBar, "product_RayBanTopBar_1.jpg", "rayban-top-bar-1"); + AddProductPicture(productRayBanTopBar, "product_RayBanTopBar_2.jpg", "rayban-top-bar-2"); + AddProductPicture(productRayBanTopBar, "product_RayBanTopBar_3.jpg", "rayban-top-bar-3"); - productRayBanTopBar.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_RayBanTopBar_2.jpg"), "image/png", GetSeName(productRayBanTopBar.Name)), - DisplayOrder = 1, - }); - - productRayBanTopBar.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_RayBanTopBar_3.jpg"), "image/png", GetSeName(productRayBanTopBar.Name)), - DisplayOrder = 1, - }); + productRayBanTopBar.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); productRayBanTopBar.ProductManufacturers.Add(new ProductManufacturer { @@ -10830,46 +10721,17 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productOriginalWayfarer.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); - - productOriginalWayfarer.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_1.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_blue-gray-classic-black"), - DisplayOrder = 1, - }); + AddProductPicture(productOriginalWayfarer, "product_productOriginalWayfarer_1.jpg", "wayfarer-blue-gray-classic-black-1"); + AddProductPicture(productOriginalWayfarer, "product_productOriginalWayfarer_2.jpg", "wayfarer-blue-gray-classic-black-2"); + AddProductPicture(productOriginalWayfarer, "product_productOriginalWayfarer_3.jpg", "wayfarer-gray-course-black"); + AddProductPicture(productOriginalWayfarer, "product_productOriginalWayfarer_4.jpg", "wayfarer-brown-course-havana"); + AddProductPicture(productOriginalWayfarer, "product_productOriginalWayfarer_5.jpg", "wayfarer-green-classic-havana-black"); + AddProductPicture(productOriginalWayfarer, "product_productOriginalWayfarer_6.jpg", "wayfarer-blue-gray-classic-black-3"); - productOriginalWayfarer.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_2.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_blue-gray-classic-black"), - DisplayOrder = 1, - }); - - productOriginalWayfarer.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_3.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_gray-course-black"), - DisplayOrder = 1, - }); - - productOriginalWayfarer.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_4.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_brown-course-havana"), - DisplayOrder = 1, - }); - - productOriginalWayfarer.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_5.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_green-classic-havana-black"), - DisplayOrder = 1, - }); - - productOriginalWayfarer.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_6.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_blue-gray-classic-black"), - DisplayOrder = 1, - }); + productOriginalWayfarer.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); productOriginalWayfarer.ProductManufacturers.Add(new ProductManufacturer { @@ -10903,16 +10765,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productRadarEVPrizmSportsSunglasses.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); + AddProductPicture(productRadarEVPrizmSportsSunglasses, "product_radar_ev_prizm.jpg"); - productRadarEVPrizmSportsSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_radar_ev_prizm.jpg"), "image/png", GetSeName(productRadarEVPrizmSportsSunglasses.Name)), - DisplayOrder = 1, - }); + productRadarEVPrizmSportsSunglasses.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); productRadarEVPrizmSportsSunglasses.ProductManufacturers.Add(new ProductManufacturer { @@ -10946,264 +10804,58 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productCustomFlakSunglasses.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlakSunglasses.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlakSunglasses.jpg"), - DisplayOrder = 1, - }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "productCustomFlakSunglasses_black_white.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "productCustomFlakSunglasses_black_white.jpg"), - DisplayOrder = 1, - }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_gray.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_clear.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_jadeiridium.jpg"), - DisplayOrder = 1, - }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_positiverediridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_rubyiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_sapphireiridium.jpg"), - DisplayOrder = 1, - }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_violetiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_24kiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_fireiridium.jpg"), - DisplayOrder = 1, - }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_24kiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_clear.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_fireiridium.jpg"), - DisplayOrder = 1, - }); - - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_gray.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_jadeiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_positiverediridium.jpg"), - DisplayOrder = 1, - }); - - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_rubyiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_sapphireiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_violetiridium.jpg"), - DisplayOrder = 1, - }); - - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_24kiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_clear.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_fireiridium.jpg"), - DisplayOrder = 1, - }); - - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_gray.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_jadeiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_rubyiridium.jpg"), - DisplayOrder = 1, - }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_sapphireiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_violetiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_positiverediridium.jpg"), - DisplayOrder = 1, - }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_24kiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_clear.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_fireiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_gray.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_jadeiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_positiverediridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_rubyiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_sapphireiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_violetiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_24kiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_clear.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_fireiridium.jpg"), - DisplayOrder = 1, - }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_gray.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_jadeiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_positiverediridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_rubyiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_sapphireiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_violetiridium.jpg"), - DisplayOrder = 1, - }); - + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlakSunglasses.jpg", "custom_flak"); + AddProductPicture(productCustomFlakSunglasses, "productCustomFlakSunglasses_black_white.jpg", "custom_flak_black_white"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_gray.jpg", "custom_flak_matteblack_gray"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_clear.jpg", "custom_flak_matteblack_clear"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_jadeiridium.jpg", "custom_flak_matteblack_jadeiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_positiverediridium.jpg", "custom_flak_matteblack_positiverediridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_rubyiridium.jpg", "custom_flak_matteblack_rubyiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_sapphireiridium.jpg", "custom_flak_matteblack_sapphireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_violetiridium.jpg", "custom_flak_matteblack_violetiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_24kiridium.jpg", "custom_flak_matteblack_24kiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_fireiridium.jpg", "custom_flak_matteblack_fireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_24kiridium.jpg", "custom_flak_orangeflare_24kiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_clear.jpg", "custom_flak_orangeflare_clear"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_fireiridium.jpg", "custom_flak_orangeflare_fireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_gray.jpg", "custom_flak_orangeflare_gray"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_jadeiridium.jpg", "custom_flak_orangeflare_jadeiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_positiverediridium.jpg", "custom_flak_orangeflare_positiverediridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_rubyiridium.jpg", "custom_flak_orangeflare_rubyiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_sapphireiridium.jpg", "custom_flak_orangeflare_sapphireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_violetiridium.jpg", "custom_flak_orangeflare_violetiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_24kiridium.jpg", "custom_flak_polishedwhite_24kiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_clear.jpg", "custom_flak_polishedwhite_clear"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_fireiridium.jpg", "custom_flak_polishedwhite_fireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_gray.jpg", "custom_flak_polishedwhite_gray"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_jadeiridium.jpg", "custom_flak_polishedwhite_jadeiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_rubyiridium.jpg", "custom_flak_polishedwhite_rubyiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_sapphireiridium.jpg", "custom_flak_polishedwhite_sapphireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_violetiridium.jpg", "custom_flak_polishedwhite_violetiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_positiverediridium.jpg", "custom_flak_polishedwhite_positiverediridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_24kiridium.jpg", "custom_flak_redline_24kiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_clear.jpg", "custom_flak_redline_clear"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_fireiridium.jpg", "custom_flak_redline_fireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_gray.jpg", "custom_flak_redline_gray"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_jadeiridium.jpg", "custom_flak_redline_jadeiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_positiverediridium.jpg", "custom_flak_redline_positiverediridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_rubyiridium.jpg", "custom_flak_redline_rubyiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_sapphireiridium.jpg", "custom_flak_redline_sapphireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_violetiridium.jpg", "custom_flak_redline_violetiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_24kiridium.jpg", "custom_flak_skyblue_24kiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_clear.jpg", "custom_flak_skyblue_clear"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_fireiridium.jpg", "custom_flak_skyblue_fireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_gray.jpg", "custom_flak_skyblue_gray"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_jadeiridium.jpg", "custom_flak_skyblue_jadeiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_positiverediridium.jpg", "custom_flak_skyblue_positiverediridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_rubyiridium.jpg", "custom_flak_skyblue_rubyiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_sapphireiridium.jpg", "custom_flak_skyblue_sapphireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_violetiridium.jpg", "custom_flak_skyblue_violetiridium"); + productCustomFlakSunglasses.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); productCustomFlakSunglasses.ProductManufacturers.Add(new ProductManufacturer { @@ -11214,7 +10866,6 @@ public IList Products() #endregion product Custom Flak Sunglasses - #endregion category sunglasses #region category apple @@ -11247,52 +10898,18 @@ public IList Products() AllowBackInStockSubscriptions = false, IsShipEnabled = true, IsFreeShipping = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - - productIphoneplus.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); - - productIphoneplus.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_all_colors.jpg"), "image/png", GetSeName(productIphoneplus.Name)), - DisplayOrder = 1, - }); - - productIphoneplus.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphoneplus_1.jpg"), "image/png", GetSeName(productIphoneplus.Name)), - DisplayOrder = 2, - }); - - productIphoneplus.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_red.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-red"), - DisplayOrder = 2, - }); - - productIphoneplus.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_silver.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-silver"), - DisplayOrder = 2, - }); - - productIphoneplus.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_black.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-black"), - DisplayOrder = 2, - }); - productIphoneplus.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_rose.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-rose"), - DisplayOrder = 2, - }); + AddProductPicture(productIphoneplus, "product_iphone-plus_all_colors.jpg", "iphone-plus-all-colors"); + AddProductPicture(productIphoneplus, "product_iphoneplus_1.jpg", "iphone-plus-default"); + AddProductPicture(productIphoneplus, "product_iphone-plus_red.jpg", "iphone-plus-red"); + AddProductPicture(productIphoneplus, "product_iphone-plus_silver.jpg", "iphone-plus-silver"); + AddProductPicture(productIphoneplus, "product_iphone-plus_black.jpg", "iphone-plus-black"); + AddProductPicture(productIphoneplus, "product_iphone-plus_rose.jpg", "iphone-plus-rose"); + AddProductPicture(productIphoneplus, "product_iphone-plus_gold.jpg", "iphone-plus-gold"); - productIphoneplus.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_gold.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-gold"), - DisplayOrder = 2, - }); + productIphoneplus.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { @@ -11355,22 +10972,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productWatchSeries2.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); + AddProductPicture(productWatchSeries2, "product_watchseries2_1.jpg", "watchseries-1"); + AddProductPicture(productWatchSeries2, "product_watchseries2_2.jpg", "watchseries-2"); - productWatchSeries2.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_watchseries2_1.jpg"), "image/png", GetSeName(productWatchSeries2.Name)), - DisplayOrder = 1, - }); - - productWatchSeries2.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_watchseries2_2.jpg"), "image/png", GetSeName(productWatchSeries2.Name)), - DisplayOrder = 2, - }); + productWatchSeries2.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); productWatchSeries2.ProductManufacturers.Add(new ProductManufacturer { @@ -11429,49 +11037,20 @@ public IList Products() OrderMinimumQuantity = 1, OrderMaximumQuantity = 10000, StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() - }; - - productAirpods.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); - - productAirpods.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_white.jpg"), "image/png", GetSeName(productAirpods.Name) + "-white"), - DisplayOrder = 1, - }); - - productAirpods.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_turquoise.jpg"), "image/png", GetSeName(productAirpods.Name) + "-turquoise"), - DisplayOrder = 2, - }); - - productAirpods.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_lightblue.jpg"), "image/png", GetSeName(productAirpods.Name) + "-lightblue"), - DisplayOrder = 3, - }); - - productAirpods.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_rose.jpg"), "image/png", GetSeName(productAirpods.Name) + "-rose"), - DisplayOrder = 4, - }); + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; - productAirpods.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_gold.jpg"), "image/png", GetSeName(productAirpods.Name) + "-gold"), - DisplayOrder = 5, - }); + AddProductPicture(productAirpods, "products_airpods_white.jpg", "airpods-white"); + AddProductPicture(productAirpods, "products_airpods_turquoise.jpg", "airpods-turquoise"); + AddProductPicture(productAirpods, "products_airpods_lightblue.jpg", "airpods-lightblue"); + AddProductPicture(productAirpods, "products_airpods_rose.jpg", "airpods-rose"); + AddProductPicture(productAirpods, "products_airpods_gold.jpg", "airpods-gold"); + AddProductPicture(productAirpods, "products_airpods_mint.jpg", "airpods-mint"); - productAirpods.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_mint.jpg"), "image/png", GetSeName(productAirpods.Name) + "-mint"), - DisplayOrder = 6, - }); + productAirpods.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); productAirpods.ProductManufacturers.Add(new ProductManufacturer { @@ -11506,43 +11085,19 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single(), + DeliveryTimeId = thirdDeliveryTime.Id, BundleTitleText = "Bundle includes", BundlePerItemPricing = true, BundlePerItemShoppingCart = true }; - - productAppleProHipsterBundle.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); - - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ultimate-apple-pro-hipster-bundle.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 1, - }); - - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_white.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 2, - }); - - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_watchseries2_2.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 2, - }); - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphoneplus_2.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 2, - }); + AddProductPicture(productAppleProHipsterBundle, "product_ultimate-apple-pro-hipster-bundle.jpg", "apple-pro-hipster-bundle"); + AddProductPicture(productAppleProHipsterBundle, "products_airpods_white.jpg", "bundle-airpods-white"); + AddProductPicture(productAppleProHipsterBundle, "product_watchseries2_2.jpg", "bundle-watchseries"); + AddProductPicture(productAppleProHipsterBundle, "product_iphoneplus_2.jpg", "bundle-iphoneplus"); + AddProductPicture(productAppleProHipsterBundle, "category_apple.png", "bundle-apple"); - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_apple.png"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 2, - }); + productAppleProHipsterBundle.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); productAppleProHipsterBundle.ProductManufacturers.Add(new ProductManufacturer { @@ -11581,76 +11136,22 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - product97ipad.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); - - product97ipad.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ipad_1.jpg"), "image/png", GetSeName(product97ipad.Name)), - DisplayOrder = 1, - }); - - product97ipad.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ipad_2.jpg"), "image/png", GetSeName(product97ipad.Name)), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-yellow.jpg"), "image/png", GetSeName(product97ipad.Name) + "-yellow"), - DisplayOrder = 2, - }); - product97ipad.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-turquoise.jpg"), "image/png", GetSeName(product97ipad.Name) + "-turquoise"), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-lightblue.jpg"), "image/png", GetSeName(product97ipad.Name) + "-lightblue"), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-purple.jpg"), "image/png", GetSeName(product97ipad.Name) + "-purple"), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-mint.jpg"), "image/png", GetSeName(product97ipad.Name) + "-mint"), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-rose.jpg"), "image/png", GetSeName(product97ipad.Name) + "-rose"), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-spacegray.jpg"), "image/png", GetSeName(product97ipad.Name) + "-spacegray"), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-gold.jpg"), "image/png", GetSeName(product97ipad.Name) + "-gold"), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-silver.jpg"), "image/png", GetSeName(product97ipad.Name) + "-silver"), - DisplayOrder = 2, - }); + AddProductPicture(product97ipad, "product_ipad_1.jpg", "ipad-1"); + AddProductPicture(product97ipad, "product_ipad_2.jpg", "ipad-2"); + AddProductPicture(product97ipad, "product_97-ipad-yellow.jpg", "ipad-yellow"); + AddProductPicture(product97ipad, "product_97-ipad-turquoise.jpg", "ipad-turquoise"); + AddProductPicture(product97ipad, "product_97-ipad-lightblue.jpg", "ipad-lightblue"); + AddProductPicture(product97ipad, "product_97-ipad-purple.jpg", "ipad-purple"); + AddProductPicture(product97ipad, "product_97-ipad-mint.jpg", "ipad-mint"); + AddProductPicture(product97ipad, "product_97-ipad-rose.jpg", "ipad-rose"); + AddProductPicture(product97ipad, "product_97-ipad-spacegray.jpg", "ipad-spacegray"); + AddProductPicture(product97ipad, "product_97-ipad-gold.jpg", "ipad-gold"); + AddProductPicture(product97ipad, "product_97-ipad-silver.jpg", "ipad-silver"); + product97ipad.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); product97ipad.ProductManufacturers.Add(new ProductManufacturer { @@ -11724,17 +11225,12 @@ public IList Products() StockQuantity = 10000, NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, - DisplayOrder = 1 - + DisplayOrder = 1 }; + AddProductPicture(product10GiftCard, "product_gift_card_10.png"); product10GiftCard.ProductCategories.Add(new ProductCategory { Category = categoryGiftCards, DisplayOrder = 1 }); - product10GiftCard.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_10.png"), "image/png", GetSeName(product10GiftCard.Name)) - }); - #endregion product10GiftCard #region product25GiftCard @@ -11764,13 +11260,9 @@ public IList Products() DisplayOrder = 2 }; + AddProductPicture(product25GiftCard, "product_gift_card_25.png"); product25GiftCard.ProductCategories.Add(new ProductCategory { Category = categoryGiftCards, DisplayOrder = 1 }); - product25GiftCard.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_25.png"), "image/png", GetSeName(product25GiftCard.Name)) - }); - #endregion product25GiftCard #region product50GiftCard @@ -11800,13 +11292,9 @@ public IList Products() DisplayOrder = 3 }; + AddProductPicture(product50GiftCard, "product_gift_card_50.png"); product50GiftCard.ProductCategories.Add(new ProductCategory { Category = categoryGiftCards, DisplayOrder = 1 }); - product50GiftCard.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_50.png"), "image/png", GetSeName(product50GiftCard.Name)) - }); - #endregion product50GiftCard #region product100GiftCard @@ -11836,13 +11324,9 @@ public IList Products() DisplayOrder = 4, }; + AddProductPicture(product100GiftCard, "product_gift_card_100.png"); product100GiftCard.ProductCategories.Add(new ProductCategory { Category = categoryGiftCards, DisplayOrder = 1 }); - product100GiftCard.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_100.png"), "image/png", GetSeName(product100GiftCard.Name)), - }); - #endregion product100GiftCard #endregion category Gift Cards @@ -11877,14 +11361,9 @@ public IList Products() IsShipEnabled = true }; + AddProductPicture(productBooksUberMan, "0000932_uberman-der-roman.jpeg"); productBooksUberMan.ProductCategories.Add(new ProductCategory { Category = categorySpiegelBestseller, DisplayOrder = 1 }); - productBooksUberMan.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000932_uberman-der-roman.jpeg"), "image/jpeg", GetSeName(productBooksUberMan.Name)), - DisplayOrder = 1, - }); - productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, @@ -11931,17 +11410,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; + DeliveryTimeId = firstDeliveryTime.Id + }; + AddProductPicture(productBooksGefangeneDesHimmels, "0000935_der-gefangene-des-himmels-roman_300.jpeg"); productBooksGefangeneDesHimmels.ProductCategories.Add(new ProductCategory { Category = categorySpiegelBestseller, DisplayOrder = 1 }); - productBooksGefangeneDesHimmels.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000935_der-gefangene-des-himmels-roman_300.jpeg"), "image/jpeg", GetSeName(productBooksGefangeneDesHimmels.Name)), - DisplayOrder = 1, - }); - productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, @@ -11992,17 +11466,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; + DeliveryTimeId = firstDeliveryTime.Id + }; + AddProductPicture(productBooksBestGrillingRecipes, "product_bestgrillingrecipes.jpg"); productBooksBestGrillingRecipes.ProductCategories.Add(new ProductCategory { Category = categoryCookAndEnjoy, DisplayOrder = 1 }); - productBooksBestGrillingRecipes.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_bestgrillingrecipes.jpg"), "image/jpeg", GetSeName(productBooksBestGrillingRecipes.Name)), - DisplayOrder = 1, - }); - productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, @@ -12052,17 +11521,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 1).Single() - }; + DeliveryTimeId = secondDeliveryTime.Id + }; + AddProductPicture(productBooksCookingForTwo, "product_cookingfortwo.jpg"); productBooksCookingForTwo.ProductCategories.Add(new ProductCategory { Category = categoryCookAndEnjoy, DisplayOrder = 1 }); - productBooksCookingForTwo.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_cookingfortwo.jpg"), "image/jpeg", GetSeName(productBooksCookingForTwo.Name)), - DisplayOrder = 1, - }); - productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, @@ -12112,17 +11576,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() - }; + DeliveryTimeId = thirdDeliveryTime.Id + }; + AddProductPicture(productBooksAutosDerSuperlative, "0000944_autos-der-superlative-die-starksten-die-ersten-die-schonsten-die-schnellsten.jpeg"); productBooksAutosDerSuperlative.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); - productBooksAutosDerSuperlative.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000944_autos-der-superlative-die-starksten-die-ersten-die-schonsten-die-schnellsten.jpeg"), "image/jpeg", GetSeName(productBooksAutosDerSuperlative.Name)), - DisplayOrder = 1, - }); - productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, @@ -12172,17 +11631,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; + DeliveryTimeId = firstDeliveryTime.Id + }; + AddProductPicture(productBooksBildatlasMotorraeder, "0000942_bildatlas-motorrader-mit-mehr-als-350-brillanten-abbildungen.jpeg"); productBooksBildatlasMotorraeder.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); - productBooksBildatlasMotorraeder.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000942_bildatlas-motorrader-mit-mehr-als-350-brillanten-abbildungen.jpeg"), "image/jpeg", GetSeName(productBooksBildatlasMotorraeder.Name)), - DisplayOrder = 1, - }); - productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, @@ -12232,17 +11686,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; + DeliveryTimeId = firstDeliveryTime.Id + }; + AddProductPicture(productBooksAutoBuch, "0000947_das-auto-buch-die-grose-chronik-mit-uber-1200-modellen_300.jpeg"); productBooksAutoBuch.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); - productBooksAutoBuch.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000947_das-auto-buch-die-grose-chronik-mit-uber-1200-modellen_300.jpeg"), "image/jpeg", GetSeName(productBooksAutoBuch.Name)), - DisplayOrder = 1, - }); - productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, @@ -12292,19 +11741,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; + DeliveryTimeId = firstDeliveryTime.Id + }; + AddProductPicture(productBooksFastCars, "0000946_fast-cars-bildkalender-2013_300.jpeg"); productBooksFastCars.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); - //pictures - productBooksFastCars.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000946_fast-cars-bildkalender-2013_300.jpeg"), "image/jpeg", GetSeName(productBooksFastCars.Name)), - DisplayOrder = 1, - }); - - //attributes productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, @@ -12354,19 +11796,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 1).Single() - }; + DeliveryTimeId = secondDeliveryTime.Id + }; + AddProductPicture(productBooksMotorradAbenteuer, "0000943_motorrad-abenteuer-fahrtechnik-fur-reise-enduros.jpeg"); productBooksMotorradAbenteuer.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); - //pictures - productBooksMotorradAbenteuer.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000943_motorrad-abenteuer-fahrtechnik-fur-reise-enduros.jpeg"), "image/jpeg", GetSeName(productBooksMotorradAbenteuer.Name)), - DisplayOrder = 1, - }); - - //attributes productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, @@ -12426,14 +11861,9 @@ public IList Products() HasSampleDownload = true }; + AddProductPicture(productBooksStoneOfTheWise, "stone_of_wisdom.jpg"); productBooksStoneOfTheWise.ProductCategories.Add(new ProductCategory { Category = categoryDigitalProducts, DisplayOrder = 1 }); - productBooksStoneOfTheWise.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "stone_of_wisdom.jpg"), "image/jpeg", GetSeName(productBooksStoneOfTheWise.Name)), - DisplayOrder = 1, - }); - productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, @@ -12488,14 +11918,9 @@ public IList Products() HasSampleDownload = true }; + AddProductPicture(productInstantDownloadVivaldi, "vivaldi.jpg"); productInstantDownloadVivaldi.ProductCategories.Add(new ProductCategory { Category = categoryDigitalProducts, DisplayOrder = 1 }); - productInstantDownloadVivaldi.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "vivaldi.jpg"), "image/jpeg", GetSeName(productInstantDownloadVivaldi.Name)), - DisplayOrder = 1, - }); - productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, @@ -12542,14 +11967,9 @@ public IList Products() HasSampleDownload = true }; + AddProductPicture(productInstantDownloadBeethoven, "Beethoven.jpg"); productInstantDownloadBeethoven.ProductCategories.Add(new ProductCategory { Category = categoryDigitalProducts, DisplayOrder = 1 }); - productInstantDownloadBeethoven.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "Beethoven.jpg"), "image/jpeg", GetSeName(productInstantDownloadBeethoven.Name)), - DisplayOrder = 1, - }); - productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, @@ -12599,17 +12019,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; + AddProductPicture(productTRANSOCEANCHRONOGRAPH, "product_transocean-chronograph.jpg"); productTRANSOCEANCHRONOGRAPH.ProductCategories.Add(new ProductCategory { Category = categoryWatches, DisplayOrder = 1 }); - productTRANSOCEANCHRONOGRAPH.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_transocean-chronograph.jpg"), "image/png", GetSeName(productTRANSOCEANCHRONOGRAPH.Name)), - DisplayOrder = 1, - }); - productTRANSOCEANCHRONOGRAPH.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Breitling").Single(), @@ -12707,22 +12122,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productTissotTTouchExpertSolar.ProductCategories.Add(new ProductCategory { Category = categoryWatches, DisplayOrder = 1 }); - - productTissotTTouchExpertSolar.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_tissot-t-touch-expert-solar.jpg"), "image/png", GetSeName(productTissotTTouchExpertSolar.Name)), - DisplayOrder = 1, - }); + AddProductPicture(productTissotTTouchExpertSolar, "product_tissot-t-touch-expert-solar.jpg", "tissot-1"); + AddProductPicture(productTissotTTouchExpertSolar, "product_tissot-t-touch-expert-solar-t091_2.jpg", "tissot-2"); - productTissotTTouchExpertSolar.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_tissot-t-touch-expert-solar-t091_2.jpg"), "image/png", GetSeName(productTissotTTouchExpertSolar.Name)), - DisplayOrder = 1, - }); + productTissotTTouchExpertSolar.ProductCategories.Add(new ProductCategory { Category = categoryWatches, DisplayOrder = 1 }); productTissotTTouchExpertSolar.ProductManufacturers.Add(new ProductManufacturer { @@ -12821,17 +12227,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; + AddProductPicture(productSeikoSRPA49K1, "product_SeikoSRPA49K1.jpg"); productSeikoSRPA49K1.ProductCategories.Add(new ProductCategory { Category = categoryWatches, DisplayOrder = 1 }); - productSeikoSRPA49K1.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_SeikoSRPA49K1.jpg"), "image/png", GetSeName(productSeikoSRPA49K1.Name)), - DisplayOrder = 1, - }); - productSeikoSRPA49K1.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Seiko").Single(), @@ -12921,17 +12322,12 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() - }; + DeliveryTimeId = thirdDeliveryTime.Id + }; + AddProductPicture(productWatchesCertinaDSPodiumBigSize, "product_certina_ds_podium_big.png"); productWatchesCertinaDSPodiumBigSize.ProductCategories.Add(new ProductCategory { Category = categoryWatches, DisplayOrder = 1 }); - productWatchesCertinaDSPodiumBigSize.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_certina_ds_podium_big.png"), "image/png", GetSeName(productWatchesCertinaDSPodiumBigSize.Name)), - DisplayOrder = 1, - }); - productWatchesCertinaDSPodiumBigSize.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Certina").Single(), @@ -13033,22 +12429,14 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime + DeliveryTimeId = firstDeliveryTime.Id }; - productPs3.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs3.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 4 }); + AddProductPicture(productPs3, "product_ps4_w_controller.jpg", "ps4-w-controller"); + AddProductPicture(productPs3, "product_ps4_wo_controller.jpg", "ps4-wo-single"); - productPs3.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ps4_w_controller.jpg"), "image/png", GetSeName(productPs3.Name) + "-controller"), - DisplayOrder = 1 - }); - productPs3.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ps4_wo_controller.jpg"), "image/jpeg", GetSeName(productPs3.Name) + "-single"), - DisplayOrder = 2 - }); + productPs3.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productPs3.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 4 }); var productDualshock4Controller = new Product { @@ -13071,18 +12459,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + DeliveryTimeId = firstDeliveryTime.Id + }; + AddProductPicture(productDualshock4Controller, "product_dualshock4.jpg"); productDualshock4Controller.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); productDualshock4Controller.ProductCategories.Add(new ProductCategory { Category = categoryGamingAccessories, DisplayOrder = 1 }); - productDualshock4Controller.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_dualshock4.jpg"), "image/png", GetSeName(productDualshock4Controller.Name)), - DisplayOrder = 1 - }); - var productMinecraft = new Product { @@ -13107,18 +12490,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + DeliveryTimeId = firstDeliveryTime.Id + }; + AddProductPicture(productMinecraft, "product_minecraft.jpg"); productMinecraft.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); productMinecraft.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 4 }); - productMinecraft.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_minecraft.jpg"), "image/jpeg", GetSeName("Minecraft - Playstation 4 Edition")), - DisplayOrder = 1 - }); - var productBundlePs3AssassinCreed = new Product { @@ -13141,22 +12519,17 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime, - ShowOnHomePage = true, + DeliveryTimeId = firstDeliveryTime.Id, + ShowOnHomePage = true, BundleTitleText = "Bundle includes", BundlePerItemPricing = true, BundlePerItemShoppingCart = true }; + AddProductPicture(productBundlePs3AssassinCreed, "ps4_bundle_minecraft.jpg"); productBundlePs3AssassinCreed.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); productBundlePs3AssassinCreed.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 1 }); - productBundlePs3AssassinCreed.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "ps4_bundle_minecraft.jpg"), "image/png", GetSeName(productBundlePs3AssassinCreed.Name)), - DisplayOrder = 1 - }); - #endregion bundlePs3AssassinCreed #region bundlePs4 @@ -13183,22 +12556,14 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + DeliveryTimeId = firstDeliveryTime.Id + }; - productPs4.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs4.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 5 }); + AddProductPicture(productPs4, "product_sony_ps4.png", "sony-ps4"); + AddProductPicture(productPs4, "product_sony_dualshock4_wirelesscontroller.png", "sony-ps4-dualshock"); - productPs4.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4.png"), "image/png", GetSeName(productPs4.Name)), - DisplayOrder = 1 - }); - productPs4.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_dualshock4_wirelesscontroller.png"), "image/png", GetSeName(productPs4.Name)), - DisplayOrder = 1 - }); + productPs4.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productPs4.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 5 }); var productPs4Camera = new Product @@ -13221,18 +12586,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + DeliveryTimeId = firstDeliveryTime.Id + }; + AddProductPicture(productPs4Camera, "product_sony_ps4_camera.png"); productPs4Camera.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); productPs4Camera.ProductCategories.Add(new ProductCategory { Category = categoryGamingAccessories, DisplayOrder = 3 }); - productPs4Camera.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4_camera.png"), "image/png", GetSeName(productPs4Camera.Name)), - DisplayOrder = 1 - }); - var productBundlePs4 = new Product { @@ -13256,19 +12616,14 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime, - BundleTitleText = "Bundle includes" + DeliveryTimeId = firstDeliveryTime.Id, + BundleTitleText = "Bundle includes" }; + AddProductPicture(productBundlePs4, "product_sony_ps4_bundle.png"); productBundlePs4.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); productBundlePs4.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 2 }); - productBundlePs4.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4_bundle.png"), "image/png", GetSeName(productBundlePs4.Name)), - DisplayOrder = 1 - }); - #endregion bundlePs4 #region groupAccessories @@ -13296,15 +12651,10 @@ public IList Products() ShowOnHomePage = true }; + AddProductPicture(productGroupAccessories, "category_gaming_accessories.png"); productGroupAccessories.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); productGroupAccessories.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 3 }); - productGroupAccessories.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gaming_accessories.png"), "image/png", GetSeName(productGroupAccessories.Name)), - DisplayOrder = 1 - }); - #endregion groupAccessories #region Ps3PlusOneGame @@ -13329,18 +12679,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + DeliveryTimeId = firstDeliveryTime.Id + }; + AddProductPicture(productPrinceOfPersia, "products_princeofpersia.jpg"); productPrinceOfPersia.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuUbisoft, DisplayOrder = 1 }); productPrinceOfPersia.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 2 }); - productPrinceOfPersia.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_princeofpersia.jpg"), "image/jpeg", GetSeName(productPrinceOfPersia.Name)), - DisplayOrder = 1 - }); - #endregion Ps3PlusOneGame #region Horizon Zero Down @@ -13367,18 +12712,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime + DeliveryTimeId = firstDeliveryTime.Id }; + AddProductPicture(productHorizonZeroDown, "product_horizon.jpg"); productHorizonZeroDown.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); productHorizonZeroDown.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 2 }); - productHorizonZeroDown.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_horizon.jpg"), "image/jpeg", GetSeName(productHorizonZeroDown.Name)), - DisplayOrder = 1 - }); - #endregion Horizon Zero Down #region Fifa 17 @@ -13405,18 +12745,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime + DeliveryTimeId = firstDeliveryTime.Id }; + AddProductPicture(productFifa17, "product_fifa17.jpg"); productFifa17.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuEASports, DisplayOrder = 1 }); productFifa17.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 2 }); - productFifa17.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_fifa17.jpg"), "image/jpeg", GetSeName(productFifa17.Name)), - DisplayOrder = 1 - }); - #endregion Fifa 17 #region Lego Worlds @@ -13442,18 +12777,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + DeliveryTimeId = firstDeliveryTime.Id + }; + AddProductPicture(productLegoWorlds, "product_legoworlds.jpg"); productLegoWorlds.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuWarnerHomme, DisplayOrder = 1 }); productLegoWorlds.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 3 }); - productLegoWorlds.ProductPictures.Add(new ProductPicture - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_legoworlds.jpg"), "image/jpeg", GetSeName(productLegoWorlds.Name)), - DisplayOrder = 1 - }); - #endregion Lego Worlds #endregion gaming @@ -14348,21 +13678,30 @@ protected Picture CreatePicture(byte[] pictureBinary, string mimeType, string se return picture; } - protected void AddProductPictures( + protected void AddProductPicture( Product product, string imageName, + string seName = null, int displayOrder = 1) { - var mimeType = imageName.EndsWith(".png") ? "image/png" : "image/jpg"; + string mimeType = null; + if (imageName.EndsWith(".png")) + { + mimeType = "image/png"; + } + else if (imageName.EndsWith(".gif")) + { + mimeType = "image/gif"; + } product.ProductPictures.Add(new ProductPicture { - Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + imageName), mimeType, GetSeName(product.Name)), + Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + imageName), mimeType ?? "image/jpeg", seName ?? GetSeName(product.Name)), DisplayOrder = displayOrder }); } - protected string GetSeName(string name) + protected string GetSeName(string name) { return SeoHelper.GetSeName(name, true, false); } From ab8ef5c34adaa8231b41946cb3e25e26067a2356 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 8 Aug 2018 01:56:56 +0200 Subject: [PATCH 017/932] Better mime type mapping --- src/Libraries/SmartStore.Core/IO/MimeTypes.cs | 843 +++++++++++++++++- 1 file changed, 810 insertions(+), 33 deletions(-) diff --git a/src/Libraries/SmartStore.Core/IO/MimeTypes.cs b/src/Libraries/SmartStore.Core/IO/MimeTypes.cs index d9edddeef5..506821b06f 100644 --- a/src/Libraries/SmartStore.Core/IO/MimeTypes.cs +++ b/src/Libraries/SmartStore.Core/IO/MimeTypes.cs @@ -1,19 +1,767 @@ using System; -using System.Collections.Concurrent; +using System.Linq; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Web; using Microsoft.Win32; +using System.IO; namespace SmartStore.Core.IO { public static class MimeTypes { - private static readonly ConcurrentDictionary _mimeMap = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private static readonly char[] _pathSeparatorChars = new [] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, Path.VolumeSeparatorChar }; + private static readonly Dictionary _mimeMap; + private static readonly Dictionary _extMap; - public static string MapNameToMimeType(string fileNameOrExtension) + const string DefaultMimeType = "application/octet-stream"; + + static MimeTypes() + { + // maps both ways, + // extension -> mime type + // and + // mime type -> extension + // + // any mime type on left side not pre-loaded on right side, are added automatically. + // Some mime types can map to multiple extensions, so to get a deterministic mapping, + // add those to the dictionary specifcially + // + // Some added based on http://www.iana.org/assignments/media-types/media-types.xhtml + // which lists mime types, but not extensions + // + + _mimeMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + #region Mime types > extension map + + {"application/fsharp-script", ".fsx"}, + {"application/msaccess", ".adp"}, + {"application/msword", ".doc"}, + {"application/octet-stream", ".bin"}, + {"application/onenote", ".one"}, + {"application/postscript", ".eps"}, + {"application/step", ".step"}, + {"application/vnd.ms-excel", ".xls"}, + {"application/vnd.ms-powerpoint", ".ppt"}, + {"application/vnd.ms-works", ".wks"}, + {"application/vnd.visio", ".vsd"}, + {"application/x-director", ".dir"}, + {"application/x-shockwave-flash", ".swf"}, + {"application/x-x509-ca-cert", ".cer"}, + {"application/x-zip-compressed", ".zip"}, + {"application/xhtml+xml", ".xhtml"}, + {"application/xml", ".xml"}, // anomoly, .xml -> text/xml, but application/xml -> many thingss, but all are xml, so safest is .xml + {"audio/aac", ".AAC"}, + {"audio/aiff", ".aiff"}, + {"audio/basic", ".snd"}, + {"audio/mid", ".midi"}, + {"audio/wav", ".wav"}, + {"audio/x-m4a", ".m4a"}, + {"audio/x-mpegurl", ".m3u"}, + {"audio/x-pn-realaudio", ".ra"}, + {"audio/x-smd", ".smd"}, + {"image/bmp", ".bmp"}, + {"image/jpeg", ".jpg"}, + {"image/pict", ".pic"}, + {"image/png", ".png"}, //Defined in [RFC-2045], [RFC-2048] + {"image/x-png", ".png"}, //See https://www.w3.org/TR/PNG/#A-Media-type :"It is recommended that implementations also recognize the media type "image/x-png"." + {"image/tiff", ".tiff"}, + {"image/x-macpaint", ".mac"}, + {"image/x-quicktime", ".qti"}, + {"message/rfc822", ".eml"}, + {"text/html", ".html"}, + {"text/plain", ".txt"}, + {"text/scriptlet", ".wsc"}, + {"text/xml", ".xml"}, + {"video/3gpp", ".3gp"}, + {"video/3gpp2", ".3gp2"}, + {"video/mp4", ".mp4"}, + {"video/mpeg", ".mpg"}, + {"video/quicktime", ".mov"}, + {"video/vnd.dlna.mpeg-tts", ".m2t"}, + {"video/x-dv", ".dv"}, + {"video/x-la-asf", ".lsf"}, + {"video/x-ms-asf", ".asf"}, + {"x-world/x-vrml", ".xof"}, + + #endregion + }; + + _extMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + #region Extension > mime type map + + {".323", "text/h323"}, + {".3g2", "video/3gpp2"}, + {".3gp", "video/3gpp"}, + {".3gp2", "video/3gpp2"}, + {".3gpp", "video/3gpp"}, + {".7z", "application/x-7z-compressed"}, + {".aa", "audio/audible"}, + {".AAC", "audio/aac"}, + {".aaf", "application/octet-stream"}, + {".aax", "audio/vnd.audible.aax"}, + {".ac3", "audio/ac3"}, + {".aca", "application/octet-stream"}, + {".accda", "application/msaccess.addin"}, + {".accdb", "application/msaccess"}, + {".accdc", "application/msaccess.cab"}, + {".accde", "application/msaccess"}, + {".accdr", "application/msaccess.runtime"}, + {".accdt", "application/msaccess"}, + {".accdw", "application/msaccess.webapplication"}, + {".accft", "application/msaccess.ftemplate"}, + {".acx", "application/internet-property-stream"}, + {".AddIn", "text/xml"}, + {".ade", "application/msaccess"}, + {".adobebridge", "application/x-bridge-url"}, + {".adp", "application/msaccess"}, + {".ADT", "audio/vnd.dlna.adts"}, + {".ADTS", "audio/aac"}, + {".afm", "application/octet-stream"}, + {".ai", "application/postscript"}, + {".aif", "audio/aiff"}, + {".aifc", "audio/aiff"}, + {".aiff", "audio/aiff"}, + {".air", "application/vnd.adobe.air-application-installer-package+zip"}, + {".amc", "application/mpeg"}, + {".anx", "application/annodex"}, + {".apk", "application/vnd.android.package-archive" }, + {".application", "application/x-ms-application"}, + {".art", "image/x-jg"}, + {".asa", "application/xml"}, + {".asax", "application/xml"}, + {".ascx", "application/xml"}, + {".asd", "application/octet-stream"}, + {".asf", "video/x-ms-asf"}, + {".ashx", "application/xml"}, + {".asi", "application/octet-stream"}, + {".asm", "text/plain"}, + {".asmx", "application/xml"}, + {".aspx", "application/xml"}, + {".asr", "video/x-ms-asf"}, + {".asx", "video/x-ms-asf"}, + {".atom", "application/atom+xml"}, + {".au", "audio/basic"}, + {".avi", "video/x-msvideo"}, + {".axa", "audio/annodex"}, + {".axs", "application/olescript"}, + {".axv", "video/annodex"}, + {".bas", "text/plain"}, + {".bcpio", "application/x-bcpio"}, + {".bin", "application/octet-stream"}, + {".bmp", "image/bmp"}, + {".c", "text/plain"}, + {".cab", "application/octet-stream"}, + {".caf", "audio/x-caf"}, + {".calx", "application/vnd.ms-office.calx"}, + {".cat", "application/vnd.ms-pki.seccat"}, + {".cc", "text/plain"}, + {".cd", "text/plain"}, + {".cdda", "audio/aiff"}, + {".cdf", "application/x-cdf"}, + {".cer", "application/x-x509-ca-cert"}, + {".cfg", "text/plain"}, + {".chm", "application/octet-stream"}, + {".class", "application/x-java-applet"}, + {".clp", "application/x-msclip"}, + {".cmd", "text/plain"}, + {".cmx", "image/x-cmx"}, + {".cnf", "text/plain"}, + {".cod", "image/cis-cod"}, + {".config", "application/xml"}, + {".contact", "text/x-ms-contact"}, + {".coverage", "application/xml"}, + {".cpio", "application/x-cpio"}, + {".cpp", "text/plain"}, + {".crd", "application/x-mscardfile"}, + {".crl", "application/pkix-crl"}, + {".crt", "application/x-x509-ca-cert"}, + {".cs", "text/plain"}, + {".csdproj", "text/plain"}, + {".csh", "application/x-csh"}, + {".csproj", "text/plain"}, + {".css", "text/css"}, + {".csv", "text/csv"}, + {".cur", "application/octet-stream"}, + {".cxx", "text/plain"}, + {".dat", "application/octet-stream"}, + {".datasource", "application/xml"}, + {".dbproj", "text/plain"}, + {".dcr", "application/x-director"}, + {".def", "text/plain"}, + {".deploy", "application/octet-stream"}, + {".der", "application/x-x509-ca-cert"}, + {".dgml", "application/xml"}, + {".dib", "image/bmp"}, + {".dif", "video/x-dv"}, + {".dir", "application/x-director"}, + {".disco", "text/xml"}, + {".divx", "video/divx"}, + {".dll", "application/x-msdownload"}, + {".dll.config", "text/xml"}, + {".dlm", "text/dlm"}, + {".doc", "application/msword"}, + {".docm", "application/vnd.ms-word.document.macroEnabled.12"}, + {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {".dot", "application/msword"}, + {".dotm", "application/vnd.ms-word.template.macroEnabled.12"}, + {".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, + {".dsp", "application/octet-stream"}, + {".dsw", "text/plain"}, + {".dtd", "text/xml"}, + {".dtsConfig", "text/xml"}, + {".dv", "video/x-dv"}, + {".dvi", "application/x-dvi"}, + {".dwf", "drawing/x-dwf"}, + {".dwg", "application/acad"}, + {".dwp", "application/octet-stream"}, + {".dxf", "application/x-dxf" }, + {".dxr", "application/x-director"}, + {".eml", "message/rfc822"}, + {".emz", "application/octet-stream"}, + {".eot", "application/vnd.ms-fontobject"}, + {".eps", "application/postscript"}, + {".etl", "application/etl"}, + {".etx", "text/x-setext"}, + {".evy", "application/envoy"}, + {".exe", "application/octet-stream"}, + {".exe.config", "text/xml"}, + {".fdf", "application/vnd.fdf"}, + {".fif", "application/fractals"}, + {".filters", "application/xml"}, + {".fla", "application/octet-stream"}, + {".flac", "audio/flac"}, + {".flr", "x-world/x-vrml"}, + {".flv", "video/x-flv"}, + {".fsscript", "application/fsharp-script"}, + {".fsx", "application/fsharp-script"}, + {".generictest", "application/xml"}, + {".gif", "image/gif"}, + {".gpx", "application/gpx+xml"}, + {".group", "text/x-ms-group"}, + {".gsm", "audio/x-gsm"}, + {".gtar", "application/x-gtar"}, + {".gz", "application/x-gzip"}, + {".h", "text/plain"}, + {".hdf", "application/x-hdf"}, + {".hdml", "text/x-hdml"}, + {".hhc", "application/x-oleobject"}, + {".hhk", "application/octet-stream"}, + {".hhp", "application/octet-stream"}, + {".hlp", "application/winhlp"}, + {".hpp", "text/plain"}, + {".hqx", "application/mac-binhex40"}, + {".hta", "application/hta"}, + {".htc", "text/x-component"}, + {".htm", "text/html"}, + {".html", "text/html"}, + {".htt", "text/webviewhtml"}, + {".hxa", "application/xml"}, + {".hxc", "application/xml"}, + {".hxd", "application/octet-stream"}, + {".hxe", "application/xml"}, + {".hxf", "application/xml"}, + {".hxh", "application/octet-stream"}, + {".hxi", "application/octet-stream"}, + {".hxk", "application/xml"}, + {".hxq", "application/octet-stream"}, + {".hxr", "application/octet-stream"}, + {".hxs", "application/octet-stream"}, + {".hxt", "text/html"}, + {".hxv", "application/xml"}, + {".hxw", "application/octet-stream"}, + {".hxx", "text/plain"}, + {".i", "text/plain"}, + {".ico", "image/x-icon"}, + {".ics", "application/octet-stream"}, + {".idl", "text/plain"}, + {".ief", "image/ief"}, + {".iii", "application/x-iphone"}, + {".inc", "text/plain"}, + {".inf", "application/octet-stream"}, + {".ini", "text/plain"}, + {".inl", "text/plain"}, + {".ins", "application/x-internet-signup"}, + {".ipa", "application/x-itunes-ipa"}, + {".ipg", "application/x-itunes-ipg"}, + {".ipproj", "text/plain"}, + {".ipsw", "application/x-itunes-ipsw"}, + {".iqy", "text/x-ms-iqy"}, + {".isp", "application/x-internet-signup"}, + {".ite", "application/x-itunes-ite"}, + {".itlp", "application/x-itunes-itlp"}, + {".itms", "application/x-itunes-itms"}, + {".itpc", "application/x-itunes-itpc"}, + {".IVF", "video/x-ivf"}, + {".jar", "application/java-archive"}, + {".java", "application/octet-stream"}, + {".jck", "application/liquidmotion"}, + {".jcz", "application/liquidmotion"}, + {".jfif", "image/pjpeg"}, + {".jnlp", "application/x-java-jnlp-file"}, + {".jpb", "application/octet-stream"}, + {".jpe", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".jpg", "image/jpeg"}, + {".js", "application/javascript"}, + {".json", "application/json"}, + {".jsx", "text/jscript"}, + {".jsxbin", "text/plain"}, + {".latex", "application/x-latex"}, + {".library-ms", "application/windows-library+xml"}, + {".lit", "application/x-ms-reader"}, + {".loadtest", "application/xml"}, + {".lpk", "application/octet-stream"}, + {".lsf", "video/x-la-asf"}, + {".lst", "text/plain"}, + {".lsx", "video/x-la-asf"}, + {".lzh", "application/octet-stream"}, + {".m13", "application/x-msmediaview"}, + {".m14", "application/x-msmediaview"}, + {".m1v", "video/mpeg"}, + {".m2t", "video/vnd.dlna.mpeg-tts"}, + {".m2ts", "video/vnd.dlna.mpeg-tts"}, + {".m2v", "video/mpeg"}, + {".m3u", "audio/x-mpegurl"}, + {".m3u8", "audio/x-mpegurl"}, + {".m4a", "audio/m4a"}, + {".m4b", "audio/m4b"}, + {".m4p", "audio/m4p"}, + {".m4r", "audio/x-m4r"}, + {".m4v", "video/x-m4v"}, + {".mac", "image/x-macpaint"}, + {".mak", "text/plain"}, + {".man", "application/x-troff-man"}, + {".manifest", "application/x-ms-manifest"}, + {".map", "text/plain"}, + {".master", "application/xml"}, + {".mbox", "application/mbox"}, + {".mda", "application/msaccess"}, + {".mdb", "application/x-msaccess"}, + {".mde", "application/msaccess"}, + {".mdp", "application/octet-stream"}, + {".me", "application/x-troff-me"}, + {".mfp", "application/x-shockwave-flash"}, + {".mht", "message/rfc822"}, + {".mhtml", "message/rfc822"}, + {".mid", "audio/mid"}, + {".midi", "audio/mid"}, + {".mix", "application/octet-stream"}, + {".mk", "text/plain"}, + {".mk3d", "video/x-matroska-3d"}, + {".mka", "audio/x-matroska"}, + {".mkv", "video/x-matroska"}, + {".mmf", "application/x-smaf"}, + {".mno", "text/xml"}, + {".mny", "application/x-msmoney"}, + {".mod", "video/mpeg"}, + {".mov", "video/quicktime"}, + {".movie", "video/x-sgi-movie"}, + {".mp2", "video/mpeg"}, + {".mp2v", "video/mpeg"}, + {".mp3", "audio/mpeg"}, + {".mp4", "video/mp4"}, + {".mp4v", "video/mp4"}, + {".mpa", "video/mpeg"}, + {".mpe", "video/mpeg"}, + {".mpeg", "video/mpeg"}, + {".mpf", "application/vnd.ms-mediapackage"}, + {".mpg", "video/mpeg"}, + {".mpp", "application/vnd.ms-project"}, + {".mpv2", "video/mpeg"}, + {".mqv", "video/quicktime"}, + {".ms", "application/x-troff-ms"}, + {".msg", "application/vnd.ms-outlook"}, + {".msi", "application/octet-stream"}, + {".mso", "application/octet-stream"}, + {".mts", "video/vnd.dlna.mpeg-tts"}, + {".mtx", "application/xml"}, + {".mvb", "application/x-msmediaview"}, + {".mvc", "application/x-miva-compiled"}, + {".mxp", "application/x-mmxp"}, + {".nc", "application/x-netcdf"}, + {".nsc", "video/x-ms-asf"}, + {".nws", "message/rfc822"}, + {".ocx", "application/octet-stream"}, + {".oda", "application/oda"}, + {".odb", "application/vnd.oasis.opendocument.database"}, + {".odc", "application/vnd.oasis.opendocument.chart"}, + {".odf", "application/vnd.oasis.opendocument.formula"}, + {".odg", "application/vnd.oasis.opendocument.graphics"}, + {".odh", "text/plain"}, + {".odi", "application/vnd.oasis.opendocument.image"}, + {".odl", "text/plain"}, + {".odm", "application/vnd.oasis.opendocument.text-master"}, + {".odp", "application/vnd.oasis.opendocument.presentation"}, + {".ods", "application/vnd.oasis.opendocument.spreadsheet"}, + {".odt", "application/vnd.oasis.opendocument.text"}, + {".oga", "audio/ogg"}, + {".ogg", "audio/ogg"}, + {".ogv", "video/ogg"}, + {".ogx", "application/ogg"}, + {".one", "application/onenote"}, + {".onea", "application/onenote"}, + {".onepkg", "application/onenote"}, + {".onetmp", "application/onenote"}, + {".onetoc", "application/onenote"}, + {".onetoc2", "application/onenote"}, + {".opus", "audio/ogg"}, + {".orderedtest", "application/xml"}, + {".osdx", "application/opensearchdescription+xml"}, + {".otf", "application/font-sfnt"}, + {".otg", "application/vnd.oasis.opendocument.graphics-template"}, + {".oth", "application/vnd.oasis.opendocument.text-web"}, + {".otp", "application/vnd.oasis.opendocument.presentation-template"}, + {".ots", "application/vnd.oasis.opendocument.spreadsheet-template"}, + {".ott", "application/vnd.oasis.opendocument.text-template"}, + {".oxt", "application/vnd.openofficeorg.extension"}, + {".p10", "application/pkcs10"}, + {".p12", "application/x-pkcs12"}, + {".p7b", "application/x-pkcs7-certificates"}, + {".p7c", "application/pkcs7-mime"}, + {".p7m", "application/pkcs7-mime"}, + {".p7r", "application/x-pkcs7-certreqresp"}, + {".p7s", "application/pkcs7-signature"}, + {".pbm", "image/x-portable-bitmap"}, + {".pcast", "application/x-podcast"}, + {".pct", "image/pict"}, + {".pcx", "application/octet-stream"}, + {".pcz", "application/octet-stream"}, + {".pdf", "application/pdf"}, + {".pfb", "application/octet-stream"}, + {".pfm", "application/octet-stream"}, + {".pfx", "application/x-pkcs12"}, + {".pgm", "image/x-portable-graymap"}, + {".pic", "image/pict"}, + {".pict", "image/pict"}, + {".pkgdef", "text/plain"}, + {".pkgundef", "text/plain"}, + {".pko", "application/vnd.ms-pki.pko"}, + {".pls", "audio/scpls"}, + {".pma", "application/x-perfmon"}, + {".pmc", "application/x-perfmon"}, + {".pml", "application/x-perfmon"}, + {".pmr", "application/x-perfmon"}, + {".pmw", "application/x-perfmon"}, + {".png", "image/png"}, + {".pnm", "image/x-portable-anymap"}, + {".pnt", "image/x-macpaint"}, + {".pntg", "image/x-macpaint"}, + {".pnz", "image/png"}, + {".pot", "application/vnd.ms-powerpoint"}, + {".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"}, + {".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"}, + {".ppa", "application/vnd.ms-powerpoint"}, + {".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"}, + {".ppm", "image/x-portable-pixmap"}, + {".pps", "application/vnd.ms-powerpoint"}, + {".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"}, + {".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, + {".ppt", "application/vnd.ms-powerpoint"}, + {".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"}, + {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, + {".prf", "application/pics-rules"}, + {".prm", "application/octet-stream"}, + {".prx", "application/octet-stream"}, + {".ps", "application/postscript"}, + {".psc1", "application/PowerShell"}, + {".psd", "application/octet-stream"}, + {".psess", "application/xml"}, + {".psm", "application/octet-stream"}, + {".psp", "application/octet-stream"}, + {".pst", "application/vnd.ms-outlook"}, + {".pub", "application/x-mspublisher"}, + {".pwz", "application/vnd.ms-powerpoint"}, + {".qht", "text/x-html-insertion"}, + {".qhtm", "text/x-html-insertion"}, + {".qt", "video/quicktime"}, + {".qti", "image/x-quicktime"}, + {".qtif", "image/x-quicktime"}, + {".qtl", "application/x-quicktimeplayer"}, + {".qxd", "application/octet-stream"}, + {".ra", "audio/x-pn-realaudio"}, + {".ram", "audio/x-pn-realaudio"}, + {".rar", "application/x-rar-compressed"}, + {".ras", "image/x-cmu-raster"}, + {".rat", "application/rat-file"}, + {".rc", "text/plain"}, + {".rc2", "text/plain"}, + {".rct", "text/plain"}, + {".rdlc", "application/xml"}, + {".reg", "text/plain"}, + {".resx", "application/xml"}, + {".rf", "image/vnd.rn-realflash"}, + {".rgb", "image/x-rgb"}, + {".rgs", "text/plain"}, + {".rm", "application/vnd.rn-realmedia"}, + {".rmi", "audio/mid"}, + {".rmp", "application/vnd.rn-rn_music_package"}, + {".roff", "application/x-troff"}, + {".rpm", "audio/x-pn-realaudio-plugin"}, + {".rqy", "text/x-ms-rqy"}, + {".rtf", "application/rtf"}, + {".rtx", "text/richtext"}, + {".rvt", "application/octet-stream" }, + {".ruleset", "application/xml"}, + {".s", "text/plain"}, + {".safariextz", "application/x-safari-safariextz"}, + {".scd", "application/x-msschedule"}, + {".scr", "text/plain"}, + {".sct", "text/scriptlet"}, + {".sd2", "audio/x-sd2"}, + {".sdp", "application/sdp"}, + {".sea", "application/octet-stream"}, + {".searchConnector-ms", "application/windows-search-connector+xml"}, + {".setpay", "application/set-payment-initiation"}, + {".setreg", "application/set-registration-initiation"}, + {".settings", "application/xml"}, + {".sgimb", "application/x-sgimb"}, + {".sgml", "text/sgml"}, + {".sh", "application/x-sh"}, + {".shar", "application/x-shar"}, + {".shtml", "text/html"}, + {".sit", "application/x-stuffit"}, + {".sitemap", "application/xml"}, + {".skin", "application/xml"}, + {".skp", "application/x-koan" }, + {".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"}, + {".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"}, + {".slk", "application/vnd.ms-excel"}, + {".sln", "text/plain"}, + {".slupkg-ms", "application/x-ms-license"}, + {".smd", "audio/x-smd"}, + {".smi", "application/octet-stream"}, + {".smx", "audio/x-smd"}, + {".smz", "audio/x-smd"}, + {".snd", "audio/basic"}, + {".snippet", "application/xml"}, + {".snp", "application/octet-stream"}, + {".sol", "text/plain"}, + {".sor", "text/plain"}, + {".spc", "application/x-pkcs7-certificates"}, + {".spl", "application/futuresplash"}, + {".spx", "audio/ogg"}, + {".src", "application/x-wais-source"}, + {".srf", "text/plain"}, + {".SSISDeploymentManifest", "text/xml"}, + {".ssm", "application/streamingmedia"}, + {".sst", "application/vnd.ms-pki.certstore"}, + {".stl", "application/vnd.ms-pki.stl"}, + {".sv4cpio", "application/x-sv4cpio"}, + {".sv4crc", "application/x-sv4crc"}, + {".svc", "application/xml"}, + {".svg", "image/svg+xml"}, + {".swf", "application/x-shockwave-flash"}, + {".step", "application/step"}, + {".stp", "application/step"}, + {".t", "application/x-troff"}, + {".tar", "application/x-tar"}, + {".tcl", "application/x-tcl"}, + {".testrunconfig", "application/xml"}, + {".testsettings", "application/xml"}, + {".tex", "application/x-tex"}, + {".texi", "application/x-texinfo"}, + {".texinfo", "application/x-texinfo"}, + {".tgz", "application/x-compressed"}, + {".thmx", "application/vnd.ms-officetheme"}, + {".thn", "application/octet-stream"}, + {".tif", "image/tiff"}, + {".tiff", "image/tiff"}, + {".tlh", "text/plain"}, + {".tli", "text/plain"}, + {".toc", "application/octet-stream"}, + {".tr", "application/x-troff"}, + {".trm", "application/x-msterminal"}, + {".trx", "application/xml"}, + {".ts", "video/vnd.dlna.mpeg-tts"}, + {".tsv", "text/tab-separated-values"}, + {".ttf", "application/font-sfnt"}, + {".tts", "video/vnd.dlna.mpeg-tts"}, + {".txt", "text/plain"}, + {".u32", "application/octet-stream"}, + {".uls", "text/iuls"}, + {".user", "text/plain"}, + {".ustar", "application/x-ustar"}, + {".vb", "text/plain"}, + {".vbdproj", "text/plain"}, + {".vbk", "video/mpeg"}, + {".vbproj", "text/plain"}, + {".vbs", "text/vbscript"}, + {".vcf", "text/x-vcard"}, + {".vcproj", "application/xml"}, + {".vcs", "text/plain"}, + {".vcxproj", "application/xml"}, + {".vddproj", "text/plain"}, + {".vdp", "text/plain"}, + {".vdproj", "text/plain"}, + {".vdx", "application/vnd.ms-visio.viewer"}, + {".vml", "text/xml"}, + {".vscontent", "application/xml"}, + {".vsct", "text/xml"}, + {".vsd", "application/vnd.visio"}, + {".vsi", "application/ms-vsi"}, + {".vsix", "application/vsix"}, + {".vsixlangpack", "text/xml"}, + {".vsixmanifest", "text/xml"}, + {".vsmdi", "application/xml"}, + {".vspscc", "text/plain"}, + {".vss", "application/vnd.visio"}, + {".vsscc", "text/plain"}, + {".vssettings", "text/xml"}, + {".vssscc", "text/plain"}, + {".vst", "application/vnd.visio"}, + {".vstemplate", "text/xml"}, + {".vsto", "application/x-ms-vsto"}, + {".vsw", "application/vnd.visio"}, + {".vsx", "application/vnd.visio"}, + {".vtt", "text/vtt"}, + {".vtx", "application/vnd.visio"}, + {".wasm", "application/wasm"}, + {".wav", "audio/wav"}, + {".wave", "audio/wav"}, + {".wax", "audio/x-ms-wax"}, + {".wbk", "application/msword"}, + {".wbmp", "image/vnd.wap.wbmp"}, + {".wcm", "application/vnd.ms-works"}, + {".wdb", "application/vnd.ms-works"}, + {".wdp", "image/vnd.ms-photo"}, + {".webarchive", "application/x-safari-webarchive"}, + {".webm", "video/webm"}, + {".webp", "image/webp"}, /* https://en.wikipedia.org/wiki/WebP */ + {".webtest", "application/xml"}, + {".wiq", "application/xml"}, + {".wiz", "application/msword"}, + {".wks", "application/vnd.ms-works"}, + {".WLMP", "application/wlmoviemaker"}, + {".wlpginstall", "application/x-wlpg-detect"}, + {".wlpginstall3", "application/x-wlpg3-detect"}, + {".wm", "video/x-ms-wm"}, + {".wma", "audio/x-ms-wma"}, + {".wmd", "application/x-ms-wmd"}, + {".wmf", "application/x-msmetafile"}, + {".wml", "text/vnd.wap.wml"}, + {".wmlc", "application/vnd.wap.wmlc"}, + {".wmls", "text/vnd.wap.wmlscript"}, + {".wmlsc", "application/vnd.wap.wmlscriptc"}, + {".wmp", "video/x-ms-wmp"}, + {".wmv", "video/x-ms-wmv"}, + {".wmx", "video/x-ms-wmx"}, + {".wmz", "application/x-ms-wmz"}, + {".woff", "application/font-woff"}, + {".woff2", "application/font-woff2"}, + {".wpl", "application/vnd.ms-wpl"}, + {".wps", "application/vnd.ms-works"}, + {".wri", "application/x-mswrite"}, + {".wrl", "x-world/x-vrml"}, + {".wrz", "x-world/x-vrml"}, + {".wsc", "text/scriptlet"}, + {".wsdl", "text/xml"}, + {".wvx", "video/x-ms-wvx"}, + {".x", "application/directx"}, + {".xaf", "x-world/x-vrml"}, + {".xaml", "application/xaml+xml"}, + {".xap", "application/x-silverlight-app"}, + {".xbap", "application/x-ms-xbap"}, + {".xbm", "image/x-xbitmap"}, + {".xdr", "text/plain"}, + {".xht", "application/xhtml+xml"}, + {".xhtml", "application/xhtml+xml"}, + {".xla", "application/vnd.ms-excel"}, + {".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"}, + {".xlc", "application/vnd.ms-excel"}, + {".xld", "application/vnd.ms-excel"}, + {".xlk", "application/vnd.ms-excel"}, + {".xll", "application/vnd.ms-excel"}, + {".xlm", "application/vnd.ms-excel"}, + {".xls", "application/vnd.ms-excel"}, + {".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"}, + {".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"}, + {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {".xlt", "application/vnd.ms-excel"}, + {".xltm", "application/vnd.ms-excel.template.macroEnabled.12"}, + {".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"}, + {".xlw", "application/vnd.ms-excel"}, + {".xml", "text/xml"}, + {".xmp", "application/octet-stream" }, + {".xmta", "application/xml"}, + {".xof", "x-world/x-vrml"}, + {".XOML", "text/plain"}, + {".xpm", "image/x-xpixmap"}, + {".xps", "application/vnd.ms-xpsdocument"}, + {".xrm-ms", "text/xml"}, + {".xsc", "application/xml"}, + {".xsd", "text/xml"}, + {".xsf", "text/xml"}, + {".xsl", "text/xml"}, + {".xslt", "text/xml"}, + {".xsn", "application/octet-stream"}, + {".xss", "application/xml"}, + {".xspf", "application/xspf+xml"}, + {".xtp", "application/octet-stream"}, + {".xwd", "image/x-xwindowdump"}, + {".z", "application/x-compress"}, + {".zip", "application/zip"}, + + #endregion + }; + + // Shuffle entries + ShuffleMap(_mimeMap, _extMap); + ShuffleMap(_extMap, _mimeMap); + + // Complement maps with entries from registry + try + { + using (var key = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type", false)) + { + if (key != null) + { + var subKeyNames = key.GetSubKeyNames(); + foreach (var contentType in subKeyNames) + { + if (!_mimeMap.ContainsKey(contentType)) + { + // Our map does not contain this entry. We gonna add it. + using (var subKey = key.OpenSubKey(contentType, false)) + { + object extension = subKey.GetValue("Extension", null); + if (extension != null) + { + // MimeType > Extension + var ext = extension.ToString(); + _mimeMap.Add(contentType, ext); + + // Vice versa, Extension > MimeType + if (!_extMap.ContainsKey(ext)) + { + _extMap.Add(ext, contentType); + } + } + } + } + } + } + } + } + catch { } + } + + public static string MapNameToMimeType(string fileNameOrExtension) { - return MimeMapping.GetMimeMapping(fileNameOrExtension); + var fileName = GetFileName(fileNameOrExtension); + + for (int i = 0; i < fileName.Length; i++) + { + if ((fileName[i] == '.') && _extMap.TryGetValue(fileName.Substring(i), out var str)) + { + return str; + } + } + + + return DefaultMimeType; } /// @@ -27,37 +775,66 @@ public static string MapMimeTypeToExtension(string mimeType) if (mimeType.IsEmpty()) return null; - return _mimeMap.GetOrAdd(mimeType, k => { - string result; + if (_mimeMap.TryGetValue(mimeType, out var extension)) + { + return extension.TrimStart('.'); + } - try - { - using (var key = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type\" + mimeType, false)) - { - object value = key != null ? key.GetValue("Extension", null) : null; - result = value != null ? value.ToString().Trim('.') : null; - } - } - catch + return null; + + //return _mimeMap.GetOrAdd(mimeType, k => { + // string result; + + // try + // { + // using (var key = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type\" + mimeType, false)) + // { + // object value = key != null ? key.GetValue("Extension", null) : null; + // result = value != null ? value.ToString().Trim('.') : null; + // } + // } + // catch + // { + // string[] parts = mimeType.Split('/'); + // result = parts[parts.Length - 1]; + // switch (result) + // { + // case "pjpeg": + // result = "jpg"; + // break; + // case "x-png": + // result = "png"; + // break; + // case "x-icon": + // result = "ico"; + // break; + // } + // } + + // return result; + //}); + } + + private static void ShuffleMap(Dictionary source, Dictionary target) + { + foreach (var kvp in source) + { + if (!target.ContainsKey(kvp.Value)) { - string[] parts = mimeType.Split('/'); - result = parts[parts.Length - 1]; - switch (result) - { - case "pjpeg": - result = "jpg"; - break; - case "x-png": - result = "png"; - break; - case "x-icon": - result = "ico"; - break; - } + target.Add(kvp.Value, kvp.Key); } + } + } - return result; - }); - } - } + private static string GetFileName(string path) + { + var startIndex = path.LastIndexOfAny(_pathSeparatorChars); + if (startIndex < 0) + { + return path; + } + + return path.Substring(startIndex); + } + } } From 005462e5aff0b3f40d86b6ffa5d6b0a863fe830c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 8 Aug 2018 02:09:08 +0200 Subject: [PATCH 018/932] Fixed a previous commit --- src/Libraries/SmartStore.Core/WebHelper.cs | 11 +++++++++-- .../Views/Product/Partials/Product.Picture.cshtml | 6 +++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Libraries/SmartStore.Core/WebHelper.cs b/src/Libraries/SmartStore.Core/WebHelper.cs index b80f0e9381..6304d682bd 100644 --- a/src/Libraries/SmartStore.Core/WebHelper.cs +++ b/src/Libraries/SmartStore.Core/WebHelper.cs @@ -630,7 +630,7 @@ public static string MakeAllUrlsAbsolute(string html, string protocol, string ho /// Prepends protocol and host to the given (relative) url /// [SuppressMessage("ReSharper", "AccessToModifiedClosure")] - public static string GetAbsoluteUrl(string url, HttpRequestBase request) + public static string GetAbsoluteUrl(string url, HttpRequestBase request, bool enforceScheme = false) { Guard.NotEmpty(url, nameof(url)); Guard.NotNull(request, nameof(request)); @@ -640,11 +640,18 @@ public static string GetAbsoluteUrl(string url, HttpRequestBase request) return url; } - if (url.Contains("://") || url.StartsWith("//")) + if (url.Contains("://")) { return url; } + if (url.StartsWith("//")) + { + return enforceScheme + ? String.Concat(request.Url.Scheme, ":", url) + : url; + } + if (url.StartsWith("~")) { url = VirtualPathUtility.ToAbsolute(url); diff --git a/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Picture.cshtml b/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Picture.cshtml index 2af1ac401e..663efe9ae4 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Picture.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Picture.cshtml @@ -10,9 +10,9 @@ var oImg = Model.PictureModels?.FirstOrDefault(); if (oImg != null && oImg.FullSizeImageUrl.HasValue()) { - var url = WebHelper.GetAbsoluteUrl(oImg.FullSizeImageUrl, this.Request); - Html.AddCustomHeadParts("".FormatInvariant(this.Request.Url.Scheme, url)); - Html.AddCustomHeadParts("".FormatInvariant(this.Request.Url.Scheme, url)); + var url = WebHelper.GetAbsoluteUrl(oImg.FullSizeImageUrl, this.Request, true); + Html.AddCustomHeadParts("".FormatInvariant(url)); + Html.AddCustomHeadParts("".FormatInvariant(url)); if (oImg.FullSizeImageWidth > 0 && oImg.FullSizeImageHeight > 0) { Html.AddCustomHeadParts("".FormatInvariant(oImg.FullSizeImageWidth.Value)); From 94ab58514f68215e4bbadf4508badd97d21a76cf Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 8 Aug 2018 13:25:03 +0200 Subject: [PATCH 019/932] Improved validation rule for Store.Url --- .../Administration/Models/Stores/StoreModel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Stores/StoreModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Stores/StoreModel.cs index fb80bdb8ba..36a1717b16 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Stores/StoreModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Stores/StoreModel.cs @@ -93,7 +93,10 @@ public partial class StoreValidator : AbstractValidator public StoreValidator(Localizer T) { RuleFor(x => x.Name).NotEmpty(); - RuleFor(x => x.Url).NotEmpty(); + + RuleFor(x => x.Url) + .Must(x => x.HasValue() && x.IsWebUrl()) + .WithMessage(T("Admin.Validation.Url")); RuleFor(x => x.HtmlBodyId).Matches(@"^([A-Za-z])(\w|\-)*$") .WithMessage(T("Admin.Configuration.Stores.Fields.HtmlBodyId.Validation")); From 1c183eb1020e63ba8a63bf67f0263a82f3892c3b Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 9 Aug 2018 01:02:55 +0200 Subject: [PATCH 020/932] Minor resource fix --- .../SmartStore.Data/Migrations/MigrationsConfiguration.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index d78eded090..2c9eed2f87 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -359,10 +359,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Die angegebenen Versionsinformationen sind nicht gültig. Bitte geben Sie die Versionsnummer in korrektem Format an (z.B.: 1.0.0.0, 2.0 oder 3.1.5)."); builder.AddOrUpdate("Admin.Catalog.Products.Fields.HasPreviewPicture", - "Has preview picture", - "Hat Vorschaubild", - "If this option is activated, the first image is used as a preview image in product lists and is no longer displayed in the product detail view.", - "Wenn diese Option aktiviert ist wird das erste Bild als Vorschaubild in Produktlisten genutzt und in der Produktdetailanischt nicht mehr angezeigt."); + "Exclude first image from gallery", + "Erstes Bild aus Gallerie ausschließen", + "Activate this option if the first image should be displayed as a preview in product lists but not in the product detail gallery.", + "Aktivieren Sie diese Option, wenn das erste Bild als Vorschau in Produktlisten, nicht aber in der Produktdetail-Gallerie angezeigt werden soll."); builder.AddOrUpdate("Products.Free", "Free", "Kostenlos"); From ecf137cd3abb98f1fab3e9c9db35629b5faa13f3 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 9 Aug 2018 02:35:30 +0200 Subject: [PATCH 021/932] Several fixes & enhancement for download management --- .../Migrations/MigrationsConfiguration.cs | 6 +-- .../Catalog/CopyProductService.cs | 4 +- .../Media/DownloadService.cs | 46 +++++++++++++------ .../Media/IDownloadService.cs | 28 +++++++---- .../SmartStore.Services.csproj | 6 +++ .../SmartStore.Services/packages.config | 2 + .../Controllers/ProductController.cs | 17 ++----- .../Controllers/CustomerController.cs | 3 +- .../Controllers/DownloadController.cs | 12 +++-- 9 files changed, 76 insertions(+), 48 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 2c9eed2f87..a106e742f6 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -350,9 +350,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Upload a new version of the download file here.", "Laden Sie hier eine neue Version der Download-Datei hoch."); - builder.AddOrUpdate("Admin.Catalog.Products.Download.VersionDelete", "Delete this file version of the download.", "Diese Dateiversion des Downloads löschen."); - builder.AddOrUpdate("Admin.Catalog.Products.Download.AddChangelog", "Edit changelog", "Changelog bearbeiten"); - builder.AddOrUpdate("Customer.Downloads.NoChangelogAvailable", "No changelog information available.", "Keine Changelog-Informationen verfügbar."); + builder.AddOrUpdate("Admin.Catalog.Products.Download.VersionDelete", "Delete this file version.", "Diese Dateiversion löschen."); + builder.AddOrUpdate("Admin.Catalog.Products.Download.AddChangelog", "Edit changelog", "Änderungshistorie bearbeiten"); + builder.AddOrUpdate("Customer.Downloads.NoChangelogAvailable", "No changelog available.", "Keine Änderungshistorie verfügbar."); builder.AddOrUpdate("Admin.Catalog.Products.Download.SemanticVersion.NotValid", "The specified version information is not valid. Please enter the version number in the correct format (e.g.: 1.0.0.0, 2.0 or 3.1.5).", diff --git a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs index ddb962499e..c18e437254 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs @@ -366,9 +366,7 @@ private void Commit() private Download CopyDownload(int productId, string entityName = "") { - var download = _downloadService.GetDownloadsByEntityId(productId, entityName) - .OrderByDescending(x => x.FileVersion) - .FirstOrDefault(); + var download = _downloadService.GetDownloadsFor(productId, entityName).FirstOrDefault(); if (download == null) { diff --git a/src/Libraries/SmartStore.Services/Media/DownloadService.cs b/src/Libraries/SmartStore.Services/Media/DownloadService.cs index 79809b45d6..acff6cc3db 100644 --- a/src/Libraries/SmartStore.Services/Media/DownloadService.cs +++ b/src/Libraries/SmartStore.Services/Media/DownloadService.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using NuGet; using SmartStore.Collections; +using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Media; @@ -71,30 +73,48 @@ where downloadIds.Contains(dl.Id) return downloads.OrderBySequence(downloadIds).ToList(); } - public virtual IList GetDownloadsByEntityId(int entityId, string entityName) - { - if (entityId == 0) - return new List(); + public virtual IList GetDownloadsFor(TEntity entity) where TEntity : BaseEntity + { + Guard.NotNull(entity, nameof(entity)); - var query = from dl in _downloadRepository.Table - where dl.EntityId == entityId && dl.EntityName.Equals(entityName) - select dl; + return GetDownloadsFor(entity.Id, entity.GetUnproxiedType().Name); + } - var downloads = query.OrderBy(x => x.FileVersion); + public virtual IList GetDownloadsFor(int entityId, string entityName) + { + if (entityId > 0) + { + var downloads = (from x in _downloadRepository.Table + where x.EntityId == entityId && x.EntityName == entityName + select x).ToList(); - return downloads.ToList(); - } + if (downloads.Count > 0) + { + var idsOrderedByVersion = downloads + .Select(x => new { x.Id, Version = new SemanticVersion(x.FileVersion) }) + .OrderBy(x => x.Version) + .Select(x => x.Id); + + downloads = new List(downloads.OrderBySequence(idsOrderedByVersion)); + + return downloads; + } + + } + + return new List(); + } - public virtual Multimap GetDownloadsByEntityIds(int[] entityIds, string entityName) + public virtual Multimap GetDownloadsByEntityIds(int[] entityIds, string entityName) { Guard.NotNull(entityIds, nameof(entityIds)); Guard.NotEmpty(entityName, nameof(entityName)); var query = _downloadRepository.TableUntracked - .Where(x => entityIds.Contains(x.EntityId) && x.EntityName == entityName); + .Where(x => entityIds.Contains(x.EntityId) && x.EntityName == entityName) + .OrderBy(x => x.FileVersion); var map = query - .OrderBy(x => x.FileVersion) .ToList() .ToMultimap(x => x.EntityId, x => x); diff --git a/src/Libraries/SmartStore.Services/Media/IDownloadService.cs b/src/Libraries/SmartStore.Services/Media/IDownloadService.cs index d5282fd3d8..9a09d5ad28 100644 --- a/src/Libraries/SmartStore.Services/Media/IDownloadService.cs +++ b/src/Libraries/SmartStore.Services/Media/IDownloadService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using SmartStore.Collections; +using SmartStore.Core; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; @@ -22,21 +23,28 @@ public partial interface IDownloadService /// List of download entities IList GetDownloadsByIds(int[] downloadIds); - /// + /// + /// Gets downloads assigned to an entity + /// + /// Entity to get download for + /// List of download entities sorted by FileVersion + IList GetDownloadsFor(TEntity entity) where TEntity : BaseEntity; + + /// /// Gets downloads by entity identifier /// /// Entity identifier - /// Entity name + /// Entity name /// List of download entities - IList GetDownloadsByEntityId(int entityId, string entityName); + IList GetDownloadsFor(int entityId, string entityName); - /// - /// Gets downloads by entity identifiers - /// - /// Entity identifiers - /// Entity name - /// Multimap of download entities - Multimap GetDownloadsByEntityIds(int[] entityIds, string entityName); + /// + /// Gets downloads by entity identifiers + /// + /// Entity identifiers + /// Entity name + /// Multimap of download entities + Multimap GetDownloadsByEntityIds(int[] entityIds, string entityName); /// /// Gets a download by GUID diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 16ebe81fb2..4101686e95 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -109,6 +109,9 @@ True ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + ..\..\packages\Microsoft.Web.Xdt.2.1.1\lib\net40\Microsoft.Web.XmlTransform.dll + ..\..\packages\ncrontab.3.3.0\lib\net35\NCrontab.dll True @@ -121,6 +124,9 @@ ..\..\packages\NReco.PdfGenerator.1.1.15\lib\net20\NReco.PdfGenerator.dll True + + ..\..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll + ..\..\packages\PreMailer.Net.1.5.5\lib\net45\PreMailer.Net.dll diff --git a/src/Libraries/SmartStore.Services/packages.config b/src/Libraries/SmartStore.Services/packages.config index dc8b84bdc8..c369c9c9bc 100644 --- a/src/Libraries/SmartStore.Services/packages.config +++ b/src/Libraries/SmartStore.Services/packages.config @@ -13,9 +13,11 @@ + + diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 2e61a31dad..838706157f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -292,9 +292,7 @@ protected void UpdateProductDownloads(Product product, ProductModel model) } else if (m.DownloadFileVersion.HasValue()) { - var download = _downloadService.GetDownloadsByEntityId(p.Id, "Product") - .OrderByDescending(x => x.FileVersion) - .FirstOrDefault(); + var download = _downloadService.GetDownloadsFor(p).FirstOrDefault(); download.FileVersion = new SemanticVersion(m.DownloadFileVersion).ToString(); download.EntityId = p.Id; @@ -551,9 +549,7 @@ private void UpdateDataOfExistingProduct(Product product, ProductModel model, bo var seoTabLoaded = m.LoadedTabs.Contains("SEO", StringComparer.OrdinalIgnoreCase); // Handle Download transiency - var download = _downloadService.GetDownloadsByEntityId(p.Id, "Product") - .OrderByDescending(x => x.FileVersion) - .FirstOrDefault(); + var download = _downloadService.GetDownloadsFor(p).FirstOrDefault(); MediaHelper.UpdateDownloadTransientStateFor(p, x => download.Id); MediaHelper.UpdateDownloadTransientStateFor(p, x => x.SampleDownloadId); @@ -816,8 +812,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set .ToList(); // downloads - model.DownloadVersions = _downloadService.GetDownloadsByEntityId(product.Id, "Product") - .OrderByDescending(x => x.FileVersion) + model.DownloadVersions = _downloadService.GetDownloadsFor(product) .Select(x => new DownloadVersion { FileVersion = x.FileVersion, @@ -827,12 +822,10 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set }) .ToList(); - var currentDownload = _downloadService.GetDownloadsByEntityId(product.Id, "Product") - .OrderByDescending(x => x.FileVersion) - .FirstOrDefault(); + var currentDownload = _downloadService.GetDownloadsFor(product).FirstOrDefault(); model.DownloadId = currentDownload.Id; - model.DownloadFileVersion = currentDownload != null ? currentDownload.FileVersion : String.Empty; + model.DownloadFileVersion = (currentDownload?.FileVersion).EmptyNull(); if (setPredefinedValues) { diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index 15484ebf64..7a6eff8a97 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -1553,8 +1553,7 @@ public ActionResult DownloadableProducts() if (itemModel.IsDownloadAllowed) { - itemModel.DownloadVersions = _downloadService.GetDownloadsByEntityId(item.Product.Id, "Product") - .OrderByDescending(x => x.FileVersion) + itemModel.DownloadVersions = _downloadService.GetDownloadsFor(item.Product) .Select(x => new DownloadVersion { FileVersion = x.FileVersion, diff --git a/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs b/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs index 3747afa1b4..73082683ee 100644 --- a/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs @@ -115,16 +115,18 @@ public ActionResult GetDownload(Guid id, bool agree = false, string fileVersion } } - var download = _downloadService.GetDownloadsByEntityId(product.Id, "Product") - .OrderByDescending(x => x.FileVersion) - .FirstOrDefault(); + Download download; if (fileVersion.HasValue()) { - download = _downloadService.GetDownloadsByEntityId(product.Id, "Product") + download = _downloadService.GetDownloadsFor(product) .Where(x => x.FileVersion.Equals(fileVersion)) .FirstOrDefault(); } + else + { + download = _downloadService.GetDownloadsFor(product).FirstOrDefault(); + } if (download == null) { @@ -145,7 +147,7 @@ public ActionResult GetDownload(Guid id, bool agree = false, string fileVersion if (hasNotification) { - return RedirectToAction("UserAgreement", "Customer", new { id = id, fileVersion = fileVersion }); + return RedirectToAction("UserAgreement", "Customer", new { id, fileVersion }); } if (download.UseDownloadUrl) From 70218725a033ea45dd6dde0fa43ce9f0895e51e5 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 9 Aug 2018 11:43:12 +0200 Subject: [PATCH 022/932] Minor change --- .../Administration/Views/Export/_Tab.Deployment.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml index df552ed60f..7498faa038 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -271,7 +271,7 @@ // delete deployment $('#export-deployment-list').find('.delete-depoyment').click(function () { $(this).doPostData({ - ask: '@T("Admin.Common.AskToProceed").JsText' + ask: @T("Admin.Common.AskToProceed").JsText }); }); From 7f416ffd1622dec865ea52316463f520a09d3b5c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 9 Aug 2018 18:38:40 +0200 Subject: [PATCH 023/932] Re-enable saving settings as JSON --- .../SmartStore.Core/Configuration/JsonPersistAttribute.cs | 5 +---- .../SmartStore.Services/Configuration/SettingService.cs | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Configuration/JsonPersistAttribute.cs b/src/Libraries/SmartStore.Core/Configuration/JsonPersistAttribute.cs index d0ed7563d2..1e467280a9 100644 --- a/src/Libraries/SmartStore.Core/Configuration/JsonPersistAttribute.cs +++ b/src/Libraries/SmartStore.Core/Configuration/JsonPersistAttribute.cs @@ -4,9 +4,7 @@ using System.Text; namespace SmartStore.Core.Configuration -{ - - // codehint: sm-add +{ /// /// Marker attribute. Indicates that the settings should /// be persisted as a JSON string rather than splitted @@ -16,5 +14,4 @@ namespace SmartStore.Core.Configuration public class JsonPersistAttribute : Attribute { } - } diff --git a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs index f91205f0ba..5eab6f9f14 100644 --- a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs +++ b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs @@ -372,8 +372,8 @@ protected virtual void SaveSettingCore(ISettings settings, int storeId = 0) if (settingType.HasAttribute(true)) { - //SaveSettingsJson(settings); - //return; + SaveSettingsJson(settings); + return; } /* We do not clear cache after each setting update. From baff000f86775083d9d01d592bd99b451c844136 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 9 Aug 2018 21:02:34 +0200 Subject: [PATCH 024/932] Fixes "Value cannot be null or an empty string. Parameter name: version" --- src/Libraries/SmartStore.Services/Media/DownloadService.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Media/DownloadService.cs b/src/Libraries/SmartStore.Services/Media/DownloadService.cs index acff6cc3db..bcca316d5e 100644 --- a/src/Libraries/SmartStore.Services/Media/DownloadService.cs +++ b/src/Libraries/SmartStore.Services/Media/DownloadService.cs @@ -88,10 +88,10 @@ public virtual IList GetDownloadsFor(int entityId, string entityName) where x.EntityId == entityId && x.EntityName == entityName select x).ToList(); - if (downloads.Count > 0) + if (downloads.Any()) { var idsOrderedByVersion = downloads - .Select(x => new { x.Id, Version = new SemanticVersion(x.FileVersion) }) + .Select(x => new { x.Id, Version = new SemanticVersion(x.FileVersion.HasValue() ? x.FileVersion : "1.0.0.0") }) .OrderBy(x => x.Version) .Select(x => x.Id); @@ -99,7 +99,6 @@ public virtual IList GetDownloadsFor(int entityId, string entityName) return downloads; } - } return new List(); From 42ace9966d8c62c030b053e7106965c4b9cf815e Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 11 Aug 2018 03:29:23 +0200 Subject: [PATCH 025/932] Minor theming stuff --- src/Presentation/SmartStore.Web/Views/Home/Index.cshtml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Presentation/SmartStore.Web/Views/Home/Index.cshtml b/src/Presentation/SmartStore.Web/Views/Home/Index.cshtml index c7077431a2..aa71267f32 100644 --- a/src/Presentation/SmartStore.Web/Views/Home/Index.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Home/Index.cshtml @@ -1,5 +1,6 @@ @{ Layout = "_Layout"; + Html.AddBodyCssClass("home"); }
From ba528170ba9817b0c9c781984d9f4dd88a583bcc Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 13 Aug 2018 09:25:06 +0200 Subject: [PATCH 026/932] Resolves #1492 Null Refrence Error in sample project --- .../Controllers/ProductController.cs | 54 ++++++++++--------- .../Models/Catalog/ProductModel.cs | 1 + 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 838706157f..007b026184 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -293,11 +293,13 @@ protected void UpdateProductDownloads(Product product, ProductModel model) else if (m.DownloadFileVersion.HasValue()) { var download = _downloadService.GetDownloadsFor(p).FirstOrDefault(); - - download.FileVersion = new SemanticVersion(m.DownloadFileVersion).ToString(); - download.EntityId = p.Id; - download.IsTransient = false; - _downloadService.UpdateDownload(download); + if (download != null) + { + download.FileVersion = new SemanticVersion(m.DownloadFileVersion).ToString(); + download.EntityId = p.Id; + download.IsTransient = false; + _downloadService.UpdateDownload(download); + } } } @@ -550,8 +552,10 @@ private void UpdateDataOfExistingProduct(Product product, ProductModel model, bo // Handle Download transiency var download = _downloadService.GetDownloadsFor(p).FirstOrDefault(); - - MediaHelper.UpdateDownloadTransientStateFor(p, x => download.Id); + if (download != null) + { + MediaHelper.UpdateDownloadTransientStateFor(p, x => download.Id); + } MediaHelper.UpdateDownloadTransientStateFor(p, x => x.SampleDownloadId); // SEO @@ -658,9 +662,25 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set { model.ProductUrl = Url.RouteUrl("Product", new { SeName = product.GetSeName() }, Request.Url.Scheme); } - } - model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; + // downloads + model.DownloadVersions = _downloadService.GetDownloadsFor(product) + .Select(x => new DownloadVersion + { + FileVersion = x.FileVersion, + DownloadId = x.Id, + FileName = string.Concat(x.Filename, x.Extension), + DownloadUrl = Url.Action("DownloadFile", "Download", new { downloadId = x.Id }) + }) + .ToList(); + + var currentDownload = _downloadService.GetDownloadsFor(product).FirstOrDefault(); + + model.DownloadId = currentDownload?.Id; + model.DownloadFileVersion = (currentDownload?.FileVersion).EmptyNull(); + } + + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; model.BaseWeightIn = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId)?.Name; model.BaseDimensionIn = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId)?.Name; @@ -810,22 +830,6 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set Selected = product != null && x.Id == product.CountryOfOriginId }) .ToList(); - - // downloads - model.DownloadVersions = _downloadService.GetDownloadsFor(product) - .Select(x => new DownloadVersion - { - FileVersion = x.FileVersion, - DownloadId = x.Id, - FileName = string.Concat(x.Filename, x.Extension), - DownloadUrl = Url.Action("DownloadFile", "Download", new { downloadId = x.Id }) - }) - .ToList(); - - var currentDownload = _downloadService.GetDownloadsFor(product).FirstOrDefault(); - - model.DownloadId = currentDownload.Id; - model.DownloadFileVersion = (currentDownload?.FileVersion).EmptyNull(); if (setPredefinedValues) { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs index a412c4e65d..ba2127e885 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs @@ -32,6 +32,7 @@ public ProductModel() AddSpecificationAttributeModel = new AddProductSpecificationAttributeModel(); AvailableManageInventoryMethods = new List(); AvailableCountries = new List(); + DownloadVersions = new List(); } [SmartResourceDisplayName("Admin.Catalog.Products.Fields.ID")] From 40f4da04ce2fd0e957b9dd6c066c83df7c53a8a2 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 13 Aug 2018 13:21:59 +0200 Subject: [PATCH 027/932] Fixes "The type or namespace name 'NuGet' could not be found" --- .../SmartStore.Web/Administration/SmartStore.Admin.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index b3b5a45611..833404a497 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -98,8 +98,7 @@ False - False - ..\Bin\NuGet.Core.dll + ..\..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll False From de667a9f0fc9540948891e8c93fe75e49ee1ed74 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 13 Aug 2018 16:54:52 +0200 Subject: [PATCH 028/932] Fixes several broken unit tests --- .../Infrastructure/AutoMapperAdminProfile.cs | 90 ++++++++++--------- .../Media/DownloadPersistenceTests.cs | 6 +- .../Messages/QueuedEmailPersistenceTests.cs | 4 +- .../Tasks/ScheduleTaskPersistenceTests.cs | 2 +- .../Public/Validators/BaseValidatorTests.cs | 12 +-- .../Common/AddressValidatorTests.cs | 3 - .../Validators/CreditCardValidatorTests.cs | 13 +-- .../Customer/ChangePasswordValidatorTests.cs | 2 - .../PasswordRecoveryConfirmValidatorTests.cs | 2 - .../Customer/RegisterValidatorTests.cs | 2 - 10 files changed, 68 insertions(+), 68 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs index 3a89f6ef41..7c7557d5e4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs @@ -243,47 +243,52 @@ public AutoMapperAdminProfile() .ForMember(dest => dest.PriceRanges, mo => mo.Ignore()) #pragma warning restore 612, 618 .ForMember(dest => dest.AppliedDiscounts, mo => mo.Ignore()); - //products - CreateMap() - .ForMember(dest => dest.ProductTypeName, mo => mo.Ignore()) - .ForMember(dest => dest.AssociatedToProductId, mo => mo.Ignore()) - .ForMember(dest => dest.AssociatedToProductName, mo => mo.Ignore()) - .ForMember(dest => dest.ProductTags, mo => mo.Ignore()) - .ForMember(dest => dest.PictureThumbnailUrl, mo => mo.Ignore()) - .ForMember(dest => dest.NoThumb, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableProductTemplates, mo => mo.Ignore()) - .ForMember(dest => dest.Locales, mo => mo.Ignore()) - .ForMember(dest => dest.NumberOfAvailableCategories, mo => mo.Ignore()) - .ForMember(dest => dest.NumberOfAvailableManufacturers, mo => mo.Ignore()) - .ForMember(dest => dest.AddPictureModel, mo => mo.Ignore()) - .ForMember(dest => dest.ProductPictureModels, mo => mo.Ignore()) - .ForMember(dest => dest.AddSpecificationAttributeModel, mo => mo.Ignore()) - .ForMember(dest => dest.CopyProductModel, mo => mo.Ignore()) - .ForMember(dest => dest.SeName, mo => mo.MapFrom(src => src.GetSeName(0, true, false))) - .ForMember(dest => dest.AvailableCustomerRoles, mo => mo.Ignore()) - .ForMember(dest => dest.SelectedCustomerRoleIds, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableProductTags, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableManageInventoryMethods, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) - .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableTaxCategories, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableMeasureUnits, mo => mo.Ignore()) - .ForMember(dest => dest.PrimaryStoreCurrencyCode, mo => mo.Ignore()) - .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) - .ForMember(dest => dest.UpdatedOn, mo => mo.Ignore()) - .ForMember(dest => dest.BaseDimensionIn, mo => mo.Ignore()) - .ForMember(dest => dest.BaseWeightIn, mo => mo.Ignore()) - .ForMember(dest => dest.NumberOfAvailableProductAttributes, mo => mo.Ignore()) - .ForMember(dest => dest.Locales, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableDiscounts, mo => mo.Ignore()) - .ForMember(dest => dest.SelectedDiscountIds, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableMeasureWeights, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableDeliveryTimes, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableQuantityUnits, mo => mo.Ignore()) - .ForMember(dest => dest.ProductSelectCheckboxClass, mo => mo.Ignore()) - .ForMember(dest => dest.ProductUrl, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableCountries, mo => mo.Ignore()); - CreateMap() + //products + CreateMap() + .ForMember(dest => dest.ProductTypeName, mo => mo.Ignore()) + .ForMember(dest => dest.AssociatedToProductId, mo => mo.Ignore()) + .ForMember(dest => dest.AssociatedToProductName, mo => mo.Ignore()) + .ForMember(dest => dest.ProductTags, mo => mo.Ignore()) + .ForMember(dest => dest.PictureThumbnailUrl, mo => mo.Ignore()) + .ForMember(dest => dest.NoThumb, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableProductTemplates, mo => mo.Ignore()) + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.NumberOfAvailableCategories, mo => mo.Ignore()) + .ForMember(dest => dest.NumberOfAvailableManufacturers, mo => mo.Ignore()) + .ForMember(dest => dest.AddPictureModel, mo => mo.Ignore()) + .ForMember(dest => dest.ProductPictureModels, mo => mo.Ignore()) + .ForMember(dest => dest.AddSpecificationAttributeModel, mo => mo.Ignore()) + .ForMember(dest => dest.CopyProductModel, mo => mo.Ignore()) + .ForMember(dest => dest.SeName, mo => mo.MapFrom(src => src.GetSeName(0, true, false))) + .ForMember(dest => dest.AvailableCustomerRoles, mo => mo.Ignore()) + .ForMember(dest => dest.SelectedCustomerRoleIds, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableProductTags, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableManageInventoryMethods, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) + .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableTaxCategories, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableMeasureUnits, mo => mo.Ignore()) + .ForMember(dest => dest.PrimaryStoreCurrencyCode, mo => mo.Ignore()) + .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) + .ForMember(dest => dest.UpdatedOn, mo => mo.Ignore()) + .ForMember(dest => dest.BaseDimensionIn, mo => mo.Ignore()) + .ForMember(dest => dest.BaseWeightIn, mo => mo.Ignore()) + .ForMember(dest => dest.NumberOfAvailableProductAttributes, mo => mo.Ignore()) + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableDiscounts, mo => mo.Ignore()) + .ForMember(dest => dest.SelectedDiscountIds, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableMeasureWeights, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableDeliveryTimes, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableQuantityUnits, mo => mo.Ignore()) + .ForMember(dest => dest.ProductSelectCheckboxClass, mo => mo.Ignore()) + .ForMember(dest => dest.ProductUrl, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableCountries, mo => mo.Ignore()) + .ForMember(dest => dest.NewVersionDownloadId, mo => mo.Ignore()) + .ForMember(dest => dest.NewVersion, mo => mo.Ignore()) + .ForMember(dest => dest.DownloadVersions, mo => mo.Ignore()) + .ForMember(dest => dest.DownloadFileVersion, mo => mo.Ignore()) + .ForMember(dest => dest.AddChangelog, mo => mo.Ignore()); + CreateMap() .ForMember(dest => dest.DisplayOrder, mo => mo.Ignore()) .ForMember(dest => dest.ProductTags, mo => mo.Ignore()) .ForMember(dest => dest.CreatedOnUtc, mo => mo.Ignore()) @@ -356,7 +361,8 @@ public AutoMapperAdminProfile() .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) .ForMember(dest => dest.AvailableDomainEndings, mo => mo.Ignore()) - .ForMember(dest => dest.RoundOrderTotalPaymentMethods, mo => mo.Ignore()); + .ForMember(dest => dest.RoundOrderTotalPaymentMethods, mo => mo.Ignore()) + .ForMember(dest => dest.DomainEndingsArray, mo => mo.Ignore()); CreateMap() .ForMember(dest => dest.CreatedOnUtc, mo => mo.Ignore()) .ForMember(dest => dest.UpdatedOnUtc, mo => mo.Ignore()); diff --git a/src/Tests/SmartStore.Data.Tests/Media/DownloadPersistenceTests.cs b/src/Tests/SmartStore.Data.Tests/Media/DownloadPersistenceTests.cs index 24c24c1869..deb6910eb5 100644 --- a/src/Tests/SmartStore.Data.Tests/Media/DownloadPersistenceTests.cs +++ b/src/Tests/SmartStore.Data.Tests/Media/DownloadPersistenceTests.cs @@ -22,7 +22,9 @@ public void Can_save_and_load_download() ContentType = "application/x-zip-co", Filename = "file", Extension = ".zip", - IsNew = true + IsNew = true, + EntityName = "Product", + EntityId = 1 }; var fromDb = SaveAndLoadEntity(download); @@ -35,6 +37,8 @@ public void Can_save_and_load_download() fromDb.Filename.ShouldEqual("file"); fromDb.Extension.ShouldEqual(".zip"); fromDb.IsNew.ShouldEqual(true); + fromDb.EntityName.ShouldEqual("Product"); + fromDb.EntityId.ShouldEqual(1); } } } diff --git a/src/Tests/SmartStore.Data.Tests/Messages/QueuedEmailPersistenceTests.cs b/src/Tests/SmartStore.Data.Tests/Messages/QueuedEmailPersistenceTests.cs index 3b273e3f62..1bf1772d31 100644 --- a/src/Tests/SmartStore.Data.Tests/Messages/QueuedEmailPersistenceTests.cs +++ b/src/Tests/SmartStore.Data.Tests/Messages/QueuedEmailPersistenceTests.cs @@ -77,7 +77,9 @@ public void Can_cascade_delete_attachment() UpdatedOnUtc = DateTime.UtcNow, DownloadGuid = Guid.NewGuid(), Extension = "txt", - Filename = "file" + Filename = "file", + EntityName = "QueuedEmailAttachment", + EntityId = 1 }; // add attachment diff --git a/src/Tests/SmartStore.Data.Tests/Tasks/ScheduleTaskPersistenceTests.cs b/src/Tests/SmartStore.Data.Tests/Tasks/ScheduleTaskPersistenceTests.cs index b852b9f68c..93e8a9213a 100644 --- a/src/Tests/SmartStore.Data.Tests/Tasks/ScheduleTaskPersistenceTests.cs +++ b/src/Tests/SmartStore.Data.Tests/Tasks/ScheduleTaskPersistenceTests.cs @@ -29,7 +29,7 @@ public void Can_save_and_load_scheduleTask() fromDb.Type.ShouldEqual("some type 1"); fromDb.Enabled.ShouldEqual(true); fromDb.StopOnError.ShouldEqual(true); - fromDb.NextRunUtc.ShouldEqual(new DateTime(2020, 01, 01)); + fromDb.NextRunUtc.ShouldEqual(new DateTime(2020, 01, 02)); fromDb.RunPerMachine.ShouldEqual(true); } } diff --git a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/BaseValidatorTests.cs b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/BaseValidatorTests.cs index cbe232960c..461b2ea891 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/BaseValidatorTests.cs +++ b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/BaseValidatorTests.cs @@ -1,20 +1,16 @@ -using SmartStore.Services.Localization; -using NUnit.Framework; -using Rhino.Mocks; +using NUnit.Framework; +using SmartStore.Core.Localization; namespace SmartStore.Web.MVC.Tests.Public.Validators { [TestFixture] public abstract class BaseValidatorTests { - protected ILocalizationService _localizationService; - + protected Localizer T = NullLocalizer.Instance; + [SetUp] public void Setup() { - //set up localziation service used by almost all validators - _localizationService = MockRepository.GenerateMock(); - _localizationService.Expect(l => l.GetResource("")).Return("Invalid").IgnoreArguments(); } } } diff --git a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Common/AddressValidatorTests.cs b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Common/AddressValidatorTests.cs index 95a691b04d..69ec0e16af 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Common/AddressValidatorTests.cs +++ b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Common/AddressValidatorTests.cs @@ -2,15 +2,12 @@ using NUnit.Framework; using SmartStore.Core.Domain.Common; using SmartStore.Web.Models.Common; -using SmartStore.Core.Localization; namespace SmartStore.Web.MVC.Tests.Public.Validators.Common { [TestFixture] public class AddressValidatorTests : BaseValidatorTests { - private Localizer T; - [Test] public void Should_have_error_when_email_is_null_or_empty() { diff --git a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/CreditCardValidatorTests.cs b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/CreditCardValidatorTests.cs index 207568d9af..0196399428 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/CreditCardValidatorTests.cs +++ b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/CreditCardValidatorTests.cs @@ -1,8 +1,8 @@ -using FluentValidation; +using System.Globalization; +using System.Threading; +using FluentValidation; using NUnit.Framework; using SmartStore.Tests; -using System.Globalization; -using System.Threading; namespace SmartStore.Web.MVC.Tests.Public.Validators { @@ -16,16 +16,17 @@ public void Setup() { Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); - _validator = new TestValidator { + _validator = new TestValidator + { v => v.RuleFor(x => x.CreditCard).CreditCard() }; } [Test] - public void IsValidTests() + public void CreditCardsAreValid() { // Optional value is not valid - _validator.Validate(new Person { CreditCard = null }).IsValid.ShouldBeFalse(); + //_validator.Validate(new Person { CreditCard = null }).IsValid.ShouldBeFalse(); // Simplest valid value _validator.Validate(new Person { CreditCard = "0000000000000000" }).IsValid.ShouldBeTrue(); diff --git a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/ChangePasswordValidatorTests.cs b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/ChangePasswordValidatorTests.cs index 57123bf7d4..487f1a96ad 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/ChangePasswordValidatorTests.cs +++ b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/ChangePasswordValidatorTests.cs @@ -1,7 +1,6 @@ using FluentValidation.TestHelper; using NUnit.Framework; using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Localization; using SmartStore.Web.Models.Customer; namespace SmartStore.Web.MVC.Tests.Public.Validators.Customer @@ -11,7 +10,6 @@ public class ChangePasswordValidatorTests : BaseValidatorTests { private ChangePasswordValidator _validator; private CustomerSettings _customerSettings; - private Localizer T; [SetUp] public new void Setup() diff --git a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/PasswordRecoveryConfirmValidatorTests.cs b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/PasswordRecoveryConfirmValidatorTests.cs index 2e8932857a..826675a9d2 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/PasswordRecoveryConfirmValidatorTests.cs +++ b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/PasswordRecoveryConfirmValidatorTests.cs @@ -1,7 +1,6 @@ using FluentValidation.TestHelper; using NUnit.Framework; using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Localization; using SmartStore.Web.Models.Customer; namespace SmartStore.Web.MVC.Tests.Public.Validators.Customer @@ -11,7 +10,6 @@ public class PasswordRecoveryConfirmValidatorTests : BaseValidatorTests { private PasswordRecoveryConfirmValidator _validator; private CustomerSettings _customerSettings; - private Localizer T; [SetUp] public new void Setup() diff --git a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/RegisterValidatorTests.cs b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/RegisterValidatorTests.cs index b75bb1d86b..428626ef03 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/RegisterValidatorTests.cs +++ b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/RegisterValidatorTests.cs @@ -2,7 +2,6 @@ using NUnit.Framework; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Tax; -using SmartStore.Core.Localization; using SmartStore.Web.Models.Customer; namespace SmartStore.Web.MVC.Tests.Public.Validators.Customer @@ -13,7 +12,6 @@ public class RegisterValidatorTests : BaseValidatorTests private RegisterValidator _validator; private CustomerSettings _customerSettings; private TaxSettings _taxSettings = new TaxSettings(); - private Localizer T; [SetUp] public new void Setup() From d38697b3e98b9677a5b86e29b2ebc45892c2c7bb Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 15 Aug 2018 12:12:36 +0200 Subject: [PATCH 029/932] Minor improvement in payment service --- .../Payments/PaymentService.cs | 95 +++++++++---------- 1 file changed, 45 insertions(+), 50 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs index 7e5639bba9..4f6298a3b5 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs @@ -8,18 +8,17 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.Infrastructure; using SmartStore.Core.Localization; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Stores; namespace SmartStore.Services.Payments { - /// - /// Payment service - /// - public partial class PaymentService : IPaymentService + /// + /// Payment service + /// + public partial class PaymentService : IPaymentService { - #region Fields - private readonly static object _lock = new object(); private static IList _paymentMethodFilterTypes = null; @@ -32,17 +31,6 @@ public partial class PaymentService : IPaymentService private readonly ICommonServices _services; private readonly ITypeFinder _typeFinder; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Payment settings - /// Plugin finder - /// Shopping cart settings - /// Plugin service public PaymentService( IRepository paymentMethodRepository, IRepository storeMappingRepository, @@ -63,15 +51,15 @@ public PaymentService( _typeFinder = typeFinder; T = NullLocalizer.Instance; - QuerySettings = DbQuerySettings.Default; + Logger = NullLogger.Instance; + QuerySettings = DbQuerySettings.Default; } public Localizer T { get; set; } - public DbQuerySettings QuerySettings { get; set; } + public ILogger Logger { get; set; } + public DbQuerySettings QuerySettings { get; set; } - #endregion - - #region Methods + #region Methods public virtual bool IsPaymentMethodActive(string systemName, int storeId = 0) { @@ -103,9 +91,6 @@ public virtual IEnumerable> LoadActivePaymentMethods( PaymentMethodType[] types = null, bool provideFallbackMethod = true) { - IList allFilters = null; - IEnumerable> allProviders = null; - var filterRequest = new PaymentFilterRequest { Cart = cart, @@ -113,25 +98,34 @@ public virtual IEnumerable> LoadActivePaymentMethods( Customer = customer }; - if (types != null && types.Any()) - allProviders = LoadAllPaymentMethods(storeId).Where(x => types.Contains(x.Value.PaymentMethodType)); - else - allProviders = LoadAllPaymentMethods(storeId); + var allFilters = GetAllPaymentMethodFilters(); + var allProviders = types != null && types.Any() + ? LoadAllPaymentMethods(storeId).Where(x => types.Contains(x.Value.PaymentMethodType)) + : LoadAllPaymentMethods(storeId); var activeProviders = allProviders .Where(p => { - if (!p.Value.IsActive || !_paymentSettings.ActivePaymentMethodSystemNames.Contains(p.Metadata.SystemName, StringComparer.InvariantCultureIgnoreCase)) - return false; - - // payment method filtering - if (allFilters == null) - allFilters = GetAllPaymentMethodFilters(); - - filterRequest.PaymentMethod = p; - - if (allFilters.Any(x => x.IsExcluded(filterRequest))) - return false; + try + { + // Only active payment methods. + if (!p.Value.IsActive || !_paymentSettings.ActivePaymentMethodSystemNames.Contains(p.Metadata.SystemName, StringComparer.InvariantCultureIgnoreCase)) + { + return false; + } + + filterRequest.PaymentMethod = p; + + // Only payment methods that have not been filtered out. + if (allFilters.Any(x => x.IsExcluded(filterRequest))) + { + return false; + } + } + catch (Exception ex) + { + Logger.Error(ex); + } return true; }); @@ -139,22 +133,23 @@ public virtual IEnumerable> LoadActivePaymentMethods( if (!activeProviders.Any() && provideFallbackMethod) { var fallbackMethod = allProviders.FirstOrDefault(x => x.IsPaymentMethodActive(_paymentSettings)); - - if (fallbackMethod == null) - fallbackMethod = allProviders.FirstOrDefault(); + if (fallbackMethod == null) + { + fallbackMethod = allProviders.FirstOrDefault(); + } if (fallbackMethod != null) { return new Provider[] { fallbackMethod }; } - else - { - if (DataSettings.DatabaseIsInstalled()) - throw new SmartException(T("Payment.OneActiveMethodProviderRequired")); - } - } - return activeProviders; + if (DataSettings.DatabaseIsInstalled()) + { + throw new SmartException(T("Payment.OneActiveMethodProviderRequired")); + } + } + + return activeProviders; } public virtual Provider LoadPaymentMethodBySystemName(string systemName, bool onlyWhenActive = false, int storeId = 0) From 1bfb694c1aa852edcaaf82456606f39ab53ae19e Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 16 Aug 2018 03:23:01 +0200 Subject: [PATCH 030/932] Theming: added .flip-(h|v|hv) classes --- .../SmartStore.Web/Content/shared/_utils.scss | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Presentation/SmartStore.Web/Content/shared/_utils.scss b/src/Presentation/SmartStore.Web/Content/shared/_utils.scss index e06b41a45e..dc11f76300 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_utils.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_utils.scss @@ -296,6 +296,23 @@ table th { } +// +// Flip +// -------------------------------------------------- + +.flip-h { + transform: scale(-1, 1) !important; +} + +.flip-v { + transform: scale(1, -1) !important; +} + +.flip-hv { + transform: scale(-1, -1) !important; +} + + // Misc utils // ------------------------- From d2e06e68684140db338302ed58c5131251542674 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 16 Aug 2018 12:29:18 +0200 Subject: [PATCH 031/932] Added a search document type --- src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs b/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs index fe80ff0036..d7de9eb81b 100644 --- a/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs +++ b/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs @@ -9,6 +9,7 @@ public enum SearchDocumentType Attribute, AttributeValue, Variant, - VariantValue + VariantValue, + ForumPost = 50 } } From 653988ecb73a560840b1159092904796b497c45b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 16 Aug 2018 14:57:56 +0200 Subject: [PATCH 032/932] Resolves #1494 Editing of product attributes does not work --- .../SmartStore.Web/Scripts/smartstore.selectwrapper.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.selectwrapper.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.selectwrapper.js index 4cf2c4025c..9d69cbc932 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.selectwrapper.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.selectwrapper.js @@ -54,8 +54,9 @@ if (initId) { // Add the option tag to the select element, // otherwise the current val() will not be resolved. - var $option = select.find('option').filter(function (i, elm) { - return elm.value === initId; + var $option = select.find('option').filter(function (i, elm) { + // Do not === otherwise infinite loop ;-) + return elm.value == initId; }); if ($option.length === 0) { From 725dc8bb1a01cb01ebc7358a261fda7a93d5411e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 16 Aug 2018 16:13:43 +0200 Subject: [PATCH 033/932] Customer should have the possibility to delete his date of birth --- .../Controllers/CustomerController.cs | 118 ++++++++++-------- 1 file changed, 68 insertions(+), 50 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index 7a6eff8a97..681130ac4b 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -1136,17 +1136,16 @@ public ActionResult Info() public ActionResult Info(CustomerInfoModel model) { if (!IsCurrentUserRegistered()) + { return new HttpUnauthorizedResult(); + } var customer = _workContext.CurrentCustomer; - // email if (model.Email.IsEmpty()) { ModelState.AddModelError("", "Email is not provided."); } - - // username if (_customerSettings.UsernamesEnabled && _customerSettings.AllowUsersToChangeUsernames && model.Username.IsEmpty()) { ModelState.AddModelError("", "Username is not provided."); @@ -1156,75 +1155,59 @@ public ActionResult Info(CustomerInfoModel model) { if (ModelState.IsValid) { - // username + customer.FirstName = model.FirstName; + customer.LastName = model.LastName; + + // Username. if (_customerSettings.UsernamesEnabled && _customerSettings.AllowUsersToChangeUsernames) { if (!customer.Username.Equals(model.Username.Trim(), StringComparison.InvariantCultureIgnoreCase)) { - //change username + // Change username. _customerRegistrationService.SetUsername(customer, model.Username.Trim()); - //re-authenticate + // Re-authenticate. _authenticationService.SignIn(customer, true); } } - // email + // Email. if (!customer.Email.Equals(model.Email.Trim(), StringComparison.InvariantCultureIgnoreCase)) { - //change email + // Change email. _customerRegistrationService.SetEmail(customer, model.Email.Trim()); - //re-authenticate (if usernames are disabled) + // Re-authenticate (if usernames are disabled). if (!_customerSettings.UsernamesEnabled) { _authenticationService.SignIn(customer, true); } } - // properties - if (_dateTimeSettings.AllowCustomersToSetTimeZone) - { - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.TimeZoneId, model.TimeZoneId); - } - - // VAT number + // VAT number. if (_taxSettings.EuVatEnabled) { var prevVatNumber = customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.VatNumber, model.VatNumber); + if (prevVatNumber != model.VatNumber) { var vatNumberStatus = _taxService.GetVatNumberStatus(model.VatNumber, out var vatName, out var vatAddress); - _genericAttributeService.SaveAttribute(customer, - SystemCustomerAttributeNames.VatNumberStatusId, - (int)vatNumberStatus); - // send VAT number admin notification - if (!String.IsNullOrEmpty(model.VatNumber) && _taxSettings.EuVatEmailAdminWhenNewVatSubmitted) - Services.MessageFactory.SendNewVatSubmittedStoreOwnerNotification(customer, model.VatNumber, vatAddress, _localizationSettings.DefaultAdminLanguageId); - } - } + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.VatNumberStatusId, (int)vatNumberStatus); - // form fields - customer.FirstName = model.FirstName; - customer.LastName = model.LastName; - - if (_customerSettings.GenderEnabled) - { - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Gender, model.Gender); - } - - if (_customerSettings.TitleEnabled) - { - customer.Title = model.Title; + // Send VAT number admin notification. + if (model.VatNumber.HasValue() && _taxSettings.EuVatEmailAdminWhenNewVatSubmitted) + { + Services.MessageFactory.SendNewVatSubmittedStoreOwnerNotification(customer, model.VatNumber, vatAddress, _localizationSettings.DefaultAdminLanguageId); + } + } } - // Customer number + // Customer number. if (_customerSettings.CustomerNumberMethod != CustomerNumberMethod.Disabled) { var numberExists = _customerService.SearchCustomers(new CustomerSearchQuery { CustomerNumber = model.CustomerNumber }).SourceQuery.Any(); if (model.CustomerNumber != customer.CustomerNumber && numberExists) { - this.NotifyError("Common.CustomerNumberAlreadyExists"); + NotifyError("Common.CustomerNumberAlreadyExists"); } else { @@ -1236,51 +1219,86 @@ public ActionResult Info(CustomerInfoModel model) { try { - customer.BirthDate = new DateTime(model.DateOfBirthYear.Value, model.DateOfBirthMonth.Value, model.DateOfBirthDay.Value); + if (model.DateOfBirthYear.HasValue && model.DateOfBirthMonth.HasValue && model.DateOfBirthDay.HasValue) + { + customer.BirthDate = new DateTime(model.DateOfBirthYear.Value, model.DateOfBirthMonth.Value, model.DateOfBirthDay.Value); + } + else + { + customer.BirthDate = null; + } } catch { } } if (_customerSettings.CompanyEnabled) - customer.Company = model.Company; - if (_customerSettings.StreetAddressEnabled) + { + customer.Company = model.Company; + } + if (_customerSettings.TitleEnabled) + { + customer.Title = model.Title; + } + if (_customerSettings.GenderEnabled) + { + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Gender, model.Gender); + } + 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); - - // newsletter + } if (_customerSettings.NewsletterEnabled) { _newsLetterSubscriptionService.AddNewsLetterSubscriptionFor(model.Newsletter, customer.Email, _storeContext.CurrentStore.Id); } - if (_forumSettings.ForumsEnabled && _forumSettings.SignaturesEnabled) { _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Signature, model.Signature); } + if (_dateTimeSettings.AllowCustomersToSetTimeZone) + { + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.TimeZoneId, model.TimeZoneId); + } - return RedirectToAction("Info"); + _customerService.UpdateCustomer(customer); + + return RedirectToAction("Info"); } } - catch (Exception exc) + catch (Exception ex) { - ModelState.AddModelError("", exc.Message); + ModelState.AddModelError("", ex.Message); } - - // If we got this far, something failed, redisplay form + // If we got this far, something failed, redisplay form. PrepareCustomerInfoModel(model, customer, true); return View(model); } From 41e09af3c3a528dfaeb4b223cecf7dccf08410c2 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 16 Aug 2018 17:36:54 +0200 Subject: [PATCH 034/932] Appended '$' to some robots.txt disallow entries --- .../Controllers/CommonController.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs index fef602bd89..0090cefcbd 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs @@ -597,7 +597,7 @@ public ActionResult RobotsTextFile() "/bin/", "/Exchange/", "/Country/GetStatesByCountryId", - "/Install", + "/Install$", "/Product/SetReviewHelpfulness", }; var localizableDisallowPaths = new List() @@ -611,7 +611,7 @@ public ActionResult RobotsTextFile() "/Boards/TopicCreate", "/Boards/TopicMove", "/Boards/TopicWatch", - "/Cart", + "/Cart$", "/Checkout", "/Product/ClearCompareList", "/CompareProducts", @@ -630,7 +630,7 @@ public ActionResult RobotsTextFile() "/Customer/RewardPoints", "/PrivateMessages", "/Newsletter/SubscriptionActivation", - "/Order", + "/Order$", "/PasswordRecovery", "/Poll/Vote", "/ReturnRequest", @@ -639,11 +639,11 @@ public ActionResult RobotsTextFile() "/Wishlist", "/Product/AskQuestion", "/Product/EmailAFriend", - "/Search", - "/Config", - "/Settings", - "/Login", - "/Register" + //"/Search", + "/Config$", + "/Settings$", + "/Login$", + "/Register$" }; From 8a97f963cecd69a70d4d1755f35c72ed2d035bf2 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 18 Aug 2018 01:08:15 +0200 Subject: [PATCH 035/932] Shorter JS functtion aliases --- .../SmartStore.Web/Scripts/smartstore.jquery.utils.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.jquery.utils.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.jquery.utils.js index 1b7b14c166..0edf151666 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.jquery.utils.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.jquery.utils.js @@ -289,10 +289,11 @@ } }); } - - - }); // $.fn.extend + // Shorter aliases + $.fn.gap = $.fn.cushioning; + $.fn.hgap = $.fn.horizontalCushioning; + $.fn.vgap = $.fn.verticalCushioning; })(jQuery); \ No newline at end of file From 15b09e2c2d7aa53fc0403c0c72fa917d53668665 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 20 Aug 2018 16:25:22 +0200 Subject: [PATCH 036/932] Code readability --- .../Forums/ForumExtensions.cs | 2 +- .../Forums/ForumService.cs | 5 - .../Forums/IForumService.cs | 12 - .../Controllers/BoardsController.cs | 466 ++++++++---------- 4 files changed, 216 insertions(+), 269 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs b/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs index cafd22688c..db1ac72671 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs @@ -154,7 +154,7 @@ public static ForumPost GetFirstPost(this ForumTopic forumTopic, IForumService f if (forumTopic == null) throw new ArgumentNullException("forumTopic"); - var forumPosts = forumService.GetAllPosts(forumTopic.Id, 0, string.Empty, 0, 1); + var forumPosts = forumService.GetAllPosts(forumTopic.Id, 0, string.Empty, true, 0, 1); if (forumPosts.Count > 0) return forumPosts[0]; diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index 2fe46d227c..4a941e8607 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -581,11 +581,6 @@ public virtual ForumPost GetPostById(int forumPostId) return forumPost; } - public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, string keywords, int pageIndex, int pageSize) - { - return GetAllPosts(forumTopicId, customerId, keywords, true, pageIndex, pageSize); - } - public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, string keywords, bool ascSort, int pageIndex, int pageSize) { var query = _forumPostRepository.Table; diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index 46e0f2f428..3635548206 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -149,18 +149,6 @@ public partial interface IForumService /// Forum Post ForumPost GetPostById(int forumPostId); - /// - /// Gets all forum posts - /// - /// The forum topic identifier - /// The customer identifier - /// Keywords - /// Page index - /// Page size - /// Posts - IPagedList GetAllPosts(int forumTopicId, - int customerId, string keywords, int pageIndex, int pageSize); - /// /// Gets all forum posts /// diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 763abb16e9..104a4130d2 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -3,7 +3,6 @@ using System.Linq; using System.ServiceModel.Syndication; using System.Web.Mvc; -using SmartStore.Core; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.Media; @@ -31,25 +30,18 @@ namespace SmartStore.Web.Controllers public partial class BoardsController : PublicControllerBase { private readonly IForumService _forumService; - private readonly ILocalizationService _localizationService; private readonly IPictureService _pictureService; private readonly ICountryService _countryService; - private readonly IWebHelper _webHelper; - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; private readonly ForumSettings _forumSettings; private readonly CustomerSettings _customerSettings; private readonly MediaSettings _mediaSettings; private readonly IDateTimeHelper _dateTimeHelper; private readonly IBreadcrumb _breadcrumb; - public BoardsController(IForumService forumService, - ILocalizationService localizationService, + public BoardsController( + IForumService forumService, IPictureService pictureService, ICountryService countryService, - IWebHelper webHelper, - IWorkContext workContext, - IStoreContext storeContext, ForumSettings forumSettings, CustomerSettings customerSettings, MediaSettings mediaSettings, @@ -57,17 +49,13 @@ public BoardsController(IForumService forumService, IBreadcrumb breadcrumb) { _forumService = forumService; - _localizationService = localizationService; _pictureService = pictureService; _countryService = countryService; - _webHelper = webHelper; - _workContext = workContext; - _storeContext = storeContext; _forumSettings = forumSettings; _customerSettings = customerSettings; _mediaSettings = mediaSettings; _dateTimeHelper = dateTimeHelper; - _breadcrumb = breadcrumb; + _breadcrumb = breadcrumb; } [NonAction] @@ -89,7 +77,7 @@ protected ForumTopicRowModel PrepareForumTopicRowModel(ForumTopic topic) IsCustomerGuest = topic.Customer.IsGuest() }; - var forumPosts = _forumService.GetAllPosts(topic.Id, 0, string.Empty, 1, _forumSettings.PostsPageSize); + var forumPosts = _forumService.GetAllPosts(topic.Id, 0, string.Empty, true, 1, _forumSettings.PostsPageSize); topicModel.TotalPostPages = forumPosts.TotalPages; return topicModel; @@ -108,6 +96,7 @@ protected ForumRowModel PrepareForumRowModel(Forum forum) NumPosts = forum.NumPosts, LastPostId = forum.LastPostId, }; + return forumModel; } @@ -123,12 +112,12 @@ protected ForumGroupModel PrepareForumGroupModel(ForumGroup forumGroup) }; var forums = _forumService.GetAllForumsByGroupId(forumGroup.Id); - foreach (var forum in forums) { var forumModel = PrepareForumRowModel(forum); forumGroupModel.Forums.Add(forumModel); } + return forumGroupModel; } @@ -139,19 +128,19 @@ protected IEnumerable ForumTopicTypesList() list.Add(new SelectListItem { - Text = _localizationService.GetResource("Forum.Normal"), + Text = T("Forum.Normal"), Value = ((int)ForumTopicType.Normal).ToString() }); list.Add(new SelectListItem { - Text = _localizationService.GetResource("Forum.Sticky"), + Text = T("Forum.Sticky"), Value = ((int)ForumTopicType.Sticky).ToString() }); list.Add(new SelectListItem { - Text = _localizationService.GetResource("Forum.Announcement"), + Text = T("Forum.Announcement"), Value = ((int)ForumTopicType.Announcement).ToString() }); @@ -167,7 +156,7 @@ protected IEnumerable ForumGroupsForumsList() foreach (var fg in forumGroups) { - // Add the forum group with Value of 0 so it won't be used as a target forum + // Add the forum group with Value of 0 so it won't be used as a target forum. forumsList.Add(new SelectListItem { Text = fg.GetLocalized(x => x.Name), Value = "0" }); var forums = _forumService.GetAllForumsByGroupId(fg.Id); @@ -183,15 +172,13 @@ protected IEnumerable ForumGroupsForumsList() [NonAction] protected void CreateForumBreadcrumb(ForumGroup group = null, Forum forum = null, ForumTopic topic = null) { - // Forum Home _breadcrumb.Track(new MenuItem { Text = T("Forum.Forums"), - Rtl = _workContext.WorkingLanguage.Rtl, + Rtl = Services.WorkContext.WorkingLanguage.Rtl, Url = Url.RouteUrl("Boards") }); - // Group group = group ?? forum?.ForumGroup ?? topic?.Forum?.ForumGroup; if (group != null) { @@ -204,7 +191,6 @@ protected void CreateForumBreadcrumb(ForumGroup group = null, Forum forum = null }); } - // Forum forum = forum ?? topic?.Forum; if (forum != null) { @@ -217,13 +203,12 @@ protected void CreateForumBreadcrumb(ForumGroup group = null, Forum forum = null }); } - // Topic if (topic != null) { _breadcrumb.Track(new MenuItem { Text = topic.Subject, - Rtl = _workContext.WorkingLanguage.Rtl, + Rtl = Services.WorkContext.WorkingLanguage.Rtl, Url = Url.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }) }); } @@ -245,7 +230,6 @@ public ActionResult Index() foreach (var forumGroup in forumGroups) { - var forumGroupModel = PrepareForumGroupModel(forumGroup); model.ForumGroups.Add(forumGroupModel); } @@ -264,7 +248,9 @@ public ActionResult ActiveDiscussionsSmall() int topicLimit = _forumSettings.HomePageActiveDiscussionsTopicCount; var topics = _forumService.GetActiveTopics(0, topicLimit); if (topics.Count == 0) - return Content(""); + { + return new EmptyResult(); + } var model = new ActiveDiscussionsModel(); @@ -288,9 +274,7 @@ public ActionResult ActiveDiscussions(int forumId = 0) return HttpNotFound(); } - int topicLimit = _forumSettings.ActiveDiscussionsPageTopicCount; - var topics = _forumService.GetActiveTopics(forumId, topicLimit); - + var topics = _forumService.GetActiveTopics(forumId, _forumSettings.ActiveDiscussionsPageTopicCount); var model = new ActiveDiscussionsModel(); foreach (var topic in topics) @@ -310,36 +294,38 @@ public ActionResult ActiveDiscussions(int forumId = 0) public ActionResult ActiveDiscussionsRss(int forumId = 0) { if (!_forumSettings.ForumsEnabled) - return HttpNotFound(); + { + return HttpNotFound(); + } - var language = _workContext.WorkingLanguage; - var protocol = _webHelper.IsCurrentConnectionSecured() ? "https" : "http"; + var store = Services.StoreContext.CurrentStore; + var language = Services.WorkContext.WorkingLanguage; + var protocol = Services.WebHelper.IsCurrentConnectionSecured() ? "https" : "http"; var selfLink = Url.Action("ActiveDiscussionsRSS", "Boards", null, protocol); var discussionLink = Url.Action("ActiveDiscussions", "Boards", null, protocol); - var title = "{0} - {1}".FormatInvariant(_storeContext.CurrentStore.Name, T("Forum.ActiveDiscussionsFeedTitle")); - + var title = "{0} - {1}".FormatInvariant(store.Name, T("Forum.ActiveDiscussionsFeedTitle")); var feed = new SmartSyndicationFeed(new Uri(discussionLink), title, T("Forum.ActiveDiscussionsFeedDescription")); feed.AddNamespaces(false); feed.Init(selfLink, language); - if (!_forumSettings.ActiveDiscussionsFeedEnabled) - return new RssActionResult { Feed = feed }; + if (!_forumSettings.ActiveDiscussionsFeedEnabled) + { + return new RssActionResult { Feed = feed }; + } var items = new List(); var topics = _forumService.GetActiveTopics(forumId, _forumSettings.ActiveDiscussionsFeedCount); - var viewsText = T("Forum.Views"); var repliesText = T("Forum.Replies"); foreach (var topic in topics) { - string topicUrl = Url.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, protocol); + var topicUrl = Url.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, protocol); var synopsis = "{0}: {1}, {2}: {3}".FormatInvariant(repliesText, topic.NumReplies, viewsText, topic.Views); var item = feed.CreateItem(topic.Subject, synopsis, topicUrl, topic.LastPostTime ?? topic.UpdatedOnUtc); - items.Add(item); } @@ -357,10 +343,11 @@ public ActionResult ForumGroup(int id) var forumGroup = _forumService.GetForumGroupById(id); if (forumGroup == null) + { return RedirectToRoute("Boards"); + } var model = PrepareForumGroupModel(forumGroup); - CreateForumBreadcrumb(group: forumGroup); return View(model); @@ -373,6 +360,7 @@ public ActionResult Forum(int id, int page = 1) return HttpNotFound(); } + var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(id); if (forum != null) @@ -383,25 +371,20 @@ public ActionResult Forum(int id, int page = 1) model.SeName = forum.GetSeName(); model.Description = forum.GetLocalized(x => x.Description); - int pageSize = 10; - if (_forumSettings.TopicsPageSize > 0) + // Subscription. + if (_forumService.IsCustomerAllowedToSubscribe(customer)) { - pageSize = _forumSettings.TopicsPageSize; - } - - //subscription - if (_forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer)) - { - model.WatchForumText = _localizationService.GetResource("Forum.WatchForum"); + model.WatchForumText = T("Forum.WatchForum"); - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, forum.Id, 0, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, forum.Id, 0, 0, 1).FirstOrDefault(); if (forumSubscription != null) { - model.WatchForumText = _localizationService.GetResource("Forum.UnwatchForum"); + model.WatchForumText = T("Forum.UnwatchForum"); model.WatchForumSubscribed = true; } } + var pageSize = _forumSettings.TopicsPageSize > 0 ? _forumSettings.TopicsPageSize : 10; var topics = _forumService.GetAllTopics(forum.Id, 0, string.Empty, ForumSearchType.All, 0, (page - 1), pageSize); model.TopicPageSize = topics.PageSize; @@ -414,7 +397,7 @@ public ActionResult Forum(int id, int page = 1) model.ForumTopics.Add(topicModel); } - model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer); + model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); model.ForumFeedsEnabled = _forumSettings.ForumFeedsEnabled; model.PostsPageSize = _forumSettings.PostsPageSize; @@ -430,41 +413,44 @@ public ActionResult Forum(int id, int page = 1) public ActionResult ForumRss(int id = 0) { if (!_forumSettings.ForumsEnabled) - return HttpNotFound(); + { + return HttpNotFound(); + } - var language = _workContext.WorkingLanguage; - var protocol = _webHelper.IsCurrentConnectionSecured() ? "https" : "http"; + var store = Services.StoreContext.CurrentStore; + var language = Services.WorkContext.WorkingLanguage; + var protocol = Services.WebHelper.IsCurrentConnectionSecured() ? "https" : "http"; var selfLink = Url.Action("ForumRSS", "Boards", null, protocol); - var forumLink = Url.Action("Forum", "Boards", new { id = id }, protocol); - - var feed = new SmartSyndicationFeed(new Uri(forumLink), _storeContext.CurrentStore.Name, T("Forum.ForumFeedDescription")); + var forumLink = Url.Action("Forum", "Boards", new { id }, protocol); + var feed = new SmartSyndicationFeed(new Uri(forumLink), store.Name, T("Forum.ForumFeedDescription")); feed.AddNamespaces(false); feed.Init(selfLink, language); - if (!_forumSettings.ForumFeedsEnabled) - return new RssActionResult { Feed = feed }; + if (!_forumSettings.ForumFeedsEnabled) + { + return new RssActionResult { Feed = feed }; + } var forum = _forumService.GetForumById(id); + if (forum == null) + { + return new RssActionResult { Feed = feed }; + } - if (forum == null) - return new RssActionResult { Feed = feed }; - - feed.Title = new TextSyndicationContent("{0} - {1}".FormatInvariant(_storeContext.CurrentStore.Name, forum.GetLocalized(x => x.Name, language))); + feed.Title = new TextSyndicationContent("{0} - {1}".FormatInvariant(store.Name, forum.GetLocalized(x => x.Name, language))); var items = new List(); var topics = _forumService.GetAllTopics(id, 0, string.Empty, ForumSearchType.All, 0, 0, _forumSettings.ForumFeedCount); - var viewsText = T("Forum.Views"); var repliesText = T("Forum.Replies"); foreach (var topic in topics) { - string topicUrl = Url.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, protocol); + var topicUrl = Url.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, protocol); var synopsis = "{0}: {1}, {2}: {3}".FormatInvariant(repliesText, topic.NumReplies, viewsText, topic.Views); var item = feed.CreateItem(topic.Subject, synopsis, topicUrl, topic.LastPostTime ?? topic.UpdatedOnUtc); - items.Add(item); } @@ -476,37 +462,35 @@ public ActionResult ForumRss(int id = 0) [HttpPost] public ActionResult ForumWatch(int id) { - bool subscribed = false; - string watchTopic = _localizationService.GetResource("Forum.WatchForum"); - string unwatchTopic = _localizationService.GetResource("Forum.UnwatchForum"); - string returnText = watchTopic; - + var subscribed = false; + var returnText = T("Forum.WatchForum").Text; + var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(id); + if (forum == null) { return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } - if (!_forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer)) + if (!_forumService.IsCustomerAllowedToSubscribe(customer)) { return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, forum.Id, 0, 0, 1).FirstOrDefault(); - + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, forum.Id, 0, 0, 1).FirstOrDefault(); if (forumSubscription == null) { forumSubscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), - CustomerId = _workContext.CurrentCustomer.Id, + CustomerId = customer.Id, ForumId = forum.Id, CreatedOnUtc = DateTime.UtcNow }; _forumService.InsertSubscription(forumSubscription); subscribed = true; - returnText = unwatchTopic; + returnText = T("Forum.UnwatchForum"); } else { @@ -524,42 +508,40 @@ public ActionResult Topic(int id, int page = 1) return HttpNotFound(); } + var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(id); if (forumTopic != null) { - //load posts - var posts = _forumService.GetAllPosts(forumTopic.Id, 0, string.Empty, page - 1, _forumSettings.PostsPageSize); + var posts = _forumService.GetAllPosts(forumTopic.Id, 0, string.Empty, true, page - 1, _forumSettings.PostsPageSize); - // if no posts area loaded, redirect to the first page + // If no posts area loaded, redirect to the first page. if (posts.Count == 0 && page > 1) { return RedirectToRoute("TopicSlug", new {id = forumTopic.Id, slug = forumTopic.GetSeName()}); } - //update view count + // Update view count. forumTopic.Views += 1; _forumService.UpdateTopic(forumTopic); - //prepare model var model = new ForumTopicPageModel(); model.Id = forumTopic.Id; model.Subject= forumTopic.Subject; model.SeName = forumTopic.GetSeName(); - - model.IsCustomerAllowedToEditTopic = _forumService.IsCustomerAllowedToEditTopic(_workContext.CurrentCustomer, forumTopic); - model.IsCustomerAllowedToDeleteTopic = _forumService.IsCustomerAllowedToDeleteTopic(_workContext.CurrentCustomer, forumTopic); - model.IsCustomerAllowedToMoveTopic = _forumService.IsCustomerAllowedToMoveTopic(_workContext.CurrentCustomer, forumTopic); - model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer); + model.IsCustomerAllowedToEditTopic = _forumService.IsCustomerAllowedToEditTopic(customer, forumTopic); + model.IsCustomerAllowedToDeleteTopic = _forumService.IsCustomerAllowedToDeleteTopic(customer, forumTopic); + model.IsCustomerAllowedToMoveTopic = _forumService.IsCustomerAllowedToMoveTopic(customer, forumTopic); + model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); if (model.IsCustomerAllowedToSubscribe) { - model.WatchTopicText = _localizationService.GetResource("Forum.WatchTopic"); + model.WatchTopicText = T("Forum.WatchTopic"); - var forumTopicSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumTopicSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); if (forumTopicSubscription != null) { - model.WatchTopicText = _localizationService.GetResource("Forum.UnwatchTopic"); + model.WatchTopicText = T("Forum.UnwatchTopic"); } } @@ -575,8 +557,8 @@ public ActionResult Topic(int id, int page = 1) ForumTopicId = post.TopicId, ForumTopicSeName = forumTopic.GetSeName(), FormattedText = post.FormatPostText(), - IsCurrentCustomerAllowedToEditPost = _forumService.IsCustomerAllowedToEditPost(_workContext.CurrentCustomer, post), - IsCurrentCustomerAllowedToDeletePost = _forumService.IsCustomerAllowedToDeletePost(_workContext.CurrentCustomer, post), + IsCurrentCustomerAllowedToEditPost = _forumService.IsCustomerAllowedToEditPost(customer, post), + IsCurrentCustomerAllowedToDeletePost = _forumService.IsCustomerAllowedToDeletePost(customer, post), CustomerId = post.CustomerId, AllowViewingProfiles = _customerSettings.AllowViewingProfiles, CustomerName = post.Customer.FormatUserName(true), @@ -591,23 +573,21 @@ public ActionResult Topic(int id, int page = 1) FormattedSignature = post.Customer.GetAttribute(SystemCustomerAttributeNames.Signature).FormatForumSignatureText(), }; - //created on string - if (_forumSettings.RelativeDateTimeFormattingEnabled) - forumPostModel.PostCreatedOnStr = post.CreatedOnUtc.RelativeFormat(true, "f"); - else - forumPostModel.PostCreatedOnStr =_dateTimeHelper.ConvertToUserTime(post.CreatedOnUtc, DateTimeKind.Utc).ToString("f"); + forumPostModel.PostCreatedOnStr = _forumSettings.RelativeDateTimeFormattingEnabled + ? post.CreatedOnUtc.RelativeFormat(true, "f") + : _dateTimeHelper.ConvertToUserTime(post.CreatedOnUtc, DateTimeKind.Utc).ToString("f"); - //avatar if (_customerSettings.AllowCustomersToUploadAvatars) { - var customer = post.Customer; - string avatarUrl = _pictureService.GetUrl(customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId), _mediaSettings.AvatarPictureSize, FallbackPictureType.NoFallback); - if (String.IsNullOrEmpty(avatarUrl) && _customerSettings.DefaultAvatarEnabled) - avatarUrl = _pictureService.GetFallbackUrl(_mediaSettings.AvatarPictureSize, FallbackPictureType.Avatar); - forumPostModel.CustomerAvatarUrl = avatarUrl; + var avatarId = post.Customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); + forumPostModel.CustomerAvatarUrl = _pictureService.GetUrl(avatarId, _mediaSettings.AvatarPictureSize, FallbackPictureType.NoFallback); + if (forumPostModel.CustomerAvatarUrl.IsEmpty() && _customerSettings.DefaultAvatarEnabled) + { + forumPostModel.CustomerAvatarUrl = _pictureService.GetFallbackUrl(_mediaSettings.AvatarPictureSize, FallbackPictureType.Avatar); + } } - //location + // Location. forumPostModel.ShowCustomersLocation = _customerSettings.ShowCustomersLocation; if (_customerSettings.ShowCustomersLocation) { @@ -616,7 +596,7 @@ public ActionResult Topic(int id, int page = 1) forumPostModel.CustomerLocation = country != null ? country.GetLocalized(x => x.Name) : string.Empty; } - // page number is needed for creating post link in _ForumPost partial view + // Page number is needed for creating post link in _ForumPost partial view. forumPostModel.CurrentTopicPage = page; model.ForumPostModels.Add(forumPostModel); } @@ -632,37 +612,36 @@ public ActionResult Topic(int id, int page = 1) [HttpPost] public ActionResult TopicWatch(int id) { - bool subscribed = false; - string watchTopic = _localizationService.GetResource("Forum.WatchTopic"); - string unwatchTopic = _localizationService.GetResource("Forum.UnwatchTopic"); - string returnText = watchTopic; - + var subscribed = false; + var returnText = T("Forum.WatchTopic").Text; + var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(id); + if (forumTopic == null) { return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } - if (!_forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer)) + if (!_forumService.IsCustomerAllowedToSubscribe(customer)) { return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); if (forumSubscription == null) { forumSubscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), - CustomerId = _workContext.CurrentCustomer.Id, + CustomerId = customer.Id, TopicId = forumTopic.Id, CreatedOnUtc = DateTime.UtcNow }; _forumService.InsertSubscription(forumSubscription); subscribed = true; - returnText = unwatchTopic; + returnText = T("Forum.UnwatchTopic"); } else { @@ -707,7 +686,6 @@ public ActionResult TopicMove(TopicMoveModel model) } var forumTopic = _forumService.GetTopicById(model.Id); - if (forumTopic == null) { return RedirectToRoute("Boards"); @@ -734,7 +712,7 @@ public ActionResult TopicDelete(int id) var forumTopic = _forumService.GetTopicById(id); if (forumTopic != null) { - if (!_forumService.IsCustomerAllowedToDeleteTopic(_workContext.CurrentCustomer, forumTopic)) + if (!_forumService.IsCustomerAllowedToDeleteTopic(Services.WorkContext.CurrentCustomer, forumTopic)) { return new HttpUnauthorizedResult(); } @@ -760,14 +738,14 @@ public ActionResult TopicCreate(int id) return HttpNotFound(); } + var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(id); - if (forum == null) { return HttpNotFound(); } - if (_forumService.IsCustomerAllowedToCreateTopic(_workContext.CurrentCustomer, forum) == false) + if (_forumService.IsCustomerAllowedToCreateTopic(customer, forum) == false) { return new HttpUnauthorizedResult(); } @@ -779,9 +757,9 @@ public ActionResult TopicCreate(int id) model.ForumName = forum.GetLocalized(x => x.Name); model.ForumSeName = forum.GetSeName(); model.ForumEditor = _forumSettings.ForumEditor; - model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(_workContext.CurrentCustomer); + model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer); model.TopicPriorities = ForumTopicTypesList(); - model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer); + model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); model.Subscribed = false; CreateForumBreadcrumb(forum: forum); @@ -799,8 +777,8 @@ public ActionResult TopicCreate(EditForumTopicModel model) return HttpNotFound(); } + var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(model.ForumId); - if (forum == null) { return RedirectToRoute("Boards"); @@ -810,12 +788,12 @@ public ActionResult TopicCreate(EditForumTopicModel model) { try { - if (!_forumService.IsCustomerAllowedToCreateTopic(_workContext.CurrentCustomer, forum)) + if (!_forumService.IsCustomerAllowedToCreateTopic(customer, forum)) { return new HttpUnauthorizedResult(); } - string subject = model.Subject; + var subject = model.Subject; var maxSubjectLength = _forumSettings.TopicSubjectMaxLength; if (maxSubjectLength > 0 && subject.Length > maxSubjectLength) { @@ -830,37 +808,32 @@ public ActionResult TopicCreate(EditForumTopicModel model) } var topicType = ForumTopicType.Normal; - var nowUtc = DateTime.UtcNow; - string ipAddress = _webHelper.GetCurrentIpAddress(); + var utcNow = DateTime.UtcNow; + var ipAddress = Services.WebHelper.GetCurrentIpAddress(); - if (_forumService.IsCustomerAllowedToSetTopicPriority(_workContext.CurrentCustomer)) + if (_forumService.IsCustomerAllowedToSetTopicPriority(customer)) { - topicType = (ForumTopicType) Enum.ToObject(typeof (ForumTopicType), model.TopicTypeId); + topicType = (ForumTopicType)Enum.ToObject(typeof (ForumTopicType), model.TopicTypeId); } - //forum topic var forumTopic = new ForumTopic { ForumId = forum.Id, - CustomerId = _workContext.CurrentCustomer.Id, + CustomerId = customer.Id, TopicTypeId = (int) topicType, Subject = subject }; - _forumService.InsertTopic(forumTopic, true); - //forum post var forumPost = new ForumPost { TopicId = forumTopic.Id, - CustomerId = _workContext.CurrentCustomer.Id, + CustomerId = customer.Id, Text = text, IPAddress = ipAddress, }; - _forumService.InsertPost(forumPost, false); - //update forum topic forumTopic.NumPosts = 1; forumTopic.LastPostId = forumPost.Id; forumTopic.LastPostCustomerId = forumPost.CustomerId; @@ -868,17 +841,17 @@ public ActionResult TopicCreate(EditForumTopicModel model) _forumService.UpdateTopic(forumTopic); - //subscription - if (_forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer)) + // Subscription. + if (_forumService.IsCustomerAllowedToSubscribe(customer)) { if (model.Subscribed) { var forumSubscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), - CustomerId = _workContext.CurrentCustomer.Id, + CustomerId = customer.Id, TopicId = forumTopic.Id, - CreatedOnUtc = nowUtc + CreatedOnUtc = utcNow }; _forumService.InsertSubscription(forumSubscription); @@ -893,15 +866,15 @@ public ActionResult TopicCreate(EditForumTopicModel model) } } - // redisplay form + // Redisplay form. model.TopicPriorities = ForumTopicTypesList(); model.IsEdit = false; model.ForumId = forum.Id; model.ForumName = forum.GetLocalized(x => x.Name); model.ForumSeName = forum.GetSeName(); model.Id = 0; - model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(_workContext.CurrentCustomer); - model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer); + model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer); + model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); model.ForumEditor = _forumSettings.ForumEditor; return View(model); @@ -914,14 +887,14 @@ public ActionResult TopicEdit(int id) return HttpNotFound(); } + var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(id); - if (forumTopic == null) { return RedirectToRoute("Boards"); } - if (!_forumService.IsCustomerAllowedToEditTopic(_workContext.CurrentCustomer, forumTopic)) + if (!_forumService.IsCustomerAllowedToEditTopic(customer, forumTopic)) { return new HttpUnauthorizedResult(); } @@ -946,13 +919,13 @@ public ActionResult TopicEdit(int id) model.ForumId = forum.Id; model.ForumEditor = _forumSettings.ForumEditor; - model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(_workContext.CurrentCustomer); - model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer); + model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer); + model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); - //subscription + // Subscription. if (model.IsCustomerAllowedToSubscribe) { - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); model.Subscribed = forumSubscription != null; } @@ -970,8 +943,8 @@ public ActionResult TopicEdit(EditForumTopicModel model) return HttpNotFound(); } + var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(model.Id); - if (forumTopic == null) { return RedirectToRoute("Boards"); @@ -986,12 +959,12 @@ public ActionResult TopicEdit(EditForumTopicModel model) { try { - if (!_forumService.IsCustomerAllowedToEditTopic(_workContext.CurrentCustomer, forumTopic)) + if (!_forumService.IsCustomerAllowedToEditTopic(customer, forumTopic)) { return new HttpUnauthorizedResult(); } - string subject = model.Subject; + var subject = model.Subject; var maxSubjectLength = _forumSettings.TopicSubjectMaxLength; if (maxSubjectLength > 0 && subject.Length > maxSubjectLength) { @@ -1006,23 +979,19 @@ public ActionResult TopicEdit(EditForumTopicModel model) } var topicType = ForumTopicType.Normal; + var ipAddress = Services.WebHelper.GetCurrentIpAddress(); + var utcNow = DateTime.UtcNow; - string ipAddress = _webHelper.GetCurrentIpAddress(); - - DateTime nowUtc = DateTime.UtcNow; - - if (_forumService.IsCustomerAllowedToSetTopicPriority(_workContext.CurrentCustomer)) + if (_forumService.IsCustomerAllowedToSetTopicPriority(customer)) { topicType = (ForumTopicType) Enum.ToObject(typeof (ForumTopicType), model.TopicTypeId); } - //forum topic forumTopic.TopicTypeId = (int) topicType; forumTopic.Subject = subject; _forumService.UpdateTopic(forumTopic); - //forum post var firstPost = forumTopic.GetFirstPost(_forumService); if (firstPost != null) { @@ -1031,7 +1000,6 @@ public ActionResult TopicEdit(EditForumTopicModel model) } else { - //error (not possible) firstPost = new ForumPost { TopicId = forumTopic.Id, @@ -1043,10 +1011,10 @@ public ActionResult TopicEdit(EditForumTopicModel model) _forumService.InsertPost(firstPost, false); } - //subscription - if (_forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer)) + // Subscription. + if (_forumService.IsCustomerAllowedToSubscribe(customer)) { - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); if (model.Subscribed) { @@ -1055,9 +1023,9 @@ public ActionResult TopicEdit(EditForumTopicModel model) forumSubscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), - CustomerId = _workContext.CurrentCustomer.Id, + CustomerId = customer.Id, TopicId = forumTopic.Id, - CreatedOnUtc = nowUtc + CreatedOnUtc = utcNow }; _forumService.InsertSubscription(forumSubscription); @@ -1072,7 +1040,7 @@ public ActionResult TopicEdit(EditForumTopicModel model) } } - // redirect to the topic page with the topic slug + // Redirect to the topic page with the topic slug. return RedirectToRoute("TopicSlug", new {id = forumTopic.Id, slug = forumTopic.GetSeName()}); } catch (Exception ex) @@ -1081,7 +1049,7 @@ public ActionResult TopicEdit(EditForumTopicModel model) } } - // redisplay form + // Redisplay form. model.TopicPriorities = ForumTopicTypesList(); model.IsEdit = true; model.ForumName = forum.GetLocalized(x => x.Name); @@ -1089,8 +1057,8 @@ public ActionResult TopicEdit(EditForumTopicModel model) model.ForumId = forum.Id; model.ForumEditor = _forumSettings.ForumEditor; - model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(_workContext.CurrentCustomer); - model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer); + model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer); + model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); return View(model); } @@ -1103,10 +1071,9 @@ public ActionResult PostDelete(int id) } var forumPost = _forumService.GetPostById(id); - if (forumPost != null) { - if (!_forumService.IsCustomerAllowedToDeletePost(_workContext.CurrentCustomer, forumPost)) + if (!_forumService.IsCustomerAllowedToDeletePost(Services.WorkContext.CurrentCustomer, forumPost)) { return new HttpUnauthorizedResult(); } @@ -1114,11 +1081,11 @@ public ActionResult PostDelete(int id) var forumTopic = forumPost.ForumTopic; var forumId = forumTopic.Forum.Id; var forumSlug = forumTopic.Forum.GetSeName(); + var url = string.Empty; _forumService.DeletePost(forumPost); - string url = string.Empty; - //get topic one more time because it can be deleted (first or only post deleted) + // Get topic one more time because it can be deleted (first or only post deleted). forumTopic = _forumService.GetTopicById(forumPost.TopicId); if (forumTopic == null) { @@ -1141,14 +1108,14 @@ public ActionResult PostCreate(int id, int? quote) return HttpNotFound(); } + var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(id); - if (forumTopic == null) { return RedirectToRoute("Boards"); } - if (!_forumService.IsCustomerAllowedToCreatePost(_workContext.CurrentCustomer, forumTopic)) + if (!_forumService.IsCustomerAllowedToCreatePost(customer, forumTopic)) { return new HttpUnauthorizedResult(); } @@ -1168,19 +1135,19 @@ public ActionResult PostCreate(int id, int? quote) ForumName = forum.GetLocalized(x => x.Name), ForumTopicSubject = forumTopic.Subject, ForumTopicSeName = forumTopic.GetSeName(), - IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer), + IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), Subscribed = false, }; - //subscription + // Subscription. if (model.IsCustomerAllowedToSubscribe) { - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); model.Subscribed = forumSubscription != null; } - // Insert the quoted text - string text = string.Empty; + // Insert the quoted text. + var text = string.Empty; if (quote.HasValue) { var quotePost = _forumService.GetPostById(quote.Value); @@ -1216,6 +1183,7 @@ public ActionResult PostCreate(EditForumPostModel model) return HttpNotFound(); } + var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(model.ForumTopicId); if (forumTopic == null) { @@ -1226,31 +1194,35 @@ public ActionResult PostCreate(EditForumPostModel model) { try { - if (!_forumService.IsCustomerAllowedToCreatePost(_workContext.CurrentCustomer, forumTopic)) + if (!_forumService.IsCustomerAllowedToCreatePost(customer, forumTopic)) + { return new HttpUnauthorizedResult(); + } var text = model.Text; var maxPostLength = _forumSettings.PostMaxLength; if (maxPostLength > 0 && text.Length > maxPostLength) + { text = text.Substring(0, maxPostLength); + } - var nowUtc = DateTime.UtcNow; - string ipAddress = _webHelper.GetCurrentIpAddress(); + var utcNow = DateTime.UtcNow; + var ipAddress = Services.WebHelper.GetCurrentIpAddress(); var forumPost = new ForumPost { TopicId = forumTopic.Id, - CustomerId = _workContext.CurrentCustomer.Id, + CustomerId = customer.Id, Text = text, IPAddress = ipAddress }; _forumService.InsertPost(forumPost, true); - //subscription - if (_forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer)) + // Subscription. + if (_forumService.IsCustomerAllowedToSubscribe(customer)) { - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumPost.TopicId, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumPost.TopicId, 0, 1).FirstOrDefault(); if (model.Subscribed) { if (forumSubscription == null) @@ -1258,9 +1230,9 @@ public ActionResult PostCreate(EditForumPostModel model) forumSubscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), - CustomerId = _workContext.CurrentCustomer.Id, + CustomerId = customer.Id, TopicId = forumPost.TopicId, - CreatedOnUtc = nowUtc + CreatedOnUtc = utcNow }; _forumService.InsertSubscription(forumSubscription); @@ -1275,11 +1247,8 @@ public ActionResult PostCreate(EditForumPostModel model) } } - int pageSize = 10; - if (_forumSettings.PostsPageSize > 0) - pageSize = _forumSettings.PostsPageSize; - - int pageIndex = (_forumService.CalculateTopicPageIndex(forumPost.TopicId, pageSize, forumPost.Id) + 1); + var pageSize = _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 10; + var pageIndex = (_forumService.CalculateTopicPageIndex(forumPost.TopicId, pageSize, forumPost.Id) + 1); var url = string.Empty; if (pageIndex > 1) @@ -1299,10 +1268,12 @@ public ActionResult PostCreate(EditForumPostModel model) } } - // redisplay form + // Redisplay form. var forum = forumTopic.Forum; if (forum == null) + { return RedirectToRoute("Boards"); + } model.IsEdit = false; model.ForumName = forum.GetLocalized(x => x.Name); @@ -1310,7 +1281,7 @@ public ActionResult PostCreate(EditForumPostModel model) model.ForumTopicSubject = forumTopic.Subject; model.ForumTopicSeName = forumTopic.GetSeName(); model.Id = 0; - model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer); + model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); model.ForumEditor = _forumSettings.ForumEditor; return View(model); @@ -1323,21 +1294,24 @@ public ActionResult PostEdit(int id) return HttpNotFound(); } + var customer = Services.WorkContext.CurrentCustomer; var forumPost = _forumService.GetPostById(id); if (forumPost == null) { return RedirectToRoute("Boards"); } - if (!_forumService.IsCustomerAllowedToEditPost(_workContext.CurrentCustomer, forumPost)) + if (!_forumService.IsCustomerAllowedToEditPost(customer, forumPost)) { return new HttpUnauthorizedResult(); } + var forumTopic = forumPost.ForumTopic; if (forumTopic == null) { return RedirectToRoute("Boards"); } + var forum = forumTopic.Forum; if (forum == null) { @@ -1353,15 +1327,15 @@ public ActionResult PostEdit(int id) ForumName = forum.GetLocalized(x => x.Name), ForumTopicSubject = forumTopic.Subject, ForumTopicSeName = forumTopic.GetSeName(), - IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer), + IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), Subscribed = false, Text = forumPost.Text, }; - //subscription + // Subscription. if (model.IsCustomerAllowedToSubscribe) { - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); model.Subscribed = forumSubscription != null; } @@ -1379,13 +1353,14 @@ public ActionResult PostEdit(EditForumPostModel model) return HttpNotFound(); } + var customer = Services.WorkContext.CurrentCustomer; var forumPost = _forumService.GetPostById(model.Id); if (forumPost == null) { return RedirectToRoute("Boards"); } - if (!_forumService.IsCustomerAllowedToEditPost(_workContext.CurrentCustomer, forumPost)) + if (!_forumService.IsCustomerAllowedToEditPost(customer, forumPost)) { return new HttpUnauthorizedResult(); } @@ -1406,8 +1381,7 @@ public ActionResult PostEdit(EditForumPostModel model) { try { - var nowUtc = DateTime.UtcNow; - + var utcNow = DateTime.UtcNow; var text = model.Text; var maxPostLength = _forumSettings.PostMaxLength; if (maxPostLength > 0 && text.Length > maxPostLength) @@ -1419,10 +1393,10 @@ public ActionResult PostEdit(EditForumPostModel model) _forumService.UpdatePost(forumPost); - //subscription - if (_forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer)) + // Subscription. + if (_forumService.IsCustomerAllowedToSubscribe(customer)) { - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumPost.TopicId, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumPost.TopicId, 0, 1).FirstOrDefault(); if (model.Subscribed) { if (forumSubscription == null) @@ -1430,9 +1404,9 @@ public ActionResult PostEdit(EditForumPostModel model) forumSubscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), - CustomerId = _workContext.CurrentCustomer.Id, + CustomerId = customer.Id, TopicId = forumPost.TopicId, - CreatedOnUtc = nowUtc + CreatedOnUtc = utcNow }; _forumService.InsertSubscription(forumSubscription); @@ -1447,12 +1421,8 @@ public ActionResult PostEdit(EditForumPostModel model) } } - int pageSize = 10; - if (_forumSettings.PostsPageSize > 0) - { - pageSize = _forumSettings.PostsPageSize; - } - int pageIndex = (_forumService.CalculateTopicPageIndex(forumPost.TopicId, pageSize, forumPost.Id) + 1); + var pageSize = _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 10; + var pageIndex = (_forumService.CalculateTopicPageIndex(forumPost.TopicId, pageSize, forumPost.Id) + 1); var url = string.Empty; if (pageIndex > 1) @@ -1472,14 +1442,14 @@ public ActionResult PostEdit(EditForumPostModel model) } } - //redisplay form + // Redisplay form. model.IsEdit = true; model.ForumName = forum.GetLocalized(x => x.Name); model.ForumTopicId = forumTopic.Id; model.ForumTopicSubject = forumTopic.Subject; model.ForumTopicSeName = forumTopic.GetSeName(); model.Id = forumPost.Id; - model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer); + model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); model.ForumEditor = _forumSettings.ForumEditor; return View(model); @@ -1492,8 +1462,7 @@ public ActionResult Search(string searchterms, bool? adv, string forumId, string return HttpNotFound(); } - int pageSize = 10; - + var pageSize = 10; var model = new SearchModel(); // Create the values for the "Limit results to previous" select list @@ -1501,53 +1470,53 @@ public ActionResult Search(string searchterms, bool? adv, string forumId, string { new SelectListItem { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.AllResults"), + Text = T("Forum.Search.LimitResultsToPrevious.AllResults"), Value = "0" }, new SelectListItem { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.1day"), + Text = T("Forum.Search.LimitResultsToPrevious.1day"), Value = "1" }, new SelectListItem { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.7days"), + Text = T("Forum.Search.LimitResultsToPrevious.7days"), Value = "7" }, new SelectListItem { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.2weeks"), + Text = T("Forum.Search.LimitResultsToPrevious.2weeks"), Value = "14" }, new SelectListItem { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.1month"), + Text = T("Forum.Search.LimitResultsToPrevious.1month"), Value = "30" }, new SelectListItem { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.3months"), + Text = T("Forum.Search.LimitResultsToPrevious.3months"), Value = "92" }, new SelectListItem { - Text= _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.6months"), + Text = T("Forum.Search.LimitResultsToPrevious.6months"), Value = "183" }, new SelectListItem { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.1year"), + Text = T("Forum.Search.LimitResultsToPrevious.1year"), Value = "365" } }; model.LimitList = limitList; - // Create the values for the "Search in forum" select list + // Create the values for the "Search in forum" select list. var forumsSelectList = new List(); forumsSelectList.Add(new SelectListItem { - Text = _localizationService.GetResource("Forum.Search.SearchInForum.All"), + Text = T("Forum.Search.SearchInForum.All"), Value = "0", Selected = true, }); @@ -1556,7 +1525,7 @@ public ActionResult Search(string searchterms, bool? adv, string forumId, string var forumGroups = _forumService.GetAllForumGroups(); foreach (var fg in forumGroups) { - // Add the forum group with value as '-' so it can't be used as a target forum id + // Add the forum group with value as '-' so it can't be used as a target forum id. forumsSelectList.Add(new SelectListItem { Text = fg.GetLocalized(x => x.Name), Value = "-" }); var forums = _forumService.GetAllForumsByGroupId(fg.Id); @@ -1571,42 +1540,37 @@ public ActionResult Search(string searchterms, bool? adv, string forumId, string } model.ForumList = forumsSelectList; - // Create the values for "Search within" select list + // Create the values for "Search within" select list. var withinList = new List { new SelectListItem { Value = ((int)ForumSearchType.All).ToString(), - Text = _localizationService.GetResource("Forum.Search.SearchWithin.All") + Text = T("Forum.Search.SearchWithin.All") }, new SelectListItem { Value = ((int)ForumSearchType.TopicTitlesOnly).ToString(), - Text = _localizationService.GetResource("Forum.Search.SearchWithin.TopicTitlesOnly") + Text = T("Forum.Search.SearchWithin.TopicTitlesOnly") }, new SelectListItem { Value = ((int)ForumSearchType.PostTextOnly).ToString(), - Text = _localizationService.GetResource("Forum.Search.SearchWithin.PostTextOnly") + Text = T("Forum.Search.SearchWithin.PostTextOnly") } }; model.WithinList = withinList; - int forumIdSelected; - int.TryParse(forumId, out forumIdSelected); + int.TryParse(forumId, out var forumIdSelected); model.ForumIdSelected = forumIdSelected; - int withinSelected; - int.TryParse(within, out withinSelected); + int.TryParse(within, out var withinSelected); model.WithinSelected = withinSelected; - int limitDaysSelected; - int.TryParse(limitDays, out limitDaysSelected); + int.TryParse(limitDays, out var limitDaysSelected); model.LimitDaysSelected = limitDaysSelected; - int searchTermMinimumLength = _forumSettings.ForumSearchTermMinimumLength; - model.ShowAdvancedSearch = adv.GetValueOrDefault(); model.SearchResultsVisible = false; model.NoResultsVisisble = false; @@ -1619,13 +1583,13 @@ public ActionResult Search(string searchterms, bool? adv, string forumId, string searchterms = searchterms.Trim(); model.SearchTerms = searchterms; - if (searchterms.Length < searchTermMinimumLength) + if (searchterms.Length < _forumSettings.ForumSearchTermMinimumLength) { - throw new SmartException(string.Format(_localizationService.GetResource("Forum.SearchTermMinimumLengthIsNCharacters"), searchTermMinimumLength)); + throw new SmartException(T("Forum.SearchTermMinimumLengthIsNCharacters", _forumSettings.ForumSearchTermMinimumLength)); } ForumSearchType searchWithin = 0; - int limitResultsToPrevious = 0; + var limitResultsToPrevious = 0; if (adv.GetValueOrDefault() == true) { searchWithin = (ForumSearchType)withinSelected; @@ -1667,7 +1631,7 @@ public ActionResult Search(string searchterms, bool? adv, string forumId, string model.Error = ex.Message; } - //some exception raised + // Some exception raised. model.TopicPageSize = pageSize; model.TopicTotalRecords = 0; model.TopicPageIndex = page - 1; @@ -1693,13 +1657,13 @@ public ActionResult LastPost(int forumPostId, bool showTopic) model.AllowViewingProfiles = _customerSettings.AllowViewingProfiles; model.CustomerName = post.Customer.FormatUserName(true); model.IsCustomerGuest = post.Customer.IsGuest(); - //created on string - if (_forumSettings.RelativeDateTimeFormattingEnabled) - model.PostCreatedOnStr = post.CreatedOnUtc.RelativeFormat(true, "f"); - else - model.PostCreatedOnStr = _dateTimeHelper.ConvertToUserTime(post.CreatedOnUtc, DateTimeKind.Utc).ToString("f"); + + model.PostCreatedOnStr = _forumSettings.RelativeDateTimeFormattingEnabled + ? post.CreatedOnUtc.RelativeFormat(true, "f") + : _dateTimeHelper.ConvertToUserTime(post.CreatedOnUtc, DateTimeKind.Utc).ToString("f"); } model.ShowTopic = showTopic; + return PartialView(model); } } From c5f9ecc0b2ffd085905b1d3d1075d4e40153aca4 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 21 Aug 2018 02:19:10 +0200 Subject: [PATCH 037/932] Added SASS mixin 'center' --- .../Content/shared/_mixins.scss | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Presentation/SmartStore.Web/Content/shared/_mixins.scss b/src/Presentation/SmartStore.Web/Content/shared/_mixins.scss index 5c39b6969f..e38caf13d7 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_mixins.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_mixins.scss @@ -4,6 +4,26 @@ // Mixins // ======================================================================== +@mixin center($xy: xy) { + @if $xy == xy { + left: 50%; + top: 50%; + bottom: auto; + right: auto; + transform: translateX(-50%) translateY(-50%); + } + @else if $xy == x { + left: 50%; + right: auto; + transform: translateX(-50%); + } + @else if $xy == y { + top: 50%; + bottom: auto; + transform: translateY(-50%); + } +} + @mixin bubble($direction: "left", $alignment: "near", $size: 10, $backgroundColor: #fff, $borderColor: $card-border-color, $offset: 24px) { // defaults for direction = left $elemAlignment: "right"; From 3f6733257337f80a41735b65ebced352456a7e40 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 21 Aug 2018 11:13:36 +0200 Subject: [PATCH 038/932] Search: Allow to activate an index provider per scope --- .../Search/DefaultIndexManager.cs | 10 +++---- .../SmartStore.Core/Search/IIndexManager.cs | 30 +++++++++---------- .../SmartStore.Core/Search/IIndexProvider.cs | 18 +++++------ .../Search/IndexProviderBase.cs | 13 ++++---- .../Search/CatalogSearchService.cs | 2 +- 5 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Search/DefaultIndexManager.cs b/src/Libraries/SmartStore.Core/Search/DefaultIndexManager.cs index da73ff4841..0fe4f85550 100644 --- a/src/Libraries/SmartStore.Core/Search/DefaultIndexManager.cs +++ b/src/Libraries/SmartStore.Core/Search/DefaultIndexManager.cs @@ -4,7 +4,7 @@ namespace SmartStore.Core.Search { - public class DefaultIndexManager : IIndexManager + public class DefaultIndexManager : IIndexManager { private readonly IEnumerable> _providers; @@ -13,14 +13,14 @@ public DefaultIndexManager(IEnumerable> providers) _providers = providers; } - public bool HasAnyProvider(bool activeOnly = true) + public bool HasAnyProvider(string scope, bool activeOnly = true) { - return _providers.Any(x => !activeOnly || x.Value.IsActive); + return _providers.Any(x => !activeOnly || x.Value.IsActive(scope)); } - public IIndexProvider GetIndexProvider(bool activeOnly = true) + public IIndexProvider GetIndexProvider(string scope, bool activeOnly = true) { - return _providers.FirstOrDefault(x => !activeOnly || x.Value.IsActive)?.Value; + return _providers.FirstOrDefault(x => !activeOnly || x.Value.IsActive(scope))?.Value; } } } diff --git a/src/Libraries/SmartStore.Core/Search/IIndexManager.cs b/src/Libraries/SmartStore.Core/Search/IIndexManager.cs index da56fc1280..72868d85b5 100644 --- a/src/Libraries/SmartStore.Core/Search/IIndexManager.cs +++ b/src/Libraries/SmartStore.Core/Search/IIndexManager.cs @@ -1,25 +1,25 @@ -using System; - -namespace SmartStore.Core.Search +namespace SmartStore.Core.Search { - /// - /// A simple factory for registered search index providers - /// - public interface IIndexManager + /// + /// A simple factory for registered search index providers + /// + public interface IIndexManager { - /// - /// Whether at least one provider is available, which implements - /// - /// Whether only active providers should be queried for - /// true if at least one provider is registered, false ortherwise - /// Primarily used to skip indexing processes - bool HasAnyProvider(bool activeOnly = true); + /// + /// Whether at least one provider is available, which implements + /// + /// Index scope name + /// Whether only active providers should be queried for + /// true if at least one provider is registered, false ortherwise + /// Primarily used to skip indexing processes + bool HasAnyProvider(string scope, bool activeOnly = true); /// /// Returns the instance of the first registered index provider (e.g. a Lucene provider) /// + /// Index scope name /// Whether only active providers should be queried for /// The index provider implementation instance - IIndexProvider GetIndexProvider(bool activeOnly = true); + IIndexProvider GetIndexProvider(string scope, bool activeOnly = true); } } diff --git a/src/Libraries/SmartStore.Core/Search/IIndexProvider.cs b/src/Libraries/SmartStore.Core/Search/IIndexProvider.cs index e6c0825057..b63ac0261c 100644 --- a/src/Libraries/SmartStore.Core/Search/IIndexProvider.cs +++ b/src/Libraries/SmartStore.Core/Search/IIndexProvider.cs @@ -4,16 +4,16 @@ namespace SmartStore.Core.Search { public interface IIndexProvider { - /// - /// Gets a value indicating whether the index provider is active - /// - bool IsActive { get; } + /// + /// Gets a value indicating whether the search index given by scope is active + /// + bool IsActive(string scope); - /// - /// Enumerates the names of all EXISTING indexes. - /// A name is required for the method. - /// - IEnumerable EnumerateIndexes(); + /// + /// Enumerates the names of all EXISTING indexes. + /// A name is required for the method. + /// + IEnumerable EnumerateIndexes(); /// /// Creates an empty document diff --git a/src/Libraries/SmartStore.Core/Search/IndexProviderBase.cs b/src/Libraries/SmartStore.Core/Search/IndexProviderBase.cs index 4b6caa0796..15662c3768 100644 --- a/src/Libraries/SmartStore.Core/Search/IndexProviderBase.cs +++ b/src/Libraries/SmartStore.Core/Search/IndexProviderBase.cs @@ -4,15 +4,12 @@ namespace SmartStore.Core.Search { public abstract class IndexProviderBase : IIndexProvider { - public virtual bool IsActive - { - get - { - return true; - } - } + public virtual bool IsActive(string scope) + { + return true; + } - public abstract IEnumerable EnumerateIndexes(); + public abstract IEnumerable EnumerateIndexes(); public virtual IIndexDocument CreateDocument(int id, SearchDocumentType? documentType) { diff --git a/src/Libraries/SmartStore.Services/Search/CatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/CatalogSearchService.cs index 5f9b9bfa79..4ccf5e13fc 100644 --- a/src/Libraries/SmartStore.Services/Search/CatalogSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/CatalogSearchService.cs @@ -82,7 +82,7 @@ public CatalogSearchResult Search( Guard.NotNull(searchQuery, nameof(searchQuery)); Guard.NotNegative(searchQuery.Take, nameof(searchQuery.Take)); - var provider = _indexManager.GetIndexProvider(); + var provider = _indexManager.GetIndexProvider("Catalog"); if (!direct && provider != null) { From 4e39629eac5d9d21bdf895f277b3d13e04873437 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 21 Aug 2018 12:38:49 +0200 Subject: [PATCH 039/932] Search: Moving some classes into a subfolder --- .../{ => Catalog}/CatalogSearchEvents.cs | 0 .../{ => Catalog}/CatalogSearchQuery.cs | 0 .../{ => Catalog}/CatalogSearchResult.cs | 0 .../{ => Catalog}/CatalogSearchService.cs | 0 .../{ => Catalog}/ICatalogSearchService.cs | 0 .../{ => Catalog}/LinqCatalogSearchService.cs | 0 .../CatalogSearchQueryAliasMapper.cs | 0 .../Modelling/CatalogSearchQueryFactory.cs | 0 .../CatalogSearchQueryModelBinder.cs | 0 .../ICatalogSearchQueryAliasMapper.cs | 0 .../Modelling/ICatalogSearchQueryFactory.cs | 0 .../SmartStore.Services.csproj | 22 +++++++++---------- 12 files changed, 11 insertions(+), 11 deletions(-) rename src/Libraries/SmartStore.Services/Search/{ => Catalog}/CatalogSearchEvents.cs (100%) rename src/Libraries/SmartStore.Services/Search/{ => Catalog}/CatalogSearchQuery.cs (100%) rename src/Libraries/SmartStore.Services/Search/{ => Catalog}/CatalogSearchResult.cs (100%) rename src/Libraries/SmartStore.Services/Search/{ => Catalog}/CatalogSearchService.cs (100%) rename src/Libraries/SmartStore.Services/Search/{ => Catalog}/ICatalogSearchService.cs (100%) rename src/Libraries/SmartStore.Services/Search/{ => Catalog}/LinqCatalogSearchService.cs (100%) rename src/Libraries/SmartStore.Services/Search/{ => Catalog}/Modelling/CatalogSearchQueryAliasMapper.cs (100%) rename src/Libraries/SmartStore.Services/Search/{ => Catalog}/Modelling/CatalogSearchQueryFactory.cs (100%) rename src/Libraries/SmartStore.Services/Search/{ => Catalog}/Modelling/CatalogSearchQueryModelBinder.cs (100%) rename src/Libraries/SmartStore.Services/Search/{ => Catalog}/Modelling/ICatalogSearchQueryAliasMapper.cs (100%) rename src/Libraries/SmartStore.Services/Search/{ => Catalog}/Modelling/ICatalogSearchQueryFactory.cs (100%) diff --git a/src/Libraries/SmartStore.Services/Search/CatalogSearchEvents.cs b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchEvents.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/CatalogSearchEvents.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchEvents.cs diff --git a/src/Libraries/SmartStore.Services/Search/CatalogSearchQuery.cs b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchQuery.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/CatalogSearchQuery.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchQuery.cs diff --git a/src/Libraries/SmartStore.Services/Search/CatalogSearchResult.cs b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchResult.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/CatalogSearchResult.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchResult.cs diff --git a/src/Libraries/SmartStore.Services/Search/CatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchService.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/CatalogSearchService.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchService.cs diff --git a/src/Libraries/SmartStore.Services/Search/ICatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/ICatalogSearchService.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/ICatalogSearchService.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/ICatalogSearchService.cs diff --git a/src/Libraries/SmartStore.Services/Search/LinqCatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/LinqCatalogSearchService.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs diff --git a/src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryAliasMapper.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs diff --git a/src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryFactory.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs diff --git a/src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryModelBinder.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryModelBinder.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryModelBinder.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryModelBinder.cs diff --git a/src/Libraries/SmartStore.Services/Search/Modelling/ICatalogSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/ICatalogSearchQueryAliasMapper.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/Modelling/ICatalogSearchQueryAliasMapper.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/Modelling/ICatalogSearchQueryAliasMapper.cs diff --git a/src/Libraries/SmartStore.Services/Search/Modelling/ICatalogSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/ICatalogSearchQueryFactory.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/Modelling/ICatalogSearchQueryFactory.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/Modelling/ICatalogSearchQueryFactory.cs diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 4101686e95..23920513d0 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -536,20 +536,20 @@ - + - - - - - + + + + + - - - - - + + + + + From 65764825e3fb711abb51ca94ed25ee42abf09165 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 21 Aug 2018 15:46:58 +0200 Subject: [PATCH 040/932] Search: Getting started with forum search service --- .../Forums/ForumService.cs | 227 ++++++++++-------- .../Forums/IForumService.cs | 105 +++++--- .../Catalog/LinqCatalogSearchService.cs | 83 +------ .../Search/Forum/ForumSearchEvents.cs | 29 +++ .../Search/Forum/ForumSearchQuery.cs | 36 +++ .../Search/Forum/ForumSearchResult.cs | 103 ++++++++ .../Search/Forum/IForumSearchService.cs | 24 ++ .../Search/Forum/LinqForumSearchService.cs | 203 ++++++++++++++++ .../Search/LinqSearchServiceBase.cs | 98 ++++++++ .../SmartStore.Services.csproj | 6 + 10 files changed, 701 insertions(+), 213 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/Search/Forum/ForumSearchEvents.cs create mode 100644 src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs create mode 100644 src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs create mode 100644 src/Libraries/SmartStore.Services/Search/Forum/IForumSearchService.cs create mode 100644 src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs create mode 100644 src/Libraries/SmartStore.Services/Search/LinqSearchServiceBase.cs diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index 4a941e8607..b7bf5b292f 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -6,11 +6,9 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Events; using SmartStore.Data.Caching; using SmartStore.Services.Common; using SmartStore.Services.Customers; -using SmartStore.Services.Messages; namespace SmartStore.Services.Forums { @@ -197,15 +195,7 @@ private bool IsForumModerator(Customer customer) return false; } - public virtual void DeleteForumGroup(ForumGroup forumGroup) - { - if (forumGroup == null) - { - throw new ArgumentNullException("forumGroup"); - } - - _forumGroupRepository.Delete(forumGroup); - } + #region Group public virtual ForumGroup GetForumGroupById(int forumGroupId) { @@ -242,6 +232,16 @@ orderby fgGroup.Key return query.ToListCached(); } + public virtual void DeleteForumGroup(ForumGroup forumGroup) + { + if (forumGroup == null) + { + throw new ArgumentNullException("forumGroup"); + } + + _forumGroupRepository.Delete(forumGroup); + } + public virtual void InsertForumGroup(ForumGroup forumGroup) { if (forumGroup == null) @@ -262,8 +262,31 @@ public virtual void UpdateForumGroup(ForumGroup forumGroup) _forumGroupRepository.Update(forumGroup); } + #endregion + + #region Forum + + public virtual Forum GetForumById(int forumId) + { + if (forumId == 0) + return null; + + return _forumRepository.GetById(forumId); + } + + public virtual IList GetAllForumsByGroupId(int forumGroupId) + { + var query = from f in _forumRepository.Table + orderby f.DisplayOrder + where f.ForumGroupId == forumGroupId + select f; + + var forums = query.ToListCached(); + return forums; + } + public virtual void DeleteForum(Forum forum) - { + { if (forum == null) { throw new ArgumentNullException("forum"); @@ -271,8 +294,8 @@ public virtual void DeleteForum(Forum forum) //delete forum subscriptions (topics) var queryTopicIds = from ft in _forumTopicRepository.Table - where ft.ForumId == forum.Id - select ft.Id; + where ft.ForumId == forum.Id + select ft.Id; var queryFs1 = from fs in _forumSubscriptionRepository.Table where queryTopicIds.Contains(fs.TopicId) select fs; @@ -294,25 +317,6 @@ where queryTopicIds.Contains(fs.TopicId) _forumRepository.Delete(forum); } - public virtual Forum GetForumById(int forumId) - { - if (forumId == 0) - return null; - - return _forumRepository.GetById(forumId); - } - - public virtual IList GetAllForumsByGroupId(int forumGroupId) - { - var query = from f in _forumRepository.Table - orderby f.DisplayOrder - where f.ForumGroupId == forumGroupId - select f; - - var forums = query.ToListCached(); - return forums; - } - public virtual void InsertForum(Forum forum) { if (forum == null) @@ -333,6 +337,10 @@ public virtual void UpdateForum(Forum forum) _forumRepository.Update(forum); } + #endregion + + #region Topic + public virtual void DeleteTopic(ForumTopic forumTopic) { if (forumTopic == null) @@ -391,6 +399,19 @@ public virtual ForumTopic GetTopicById(int forumTopicId, bool increaseViews) return forumTopic; } + public virtual IList GetTopicsByIds(int[] topicIds) + { + if (topicIds == null || topicIds.Length == 0) + { + return new List(); + } + + var query = _forumTopicRepository.Table + .Where(x => topicIds.Contains(x.Id)); + + return query.OrderBySequence(topicIds).ToList(); + } + public virtual IPagedList GetAllTopics(int forumId, int customerId, string keywords, ForumSearchType searchType, int limitDays, int pageIndex, int pageSize) { DateTime? limitDate = null; @@ -527,44 +548,28 @@ public virtual ForumTopic MoveTopic(int forumTopicId, int newForumId) return forumTopic; } - public virtual void DeletePost(ForumPost forumPost) - { - if (forumPost == null) - { - throw new ArgumentNullException("forumPost"); - } - - int forumTopicId = forumPost.TopicId; - int customerId = forumPost.CustomerId; - var forumTopic = this.GetTopicById(forumTopicId); - int forumId = forumTopic.ForumId; + public virtual int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId) + { + int pageIndex = 0; + var forumPosts = GetAllPosts(forumTopicId, 0, string.Empty, true, 0, int.MaxValue); - //delete topic if it was the first post - bool deleteTopic = false; - var firstPost = forumTopic.GetFirstPost(this); - if (firstPost != null && firstPost.Id == forumPost.Id) + for (int i = 0; i < forumPosts.TotalCount; i++) { - deleteTopic = true; + if (forumPosts[i].Id == postId) + { + if (pageSize > 0) + { + pageIndex = i / pageSize; + } + } } - //delete forum post - _forumPostRepository.Delete(forumPost); - - //delete topic - if (deleteTopic) - { - DeleteTopic(forumTopic); - } + return pageIndex; + } - //update stats - if (!deleteTopic) - { - UpdateForumTopicStats(forumTopicId); - } - UpdateForumStats(forumId); - UpdateCustomerStats(customerId); + #endregion - } + #region Post public virtual ForumPost GetPostById(int forumPostId) { @@ -663,16 +668,49 @@ public virtual void UpdatePost(ForumPost forumPost) _forumPostRepository.Update(forumPost); } - public virtual void DeletePrivateMessage(PrivateMessage privateMessage) + public virtual void DeletePost(ForumPost forumPost) { - if (privateMessage == null) + if (forumPost == null) { - throw new ArgumentNullException("privateMessage"); + throw new ArgumentNullException("forumPost"); } - _forumPrivateMessageRepository.Delete(privateMessage); + int forumTopicId = forumPost.TopicId; + int customerId = forumPost.CustomerId; + var forumTopic = this.GetTopicById(forumTopicId); + int forumId = forumTopic.ForumId; + + //delete topic if it was the first post + bool deleteTopic = false; + var firstPost = forumTopic.GetFirstPost(this); + if (firstPost != null && firstPost.Id == forumPost.Id) + { + deleteTopic = true; + } + + //delete forum post + _forumPostRepository.Delete(forumPost); + + //delete topic + if (deleteTopic) + { + DeleteTopic(forumTopic); + } + + //update stats + if (!deleteTopic) + { + UpdateForumTopicStats(forumTopicId); + } + UpdateForumStats(forumId); + UpdateCustomerStats(customerId); + } + #endregion + + #region Private message + public virtual PrivateMessage GetPrivateMessageById(int privateMessageId) { if (privateMessageId == 0) @@ -727,6 +765,16 @@ public virtual IPagedList GetAllPrivateMessages( return privateMessages; } + public virtual void DeletePrivateMessage(PrivateMessage privateMessage) + { + if (privateMessage == null) + { + throw new ArgumentNullException("privateMessage"); + } + + _forumPrivateMessageRepository.Delete(privateMessage); + } + public virtual void InsertPrivateMessage(PrivateMessage privateMessage) { if (privateMessage == null) @@ -767,15 +815,9 @@ public virtual void UpdatePrivateMessage(PrivateMessage privateMessage) } } - public virtual void DeleteSubscription(ForumSubscription forumSubscription) - { - if (forumSubscription == null) - { - throw new ArgumentNullException("forumSubscription"); - } + #endregion - _forumSubscriptionRepository.Delete(forumSubscription); - } + #region Subscription public virtual ForumSubscription GetSubscriptionById(int forumSubscriptionId) { @@ -834,6 +876,18 @@ public virtual void UpdateSubscription(ForumSubscription forumSubscription) _forumSubscriptionRepository.Update(forumSubscription); } + public virtual void DeleteSubscription(ForumSubscription forumSubscription) + { + if (forumSubscription == null) + { + throw new ArgumentNullException("forumSubscription"); + } + + _forumSubscriptionRepository.Delete(forumSubscription); + } + + #endregion + public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum forum) { if (forum == null) @@ -1062,24 +1116,5 @@ public virtual bool IsCustomerAllowedToSubscribe(Customer customer) return true; } - - public virtual int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId) - { - int pageIndex = 0; - var forumPosts = GetAllPosts(forumTopicId, 0, string.Empty, true, 0, int.MaxValue); - - for (int i = 0; i < forumPosts.TotalCount; i++) - { - if (forumPosts[i].Id == postId) - { - if (pageSize > 0) - { - pageIndex = i / pageSize; - } - } - } - - return pageIndex; - } } } diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index 3635548206..f97785de79 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -10,11 +10,7 @@ namespace SmartStore.Services.Forums /// public partial interface IForumService { - /// - /// Deletes a forum group - /// - /// Forum group - void DeleteForumGroup(ForumGroup forumGroup); + #region Group /// /// Gets a forum group @@ -29,6 +25,12 @@ public partial interface IForumService /// Forum groups IList GetAllForumGroups(bool showHidden = false); + /// + /// Deletes a forum group + /// + /// Forum group + void DeleteForumGroup(ForumGroup forumGroup); + /// /// Inserts a forum group /// @@ -41,11 +43,9 @@ public partial interface IForumService /// Forum group void UpdateForumGroup(ForumGroup forumGroup); - /// - /// Deletes a forum - /// - /// Forum - void DeleteForum(Forum forum); + #endregion + + #region Forum /// /// Gets a forum @@ -61,6 +61,12 @@ public partial interface IForumService /// Forums IList GetAllForumsByGroupId(int forumGroupId); + /// + /// Deletes a forum + /// + /// Forum + void DeleteForum(Forum forum); + /// /// Inserts a forum /// @@ -73,11 +79,9 @@ public partial interface IForumService /// Forum void UpdateForum(Forum forum); - /// - /// Deletes a forum topic - /// - /// Forum topic - void DeleteTopic(ForumTopic forumTopic); + #endregion + + #region Topic /// /// Gets a forum topic @@ -94,6 +98,13 @@ public partial interface IForumService /// Forum Topic ForumTopic GetTopicById(int forumTopicId, bool increaseViews); + /// + /// Gets forum topics by identifiers + /// + /// Array of topic identifiers + /// List of topics + IList GetTopicsByIds(int[] topicIds); + /// /// Gets all forum topics /// @@ -115,6 +126,12 @@ public partial interface IForumService /// Forum Topics IList GetActiveTopics(int forumId, int topicCount); + /// + /// Deletes a forum topic + /// + /// Forum topic + void DeleteTopic(ForumTopic forumTopic); + /// /// Inserts a forum topic /// @@ -137,10 +154,17 @@ public partial interface IForumService ForumTopic MoveTopic(int forumTopicId, int newForumId); /// - /// Deletes a forum post + /// Calculates topic page index by post identifier /// - /// Forum post - void DeletePost(ForumPost forumPost); + /// Topic identifier + /// Page size + /// Post identifier + /// Page index + int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId); + + #endregion + + #region Post /// /// Gets a forum post @@ -162,6 +186,12 @@ public partial interface IForumService IPagedList GetAllPosts(int forumTopicId, int customerId, string keywords, bool ascSort, int pageIndex, int pageSize); + /// + /// Deletes a forum post + /// + /// Forum post + void DeletePost(ForumPost forumPost); + /// /// Inserts a forum post /// @@ -175,11 +205,9 @@ IPagedList GetAllPosts(int forumTopicId, int customerId, /// Forum post void UpdatePost(ForumPost forumPost); - /// - /// Deletes a private message - /// - /// Private message - void DeletePrivateMessage(PrivateMessage privateMessage); + #endregion + + #region Private message /// /// Gets a private message @@ -205,6 +233,12 @@ IPagedList GetAllPrivateMessages(int storeId, int fromCustomerId int toCustomerId, bool? isRead, bool? isDeletedByAuthor, bool? isDeletedByRecipient, string keywords, int pageIndex, int pageSize); + /// + /// Deletes a private message + /// + /// Private message + void DeletePrivateMessage(PrivateMessage privateMessage); + /// /// Inserts a private message /// @@ -217,11 +251,9 @@ IPagedList GetAllPrivateMessages(int storeId, int fromCustomerId /// Private message void UpdatePrivateMessage(PrivateMessage privateMessage); - /// - /// Deletes a forum subscription - /// - /// Forum subscription - void DeleteSubscription(ForumSubscription forumSubscription); + #endregion + + #region Subscription /// /// Gets a forum subscription @@ -242,6 +274,12 @@ IPagedList GetAllPrivateMessages(int storeId, int fromCustomerId IPagedList GetAllSubscriptions(int customerId, int forumId, int topicId, int pageIndex, int pageSize); + /// + /// Deletes a forum subscription + /// + /// Forum subscription + void DeleteSubscription(ForumSubscription forumSubscription); + /// /// Inserts a forum subscription /// @@ -254,6 +292,8 @@ IPagedList GetAllSubscriptions(int customerId, int forumId, /// Forum subscription void UpdateSubscription(ForumSubscription forumSubscription); + #endregion + /// /// Check whether customer is allowed to create new topics /// @@ -323,14 +363,5 @@ IPagedList GetAllSubscriptions(int customerId, int forumId, /// Customer /// True if allowed, otherwise false bool IsCustomerAllowedToSubscribe(Customer customer); - - /// - /// Calculates topic page index by post identifier - /// - /// Topic identifier - /// Page size - /// Post identifier - /// Page index - int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId); } } diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs index 4717517e6c..d4f9be0787 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Localization; @@ -17,8 +16,8 @@ namespace SmartStore.Services.Search { - public partial class LinqCatalogSearchService : ICatalogSearchService - { + public partial class LinqCatalogSearchService : LinqSearchServiceBase, ICatalogSearchService + { private static int[] _priceThresholds = new int[] { 10, 25, 50, 100, 250, 500, 1000 }; private readonly IProductService _productService; @@ -69,83 +68,7 @@ public LinqCatalogSearchService( public ILogger Logger { get; set; } - #region Utilities - - private void FlattenFilters(ICollection filters, List result) - { - foreach (var filter in filters) - { - var combinedFilter = filter as ICombinedSearchFilter; - if (combinedFilter != null) - { - FlattenFilters(combinedFilter.Filters, result); - } - else - { - result.Add(filter); - } - } - } - - private ISearchFilter FindFilter(ICollection filters, string fieldName) - { - if (fieldName.HasValue()) - { - foreach (var filter in filters) - { - var attributeFilter = filter as IAttributeSearchFilter; - if (attributeFilter != null && attributeFilter.FieldName == fieldName) - { - return attributeFilter; - } - - var combinedFilter = filter as ICombinedSearchFilter; - if (combinedFilter != null) - { - var filter2 = FindFilter(combinedFilter.Filters, fieldName); - if (filter2 != null) - { - return filter2; - } - } - } - } - - return null; - } - - private List GetIdList(List filters, string fieldName) - { - var result = new List(); - - foreach (IAttributeSearchFilter filter in filters) - { - if (!(filter is IRangeSearchFilter) && filter.FieldName == fieldName) - result.Add((int)filter.Term); - } - - return result; - } - - private IOrderedQueryable OrderBy(ref bool ordered, IQueryable query, Expression> keySelector, bool descending = false) - { - if (ordered) - { - if (descending) - return ((IOrderedQueryable)query).ThenByDescending(keySelector); - - return ((IOrderedQueryable)query).ThenBy(keySelector); - } - else - { - ordered = true; - - if (descending) - return query.OrderByDescending(keySelector); - - return query.OrderBy(keySelector); - } - } + #region Utilities private IQueryable QueryCategories(IQueryable query, List ids, bool? featuredOnly) { diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchEvents.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchEvents.cs new file mode 100644 index 0000000000..7c1c6f986c --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchEvents.cs @@ -0,0 +1,29 @@ +namespace SmartStore.Services.Search +{ + public class ForumSearchingEvent + { + public ForumSearchingEvent(ForumSearchQuery query) + { + Guard.NotNull(query, nameof(query)); + + Query = query; + } + + public ForumSearchQuery Query { get; private set; } + } + + public class ForumSearchedEvent + { + public ForumSearchedEvent(ForumSearchQuery query, ForumSearchResult result) + { + Guard.NotNull(query, nameof(query)); + Guard.NotNull(result, nameof(result)); + + Query = query; + Result = result; + } + + public ForumSearchQuery Query { get; private set; } + public ForumSearchResult Result { get; private set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs new file mode 100644 index 0000000000..8ba89db311 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs @@ -0,0 +1,36 @@ +using System; +using SmartStore.Core.Search; + +namespace SmartStore.Services.Search +{ + public partial class ForumSearchQuery : SearchQuery, ICloneable + { + /// + /// Initializes a new instance of the class without a search term being set + /// + public ForumSearchQuery() + : base((string[])null, null) + { + } + + public ForumSearchQuery(string field, string term, SearchMode mode = SearchMode.Contains, bool escape = true, bool isFuzzySearch = false) + : base(field.HasValue() ? new[] { field } : null, term, mode, escape, isFuzzySearch) + { + } + + public ForumSearchQuery(string[] fields, string term, SearchMode mode = SearchMode.Contains, bool escape = true, bool isFuzzySearch = false) + : base(fields, term, mode, escape, isFuzzySearch) + { + } + + public ForumSearchQuery Clone() + { + return (ForumSearchQuery)this.MemberwiseClone(); + } + + object ICloneable.Clone() + { + return this.MemberwiseClone(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs new file mode 100644 index 0000000000..44f46237ac --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Search; + +namespace SmartStore.Services.Search +{ + public partial class ForumSearchResult + { + private readonly Func> _hitsFactory; + private IPagedList _hits; + + public ForumSearchResult( + ISearchEngine engine, + ForumSearchQuery query, + int totalHitsCount, + Func> hitsFactory, + string[] spellCheckerSuggestions) + { + Guard.NotNull(query, nameof(query)); + + Engine = engine; + Query = query; + SpellCheckerSuggestions = spellCheckerSuggestions ?? new string[0]; + + _hitsFactory = hitsFactory ?? (() => new List()); + TotalHitsCount = totalHitsCount; + } + + /// + /// Constructor for an instance without any search hits + /// + /// Forum search query + public ForumSearchResult(ForumSearchQuery query) + : this(null, query, 0, () => new List(), null) + { + } + + /// + /// Forum topics found + /// + public IPagedList Hits + { + get + { + if (_hits == null) + { + var entities = TotalHitsCount == 0 + ? new List() + : _hitsFactory.Invoke(); + + _hits = new PagedList(entities, Query.PageIndex, Query.Take, TotalHitsCount); + } + + return _hits; + } + } + + public int TotalHitsCount { get; } + + /// + /// The original forum search query + /// + public ForumSearchQuery Query { get; private set; } + + /// + /// Gets spell checking suggestions/corrections + /// + public string[] SpellCheckerSuggestions { get; set; } + + public ISearchEngine Engine { get; private set; } + + /// + /// Highlights chosen terms in a text, extracting the most relevant sections + /// + /// Text to highlight terms in + /// Highlighted text fragments + public string Highlight(string input, string preMatch = "", string postMatch = "", bool useSearchEngine = true) + { + if (Query?.Term == null || input.IsEmpty()) + return input; + + string hilite = null; + + if (useSearchEngine && Engine != null) + { + try + { + hilite = Engine.Highlight(input, preMatch, postMatch); + } + catch { } + } + + if (hilite.HasValue()) + { + return hilite; + } + + return input.HighlightKeywords(Query.Term, preMatch, postMatch); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/IForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/IForumSearchService.cs new file mode 100644 index 0000000000..54575d4fe8 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/IForumSearchService.cs @@ -0,0 +1,24 @@ +using System.Linq; +using SmartStore.Core.Domain.Forums; + +namespace SmartStore.Services.Search +{ + public partial interface IForumSearchService + { + /// + /// Searches for forum topics + /// + /// Search term, filters and other parameters used for searching + /// Bypasses the index provider (if available) and directly searches in the database + /// Forum search result + ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = false); + + /// + /// Builds a forum topic query using linq search + /// + /// Search term, filters and other parameters used for searching + /// Optional query used to build the forum topic query. + /// Forum topic queryable + IQueryable PrepareQuery(ForumSearchQuery searchQuery, IQueryable baseQuery = null); + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs new file mode 100644 index 0000000000..583c8d687d --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Logging; +using SmartStore.Core.Search; +using SmartStore.Services.Forums; + +namespace SmartStore.Services.Search +{ + public partial class LinqForumSearchService : LinqSearchServiceBase, IForumSearchService + { + private readonly IRepository _forumTopicRepository; + private readonly IRepository _forumPostRepository; + private readonly IForumService _forumService; + private readonly ICommonServices _services; + + public LinqForumSearchService( + IRepository forumTopicRepository, + IRepository forumPostRepository, + IForumService forumService, + ICommonServices services) + { + _forumTopicRepository = forumTopicRepository; + _forumPostRepository = forumPostRepository; + _forumService = forumService; + _services = services; + + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + #region Utilities + + protected virtual IQueryable GetForumTopicQuery(ForumSearchQuery searchQuery, IQueryable baseQuery) + { + var ordered = false; + var term = searchQuery.Term; + var fields = searchQuery.Fields; + var filters = new List(); + var query = baseQuery ?? _forumPostRepository.Table.Expand(x => x.ForumTopic); + + //var searchType = ForumSearchType.All; //TODO, obsolete? + + // Apply search term. + if (term.HasValue() && fields != null && fields.Length != 0 && fields.Any(x => x.HasValue())) + { + if (searchQuery.Mode == SearchMode.StartsWith) + { + query = query.Where(x => + (fields.Contains("subject") && x.ForumTopic.Subject.StartsWith(term)) || + (fields.Contains("text") && x.Text.StartsWith(term))); + } + else + { + query = query.Where(x => + (fields.Contains("subject") && x.ForumTopic.Subject.Contains(term)) || + (fields.Contains("text") && x.Text.Contains(term))); + } + } + + // Filters. + FlattenFilters(searchQuery.Filters, filters); + + foreach (IAttributeSearchFilter filter in filters) + { + var rangeFilter = filter as IRangeSearchFilter; + + if (filter.FieldName == "id") + { + if (rangeFilter != null) + { + var lower = filter.Term as int?; + var upper = rangeFilter.UpperTerm as int?; + + if (lower.HasValue) + { + if (rangeFilter.IncludesLower) + query = query.Where(x => x.Id >= lower.Value); + else + query = query.Where(x => x.Id > lower.Value); + } + + if (upper.HasValue) + { + if (rangeFilter.IncludesUpper) + query = query.Where(x => x.Id <= upper.Value); + else + query = query.Where(x => x.Id < upper.Value); + } + } + } + else if (filter.FieldName == "forumId") + { + query = query.Where(x => x.ForumTopic.ForumId == (int)filter.Term); + } + else if (filter.FieldName == "customerId") + { + query = query.Where(x => x.ForumTopic.CustomerId == (int)filter.Term); + } + else if (filter.FieldName == "lastposton") + { + if (rangeFilter != null) + { + var lower = filter.Term as DateTime?; + var upper = rangeFilter.UpperTerm as DateTime?; + + if (lower.HasValue) + { + if (rangeFilter.IncludesLower) + query = query.Where(x => x.ForumTopic.LastPostTime >= lower.Value); + else + query = query.Where(x => x.ForumTopic.LastPostTime > lower.Value); + } + + if (upper.HasValue) + { + if (rangeFilter.IncludesLower) + query = query.Where(x => x.ForumTopic.LastPostTime <= upper.Value); + else + query = query.Where(x => x.ForumTopic.LastPostTime < upper.Value); + } + } + } + } + + var topicsQuery = + from ft in query.Select(x => x.ForumTopic) + group ft by ft.Id into grp + orderby grp.Key + select grp.FirstOrDefault(); + + // Sorting of topics. + foreach (var sort in searchQuery.Sorting) + { + if (sort.FieldName == "createdon") + { + query = OrderBy(ref ordered, query, x => x.CreatedOnUtc, sort.Descending); + } + } + + if (!ordered) + { + topicsQuery = topicsQuery + .OrderByDescending(x => x.TopicTypeId) + .ThenByDescending(x => x.LastPostTime) + .ThenByDescending(x => x.Id); + } + + return topicsQuery; + } + + #endregion + + public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = false) + { + _services.EventPublisher.Publish(new ForumSearchingEvent(searchQuery)); + + var totalHits = 0; + Func> hitsFactory = null; + + if (searchQuery.Take > 0) + { + var query = GetForumTopicQuery(searchQuery, null); + totalHits = query.Count(); + + // Fix paging boundaries + if (searchQuery.Skip > 0 && searchQuery.Skip >= totalHits) + { + searchQuery.Slice((totalHits / searchQuery.Take) * searchQuery.Take, searchQuery.Take); + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithHits)) + { + query = query + .Skip(searchQuery.PageIndex * searchQuery.Take) + .Take(searchQuery.Take); + + var ids = query.Select(x => x.Id).ToArray(); + hitsFactory = () => _forumService.GetTopicsByIds(ids); + } + } + + var result = new ForumSearchResult( + null, + searchQuery, + totalHits, + hitsFactory, + null); + + _services.EventPublisher.Publish(new ForumSearchedEvent(searchQuery, result)); + + return result; + } + + public IQueryable PrepareQuery(ForumSearchQuery searchQuery, IQueryable baseQuery = null) + { + return GetForumTopicQuery(searchQuery, baseQuery); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/LinqSearchServiceBase.cs b/src/Libraries/SmartStore.Services/Search/LinqSearchServiceBase.cs new file mode 100644 index 0000000000..153c799291 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/LinqSearchServiceBase.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using SmartStore.Core.Search; + +namespace SmartStore.Services.Search +{ + public abstract partial class LinqSearchServiceBase + { + protected virtual void FlattenFilters(ICollection filters, List result) + { + foreach (var filter in filters) + { + var combinedFilter = filter as ICombinedSearchFilter; + if (combinedFilter != null) + { + FlattenFilters(combinedFilter.Filters, result); + } + else + { + result.Add(filter); + } + } + } + + protected virtual ISearchFilter FindFilter(ICollection filters, string fieldName) + { + if (fieldName.HasValue()) + { + foreach (var filter in filters) + { + var attributeFilter = filter as IAttributeSearchFilter; + if (attributeFilter != null && attributeFilter.FieldName == fieldName) + { + return attributeFilter; + } + + var combinedFilter = filter as ICombinedSearchFilter; + if (combinedFilter != null) + { + var filter2 = FindFilter(combinedFilter.Filters, fieldName); + if (filter2 != null) + { + return filter2; + } + } + } + } + + return null; + } + + protected virtual List GetIdList(List filters, string fieldName) + { + var result = new List(); + + foreach (IAttributeSearchFilter filter in filters) + { + if (!(filter is IRangeSearchFilter) && filter.FieldName == fieldName) + { + result.Add((int)filter.Term); + } + } + + return result; + } + + protected virtual IOrderedQueryable OrderBy( + ref bool ordered, + IQueryable query, + Expression> keySelector, + bool descending = false) + { + if (ordered) + { + if (descending) + { + return ((IOrderedQueryable)query).ThenByDescending(keySelector); + } + + return ((IOrderedQueryable)query).ThenBy(keySelector); + } + else + { + ordered = true; + + if (descending) + { + return query.OrderByDescending(keySelector); + } + + return query.OrderBy(keySelector); + } + } + + } +} diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 23920513d0..a6c09c3f12 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -550,6 +550,12 @@ + + + + + + From 2241e7693419ba002ed324d61f80fc53ffeccac7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 22 Aug 2018 12:58:05 +0200 Subject: [PATCH 041/932] Search: Further work on forum search service Do not update forum topic hit counter on search engines --- .../Domain/Catalog/ProductSortingEnum.cs | 14 +- .../Domain/Forums/ForumTopicSorting.cs | 68 ++++++ .../Search/Settings/ForumSearchSettings.cs | 61 ++++++ .../Search/{ => Settings}/SearchSettings.cs | 0 .../SmartStore.Core/SmartStore.Core.csproj | 4 +- .../Migrations/MigrationsConfiguration.cs | 13 ++ .../Forums/ForumService.cs | 23 +- .../Forums/IForumService.cs | 8 - .../Search/Catalog/CatalogSearchService.cs | 202 ++++++++---------- .../Catalog/LinqCatalogSearchService.cs | 8 +- .../Search/Forum/ForumSearchService.cs | 138 ++++++++++++ .../Search/Forum/LinqForumSearchService.cs | 36 ++-- .../Modelling/ForumSearchQueryFactory.cs | 37 ++++ .../Modelling/IForumSearchQueryFactory.cs | 17 ++ ...rchServiceBase.cs => SearchServiceBase.cs} | 19 +- .../SmartStore.Services.csproj | 5 +- .../DependencyRegistrar.cs | 9 +- .../Controllers/BoardsController.cs | 17 +- 18 files changed, 507 insertions(+), 172 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs create mode 100644 src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs rename src/Libraries/SmartStore.Core/Search/{ => Settings}/SearchSettings.cs (100%) create mode 100644 src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs create mode 100644 src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs create mode 100644 src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryFactory.cs rename src/Libraries/SmartStore.Services/Search/{LinqSearchServiceBase.cs => SearchServiceBase.cs} (75%) diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSortingEnum.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSortingEnum.cs index dd88e87c65..1440fd556f 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSortingEnum.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSortingEnum.cs @@ -1,7 +1,7 @@ namespace SmartStore.Core.Domain.Catalog { /// - /// Represents the product sorting + /// Represents the product sorting. /// public enum ProductSortingEnum { @@ -9,30 +9,38 @@ public enum ProductSortingEnum /// Initial state /// Initial = 0, + /// /// Relevance /// Relevance = 1, + /// /// Name: A to Z /// NameAsc = 5, + /// /// Name: Z to A /// NameDesc = 6, + /// /// Price: Low to High /// PriceAsc = 10, + /// /// Price: High to Low /// PriceDesc = 11, + /// - /// Product creation date + /// Product creation date. + /// Actually CreatedOnDesc (but due to localization this remains as is). /// - CreatedOn = 15, // eigentlich CreatedOnDesc (wegen Lokalisierung bleibt das aber so) + CreatedOn = 15, + /// /// Product creation date /// diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs new file mode 100644 index 0000000000..7bc610e7cf --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs @@ -0,0 +1,68 @@ +namespace SmartStore.Core.Domain.Forums +{ + /// + /// Represents the sorting of forum topics. + /// + public enum ForumTopicSorting + { + /// + /// Initial state + /// + Initial = 0, + + /// + /// Relevance + /// + Relevance, + + /// + /// Subject: A to Z + /// + SubjectAsc, + + /// + /// Subject: Z to A + /// + SubjectDesc, + + /// + /// Creation date: Oldest first + /// + CreatedOnAsc, + + /// + /// Creation date: Newest first + /// + CreatedOnDesc, + + /// + /// Last post date: Oldest first + /// + LastPostOnAsc, + + /// + /// Last post date: Newest first + /// + LastPostOnDesc, + + /// + /// Number of posts: Low to High + /// + PostsAsc, + + /// + /// Number of posts: High to Low + /// + PostsDesc, + + /// + /// Number of views: Low to High + /// + ViewsAsc, + + /// + /// Number of views: High to Low + /// + ViewsDesc + } +} diff --git a/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs new file mode 100644 index 0000000000..b35f9918f4 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using SmartStore.Core.Configuration; +using SmartStore.Core.Domain.Forums; + +namespace SmartStore.Core.Search +{ + public class ForumSearchSettings : ISettings + { + public ForumSearchSettings() + { + SearchMode = SearchMode.Contains; + SearchFields = new List { "subject" }; + TopicSortOrder = ForumTopicSorting.Relevance; + InstantSearchEnabled = true; + InstantSearchNumberOfHits = 10; + InstantSearchTermMinLength = 2; + FilterMinHitCount = 1; + FilterMaxChoicesCount = 20; + } + + /// + /// Gets or sets the search mode + /// + public SearchMode SearchMode { get; set; } + + /// + /// Gets or sets name of fields to be searched. The name field is always searched. + /// + public List SearchFields { get; set; } + + /// + /// Gets or sets the topic sort order in search results + /// + public ForumTopicSorting TopicSortOrder { get; set; } + + /// + /// Gets or sets a value indicating whether instant-search is enabled + /// + public bool InstantSearchEnabled { get; set; } + + /// + /// Gets or sets the number of hits to return when using "instant-search" feature + /// + public int InstantSearchNumberOfHits { get; set; } + + /// + /// Gets or sets a minimum instant-search term length + /// + public int InstantSearchTermMinLength { get; set; } + + /// + /// Gets or sets the minimum hit count for a filter value. Values with a lower hit count are not displayed. + /// + public int FilterMinHitCount { get; set; } + + /// + /// Gets or sets the maximum number of filter values to be displayed. + /// + public int FilterMaxChoicesCount { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/SearchSettings.cs b/src/Libraries/SmartStore.Core/Search/Settings/SearchSettings.cs similarity index 100% rename from src/Libraries/SmartStore.Core/Search/SearchSettings.cs rename to src/Libraries/SmartStore.Core/Search/Settings/SearchSettings.cs diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index b8077a6a1e..16c87dc91c 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -232,6 +232,7 @@ + @@ -472,7 +473,8 @@ - + + diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index a106e742f6..239247cc04 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -369,6 +369,19 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Catalog.Products.Fields.ProductTags.Hint", "Product tags are keywords that this product can also be identified by. Enter a list of the tags to be associated with this product. The more products associated with a particular tag, the larger it will show on the tag cloud.", "Eine Liste von Schlüsselwörtern, die das Produkt taxonomisch charakterisieren. Je mehr Produkte einem Schlüsselwort (Tag) zugeordnet sind, desto mehr visuelles Gewicht erhält das Tag."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.Initial", "Position", "Position"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.Relevance", "Relevance", "Beste Ergebnisse"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.SubjectAsc", "Subject: A to Z", "Thema: A bis Z"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.SubjectDesc", "Subject: Z to A", "Thema: Z bis A"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.CreatedOnAsc", "Created on: Oldest first", "Erstellt am: ältere zuerst"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.CreatedOnDesc", "Created on: Newest first", "Erstellt am: neuere zuerst"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.LastPostOnAsc", "Last post: Oldest first", "Letzter Beitrag: ältere zuerst"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.LastPostOnDesc", "Last post: Newest first", "Letzter Beitrag: neuere zuerst"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.PostsAsc", "Post number: ascending", "Anzahl Beiträge: aufsteigend"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.PostsDesc", "Post number: descending", "Anzahl Beiträge: absteigend"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.ViewsAsc", "Hits: ascending", "Hits: aufsteigend"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.ViewsDesc", "Hits: descending", "Hits: absteigend"); } } } diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index b7bf5b292f..e42ea16b10 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -370,33 +370,14 @@ public virtual void DeleteTopic(ForumTopic forumTopic) } public virtual ForumTopic GetTopicById(int forumTopicId) - { - return GetTopicById(forumTopicId, false); - } - - public virtual ForumTopic GetTopicById(int forumTopicId, bool increaseViews) { if (forumTopicId == 0) { return null; } - var query = from ft in _forumTopicRepository.Table - where ft.Id == forumTopicId - select ft; - var forumTopic = query.SingleOrDefault(); - if (forumTopic == null) - { - return null; - } - - if (increaseViews) - { - forumTopic.Views = ++forumTopic.Views; - UpdateTopic(forumTopic); - } - - return forumTopic; + var entity = _forumTopicRepository.Table.FirstOrDefault(x => x.Id == forumTopicId); + return entity; } public virtual IList GetTopicsByIds(int[] topicIds) diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index f97785de79..c1b2be3c0e 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -90,14 +90,6 @@ public partial interface IForumService /// Forum Topic ForumTopic GetTopicById(int forumTopicId); - /// - /// Gets a forum topic - /// - /// The forum topic identifier - /// The value indicating whether to increase forum topic views - /// Forum Topic - ForumTopic GetTopicById(int forumTopicId, bool increaseViews); - /// /// Gets forum topics by identifiers /// diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchService.cs index 4ccf5e13fc..d3b02a0b53 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchService.cs @@ -9,12 +9,11 @@ using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; using SmartStore.Services.Catalog; -using SmartStore.Services.Customers; namespace SmartStore.Services.Search { - public partial class CatalogSearchService : ICatalogSearchService - { + public partial class CatalogSearchService : SearchServiceBase, ICatalogSearchService + { private readonly ICommonServices _services; private readonly IIndexManager _indexManager; private readonly Lazy _productService; @@ -42,30 +41,15 @@ public CatalogSearchService( public Localizer T { get; set; } - /// - /// Notifies admin that indexing is required to use the advanced search. - /// - protected virtual void IndexingRequiredNotification() - { - if (_services.WorkContext.CurrentCustomer.IsAdmin()) - { - var notification = T("Search.IndexingRequiredNotification", - _urlHelper.Action("Indexing", "MegaSearch", new { area = "SmartStore.MegaSearch" }), - _urlHelper.Action("ConfigurePlugin", "Plugin", new { area = "admin", systemName = "SmartStore.MegaSearch" })); - - _services.Notifier.Information(notification); - } - } - /// /// Bypasses the index provider and directly searches in the database /// - /// - /// - /// + /// Search query + /// LOad flags + /// Catalog search result protected virtual CatalogSearchResult SearchDirect(CatalogSearchQuery searchQuery, ProductLoadFlags loadFlags = ProductLoadFlags.None) { - // fallback to linq search + // Fallback to linq search. var linqCatalogSearchService = _services.Container.ResolveNamed("linq"); var result = linqCatalogSearchService.Search(searchQuery, loadFlags, true); @@ -74,7 +58,86 @@ protected virtual CatalogSearchResult SearchDirect(CatalogSearchQuery searchQuer return result; } - public CatalogSearchResult Search( + protected virtual void ApplyFacetLabels(IDictionary facets) + { + if (facets == null || facets.Count == 0) + { + return; + } + + FacetGroup group; + var rangeMinTemplate = T("Search.Facet.RangeMin").Text; + var rangeMaxTemplate = T("Search.Facet.RangeMax").Text; + var rangeBetweenTemplate = T("Search.Facet.RangeBetween").Text; + + // Apply "price" labels. + if (facets.TryGetValue("price", out group)) + { + // TODO: formatting without decimals would be nice + foreach (var facet in group.Facets) + { + var val = facet.Value; + + if (val.Value == null && val.UpperValue != null) + { + val.Label = rangeMaxTemplate.FormatInvariant(FormatPrice(val.UpperValue.Convert())); + } + else if (val.Value != null && val.UpperValue == null) + { + val.Label = rangeMinTemplate.FormatInvariant(FormatPrice(val.Value.Convert())); + } + else if (val.Value != null && val.UpperValue != null) + { + val.Label = rangeBetweenTemplate.FormatInvariant( + FormatPrice(val.Value.Convert()), + FormatPrice(val.UpperValue.Convert())); + } + } + } + + // Apply "rating" labels. + if (facets.TryGetValue("rating", out group)) + { + foreach (var facet in group.Facets) + { + facet.Value.Label = T(facet.Key == "1" ? "Search.Facet.1StarAndMore" : "Search.Facet.XStarsAndMore", facet.Value.Value).Text; + } + } + + // Apply "numeric range" labels. + var numericRanges = facets + .Where(x => x.Value.TemplateHint == FacetTemplateHint.NumericRange) + .Select(x => x.Value); + + foreach (var numericRange in numericRanges) + { + foreach (var facet in numericRange.SelectedFacets) + { + var val = facet.Value; + var labels = val.Label.SplitSafe("~"); + + if (val.Value == null && val.UpperValue != null) + { + val.Label = rangeMaxTemplate.FormatInvariant(labels.SafeGet(0)); + } + else if (val.Value != null && val.UpperValue == null) + { + val.Label = rangeMinTemplate.FormatInvariant(labels.SafeGet(0)); + } + else if (val.Value != null && val.UpperValue != null) + { + val.Label = rangeBetweenTemplate.FormatInvariant(labels.SafeGet(0), labels.SafeGet(1)); + } + } + } + } + + protected virtual string FormatPrice(decimal price) + { + return _priceFormatter.FormatPrice(price, true, false); + } + + public CatalogSearchResult Search( CatalogSearchQuery searchQuery, ProductLoadFlags loadFlags = ProductLoadFlags.None, bool direct = false) @@ -121,7 +184,7 @@ public CatalogSearchResult Search( { using (_services.Chronometer.Step(stepPrefix + "Collect")) { - var productIds = searchHits.Select(x => x.EntityId).ToArray(); + var productIds = searchHits.Select(x => x.EntityId).Distinct().ToArray(); hitsFactory = () => _productService.Value.GetProductsByIds(productIds, loadFlags); } } @@ -136,9 +199,9 @@ public CatalogSearchResult Search( ApplyFacetLabels(facets); } } - catch (Exception exception) + catch (Exception ex) { - _logger.Error(exception); + _logger.Error(ex); } } } @@ -152,10 +215,10 @@ public CatalogSearchResult Search( spellCheckerSuggestions = searchEngine.CheckSpelling(); } } - catch (Exception exception) + catch (Exception ex) { - // spell checking should not break the search - _logger.Error(exception); + // Spell checking should not break the search. + _logger.Error(ex); } } @@ -173,7 +236,7 @@ public CatalogSearchResult Search( } else if (searchQuery.Origin.IsCaseInsensitiveEqual("Search/Search")) { - IndexingRequiredNotification(); + IndexingRequiredNotification(_services, _urlHelper); } } @@ -185,84 +248,5 @@ public IQueryable PrepareQuery(CatalogSearchQuery searchQuery, IQueryab var linqCatalogSearchService = _services.Container.ResolveNamed("linq"); return linqCatalogSearchService.PrepareQuery(searchQuery, baseQuery); } - - protected virtual void ApplyFacetLabels(IDictionary facets) - { - if (facets == null || facets.Count == 0) - { - return; - } - - FacetGroup group; - var rangeMinTemplate = T("Search.Facet.RangeMin").Text; - var rangeMaxTemplate = T("Search.Facet.RangeMax").Text; - var rangeBetweenTemplate = T("Search.Facet.RangeBetween").Text; - - // Apply "price" labels. - if (facets.TryGetValue("price", out group)) - { - // TODO: formatting without decimals would be nice - foreach (var facet in group.Facets) - { - var val = facet.Value; - - if (val.Value == null && val.UpperValue != null) - { - val.Label = rangeMaxTemplate.FormatInvariant(FormatPrice(val.UpperValue.Convert())); - } - else if (val.Value != null && val.UpperValue == null) - { - val.Label = rangeMinTemplate.FormatInvariant(FormatPrice(val.Value.Convert())); - } - else if (val.Value != null && val.UpperValue != null) - { - val.Label = rangeBetweenTemplate.FormatInvariant( - FormatPrice(val.Value.Convert()), - FormatPrice(val.UpperValue.Convert())); - } - } - } - - // Apply "rating" labels. - if (facets.TryGetValue("rating", out group)) - { - foreach (var facet in group.Facets) - { - facet.Value.Label = T(facet.Key == "1" ? "Search.Facet.1StarAndMore" : "Search.Facet.XStarsAndMore", facet.Value.Value).Text; - } - } - - // Apply "numeric range" labels. - var numericRanges = facets - .Where(x => x.Value.TemplateHint == FacetTemplateHint.NumericRange) - .Select(x => x.Value); - - foreach (var numericRange in numericRanges) - { - foreach (var facet in numericRange.SelectedFacets) - { - var val = facet.Value; - var labels = val.Label.SplitSafe("~"); - - if (val.Value == null && val.UpperValue != null) - { - val.Label = rangeMaxTemplate.FormatInvariant(labels.SafeGet(0)); - } - else if (val.Value != null && val.UpperValue == null) - { - val.Label = rangeMinTemplate.FormatInvariant(labels.SafeGet(0)); - } - else if (val.Value != null && val.UpperValue != null) - { - val.Label = rangeBetweenTemplate.FormatInvariant(labels.SafeGet(0), labels.SafeGet(1)); - } - } - } - } - - private string FormatPrice(decimal price) - { - return _priceFormatter.FormatPrice(price, true, false); - } } } diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs index d4f9be0787..cd17aed5a5 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs @@ -7,7 +7,6 @@ using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; -using SmartStore.Core.Logging; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; using SmartStore.Services.Catalog; @@ -16,7 +15,7 @@ namespace SmartStore.Services.Search { - public partial class LinqCatalogSearchService : LinqSearchServiceBase, ICatalogSearchService + public partial class LinqCatalogSearchService : SearchServiceBase, ICatalogSearchService { private static int[] _priceThresholds = new int[] { 10, 25, 50, 100, 250, 500, 1000 }; @@ -61,13 +60,10 @@ public LinqCatalogSearchService( _categoryService = categoryService; QuerySettings = DbQuerySettings.Default; - Logger = NullLogger.Instance; } public DbQuerySettings QuerySettings { get; set; } - public ILogger Logger { get; set; } - #region Utilities private IQueryable QueryCategories(IQueryable query, List ids, bool? featuredOnly) @@ -768,7 +764,7 @@ public CatalogSearchResult Search(CatalogSearchQuery searchQuery, ProductLoadFla .Skip(searchQuery.PageIndex * searchQuery.Take) .Take(searchQuery.Take); - var ids = query.Select(x => x.Id).ToArray(); + var ids = query.Select(x => x.Id).Distinct().ToArray(); hitsFactory = () => _productService.GetProductsByIds(ids, loadFlags); } diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs new file mode 100644 index 0000000000..8e88926cb6 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using Autofac; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Logging; +using SmartStore.Core.Search; +using SmartStore.Services.Forums; + +namespace SmartStore.Services.Search +{ + public partial class ForumSearchService : SearchServiceBase, IForumSearchService + { + private readonly ICommonServices _services; + private readonly IIndexManager _indexManager; + private readonly Lazy _forumService; + private readonly ILogger _logger; + private readonly UrlHelper _urlHelper; + + public ForumSearchService( + ICommonServices services, + IIndexManager indexManager, + Lazy forumService, + ILogger logger, + UrlHelper urlHelper) + { + _services = services; + _indexManager = indexManager; + _forumService = forumService; + _logger = logger; + _urlHelper = urlHelper; + } + + /// + /// Bypasses the index provider and directly searches in the database + /// + /// Search query + /// Forum search result + protected virtual ForumSearchResult SearchDirect(ForumSearchQuery searchQuery) + { + // Fallback to linq search. + var linqForumSearchService = _services.Container.ResolveNamed("linq"); + var result = linqForumSearchService.Search(searchQuery, true); + return result; + } + + public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = false) + { + Guard.NotNull(searchQuery, nameof(searchQuery)); + Guard.NotNegative(searchQuery.Take, nameof(searchQuery.Take)); + + var provider = _indexManager.GetIndexProvider("Forum"); + + if (!direct && provider != null) + { + var indexStore = provider.GetIndexStore("Forum"); + if (indexStore.Exists) + { + var searchEngine = provider.GetSearchEngine(indexStore, searchQuery); + var stepPrefix = searchEngine.GetType().Name + " - "; + var totalCount = 0; + string[] spellCheckerSuggestions = null; + IEnumerable searchHits; + Func> hitsFactory = null; + + _services.EventPublisher.Publish(new ForumSearchingEvent(searchQuery)); + + if (searchQuery.Take > 0) + { + using (_services.Chronometer.Step(stepPrefix + "Count")) + { + totalCount = searchEngine.Count(); + // Fix paging boundaries. + if (searchQuery.Skip > 0 && searchQuery.Skip >= totalCount) + { + searchQuery.Slice((totalCount / searchQuery.Take) * searchQuery.Take, searchQuery.Take); + } + } + + using (_services.Chronometer.Step(stepPrefix + "Hits")) + { + searchHits = searchEngine.Search(); + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithHits)) + { + using (_services.Chronometer.Step(stepPrefix + "Collect")) + { + var ids = searchHits.Select(x => x.GetInt("topicid")).Distinct().ToArray(); + hitsFactory = () => _forumService.Value.GetTopicsByIds(ids); + } + } + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithSuggestions)) + { + try + { + using (_services.Chronometer.Step(stepPrefix + "Spellcheck")) + { + spellCheckerSuggestions = searchEngine.CheckSpelling(); + } + } + catch (Exception ex) + { + // Spell checking should not break the search. + _logger.Error(ex); + } + } + + var result = new ForumSearchResult( + searchEngine, + searchQuery, + totalCount, + hitsFactory, + spellCheckerSuggestions); + + _services.EventPublisher.Publish(new ForumSearchedEvent(searchQuery, result)); + + return result; + } + else if (searchQuery.Origin.IsCaseInsensitiveEqual("Search/Forum")) + { + IndexingRequiredNotification(_services, _urlHelper); + } + } + + return SearchDirect(searchQuery); + } + + public IQueryable PrepareQuery(ForumSearchQuery searchQuery, IQueryable baseQuery = null) + { + var linqForumSearchService = _services.Container.ResolveNamed("linq"); + return linqForumSearchService.PrepareQuery(searchQuery, baseQuery); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs index 583c8d687d..5c69944c12 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -3,37 +3,27 @@ using System.Linq; using SmartStore.Core.Data; using SmartStore.Core.Domain.Forums; -using SmartStore.Core.Logging; using SmartStore.Core.Search; using SmartStore.Services.Forums; namespace SmartStore.Services.Search { - public partial class LinqForumSearchService : LinqSearchServiceBase, IForumSearchService + public partial class LinqForumSearchService : SearchServiceBase, IForumSearchService { - private readonly IRepository _forumTopicRepository; private readonly IRepository _forumPostRepository; private readonly IForumService _forumService; private readonly ICommonServices _services; public LinqForumSearchService( - IRepository forumTopicRepository, IRepository forumPostRepository, IForumService forumService, ICommonServices services) { - _forumTopicRepository = forumTopicRepository; _forumPostRepository = forumPostRepository; _forumService = forumService; _services = services; - - Logger = NullLogger.Instance; } - public ILogger Logger { get; set; } - - #region Utilities - protected virtual IQueryable GetForumTopicQuery(ForumSearchQuery searchQuery, IQueryable baseQuery) { var ordered = false; @@ -135,9 +125,25 @@ orderby grp.Key // Sorting of topics. foreach (var sort in searchQuery.Sorting) { - if (sort.FieldName == "createdon") + if (sort.FieldName == "subject") + { + topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.Subject, sort.Descending); + } + else if (sort.FieldName == "createdon") + { + topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.CreatedOnUtc, sort.Descending); + } + else if (sort.FieldName == "lastposton") + { + topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.LastPostTime, sort.Descending); + } + else if (sort.FieldName == "posts") { - query = OrderBy(ref ordered, query, x => x.CreatedOnUtc, sort.Descending); + topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.NumPosts, sort.Descending); + } + else if (sort.FieldName == "views") + { + topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.Views, sort.Descending); } } @@ -152,8 +158,6 @@ orderby grp.Key return topicsQuery; } - #endregion - public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = false) { _services.EventPublisher.Publish(new ForumSearchingEvent(searchQuery)); @@ -178,7 +182,7 @@ public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = fals .Skip(searchQuery.PageIndex * searchQuery.Take) .Take(searchQuery.Take); - var ids = query.Select(x => x.Id).ToArray(); + var ids = query.Select(x => x.Id).Distinct().ToArray(); hitsFactory = () => _forumService.GetTopicsByIds(ids); } } diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs new file mode 100644 index 0000000000..de931c2107 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs @@ -0,0 +1,37 @@ +using System.Web; + +namespace SmartStore.Services.Search.Modelling +{ + /* + TOKENS: + =============================== + q - Search term + i - Page index + s - Page size + o - Order by + v - View Mode + */ + + public class ForumSearchQueryFactory : IForumSearchQueryFactory + { + protected readonly HttpContextBase _httpContext; + + public ForumSearchQueryFactory( + HttpContextBase httpContext) + { + _httpContext = httpContext; + } + + public ForumSearchQuery Current { get; private set; } + + public ForumSearchQuery CreateFromQuery() + { + var ctx = _httpContext; + + if (ctx.Request == null) + return null; + + throw new System.NotImplementedException(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryFactory.cs new file mode 100644 index 0000000000..05ca8c4866 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryFactory.cs @@ -0,0 +1,17 @@ +namespace SmartStore.Services.Search.Modelling +{ + public interface IForumSearchQueryFactory + { + /// + /// Creates a instance from the current + /// by looking up corresponding keys in posted form and/or query string + /// + /// The query object + ForumSearchQuery CreateFromQuery(); + + /// + /// The last created query instance. The MVC model binder uses this property to avoid repeated binding. + /// + ForumSearchQuery Current { get; } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/LinqSearchServiceBase.cs b/src/Libraries/SmartStore.Services/Search/SearchServiceBase.cs similarity index 75% rename from src/Libraries/SmartStore.Services/Search/LinqSearchServiceBase.cs rename to src/Libraries/SmartStore.Services/Search/SearchServiceBase.cs index 153c799291..1eb0f59027 100644 --- a/src/Libraries/SmartStore.Services/Search/LinqSearchServiceBase.cs +++ b/src/Libraries/SmartStore.Services/Search/SearchServiceBase.cs @@ -2,11 +2,14 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Web.Mvc; +using SmartStore.Core.Logging; using SmartStore.Core.Search; +using SmartStore.Services.Customers; namespace SmartStore.Services.Search { - public abstract partial class LinqSearchServiceBase + public abstract partial class SearchServiceBase { protected virtual void FlattenFilters(ICollection filters, List result) { @@ -94,5 +97,19 @@ protected virtual IOrderedQueryable OrderBy( } } + /// + /// Notifies the admin that indexing is required to use the advanced search. + /// + protected virtual void IndexingRequiredNotification(ICommonServices services, UrlHelper urlHelper) + { + if (services.WorkContext.CurrentCustomer.IsAdmin()) + { + var indexingUrl = urlHelper.Action("Indexing", "MegaSearch", new { area = "SmartStore.MegaSearch" }); + var configureUrl = urlHelper.Action("ConfigurePlugin", "Plugin", new { area = "admin", systemName = "SmartStore.MegaSearch" }); + var notification = services.Localization.GetResource("Search.IndexingRequiredNotification").FormatInvariant(indexingUrl, configureUrl); + + services.Notifier.Information(notification); + } + } } } diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index a6c09c3f12..15b1093fe9 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -550,12 +550,15 @@ + - + + + diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 3b021e1475..173587a4e8 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -575,9 +575,12 @@ protected override void Load(ContainerBuilder builder) { builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().Named("linq").InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().Named("linq").InstancePerRequest(); + builder.RegisterType().Named("linq").InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); } diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 104a4130d2..87054c878b 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -7,6 +7,7 @@ using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.Media; using SmartStore.Core.Html; +using SmartStore.Core.Logging; using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.Directory; @@ -518,12 +519,22 @@ public ActionResult Topic(int id, int page = 1) // If no posts area loaded, redirect to the first page. if (posts.Count == 0 && page > 1) { - return RedirectToRoute("TopicSlug", new {id = forumTopic.Id, slug = forumTopic.GetSeName()}); + return RedirectToRoute("TopicSlug", new { id = forumTopic.Id, slug = forumTopic.GetSeName() }); } // Update view count. - forumTopic.Views += 1; - _forumService.UpdateTopic(forumTopic); + try + { + if (!customer.IsSearchEngineAccount()) + { + forumTopic.Views += 1; + _forumService.UpdateTopic(forumTopic); + } + } + catch (Exception ex) + { + Logger.Error(ex); + } var model = new ForumTopicPageModel(); model.Id = forumTopic.Id; From 06be060ee8529659b6b775d29f95dacc4bc25135 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 23 Aug 2018 00:27:26 +0200 Subject: [PATCH 042/932] Numeric textbox editor for byte type --- .../Administration/Views/Web.config | 6 +-- .../SmartStore.Web/SmartStore.Web.csproj | 1 + .../ChoiceTemplates/Choice.FileUpload.cshtml | 1 + .../Views/Shared/EditorTemplates/Byte.cshtml | 39 +++++++++++++++++++ .../Shared/EditorTemplates/Decimal.cshtml | 9 ----- .../Shared/EditorTemplates/Double.cshtml | 10 ----- .../Views/Shared/EditorTemplates/Int32.cshtml | 9 ----- 7 files changed, 44 insertions(+), 31 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Byte.cshtml diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Web.config b/src/Presentation/SmartStore.Web/Administration/Views/Web.config index 9a432023f3..fc2bcd4794 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Web.config +++ b/src/Presentation/SmartStore.Web/Administration/Views/Web.config @@ -43,7 +43,7 @@ - + @@ -54,8 +54,8 @@ - - + + diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index e63c83ff99..f70266f8f4 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -1759,6 +1759,7 @@ + Web.config diff --git a/src/Presentation/SmartStore.Web/Views/Shared/ChoiceTemplates/Choice.FileUpload.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/ChoiceTemplates/Choice.FileUpload.cshtml index c4a2b93500..c96ff3417e 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/ChoiceTemplates/Choice.FileUpload.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/ChoiceTemplates/Choice.FileUpload.cshtml @@ -1,5 +1,6 @@ @using SmartStore.Web.Framework.UI; @using SmartStore.Web.Framework.UI.Choices; + @model ChoiceModel @{ var storeUrl = EngineContext.Current.Resolve().GetStoreLocation(false); diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Byte.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Byte.cshtml new file mode 100644 index 0000000000..8fe3cbfdb3 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Byte.cshtml @@ -0,0 +1,39 @@ +@using Telerik.Web.Mvc.UI + +@functions{ + private byte? Value + { + get + { + byte? value = null; + if (ViewData.Model != null) + { + value = Convert.ToByte(ViewData.Model, System.Globalization.CultureInfo.InvariantCulture); + } + return value; + } + } + + private string Postfix + { + get + { + return ViewData["postfix"] as string; + } + } +} + +
+ @(Html.Telerik().NumericTextBox() + .Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)) + .EmptyMessage(T("Common.EnterValue")) + .MinValue(0) + .MaxValue(255) + .Value(Value) + ) + + @if (Postfix.HasValue()) + { + @Postfix + } +
\ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Decimal.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Decimal.cshtml index 3d50a65f16..d2b0131c2c 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Decimal.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Decimal.cshtml @@ -23,20 +23,11 @@ } } - -@{ - string additionalClass = string.Empty; - //if (ViewData.ContainsKey("stretch")) { - // additionalClass = ViewData["stretch"].Convert() ? "flex-grow-1" : ""; - //} -} -
@(Html.Telerik().NumericTextBox() .Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)) .EmptyMessage(T("Common.EnterValue")) .Value(Value) - .HtmlAttributes(new { @class = additionalClass }) .DecimalDigits(4) //always display 4 digits ) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Double.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Double.cshtml index d0c4ce2e91..deccb78df2 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Double.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Double.cshtml @@ -23,21 +23,11 @@ } } - -@{ - string additionalClass = string.Empty; - //if (ViewData.ContainsKey("stretch")) - //{ - // additionalClass = ViewData["stretch"].Convert() ? "flex-grow-1" : ""; - //} -} -
@(Html.Telerik().NumericTextBox() .Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)) .EmptyMessage(T("Common.EnterValue")) .Value(Value) - .HtmlAttributes(new { @class = additionalClass }) .DecimalDigits(4) //always display 4 digits ) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Int32.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Int32.cshtml index a7c0352575..2eb3cf60f0 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Int32.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Int32.cshtml @@ -23,20 +23,11 @@ } } -@{ - string additionalClass = string.Empty; - //if (ViewData.ContainsKey("stretch")) - //{ - // additionalClass = ViewData["stretch"].Convert() ? "flex-grow-1" : ""; - //} -} -
@(Html.Telerik().IntegerTextBox() .Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)) .EmptyMessage(T("Common.EnterValue")) .Value(Value) - .HtmlAttributes(new { @class = additionalClass }) ) @if (Postfix.HasValue()) From f5f029f8d74849277eeac6f830c6680c783a42f1 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 23 Aug 2018 17:45:43 +0200 Subject: [PATCH 043/932] Search: Forum search settings and common facets --- .../Domain/Forums/ForumTopicSorting.cs | 10 - .../Search/Facets/FacetGroup.cs | 87 +++---- .../Search/SearchDocumentType.cs | 2 +- .../Search/Settings/ForumSearchSettings.cs | 50 ++-- .../Migrations/MigrationsConfiguration.cs | 44 +++- .../Forums/ForumService.cs | 40 ++-- .../Forums/IForumService.cs | 3 +- .../Catalog/LinqCatalogSearchService.cs | 23 +- .../Modelling/CatalogSearchQueryFactory.cs | 34 +-- .../Search/Extensions/FacetUtility.cs | 33 ++- .../Search/Forum/ForumSearchResult.cs | 13 +- .../Search/Forum/ForumSearchService.cs | 38 ++- .../Search/Forum/LinqForumSearchService.cs | 181 +++++++++++++-- .../Modelling/ForumSearchQueryFactory.cs | 52 ++++- .../Search/SearchQueryFactoryBase.cs | 34 +++ .../SmartStore.Services.csproj | 1 + .../Controllers/ForumController.cs | 10 +- .../Controllers/SettingController.cs | 142 +++++++++--- .../Settings/ForumSearchSettingsModel.cs | 68 ++++++ .../Models/Settings/SearchSettingsModel.cs | 25 +- .../Administration/SmartStore.Admin.csproj | 1 + .../Views/Setting/Search.cshtml | 218 ++++++++++++++---- .../Controllers/BoardsController.cs | 9 +- 23 files changed, 852 insertions(+), 266 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/Search/SearchQueryFactoryBase.cs create mode 100644 src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSearchSettingsModel.cs diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs index 7bc610e7cf..1038d4c969 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs @@ -35,16 +35,6 @@ public enum ForumTopicSorting ///
CreatedOnDesc, - /// - /// Last post date: Oldest first - /// - LastPostOnAsc, - - /// - /// Last post date: Newest first - /// - LastPostOnDesc, - /// /// Number of posts: Low to High /// diff --git a/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs b/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs index cb0bd028ec..b9c10ef0fa 100644 --- a/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs @@ -16,7 +16,10 @@ public enum FacetGroupKind Availability, NewArrivals, Attribute, - Variant + Variant, + Forum, + Customer, + Date } [DebuggerDisplay("Key: {Key}, Label: {Label}, Kind: {Kind}")] @@ -26,11 +29,12 @@ public class FacetGroup private FacetGroupKind? _kind; public FacetGroup() - : this (string.Empty, string.Empty, false, false, 0, Enumerable.Empty()) + : this (string.Empty, string.Empty, string.Empty, false, false, 0, Enumerable.Empty()) { } public FacetGroup( + string scope, string key, string label, bool isMultiSelect, @@ -38,9 +42,11 @@ public FacetGroup( int displayOrder, IEnumerable facets) { - Guard.NotNull(key, nameof(key)); + Guard.NotNull(scope, nameof(scope)); + Guard.NotNull(key, nameof(key)); Guard.NotNull(facets, nameof(facets)); + Scope = scope; Key = key; Label = label; IsMultiSelect = isMultiSelect; @@ -57,14 +63,14 @@ public FacetGroup( { _facets.Add(x.Key, x); } - catch (Exception exception) + catch (Exception ex) { - exception.Dump(); + ex.Dump(); } }); } - public static FacetGroupKind GetKindByKey(string key) + public static FacetGroupKind GetKindByKey(string scope, string key) { if (key.StartsWith("attrid")) { @@ -74,41 +80,40 @@ public static FacetGroupKind GetKindByKey(string key) { return FacetGroupKind.Variant; } - else if (key == "categoryid" || key == "notfeaturedcategoryid") - { - return FacetGroupKind.Category; - } - else if (key == "manufacturerid") - { - return FacetGroupKind.Brand; - } - else if (key == "price") - { - return FacetGroupKind.Price; - } - else if (key == "rating") - { - return FacetGroupKind.Rating; - } - else if (key == "deliveryid") - { - return FacetGroupKind.DeliveryTime; - } - else if (key == "available") - { - return FacetGroupKind.Availability; - } - else if (key == "createdon") - { - return FacetGroupKind.NewArrivals; - } - else - { - return FacetGroupKind.Unknown; - } - } - public string Key + switch (key) + { + case "categoryid": + case "notfeaturedcategoryid": + return FacetGroupKind.Category; + case "manufacturerid": + return FacetGroupKind.Brand; + case "price": + return FacetGroupKind.Price; + case "rating": + return FacetGroupKind.Rating; + case "deliveryid": + return FacetGroupKind.DeliveryTime; + case "available": + return FacetGroupKind.Availability; + case "createdon": + return scope == "Catalog" ? FacetGroupKind.NewArrivals : FacetGroupKind.Date; + case "forumid": + return FacetGroupKind.Forum; + case "customerid": + return FacetGroupKind.Customer; + default: + return FacetGroupKind.Unknown; + } + } + + public string Scope + { + get; + private set; + } + + public string Key { get; private set; @@ -170,7 +175,7 @@ public FacetGroupKind Kind { if (_kind == null) { - _kind = GetKindByKey(Key); + _kind = GetKindByKey(Scope, Key); } return _kind.Value; diff --git a/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs b/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs index d7de9eb81b..6455be0cdd 100644 --- a/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs +++ b/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs @@ -10,6 +10,6 @@ public enum SearchDocumentType AttributeValue, Variant, VariantValue, - ForumPost = 50 + ForumPost } } diff --git a/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs index b35f9918f4..8a56d2901b 100644 --- a/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs +++ b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs @@ -14,12 +14,16 @@ public ForumSearchSettings() InstantSearchEnabled = true; InstantSearchNumberOfHits = 10; InstantSearchTermMinLength = 2; - FilterMinHitCount = 1; - FilterMaxChoicesCount = 20; - } + FilterMinHitCount = 1; + FilterMaxChoicesCount = 20; + + ForumDisplayOrder = 1; + CustomerDisplayOrder = 2; + DateDisplayOrder = 3; + } /// - /// Gets or sets the search mode + /// Gets or sets the search mode. /// public SearchMode SearchMode { get; set; } @@ -29,33 +33,45 @@ public ForumSearchSettings() public List SearchFields { get; set; } /// - /// Gets or sets the topic sort order in search results + /// Gets or sets the topic sort order in search results. /// public ForumTopicSorting TopicSortOrder { get; set; } /// - /// Gets or sets a value indicating whether instant-search is enabled + /// Gets or sets a value indicating whether instant-search is enabled. /// public bool InstantSearchEnabled { get; set; } /// - /// Gets or sets the number of hits to return when using "instant-search" feature + /// Gets or sets the number of hits to return when using "instant-search" feature. /// public int InstantSearchNumberOfHits { get; set; } /// - /// Gets or sets a minimum instant-search term length + /// Gets or sets a minimum instant-search term length. /// public int InstantSearchTermMinLength { get; set; } - /// - /// Gets or sets the minimum hit count for a filter value. Values with a lower hit count are not displayed. - /// - public int FilterMinHitCount { get; set; } + /// + /// Gets or sets the minimum hit count for a filter value. Values with a lower hit count are not displayed. + /// + public int FilterMinHitCount { get; set; } - /// - /// Gets or sets the maximum number of filter values to be displayed. - /// - public int FilterMaxChoicesCount { get; set; } - } + /// + /// Gets or sets the maximum number of filter values to be displayed. + /// + public int FilterMaxChoicesCount { get; set; } + + #region Common facet settings + + public bool ForumDisabled { get; set; } + public bool CustomerDisabled { get; set; } + public bool DateDisabled { get; set; } + + public int ForumDisplayOrder { get; set; } + public int CustomerDisplayOrder { get; set; } + public int DateDisplayOrder { get; set; } + + #endregion + } } diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 239247cc04..3354b5c9a2 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -376,12 +376,46 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.SubjectDesc", "Subject: Z to A", "Thema: Z bis A"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.CreatedOnAsc", "Created on: Oldest first", "Erstellt am: ältere zuerst"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.CreatedOnDesc", "Created on: Newest first", "Erstellt am: neuere zuerst"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.LastPostOnAsc", "Last post: Oldest first", "Letzter Beitrag: ältere zuerst"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.LastPostOnDesc", "Last post: Newest first", "Letzter Beitrag: neuere zuerst"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.PostsAsc", "Post number: ascending", "Anzahl Beiträge: aufsteigend"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.PostsDesc", "Post number: descending", "Anzahl Beiträge: absteigend"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.ViewsAsc", "Hits: ascending", "Hits: aufsteigend"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.ViewsDesc", "Hits: descending", "Hits: absteigend"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.PostsDesc", "Post number: descending", "Anzahl Beiträge: absteigend"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.ViewsAsc", "Hits number: ascending", "Trefferanzahl: aufsteigend"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.ViewsDesc", "Hits number: descending", "Trefferanzahl: absteigend"); + + builder.AddOrUpdate("Search.Facet.Forum", "Forum", "Forum"); + builder.AddOrUpdate("Search.Facet.Customer", "Customer", "Kunde"); + builder.AddOrUpdate("Search.Facet.Date", "Date", "Datum"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.WildcardSearchNote", + "The wildcard mode can slow down the search for a large number of objects.", + "Der Wildcard-Modus kann bei einer großen Anzahl an Objekten die Suche verlangsamen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.SearchMode", + "Search mode", + "Suchmodus", + "Specifies the search mode. Please keep in mind that the search mode can - depending on number of objects - strongly affect search performance. 'Is equal to' is the fastest, 'Contains' the slowest.", + "Legt den Suchmodus fest. Bitte beachten Sie, dass der Suchmodus die Geschwindigkeit der Suche (abhängig von der Objektanzahl) beeinflusst. 'Ist gleich' ist am schnellsten, 'Beinhaltet' am langsamsten."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.Forum.SearchFields", + "Search fields", + "Suchfelder", + "Specifies additional search fields. The post text is always searched.", + "Legt zusätzlich zu durchsuchende Felder fest. Der Beitragstext wird grundsätzlich immer durchsucht."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.DefaultSortOrder", + "Default sort order", + "Standardsortierreihenfolge", + "Specifies the default sort order in search results.", + "Legt die Standardsortierreihenfolge in den Suchergebnissen fest."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.InstantSearchNumberOfHits", + "Number of hits", + "Anzahl der Treffer", + "Specifies the number of hits displayed in instant search.", + "Legt die Anzahl der angezeigten Suchtreffer in der Instantsuche fest."); + + builder.Delete( + "Admin.Configuration.Settings.Search.DefaultSortOrderMode", + "Admin.Configuration.Settings.Search.InstantSearchNumberOfProducts"); } } } diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index e42ea16b10..5e33010d1b 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -205,32 +205,30 @@ public virtual ForumGroup GetForumGroupById(int forumGroupId) return _forumGroupRepository.GetById(forumGroupId); } - public virtual IList GetAllForumGroups(bool showHidden = false) + public virtual IList GetAllForumGroups(int storeId = 0) { - var query = _forumGroupRepository.Table; + var query = _forumGroupRepository.Table.Expand(x => x.Forums); - if (!showHidden && !QuerySettings.IgnoreMultiStore) - { - var currentStoreId = _services.StoreContext.CurrentStore.Id; - - query = - from fg in query - join sm in _storeMappingRepository.Table on new { c1 = fg.Id, c2 = "ForumGroup" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into fg_sm - from sm in fg_sm.DefaultIfEmpty() - where !fg.LimitedToStores || currentStoreId == sm.StoreId - select fg; + if (!QuerySettings.IgnoreMultiStore && storeId > 0) + { + query = + from fg in query + join sm in _storeMappingRepository.Table on new { c1 = fg.Id, c2 = "ForumGroup" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into fg_sm + from sm in fg_sm.DefaultIfEmpty() + where !fg.LimitedToStores || storeId == sm.StoreId + select fg; - query = - from fg in query - group fg by fg.Id into fgGroup - orderby fgGroup.Key - select fgGroup.FirstOrDefault(); - } + query = + from fg in query + group fg by fg.Id into fgGroup + orderby fgGroup.Key + select fgGroup.FirstOrDefault(); + } - query = query.OrderBy(m => m.DisplayOrder); + query = query.OrderBy(x => x.DisplayOrder); - return query.ToListCached(); - } + return query.ToListCached(); + } public virtual void DeleteForumGroup(ForumGroup forumGroup) { diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index c1b2be3c0e..9df126aa89 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -22,8 +22,9 @@ public partial interface IForumService /// /// Gets all forum groups /// + /// Store identifier /// Forum groups - IList GetAllForumGroups(bool showHidden = false); + IList GetAllForumGroups(int storeId = 0); /// /// Deletes a forum group diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs index cd17aed5a5..e67f2e76d3 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs @@ -534,7 +534,7 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s { var descriptor = searchQuery.FacetDescriptors[key]; var facets = new List(); - var kind = FacetGroup.GetKindByKey(key); + var kind = FacetGroup.GetKindByKey("Catalog", key); switch (kind) { @@ -552,8 +552,6 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s if (kind == FacetGroupKind.Category) { - #region Category - var categoryTree = _categoryService.GetCategoryTree(0, false, storeId); var categories = categoryTree.Flatten(false); @@ -578,13 +576,9 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s DisplayOrder = category.DisplayOrder })); } - - #endregion } else if (kind == FacetGroupKind.Brand) { - #region Brand - var manufacturers = _manufacturerService.GetAllManufacturers(null, storeId); if (descriptor.MaxChoicesCount > 0) { @@ -607,13 +601,9 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s DisplayOrder = manu.DisplayOrder })); } - - #endregion } else if (kind == FacetGroupKind.DeliveryTime) { - #region Delivery time - var deliveryTimes = _deliveryTimeService.GetAllDeliveryTimes(); var nameQuery = _localizedPropertyRepository.TableUntracked .Where(x => x.LocaleKeyGroup == "DeliveryTime" && x.LocaleKey == "Name" && x.LanguageId == languageId); @@ -634,13 +624,9 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s DisplayOrder = deliveryTime.DisplayOrder })); } - - #endregion } else if (kind == FacetGroupKind.Price) { - #region Price - var count = 0; var hasActivePredefinedFacet = false; var minPrice = _productRepository.Table.Where(x => !x.Deleted && x.Published && !x.IsSystemProduct).Min(x => (double)x.Price); @@ -687,8 +673,6 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s { facets.Insert(0, new Facet("custom", customPriceFacetValue)); } - - #endregion } else if (kind == FacetGroupKind.Rating) { @@ -724,7 +708,8 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s //facets.Each(x => $"{key} {x.Value.ToString()}".Dump()); result.Add(key, new FacetGroup( - key, + "Catalog", + key, descriptor.Label, descriptor.IsMultiSelect, false, @@ -736,7 +721,7 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s return result; } - #endregion + #endregion public CatalogSearchResult Search(CatalogSearchQuery searchQuery, ProductLoadFlags loadFlags = ProductLoadFlags.None, bool direct = false) { diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs index 1072e3443a..44788857bd 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs @@ -33,12 +33,11 @@ namespace SmartStore.Services.Search.Modelling * - Variants & attributes */ - public class CatalogSearchQueryFactory : ICatalogSearchQueryFactory - { + public class CatalogSearchQueryFactory : SearchQueryFactoryBase, ICatalogSearchQueryFactory + { protected static readonly string[] _tokens = new string[] { "q", "i", "s", "o", "p", "c", "m", "r", "a", "n", "d", "v" }; protected static readonly string[] _instantSearchFields = new string[] { "manufacturer", "sku", "gtin", "mpn", "attrname", "variantname" }; - protected readonly HttpContextBase _httpContext; protected readonly CatalogSettings _catalogSettings; protected readonly SearchSettings _searchSettings; protected readonly ICommonServices _services; @@ -51,8 +50,8 @@ public CatalogSearchQueryFactory( SearchSettings searchSettings, ICommonServices services, ICatalogSearchQueryAliasMapper catalogSearchQueryAliasMapper) + : base(httpContext) { - _httpContext = httpContext; _catalogSettings = catalogSettings; _searchSettings = searchSettings; _services = services; @@ -130,8 +129,7 @@ public CatalogSearchQuery CreateFromQuery() OnConverted(query, routeData, origin); - this.Current = query; - + Current = query; return query; } @@ -620,32 +618,12 @@ protected virtual void OnConverted(CatalogSearchQuery query, RouteData routeData { } - protected T GetValueFor(string key) - { - T value; - return GetValueFor(key, out value) ? value : default(T); - } - - protected bool GetValueFor(string key, out T value) - { - var strValue = _httpContext.Request?.Unvalidated.Form?[key] ?? _httpContext.Request?.Unvalidated.QueryString?[key]; - - if (strValue.HasValue()) - { - value = strValue.Convert(); - return true; - } - - value = default(T); - return false; - } - - protected bool GetValueFor(CatalogSearchQuery query, string key, FacetGroupKind kind, out T value) + protected virtual bool GetValueFor(CatalogSearchQuery query, string key, FacetGroupKind kind, out T value) { return GetValueFor(_catalogSearchQueryAliasMapper.GetCommonFacetAliasByGroupKind(kind, query.LanguageId ?? 0) ?? key, out value); } - protected Multimap Aliases + protected virtual Multimap Aliases { get { diff --git a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs index 4c799338b7..7ce4268346 100644 --- a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs +++ b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Core.Localization; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; namespace SmartStore.Services.Search.Extensions { - public static class FacetUtility + public static class FacetUtility { private const double MAX_PRICE = 1000000000; @@ -120,5 +121,33 @@ public static IEnumerable GetRatings() }; } } - } + + public static List GetForumDates(Localizer T) + { + var count = 0; + var utcNow = DateTime.UtcNow; + var result = new List(); + var days = new Dictionary + { + { 1, "Forum.Search.LimitResultsToPrevious.1day" }, + { 7, "Forum.Search.LimitResultsToPrevious.7days" }, + { 14, "Forum.Search.LimitResultsToPrevious.2weeks" }, + { 30, "Forum.Search.LimitResultsToPrevious.1month" }, + { 92, "Forum.Search.LimitResultsToPrevious.3months" }, + { 183, "Forum.Search.LimitResultsToPrevious.6months" }, + { 365, "Forum.Search.LimitResultsToPrevious.1year" } + }; + + foreach (var day in days) + { + result.Add(new FacetValue(utcNow.AddDays(-day.Key), utcNow, IndexTypeCode.DateTime, true, true) + { + DisplayOrder = ++count, + Label = T(day.Value) + }); + } + + return result; + } + } } diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs index 44f46237ac..4e9a3a3e5a 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs @@ -3,6 +3,7 @@ using SmartStore.Core; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; namespace SmartStore.Services.Search { @@ -16,15 +17,17 @@ public ForumSearchResult( ForumSearchQuery query, int totalHitsCount, Func> hitsFactory, - string[] spellCheckerSuggestions) + string[] spellCheckerSuggestions, + IDictionary facets) { Guard.NotNull(query, nameof(query)); Engine = engine; Query = query; SpellCheckerSuggestions = spellCheckerSuggestions ?? new string[0]; + Facets = facets ?? new Dictionary(); - _hitsFactory = hitsFactory ?? (() => new List()); + _hitsFactory = hitsFactory ?? (() => new List()); TotalHitsCount = totalHitsCount; } @@ -33,7 +36,7 @@ public ForumSearchResult( /// /// Forum search query public ForumSearchResult(ForumSearchQuery query) - : this(null, query, 0, () => new List(), null) + : this(null, query, 0, () => new List(), null, null) { } @@ -69,7 +72,9 @@ public IPagedList Hits ///
public string[] SpellCheckerSuggestions { get; set; } - public ISearchEngine Engine { get; private set; } + public IDictionary Facets { get; private set; } + + public ISearchEngine Engine { get; private set; } /// /// Highlights chosen terms in a text, extracting the most relevant sections diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs index 8e88926cb6..08410758ce 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs @@ -6,6 +6,7 @@ using SmartStore.Core.Domain.Forums; using SmartStore.Core.Logging; using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; using SmartStore.Services.Forums; namespace SmartStore.Services.Search @@ -42,9 +43,20 @@ protected virtual ForumSearchResult SearchDirect(ForumSearchQuery searchQuery) // Fallback to linq search. var linqForumSearchService = _services.Container.ResolveNamed("linq"); var result = linqForumSearchService.Search(searchQuery, true); - return result; + ApplyFacetLabels(result.Facets); + + return result; } + protected virtual void ApplyFacetLabels(IDictionary facets) + { + if (facets == null || facets.Count == 0) + { + return; + } + + } + public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = false) { Guard.NotNull(searchQuery, nameof(searchQuery)); @@ -63,8 +75,9 @@ public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = fals string[] spellCheckerSuggestions = null; IEnumerable searchHits; Func> hitsFactory = null; + IDictionary facets = null; - _services.EventPublisher.Publish(new ForumSearchingEvent(searchQuery)); + _services.EventPublisher.Publish(new ForumSearchingEvent(searchQuery)); if (searchQuery.Take > 0) { @@ -91,7 +104,23 @@ public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = fals hitsFactory = () => _forumService.Value.GetTopicsByIds(ids); } } - } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithFacets)) + { + try + { + using (_services.Chronometer.Step(stepPrefix + "Facets")) + { + facets = searchEngine.GetFacetMap(); + ApplyFacetLabels(facets); + } + } + catch (Exception ex) + { + _logger.Error(ex); + } + } + } if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithSuggestions)) { @@ -114,7 +143,8 @@ public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = fals searchQuery, totalCount, hitsFactory, - spellCheckerSuggestions); + spellCheckerSuggestions, + facets); _services.EventPublisher.Publish(new ForumSearchedEvent(searchQuery, result)); diff --git a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs index 5c69944c12..30b2023ae7 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -2,9 +2,14 @@ using System.Collections.Generic; using System.Linq; using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Localization; using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; using SmartStore.Services.Forums; +using SmartStore.Services.Localization; +using SmartStore.Services.Search.Extensions; namespace SmartStore.Services.Search { @@ -22,9 +27,13 @@ public LinqForumSearchService( _forumPostRepository = forumPostRepository; _forumService = forumService; _services = services; - } - protected virtual IQueryable GetForumTopicQuery(ForumSearchQuery searchQuery, IQueryable baseQuery) + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + protected virtual IQueryable GetForumTopicQuery(ForumSearchQuery searchQuery, IQueryable baseQuery) { var ordered = false; var term = searchQuery.Term; @@ -90,7 +99,7 @@ protected virtual IQueryable GetForumTopicQuery(ForumSearchQuery sea { query = query.Where(x => x.ForumTopic.CustomerId == (int)filter.Term); } - else if (filter.FieldName == "lastposton") + else if (filter.FieldName == "createdon") { if (rangeFilter != null) { @@ -100,17 +109,17 @@ protected virtual IQueryable GetForumTopicQuery(ForumSearchQuery sea if (lower.HasValue) { if (rangeFilter.IncludesLower) - query = query.Where(x => x.ForumTopic.LastPostTime >= lower.Value); + query = query.Where(x => x.CreatedOnUtc >= lower.Value); else - query = query.Where(x => x.ForumTopic.LastPostTime > lower.Value); + query = query.Where(x => x.CreatedOnUtc > lower.Value); } if (upper.HasValue) { if (rangeFilter.IncludesLower) - query = query.Where(x => x.ForumTopic.LastPostTime <= upper.Value); + query = query.Where(x => x.CreatedOnUtc <= upper.Value); else - query = query.Where(x => x.ForumTopic.LastPostTime < upper.Value); + query = query.Where(x => x.CreatedOnUtc < upper.Value); } } } @@ -133,10 +142,6 @@ orderby grp.Key { topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.CreatedOnUtc, sort.Descending); } - else if (sort.FieldName == "lastposton") - { - topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.LastPostTime, sort.Descending); - } else if (sort.FieldName == "posts") { topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.NumPosts, sort.Descending); @@ -158,14 +163,154 @@ orderby grp.Key return topicsQuery; } - public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = false) + protected virtual IDictionary GetFacets(ForumSearchQuery searchQuery, int totalHits) + { + var result = new Dictionary(); + var storeId = searchQuery.StoreId ?? _services.StoreContext.CurrentStore.Id; + var languageId = searchQuery.LanguageId ?? _services.WorkContext.WorkingLanguage.Id; + + foreach (var key in searchQuery.FacetDescriptors.Keys) + { + var descriptor = searchQuery.FacetDescriptors[key]; + var facets = new List(); + var kind = FacetGroup.GetKindByKey("Forum", key); + + switch (kind) + { + case FacetGroupKind.NewArrivals: + if (totalHits == 0 && !descriptor.Values.Any(x => x.IsSelected)) + { + continue; + } + break; + } + + if (kind == FacetGroupKind.Forum) + { + var enoughFacets = false; + var groups = _forumService.GetAllForumGroups(storeId); + + foreach (var group in groups) + { + foreach (var forum in group.Forums) + { + facets.Add(new Facet(new FacetValue(forum.Id, IndexTypeCode.Int32) + { + IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(forum.Id)), + Label = forum.GetLocalized(x => x.Name, languageId), + DisplayOrder = forum.DisplayOrder + })); + + if (descriptor.MaxChoicesCount > 0 && facets.Count >= descriptor.MaxChoicesCount) + { + enoughFacets = true; + break; + } + } + + if (enoughFacets) + { + break; + } + } + } + else if (kind == FacetGroupKind.Customer) + { + var userNamesEnabled = _services.Settings.LoadSetting().UsernamesEnabled; + + // Get customers with most posts. + var groupQuery = + from p in _forumPostRepository.TableUntracked.Expand(x => x.Customer) + group p by p.CustomerId into grp + select new + { + Count = grp.Count(), + grp.FirstOrDefault().Customer + }; + + // Limit the result. Do not allow to get all customers. + var customers = groupQuery + .Where(x => x.Customer != null) + .OrderByDescending(x => x.Count) + .Select(x => x.Customer) + .Take(descriptor.MaxChoicesCount > 0 ? descriptor.MaxChoicesCount : 20) + .ToList(); + + foreach (var customer in customers) + { + // TODO: anonymize? + var name = userNamesEnabled ? customer.Username : customer.FirstName; + + facets.Add(new Facet(new FacetValue(customer.Id, IndexTypeCode.Int32) + { + IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(customer.Id)), + Label = name.NullEmpty() ?? customer.Id.ToString(), + DisplayOrder = 0 + })); + } + } + else if (kind == FacetGroupKind.Date) + { + var hasActivePredefinedFacet = false; + var dates = FacetUtility.GetForumDates(T); + + foreach (var date in dates) + { + var newFacet = new Facet(date); + newFacet.Value.IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(date.Value)); + + if (newFacet.Value.IsSelected) + { + hasActivePredefinedFacet = true; + } + + facets.Add(newFacet); + } + + // Add facet for custom date range. + var dateDescriptorValue = descriptor.Values.FirstOrDefault(); + var customDateValue = new FacetValue( + dateDescriptorValue != null && !hasActivePredefinedFacet ? dateDescriptorValue.Value : null, + dateDescriptorValue != null && !hasActivePredefinedFacet ? dateDescriptorValue.UpperValue : null, + IndexTypeCode.DateTime, + true, + true); + + customDateValue.IsSelected = customDateValue.Value != null || customDateValue.UpperValue != null; + + if (!(totalHits == 0 && !customDateValue.IsSelected)) + { + facets.Insert(0, new Facet("custom", customDateValue)); + } + } + + if (facets.Any(x => x.Published)) + { + //facets.Each(x => $"{key} {x.Value.ToString()}".Dump()); + + result.Add(key, new FacetGroup( + "Forum", + key, + descriptor.Label, + descriptor.IsMultiSelect, + false, + descriptor.DisplayOrder, + facets.OrderBy(descriptor))); + } + } + + return result; + } + + public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = false) { _services.EventPublisher.Publish(new ForumSearchingEvent(searchQuery)); var totalHits = 0; Func> hitsFactory = null; + IDictionary facets = null; - if (searchQuery.Take > 0) + if (searchQuery.Take > 0) { var query = GetForumTopicQuery(searchQuery, null); totalHits = query.Count(); @@ -185,14 +330,20 @@ public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = fals var ids = query.Select(x => x.Id).Distinct().ToArray(); hitsFactory = () => _forumService.GetTopicsByIds(ids); } - } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithFacets) && searchQuery.FacetDescriptors.Any()) + { + facets = GetFacets(searchQuery, totalHits); + } + } var result = new ForumSearchResult( null, searchQuery, totalHits, hitsFactory, - null); + null, + facets); _services.EventPublisher.Publish(new ForumSearchedEvent(searchQuery, result)); diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs index de931c2107..c77b07006d 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs @@ -1,4 +1,7 @@ -using System.Web; +using System.Collections.Generic; +using System.Web; +using System.Web.Routing; +using SmartStore.Core.Search; namespace SmartStore.Services.Search.Modelling { @@ -9,17 +12,24 @@ namespace SmartStore.Services.Search.Modelling i - Page index s - Page size o - Order by - v - View Mode + f - Forum + c - Customer + p - Last post */ - public class ForumSearchQueryFactory : IForumSearchQueryFactory + public class ForumSearchQueryFactory : SearchQueryFactoryBase, IForumSearchQueryFactory { - protected readonly HttpContextBase _httpContext; + protected readonly ICommonServices _services; + protected readonly ForumSearchSettings _searchSettings; public ForumSearchQueryFactory( - HttpContextBase httpContext) + ICommonServices services, + HttpContextBase httpContext, + ForumSearchSettings searchSettings) + : base(httpContext) { - _httpContext = httpContext; + _services = services; + _searchSettings = searchSettings; } public ForumSearchQuery Current { get; private set; } @@ -31,7 +41,35 @@ public ForumSearchQuery CreateFromQuery() if (ctx.Request == null) return null; - throw new System.NotImplementedException(); + var routeData = ctx.Request.RequestContext.RouteData; + var area = routeData.GetAreaName(); + var controller = routeData.GetRequiredString("controller"); + var action = routeData.GetRequiredString("action"); + var origin = "{0}{1}/{2}".FormatInvariant(area == null ? "" : area + "/", controller, action); + var fields = new List { "text" }; + var term = GetValueFor("q"); + var isInstantSearch = origin.IsCaseInsensitiveEqual("Search/InstantSearch"); + + fields.AddRange(_searchSettings.SearchFields); + + var query = new ForumSearchQuery(fields.ToArray(), term, _searchSettings.SearchMode) + .OriginatesFrom(origin) + .WithLanguage(_services.WorkContext.WorkingLanguage) + .WithCurrency(_services.WorkContext.WorkingCurrency); + + // Instant-Search never uses these filter parameters. + if (!isInstantSearch) + { + } + + OnConverted(query, routeData, origin); + + Current = query; + return query; + } + + protected virtual void OnConverted(ForumSearchQuery query, RouteData routeData, string origin) + { } } } diff --git a/src/Libraries/SmartStore.Services/Search/SearchQueryFactoryBase.cs b/src/Libraries/SmartStore.Services/Search/SearchQueryFactoryBase.cs new file mode 100644 index 0000000000..b6dd78ea4e --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/SearchQueryFactoryBase.cs @@ -0,0 +1,34 @@ +using System.Web; + +namespace SmartStore.Services.Search +{ + public abstract partial class SearchQueryFactoryBase + { + protected readonly HttpContextBase _httpContext; + + protected SearchQueryFactoryBase(HttpContextBase httpContext) + { + _httpContext = httpContext; + } + + protected virtual T GetValueFor(string key) + { + T value; + return GetValueFor(key, out value) ? value : default(T); + } + + protected virtual bool GetValueFor(string key, out T value) + { + var strValue = _httpContext.Request?.Unvalidated.Form?[key] ?? _httpContext.Request?.Unvalidated.QueryString?[key]; + + if (strValue.HasValue()) + { + value = strValue.Convert(); + return true; + } + + value = default(T); + return false; + } + } +} diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 15b1093fe9..d824d6533c 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -558,6 +558,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ForumController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ForumController.cs index 2f7d0bc7ff..d418935457 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ForumController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ForumController.cs @@ -108,7 +108,7 @@ public ActionResult List() if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) return AccessDeniedView(); - var forumGroupsModel = _forumService.GetAllForumGroups(true) + var forumGroupsModel = _forumService.GetAllForumGroups(0) .Select(fg => { var forumGroupModel = fg.ToModel(); @@ -187,7 +187,7 @@ public ActionResult CreateForum() AddLocales(_languageService, model.Locales); - foreach (var forumGroup in _forumService.GetAllForumGroups(true)) + foreach (var forumGroup in _forumService.GetAllForumGroups()) { var forumGroupModel = forumGroup.ToModel(); model.ForumGroups.Add(forumGroupModel); @@ -218,7 +218,7 @@ public ActionResult CreateForum(ForumModel model, bool continueEditing) } //If we got this far, something failed, redisplay form - foreach (var forumGroup in _forumService.GetAllForumGroups(true)) + foreach (var forumGroup in _forumService.GetAllForumGroups()) { var forumGroupModel = forumGroup.ToModel(); model.ForumGroups.Add(forumGroupModel); @@ -306,7 +306,7 @@ public ActionResult EditForum(int id) locale.SeName = forum.GetSeName(languageId, false, false); }); - foreach (var forumGroup in _forumService.GetAllForumGroups(true)) + foreach (var forumGroup in _forumService.GetAllForumGroups(0)) { var forumGroupModel = forumGroup.ToModel(); model.ForumGroups.Add(forumGroupModel); @@ -341,7 +341,7 @@ public ActionResult EditForum(ForumModel model, bool continueEditing) } //If we got this far, something failed, redisplay form - foreach (var forumGroup in _forumService.GetAllForumGroups(true)) + foreach (var forumGroup in _forumService.GetAllForumGroups()) { var forumGroupModel = forumGroup.ToModel(); model.ForumGroups.Add(forumGroupModel); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index 8a521d03bb..65475453c5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -154,16 +154,21 @@ private SelectListItem ResToSelectListItem(string resourceKey) return new SelectListItem { Text = value, Value = value }; } - private string CreateCommonFacetSettingKey(FacetGroupKind kind, int languageId) + private string CreateCommonFacetSettingKey(FacetGroupKind kind, int languageId, string scope = null) { + if (scope.HasValue()) + { + return $"FacetGroupKind-{kind.ToString()}-Alias-{languageId}-{scope}"; + } + return $"FacetGroupKind-{kind.ToString()}-Alias-{languageId}"; } - private void UpdateLocalizedFacetSetting(CommonFacetSettingsModel model, FacetGroupKind kind, ref bool clearCache) + private void UpdateLocalizedFacetSetting(CommonFacetSettingsModel model, FacetGroupKind kind, ref bool clearCache, string scope = null) { foreach (var localized in model.Locales) { - var key = CreateCommonFacetSettingKey(kind, localized.LanguageId); + var key = CreateCommonFacetSettingKey(kind, localized.LanguageId, scope); var existingAlias = _services.Settings.GetSettingByKey(key); if (existingAlias.IsCaseInsensitiveEqual(localized.Alias)) @@ -1385,20 +1390,26 @@ public ActionResult DataExchange(DataExchangeSettings settings, DataExchangeSett public ActionResult Search() { - if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) - return AccessDeniedView(); + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + { + return AccessDeniedView(); + } var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); var settings = Services.Settings.LoadSetting(storeScope); - var megaSearchDescriptor = _pluginFinder.GetPluginDescriptorBySystemName("SmartStore.MegaSearch"); + var fsettings = Services.Settings.LoadSetting(storeScope); + var megaSearchDescriptor = _pluginFinder.GetPluginDescriptorBySystemName("SmartStore.MegaSearch"); var megaSearchPlusDescriptor = _pluginFinder.GetPluginDescriptorBySystemName("SmartStore.MegaSearchPlus"); var model = new SearchSettingsModel(); MiniMapper.Map(settings, model); - model.IsMegaSearchInstalled = megaSearchDescriptor != null; + MiniMapper.Map(fsettings, model.ForumSearchSettings); + + model.IsMegaSearchInstalled = megaSearchDescriptor != null; model.AvailableSortOrderModes = settings.DefaultSortOrder.ToSelectList(); + model.ForumSearchSettings.AvailableTopicSortOrders = fsettings.TopicSortOrder.ToSelectList(); - if (megaSearchDescriptor == null) + if (megaSearchDescriptor == null) { model.SearchFieldsNote = T("Admin.Configuration.Settings.Search.SearchFieldsNote"); @@ -1409,7 +1420,8 @@ public ActionResult Search() }; model.AvailableSearchModes = settings.SearchMode.ToSelectList().Where(x => x.Value.ToInt() != (int)SearchMode.ExactMatch).ToList(); - } + model.ForumSearchSettings.AvailableSearchModes = fsettings.SearchMode.ToSelectList().Where(x => x.Value.ToInt() != (int)SearchMode.ExactMatch).ToList(); + } else { model.AvailableSearchFields = new List @@ -1431,9 +1443,15 @@ public ActionResult Search() } model.AvailableSearchModes = settings.SearchMode.ToSelectList().ToList(); - } + model.ForumSearchSettings.AvailableSearchModes = fsettings.SearchMode.ToSelectList().ToList(); + } - // Common facets + model.ForumSearchSettings.AvailableSearchFields = new List + { + new SelectListItem { Text = T("Forum.TopicTitle"), Value = "subject" } + }; + + // Common facets. model.BrandFacet.Disabled = settings.BrandDisabled; model.BrandFacet.DisplayOrder = settings.BrandDisplayOrder; model.PriceFacet.Disabled = settings.PriceDisabled; @@ -1448,6 +1466,13 @@ public ActionResult Search() model.NewArrivalsFacet.Disabled = settings.NewArrivalsDisabled; model.NewArrivalsFacet.DisplayOrder = settings.NewArrivalsDisplayOrder; + model.ForumSearchSettings.ForumFacet.Disabled = fsettings.ForumDisabled; + model.ForumSearchSettings.ForumFacet.DisplayOrder = fsettings.ForumDisplayOrder; + model.ForumSearchSettings.CustomerFacet.Disabled = fsettings.CustomerDisabled; + model.ForumSearchSettings.CustomerFacet.DisplayOrder = fsettings.CustomerDisplayOrder; + model.ForumSearchSettings.DateFacet.Disabled = fsettings.DateDisabled; + model.ForumSearchSettings.DateFacet.DisplayOrder = fsettings.DateDisplayOrder; + // Localized facet settings (CommonFacetSettingsLocalizedModel). foreach (var language in _languageService.GetAllLanguages(true)) { @@ -1486,48 +1511,85 @@ public ActionResult Search() LanguageId = language.Id, Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.NewArrivals, language.Id)) }); - } - // Facet settings (CommonFacetSettingsModel). + model.ForumSearchSettings.ForumFacet.Locales.Add(new CommonFacetSettingsLocalizedModel + { + LanguageId = language.Id, + Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.Forum, language.Id, "Forum")) + }); + model.ForumSearchSettings.CustomerFacet.Locales.Add(new CommonFacetSettingsLocalizedModel + { + LanguageId = language.Id, + Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.Customer, language.Id, "Forum")) + }); + model.ForumSearchSettings.DateFacet.Locales.Add(new CommonFacetSettingsLocalizedModel + { + LanguageId = language.Id, + Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.Date, language.Id, "Forum")) + }); + } + StoreDependingSettings.GetOverrideKeys(settings, model, storeScope, Services.Settings); + StoreDependingSettings.GetOverrideKeys(fsettings, model, storeScope, Services.Settings); - var keyPrefixes = new string[] { "Brand", "Price", "Rating", "DeliveryTime", "Availability", "NewArrivals" }; - foreach (var prefix in keyPrefixes) + // Facet settings (CommonFacetSettingsModel). + foreach (var prefix in new string[] { "Brand", "Price", "Rating", "DeliveryTime", "Availability", "NewArrivals" }) { StoreDependingSettings.GetOverrideKey(prefix + "Facet.Disabled", prefix + "Disabled", settings, storeScope, Services.Settings); StoreDependingSettings.GetOverrideKey(prefix + "Facet.DisplayOrder", prefix + "DisplayOrder", settings, storeScope, Services.Settings); } + foreach (var prefix in new string[] { "ForumSearchSettings.Forum", "ForumSearchSettings.Customer", "ForumSearchSettings.Date" }) + { + StoreDependingSettings.GetOverrideKey(prefix + "Facet.Disabled", prefix + "Disabled", fsettings, storeScope, Services.Settings); + StoreDependingSettings.GetOverrideKey(prefix + "Facet.DisplayOrder", prefix + "DisplayOrder", fsettings, storeScope, Services.Settings); + } + // Facet settings with a non-prefixed name. StoreDependingSettings.GetOverrideKey("AvailabilityFacet.IncludeNotAvailable", "IncludeNotAvailable", settings, storeScope, Services.Settings); - return View(model); + return View(model); } [HttpPost] public ActionResult Search(SearchSettingsModel model, FormCollection form) { - if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) - return AccessDeniedView(); + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + { + return AccessDeniedView(); + } var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); var settings = Services.Settings.LoadSetting(storeScope); + var fsettings = Services.Settings.LoadSetting(storeScope); - var validator = new SearchSettingValidator(T, x => + var validator = new SearchSettingValidator(T, x => { return storeScope == 0 || StoreDependingSettings.IsOverrideChecked(settings, x, form); }); - validator.Validate(model, ModelState); - if (!ModelState.IsValid) - return Search(); + var fvalidator = new ForumSearchSettingValidator(T, x => + { + return storeScope == 0 || StoreDependingSettings.IsOverrideChecked(fsettings, x, form); + }); + var fvResult = fvalidator.Validate(model.ForumSearchSettings); + if (!fvResult.IsValid) + { + fvResult.Errors.Each(x => ModelState.AddModelError(string.Concat(nameof(model.ForumSearchSettings), ".", x.PropertyName), x.ErrorMessage)); + } + + if (!ModelState.IsValid) + { + return Search(); + } ModelState.Clear(); MiniMapper.Map(model, settings); + MiniMapper.Map(model.ForumSearchSettings, fsettings); - // Common facets. - settings.BrandDisabled = model.BrandFacet.Disabled; + // Common facets. + settings.BrandDisabled = model.BrandFacet.Disabled; settings.BrandDisplayOrder = model.BrandFacet.DisplayOrder; settings.PriceDisabled = model.PriceFacet.Disabled; settings.PriceDisplayOrder = model.PriceFacet.DisplayOrder; @@ -1541,27 +1603,41 @@ public ActionResult Search(SearchSettingsModel model, FormCollection form) settings.NewArrivalsDisabled = model.NewArrivalsFacet.Disabled; settings.NewArrivalsDisplayOrder = model.NewArrivalsFacet.DisplayOrder; + fsettings.ForumDisabled = model.ForumSearchSettings.ForumFacet.Disabled; + fsettings.ForumDisplayOrder = model.ForumSearchSettings.ForumFacet.DisplayOrder; + fsettings.CustomerDisabled = model.ForumSearchSettings.CustomerFacet.Disabled; + fsettings.CustomerDisplayOrder = model.ForumSearchSettings.CustomerFacet.DisplayOrder; + fsettings.DateDisabled = model.ForumSearchSettings.DateFacet.Disabled; + fsettings.DateDisplayOrder = model.ForumSearchSettings.DateFacet.DisplayOrder; + // Scope to avoid duplicate SearchSettings.SearchFields records. using (Services.Settings.BeginScope()) { StoreDependingSettings.UpdateSettings(settings, form, storeScope, Services.Settings); - } + StoreDependingSettings.UpdateSettings(fsettings, form, storeScope, Services.Settings); + } var clearFacetCache = false; using (Services.Settings.BeginScope()) { _services.Settings.SaveSetting(settings, x => x.SearchFields, 0, false); + _services.Settings.SaveSetting(fsettings, x => x.SearchFields, 0, false); - // Facet settings (CommonFacetSettingsModel). - var keyPrefixes = new string[] { "Brand", "Price", "Rating", "DeliveryTime", "Availability", "NewArrivals" }; - foreach (var prefix in keyPrefixes) + // Facet settings (CommonFacetSettingsModel). + foreach (var prefix in new string[] { "Brand", "Price", "Rating", "DeliveryTime", "Availability", "NewArrivals" }) { StoreDependingSettings.UpdateSetting(prefix + "Facet.Disabled", prefix + "Disabled", settings, form, storeScope, Services.Settings); StoreDependingSettings.UpdateSetting(prefix + "Facet.DisplayOrder", prefix + "DisplayOrder", settings, form, storeScope, Services.Settings); } - // Facet settings with a non-prefixed name. - StoreDependingSettings.UpdateSetting("AvailabilityFacet.IncludeNotAvailable", "IncludeNotAvailable", settings, form, storeScope, Services.Settings); + foreach (var prefix in new string[] { "ForumSearchSettings.Forum", "ForumSearchSettings.Customer", "ForumSearchSettings.Date" }) + { + StoreDependingSettings.UpdateSetting(prefix + "Facet.Disabled", prefix + "Disabled", settings, form, storeScope, Services.Settings); + StoreDependingSettings.UpdateSetting(prefix + "Facet.DisplayOrder", prefix + "DisplayOrder", fsettings, form, storeScope, Services.Settings); + } + + // Facet settings with a non-prefixed name. + StoreDependingSettings.UpdateSetting("AvailabilityFacet.IncludeNotAvailable", "IncludeNotAvailable", settings, form, storeScope, Services.Settings); // Localized facet settings (CommonFacetSettingsLocalizedModel). UpdateLocalizedFacetSetting(model.CategoryFacet, FacetGroupKind.Category, ref clearFacetCache); @@ -1571,7 +1647,11 @@ public ActionResult Search(SearchSettingsModel model, FormCollection form) UpdateLocalizedFacetSetting(model.DeliveryTimeFacet, FacetGroupKind.DeliveryTime, ref clearFacetCache); UpdateLocalizedFacetSetting(model.AvailabilityFacet, FacetGroupKind.Availability, ref clearFacetCache); UpdateLocalizedFacetSetting(model.NewArrivalsFacet, FacetGroupKind.NewArrivals, ref clearFacetCache); - } + + UpdateLocalizedFacetSetting(model.ForumSearchSettings.ForumFacet, FacetGroupKind.Forum, ref clearFacetCache, "Forum"); + UpdateLocalizedFacetSetting(model.ForumSearchSettings.CustomerFacet, FacetGroupKind.Customer, ref clearFacetCache, "Forum"); + UpdateLocalizedFacetSetting(model.ForumSearchSettings.DateFacet, FacetGroupKind.Date, ref clearFacetCache, "Forum"); + } if (clearFacetCache) { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSearchSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSearchSettingsModel.cs new file mode 100644 index 0000000000..ec306c0f62 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSearchSettingsModel.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Web.Mvc; +using FluentValidation; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Localization; +using SmartStore.Core.Search; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; +using SmartStore.Web.Framework.Validators; + +namespace SmartStore.Admin.Models.Settings +{ + public class ForumSearchSettingsModel : ModelBase + { + public ForumSearchSettingsModel() + { + ForumFacet = new CommonFacetSettingsModel(); + CustomerFacet = new CommonFacetSettingsModel(); + DateFacet = new CommonFacetSettingsModel(); + } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Search.SearchMode")] + public SearchMode SearchMode { get; set; } + public List AvailableSearchModes { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Search.Forum.SearchFields")] + public List SearchFields { get; set; } + public List AvailableSearchFields { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Search.DefaultSortOrder")] + public ForumTopicSorting TopicSortOrder { get; set; } + public SelectList AvailableTopicSortOrders { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Search.InstantSearchEnabled")] + public bool InstantSearchEnabled { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Search.InstantSearchNumberOfHits")] + public int InstantSearchNumberOfHits { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Search.InstantSearchTermMinLength")] + public int InstantSearchTermMinLength { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Search.FilterMinHitCount")] + public int FilterMinHitCount { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Search.FilterMaxChoicesCount")] + public int FilterMaxChoicesCount { get; set; } + + public CommonFacetSettingsModel ForumFacet { get; set; } + public CommonFacetSettingsModel CustomerFacet { get; set; } + public CommonFacetSettingsModel DateFacet { get; set; } + } + + public class ForumSearchSettingValidator : SmartValidatorBase + { + public ForumSearchSettingValidator(Localizer T, Func addRule) + { + if (addRule("InstantSearchNumberOfHits")) + { + RuleFor(x => x.InstantSearchNumberOfHits) + .Must(x => x >= 1 && x <= SearchSettingValidator.MaxInstantSearchItems) + .When(x => x.InstantSearchEnabled) + .WithMessage(T("Admin.Validation.ValueRange").Text.FormatInvariant(1, SearchSettingValidator.MaxInstantSearchItems)); + } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/SearchSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/SearchSettingsModel.cs index bae13e6e63..f1acadfd47 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/SearchSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/SearchSettingsModel.cs @@ -1,18 +1,18 @@ using System; using System.Collections.Generic; using System.Web.Mvc; +using FluentValidation; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Localization; using SmartStore.Core.Search; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; using SmartStore.Web.Framework.Modelling; -using SmartStore.Core.Domain.Catalog; using SmartStore.Web.Framework.Validators; -using FluentValidation; -using SmartStore.Core.Localization; namespace SmartStore.Admin.Models.Settings { - public partial class SearchSettingsModel : ModelBase + public partial class SearchSettingsModel : ModelBase { public SearchSettingsModel() { @@ -23,6 +23,7 @@ public SearchSettingsModel() DeliveryTimeFacet = new CommonFacetSettingsModel(); AvailabilityFacet = new CommonFacetSettingsModel(); NewArrivalsFacet = new CommonFacetSettingsModel(); + ForumSearchSettings = new ForumSearchSettingsModel(); } public string SearchFieldsNote { get; set; } @@ -42,7 +43,7 @@ public SearchSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Search.ShowProductImagesInInstantSearch")] public bool ShowProductImagesInInstantSearch { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.Search.InstantSearchNumberOfProducts")] + [SmartResourceDisplayName("Admin.Configuration.Settings.Search.InstantSearchNumberOfHits")] public int InstantSearchNumberOfProducts { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.Search.InstantSearchTermMinLength")] @@ -54,7 +55,7 @@ public SearchSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Search.FilterMaxChoicesCount")] public int FilterMaxChoicesCount { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.Search.DefaultSortOrderMode")] + [SmartResourceDisplayName("Admin.Configuration.Settings.Search.DefaultSortOrder")] public ProductSortingEnum DefaultSortOrder { get; set; } public SelectList AvailableSortOrderModes { get; set; } @@ -65,7 +66,10 @@ public SearchSettingsModel() public CommonFacetSettingsModel DeliveryTimeFacet { get; set; } public CommonFacetSettingsModel AvailabilityFacet { get; set; } public CommonFacetSettingsModel NewArrivalsFacet { get; set; } - } + + // Property name must equal settings class name. + public ForumSearchSettingsModel ForumSearchSettings { get; set; } + } public class CommonFacetSettingsModel : ModelBase, ILocalizedModel { @@ -96,18 +100,19 @@ public class CommonFacetSettingsLocalizedModel : ILocalizedModelLocal public string Alias { get; set; } } + public class SearchSettingValidator : SmartValidatorBase { - private const int MAX_INSTANT_SEARCH_ITEMS = 16; + public static int MaxInstantSearchItems => 16; public SearchSettingValidator(Localizer T, Func addRule) { if (addRule("InstantSearchNumberOfProducts")) { RuleFor(x => x.InstantSearchNumberOfProducts) - .Must(x => x >= 1 && x <= MAX_INSTANT_SEARCH_ITEMS) + .Must(x => x >= 1 && x <= MaxInstantSearchItems) .When(x => x.InstantSearchEnabled) - .WithMessage(T("Admin.Validation.ValueRange").Text.FormatInvariant(1, MAX_INSTANT_SEARCH_ITEMS)); + .WithMessage(T("Admin.Validation.ValueRange").Text.FormatInvariant(1, MaxInstantSearchItems)); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index 833404a497..3200cae08c 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -296,6 +296,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Search.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Search.cshtml index 1f265fa078..ab4a349074 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Search.cshtml @@ -22,17 +22,26 @@ @Html.ValidationSummary(false) - + @(Html.SmartStore().TabStrip().Name("searchsettings-edit").Style(TabsStyle.Material).Items(x => + { + x.Add().Text(T("Admin.Catalog").Text).Content(@TabCatalogSettings()).Selected(true); + x.Add().Text(T("Forum.Forum").Text).Content(@TabForumSettings()); + })) +} + +@helper TabCatalogSettings() +{ +
- + } + + + + - + - + - - - - -
- @Html.SmartLabelFor(model => model.SearchMode) + @Html.SmartLabelFor(x => x.SearchMode) - @Html.SettingEditorFor(model => model.SearchMode, Html.DropDownListFor(model => model.SearchMode, Model.AvailableSearchModes)) - @Html.ValidationMessageFor(model => model.SearchMode) + @Html.SettingEditorFor(x => x.SearchMode, Html.DropDownListFor(x => x.SearchMode, Model.AvailableSearchModes)) + @Html.ValidationMessageFor(x => x.SearchMode)
@@ -62,6 +71,15 @@
+ @Html.SmartLabelFor(x => x.DefaultSortOrder) + + @Html.SettingEditorFor(x => x.DefaultSortOrder, Html.DropDownListFor(x => x.DefaultSortOrder, Model.AvailableSortOrderModes)) + @Html.ValidationMessageFor(x => x.DefaultSortOrder) +
@@ -71,51 +89,42 @@
- @Html.SmartLabelFor(model => model.InstantSearchEnabled) + @Html.SmartLabelFor(x => x.InstantSearchEnabled) - @Html.SettingEditorFor(model => model.InstantSearchEnabled, Html.CheckBoxFor(model => model.InstantSearchEnabled, new { data_toggler_for = "#pnlInstantSearch" })) - @Html.ValidationMessageFor(model => model.InstantSearchEnabled) + @Html.SettingEditorFor(x => x.InstantSearchEnabled, Html.CheckBoxFor(x => x.InstantSearchEnabled, new { data_toggler_for = "#pnlInstantSearch" })) + @Html.ValidationMessageFor(x => x.InstantSearchEnabled)
- @Html.SmartLabelFor(model => model.ShowProductImagesInInstantSearch) + @Html.SmartLabelFor(x => x.ShowProductImagesInInstantSearch) - @Html.SettingEditorFor(model => model.ShowProductImagesInInstantSearch) - @Html.ValidationMessageFor(model => model.ShowProductImagesInInstantSearch) + @Html.SettingEditorFor(x => x.ShowProductImagesInInstantSearch) + @Html.ValidationMessageFor(x => x.ShowProductImagesInInstantSearch)
- @Html.SmartLabelFor(model => model.InstantSearchNumberOfProducts) + @Html.SmartLabelFor(x => x.InstantSearchNumberOfProducts) - @Html.SettingEditorFor(model => model.InstantSearchNumberOfProducts) - @Html.ValidationMessageFor(model => model.InstantSearchNumberOfProducts) + @Html.SettingEditorFor(x => x.InstantSearchNumberOfProducts) + @Html.ValidationMessageFor(x => x.InstantSearchNumberOfProducts)
- @Html.SmartLabelFor(model => model.InstantSearchTermMinLength) + @Html.SmartLabelFor(x => x.InstantSearchTermMinLength) - @Html.SettingEditorFor(model => model.InstantSearchTermMinLength) - @Html.ValidationMessageFor(model => model.InstantSearchTermMinLength) + @Html.SettingEditorFor(x => x.InstantSearchTermMinLength) + @Html.ValidationMessageFor(x => x.InstantSearchTermMinLength)
- @Html.SmartLabelFor(model => model.DefaultSortOrder) - - @Html.SettingEditorFor(model => model.DefaultSortOrder, Html.DropDownListFor(model => model.DefaultSortOrder, Model.AvailableSortOrderModes)) - @Html.ValidationMessageFor(model => model.DefaultSortOrder) -
@@ -123,34 +132,31 @@
- - @if (Model.IsMegaSearchInstalled) { }
- @Html.SmartLabelFor(model => model.FilterMinHitCount) + @Html.SmartLabelFor(x => x.FilterMinHitCount) - @Html.SettingEditorFor(model => model.FilterMinHitCount) - @Html.ValidationMessageFor(model => model.FilterMinHitCount) + @Html.SettingEditorFor(x => x.FilterMinHitCount) + @Html.ValidationMessageFor(x => x.FilterMinHitCount)
- @Html.SmartLabelFor(model => model.FilterMaxChoicesCount) + @Html.SmartLabelFor(x => x.FilterMaxChoicesCount) - @Html.SettingEditorFor(model => model.FilterMaxChoicesCount) - @Html.ValidationMessageFor(model => model.FilterMaxChoicesCount) + @Html.SettingEditorFor(x => x.FilterMaxChoicesCount) + @Html.ValidationMessageFor(x => x.FilterMaxChoicesCount)
- @Html.SmartStore().TabStrip().Name("common-facets-settings").Style(TabsStyle.Material).Position(TabsPosition.Top).Items(x => + @Html.SmartStore().TabStrip().Name("common-catalog-facets-settings").Style(TabsStyle.Material).Position(TabsPosition.Top).Items(x => { x.Add() .Text(T("Search.Facet.Category").Text) @@ -184,6 +190,131 @@
} +@helper TabForumSettings() +{ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @if (Model.IsMegaSearchInstalled) + { + + + + + } + + + + +
+ @Html.SmartLabelFor(x => x.ForumSearchSettings.SearchMode) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.SearchMode, Html.DropDownListFor(x => x.ForumSearchSettings.SearchMode, Model.ForumSearchSettings.AvailableSearchModes)) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.SearchMode) +
+
+ @T("Admin.Configuration.Settings.Search.WildcardSearchNote") +
+
+ @Html.SmartLabelFor(x => x.ForumSearchSettings.SearchFields) + + @Html.ListBoxFor(x => x.ForumSearchSettings.SearchFields, + new MultiSelectList(Model.ForumSearchSettings.AvailableSearchFields, "Value", "Text"), + new { multiple = "multiple" }) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.SearchFields) +
+ @Html.SmartLabelFor(x => x.ForumSearchSettings.TopicSortOrder) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.TopicSortOrder, Html.DropDownListFor(x => x.ForumSearchSettings.TopicSortOrder, Model.ForumSearchSettings.AvailableTopicSortOrders)) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.TopicSortOrder) +
+
+
@T("Search.InstantSearch")
+
+
+ @Html.SmartLabelFor(x => x.ForumSearchSettings.InstantSearchEnabled) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.InstantSearchEnabled, Html.CheckBoxFor(x => x.ForumSearchSettings.InstantSearchEnabled, new { data_toggler_for = "#pnlForumInstantSearch" })) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.InstantSearchEnabled) +
+ @Html.SmartLabelFor(x => x.ForumSearchSettings.InstantSearchNumberOfHits) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.InstantSearchNumberOfHits) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.InstantSearchNumberOfHits) +
+ @Html.SmartLabelFor(x => x.ForumSearchSettings.InstantSearchTermMinLength) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.InstantSearchTermMinLength) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.InstantSearchTermMinLength) +
+
+
@T("Search.ResultFiltering")
+
+
+ @Html.SmartLabelFor(x => x.ForumSearchSettings.FilterMinHitCount) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.FilterMinHitCount) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.FilterMinHitCount) +
+ @Html.SmartLabelFor(x => x.ForumSearchSettings.FilterMaxChoicesCount) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.FilterMaxChoicesCount) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.FilterMaxChoicesCount) +
+ +
+ @Html.SmartStore().TabStrip().Name("common-forum-facets-settings").Style(TabsStyle.Material).Position(TabsPosition.Top).Items(x => + { + x.Add() + .Text(T("Search.Facet.Forum").Text) + .Content(Html.Partial("Partials/CommonFacet", Model.ForumSearchSettings.ForumFacet, CreateFacetViewData("ForumSearchSettings.ForumFacet")).ToHtmlString()) + .Selected(true); + + x.Add() + .Text(T("Search.Facet.Customer").Text) + .Content(Html.Partial("Partials/CommonFacet", Model.ForumSearchSettings.CustomerFacet, CreateFacetViewData("ForumSearchSettings.CustomerFacet")).ToHtmlString()); + + x.Add() + .Text(T("Search.Facet.Date").Text) + .Content(Html.Partial("Partials/CommonFacet", Model.ForumSearchSettings.DateFacet, CreateFacetViewData("ForumSearchSettings.DateFacet")).ToHtmlString()); + }) +
+} + @functions { private ViewDataDictionary CreateFacetViewData(string fieldPrefix) @@ -198,9 +329,12 @@ } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 87054c878b..820ef0b51b 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -153,7 +153,8 @@ protected IEnumerable ForumGroupsForumsList() { var forumsList = new List(); var separator = "--"; - var forumGroups = _forumService.GetAllForumGroups(); + var store = Services.StoreContext.CurrentStore; + var forumGroups = _forumService.GetAllForumGroups(store.Id); foreach (var fg in forumGroups) { @@ -222,7 +223,8 @@ public ActionResult Index() return HttpNotFound(); } - var forumGroups = _forumService.GetAllForumGroups(); + var store = Services.StoreContext.CurrentStore; + var forumGroups = _forumService.GetAllForumGroups(store.Id); var model = new BoardsIndexModel { @@ -1474,6 +1476,7 @@ public ActionResult Search(string searchterms, bool? adv, string forumId, string } var pageSize = 10; + var store = Services.StoreContext.CurrentStore; var model = new SearchModel(); // Create the values for the "Limit results to previous" select list @@ -1533,7 +1536,7 @@ public ActionResult Search(string searchterms, bool? adv, string forumId, string }); var separator = "--"; - var forumGroups = _forumService.GetAllForumGroups(); + var forumGroups = _forumService.GetAllForumGroups(store.Id); foreach (var fg in forumGroups) { // Add the forum group with value as '-' so it can't be used as a target forum id. From 4af245a6b59fab39dbd8a17f4c27c1c1865560fd Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 23 Aug 2018 17:57:49 +0200 Subject: [PATCH 044/932] Fixes NullReferenceException in manufacturer list when there is no manufacturer --- changelog.md | 1 + .../SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index c0b71535ec..9475057678 100644 --- a/changelog.md +++ b/changelog.md @@ -44,6 +44,7 @@ * Server cannot modify cookies after HTTP headers have been sent. * Wrong base price on product and cart page when a special price is active. * In a multi-store, message templates may have loaded the wrong disclaimer and conditions-of-use text. +* NullReferenceException in manufacturer list when there is no manufacturer. ## SmartStore.NET 3.1.5 diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml index 2762edc7bd..cef901de1f 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml @@ -11,7 +11,7 @@ var numericList = Model.Where(x => Char.IsNumber(x.Name.Value[0])).OrderBy(x => x.Name); var alphabeticalList = Model.Except(numericList).OrderBy(x => x.Name); var currentLetter = String.Empty; - var areaLetter = alphabeticalList.FirstOrDefault().Name.Value.Substring(0, 1).ToUpper(); + var areaLetter = alphabeticalList.FirstOrDefault()?.Name?.Value.EmptyNull().Substring(0, 1).ToUpper(); var catalogSettings = EngineContext.Current.Resolve(); } @@ -20,7 +20,6 @@

@T("Manufacturers.List")

- @{ Html.RenderWidget("manufacturers_all_top"); } @if (catalogSettings.SortManufacturersAlphabetically) From d1efc8ed893d88474e520f1366e56b267880eca4 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 23 Aug 2018 19:02:48 +0200 Subject: [PATCH 045/932] Setting for unlimited downloads wasn't stored --- .../Administration/Controllers/ProductController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 007b026184..fadeb8b7b4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -272,7 +272,7 @@ protected void UpdateProductDownloads(Product product, ProductModel model) p.IsDownload = m.IsDownload; //p.DownloadId = m.DownloadId ?? 0; - //p.UnlimitedDownloads = m.UnlimitedDownloads; + p.UnlimitedDownloads = m.UnlimitedDownloads; p.MaxNumberOfDownloads = m.MaxNumberOfDownloads; p.DownloadExpirationDays = m.DownloadExpirationDays; p.DownloadActivationTypeId = m.DownloadActivationTypeId; From 45a45814704a8e49fb5f93d80dfd2a06bbc119bc Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 23 Aug 2018 19:08:48 +0200 Subject: [PATCH 046/932] Addendum to: Fixes NullReferenceException in manufacturer list when there is no manufacturer --- .../SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml index cef901de1f..c434b793d0 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml @@ -45,7 +45,7 @@ @{ // reset currentLetter = String.Empty; - areaLetter = alphabeticalList.FirstOrDefault().Name.Value.Substring(0, 1).ToUpper(); + areaLetter = alphabeticalList.FirstOrDefault()?.Name?.Value.Substring(0, 1).ToUpper(); }
From 525cbf7ae14f413244c0804ff5e4e66a1805f79c Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 24 Aug 2018 09:51:25 +0200 Subject: [PATCH 047/932] Allow zip files in file uploader --- .../SmartStore.Web/Administration/Content/filemanager/conf.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json index 3142fe6dd1..c6e88981d2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json @@ -25,7 +25,7 @@ "RENAMEFILE": "ProcessRequest?a=RENAMEFILE", "GENERATETHUMB": true, "DEFAULTVIEW": "list", - "FORBIDDEN_UPLOADS": "zip js jsp jsb mhtml mht xhtml xht php phtml php3 php4 php5 phps shtml jhtml pl sh py cgi exe scr dll msi vbs bat com pif cmd vxd cpl htpasswd htaccess aspx", + "FORBIDDEN_UPLOADS": "js jsp jsb mhtml mht xhtml xht php phtml php3 php4 php5 phps shtml jhtml pl sh py cgi exe scr dll msi vbs bat com pif cmd vxd cpl htpasswd htaccess aspx", "ALLOWED_UPLOADS": "", "FILEPERMISSIONS": "0644", "DIRPERMISSIONS": "0755", From 7de71e3ff27dfeddd4ac790324ca07d33269948b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 24 Aug 2018 14:55:03 +0200 Subject: [PATCH 048/932] Search: More on forum common facets --- .../Search/SearchDocumentType.cs | 2 + .../Search/Extensions/FacetUtility.cs | 99 +++++++++++++++---- .../Search/Forum/ForumSearchService.cs | 53 ++++++++-- .../Search/Forum/LinqForumSearchService.cs | 65 +++++------- 4 files changed, 157 insertions(+), 62 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs b/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs index 6455be0cdd..c2597f571d 100644 --- a/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs +++ b/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs @@ -10,6 +10,8 @@ public enum SearchDocumentType AttributeValue, Variant, VariantValue, + Customer, + Forum, ForumPost } } diff --git a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs index 7ce4268346..17c9d547c1 100644 --- a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs +++ b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs @@ -1,12 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core.Localization; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; namespace SmartStore.Services.Search.Extensions { + /// + /// Contains methods that are specifically required for facet processing. + /// public static class FacetUtility { private const double MAX_PRICE = 1000000000; @@ -34,6 +40,18 @@ public static class FacetUtility { 50000000, 5000000 } }; + private static string GetPublicName(string firstName, string lastName) + { + string result = firstName; + + if (!string.IsNullOrWhiteSpace(result) && !string.IsNullOrWhiteSpace(lastName)) + { + result = string.Concat(result, " ", lastName.First(), "."); + } + + return result; + } + public static double GetNextPrice(double price) { for (var i = 0; i <= _priceThresholds.GetUpperBound(0); ++i) @@ -122,32 +140,79 @@ public static IEnumerable GetRatings() } } - public static List GetForumDates(Localizer T) + public static List GetForumDates() { var count = 0; var utcNow = DateTime.UtcNow; var result = new List(); - var days = new Dictionary - { - { 1, "Forum.Search.LimitResultsToPrevious.1day" }, - { 7, "Forum.Search.LimitResultsToPrevious.7days" }, - { 14, "Forum.Search.LimitResultsToPrevious.2weeks" }, - { 30, "Forum.Search.LimitResultsToPrevious.1month" }, - { 92, "Forum.Search.LimitResultsToPrevious.3months" }, - { 183, "Forum.Search.LimitResultsToPrevious.6months" }, - { 365, "Forum.Search.LimitResultsToPrevious.1year" } - }; - - foreach (var day in days) + + foreach (var day in new int[] { 1, 7, 14, 30, 92, 183, 365 }) { - result.Add(new FacetValue(utcNow.AddDays(-day.Key), utcNow, IndexTypeCode.DateTime, true, true) + result.Add(new FacetValue(utcNow.AddDays(-day), null, IndexTypeCode.DateTime, true, false) { - DisplayOrder = ++count, - Label = T(day.Value) + DisplayOrder = ++count }); } return result; } + + public static string GetPublicName(Customer customer, bool userNamesEnabled) + { + if (customer == null) + { + return null; + } + + var name = userNamesEnabled ? customer.Username : null; + + if (string.IsNullOrWhiteSpace(name)) + { + name = GetPublicName(customer.FirstName, customer.LastName); + } + + if (customer.BillingAddress != null && string.IsNullOrWhiteSpace(name)) + { + name = GetPublicName(customer.BillingAddress.FirstName, customer.BillingAddress.LastName); + } + + return name; + } + + public static IQueryable GetCustomersByNumberOfPosts( + IRepository forumPostRepository, + IRepository storeMappingRepository, + int storeId) + { + var postQuery = forumPostRepository.TableUntracked + .Expand(x => x.Customer) + .Expand(x => x.Customer.BillingAddress); + + if (storeId > 0) + { + postQuery = + from p in postQuery + join sm in storeMappingRepository.TableUntracked on new { eid = p.ForumTopic.Forum.ForumGroupId, ename = "ForumGroup" } equals new { eid = sm.EntityId, ename = sm.EntityName } into gsm + from sm in gsm.DefaultIfEmpty() + where !p.ForumTopic.Forum.ForumGroup.LimitedToStores || sm.StoreId == storeId + select p; + } + + var groupQuery = + from p in postQuery + group p by p.CustomerId into grp + select new + { + Count = grp.Count(), + grp.FirstOrDefault().Customer // Cannot be null. + }; + + var query = groupQuery + .OrderByDescending(x => x.Count) + .Select(x => x.Customer) + .Where(x => x.CustomerRoles.FirstOrDefault(y => y.SystemName == SystemCustomerRoleNames.Guests) == null && !x.Deleted && x.Active && !x.IsSystemAccount); + + return query; + } } } diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs index 08410758ce..c02f8aa3ef 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs @@ -4,6 +4,7 @@ using System.Web.Mvc; using Autofac; using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; @@ -31,14 +32,18 @@ public ForumSearchService( _forumService = forumService; _logger = logger; _urlHelper = urlHelper; - } - /// - /// Bypasses the index provider and directly searches in the database - /// - /// Search query - /// Forum search result - protected virtual ForumSearchResult SearchDirect(ForumSearchQuery searchQuery) + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + /// + /// Bypasses the index provider and directly searches in the database + /// + /// Search query + /// Forum search result + protected virtual ForumSearchResult SearchDirect(ForumSearchQuery searchQuery) { // Fallback to linq search. var linqForumSearchService = _services.Container.ResolveNamed("linq"); @@ -55,6 +60,40 @@ protected virtual void ApplyFacetLabels(IDictionary facets) return; } + // Apply "date" labels. + if (facets.TryGetValue("createdon", out var group)) + { + var utcNow = DateTime.UtcNow; + foreach (var facet in group.Facets) + { + var dt = (DateTime)facet.Value.Value; + var days = (utcNow - dt).TotalDays; + switch (days) + { + case 1: + facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.1day"); + break; + case 7: + facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.7days"); + break; + case 14: + facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.2weeks"); + break; + case 30: + facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.1month"); + break; + case 92: + facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.3months"); + break; + case 183: + facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.6months"); + break; + case 365: + facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.1year"); + break; + } + } + } } public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = false) diff --git a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs index 30b2023ae7..11fe757ef1 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -4,7 +4,7 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; -using SmartStore.Core.Localization; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; using SmartStore.Services.Forums; @@ -16,22 +16,25 @@ namespace SmartStore.Services.Search public partial class LinqForumSearchService : SearchServiceBase, IForumSearchService { private readonly IRepository _forumPostRepository; + private readonly IRepository _storeMappingRepository; private readonly IForumService _forumService; private readonly ICommonServices _services; public LinqForumSearchService( IRepository forumPostRepository, + IRepository storeMappingRepository, IForumService forumService, ICommonServices services) { _forumPostRepository = forumPostRepository; + _storeMappingRepository = storeMappingRepository; _forumService = forumService; _services = services; - T = NullLocalizer.Instance; + QuerySettings = DbQuerySettings.Default; } - public Localizer T { get; set; } + public DbQuerySettings QuerySettings { get; set; } protected virtual IQueryable GetForumTopicQuery(ForumSearchQuery searchQuery, IQueryable baseQuery) { @@ -168,6 +171,7 @@ protected virtual IDictionary GetFacets(ForumSearchQuery sea var result = new Dictionary(); var storeId = searchQuery.StoreId ?? _services.StoreContext.CurrentStore.Id; var languageId = searchQuery.LanguageId ?? _services.WorkContext.WorkingLanguage.Id; + var userNamesEnabled = _services.Settings.LoadSetting().UsernamesEnabled; foreach (var key in searchQuery.FacetDescriptors.Keys) { @@ -175,16 +179,6 @@ protected virtual IDictionary GetFacets(ForumSearchQuery sea var facets = new List(); var kind = FacetGroup.GetKindByKey("Forum", key); - switch (kind) - { - case FacetGroupKind.NewArrivals: - if (totalHits == 0 && !descriptor.Values.Any(x => x.IsSelected)) - { - continue; - } - break; - } - if (kind == FacetGroupKind.Forum) { var enoughFacets = false; @@ -216,43 +210,38 @@ protected virtual IDictionary GetFacets(ForumSearchQuery sea } else if (kind == FacetGroupKind.Customer) { - var userNamesEnabled = _services.Settings.LoadSetting().UsernamesEnabled; - // Get customers with most posts. - var groupQuery = - from p in _forumPostRepository.TableUntracked.Expand(x => x.Customer) - group p by p.CustomerId into grp - select new - { - Count = grp.Count(), - grp.FirstOrDefault().Customer - }; + var customerQuery = FacetUtility.GetCustomersByNumberOfPosts( + _forumPostRepository, + _storeMappingRepository, + QuerySettings.IgnoreMultiStore ? 0 : storeId); // Limit the result. Do not allow to get all customers. - var customers = groupQuery - .Where(x => x.Customer != null) - .OrderByDescending(x => x.Count) - .Select(x => x.Customer) - .Take(descriptor.MaxChoicesCount > 0 ? descriptor.MaxChoicesCount : 20) - .ToList(); + var maxChoices = descriptor.MaxChoicesCount > 0 ? descriptor.MaxChoicesCount : 20; + var customers = customerQuery.Take(maxChoices * 3).ToList(); foreach (var customer in customers) { - // TODO: anonymize? - var name = userNamesEnabled ? customer.Username : customer.FirstName; - - facets.Add(new Facet(new FacetValue(customer.Id, IndexTypeCode.Int32) + var name = FacetUtility.GetPublicName(customer, userNamesEnabled); + if (name.HasValue()) { - IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(customer.Id)), - Label = name.NullEmpty() ?? customer.Id.ToString(), - DisplayOrder = 0 - })); + facets.Add(new Facet(new FacetValue(customer.Id, IndexTypeCode.Int32) + { + IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(customer.Id)), + Label = name, + DisplayOrder = 0 + })); + if (facets.Count >= maxChoices) + { + break; + } + } } } else if (kind == FacetGroupKind.Date) { var hasActivePredefinedFacet = false; - var dates = FacetUtility.GetForumDates(T); + var dates = FacetUtility.GetForumDates(); foreach (var date in dates) { From ac97288791d50ca1f7488e070d7b50635774d24d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 24 Aug 2018 15:59:27 +0200 Subject: [PATCH 049/932] Minor improvement in language list --- .../Controllers/LanguageController.cs | 1 + .../Partials/AvailableLanguages.cshtml | 27 +++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs index 534be8acef..277db80038 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs @@ -160,6 +160,7 @@ private void PrepareAvailableLanguageModel( Language language = null, LanguageDownloadState state = null) { + // Source Id (aka SetId), not entity Id! model.Id = resources.Id; model.PreviousSetId = resources.PreviousSetId; model.IsInstalled = language != null; diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Language/Partials/AvailableLanguages.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Language/Partials/AvailableLanguages.cshtml index ee028758b5..a8c7bb96e8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Language/Partials/AvailableLanguages.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Language/Partials/AvailableLanguages.cshtml @@ -66,14 +66,25 @@
@if (language.Published) - { - - - @T("Common.Download") - + { + if (language.IsInstalled) + { + + + @T("Common.Refresh") + + } + else + { + + + @T("Common.Download") + + }
} From b6083af8824294fedaff2cea0dcbcb5d2fa787e6 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 27 Aug 2018 11:50:06 +0200 Subject: [PATCH 050/932] Search: More on forum common facets --- .../Search/Facets/FacetDescriptor.cs | 28 --- .../CatalogSearchQueryAliasMapper.cs | 12 +- .../Modelling/CatalogSearchQueryFactory.cs | 90 ++------- .../CatalogSearchQueryModelBinder.cs | 10 +- .../Search/Extensions/FacetUtility.cs | 59 +++++- .../Search/Forum/ForumSearchQuery.cs | 68 ++++++- .../Search/Forum/ForumSearchService.cs | 21 +- .../Search/Forum/LinqForumSearchService.cs | 3 +- .../Modelling/ForumSearchQueryAliasMapper.cs | 71 +++++++ .../Modelling/ForumSearchQueryFactory.cs | 182 +++++++++++++++++- .../Modelling/ForumSearchQueryModelBinder.cs | 42 ++++ .../Modelling/IForumSearchQueryAliasMapper.cs | 20 ++ .../Search/SearchQueryFactoryBase.cs | 72 ++++++- .../SmartStore.Services.csproj | 3 + .../DependencyRegistrar.cs | 20 +- .../Controllers/SettingController.cs | 69 ++++--- 16 files changed, 597 insertions(+), 173 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryAliasMapper.cs create mode 100644 src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryModelBinder.cs create mode 100644 src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryAliasMapper.cs diff --git a/src/Libraries/SmartStore.Core/Search/Facets/FacetDescriptor.cs b/src/Libraries/SmartStore.Core/Search/Facets/FacetDescriptor.cs index bef397ea48..d0e8b5b8e7 100644 --- a/src/Libraries/SmartStore.Core/Search/Facets/FacetDescriptor.cs +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetDescriptor.cs @@ -29,34 +29,6 @@ public FacetDescriptor(string key) Key = key; } - /// - /// Gets the string resource key for a facet group kind - /// - /// Facet group kind - /// Resource key - public static string GetLabelResourceKey(FacetGroupKind kind) - { - switch (kind) - { - case FacetGroupKind.Category: - return "Search.Facet.Category"; - case FacetGroupKind.Brand: - return "Search.Facet.Manufacturer"; - case FacetGroupKind.Price: - return "Search.Facet.Price"; - case FacetGroupKind.Rating: - return "Search.Facet.Rating"; - case FacetGroupKind.DeliveryTime: - return "Search.Facet.DeliveryTime"; - case FacetGroupKind.Availability: - return "Search.Facet.Availability"; - case FacetGroupKind.NewArrivals: - return "Search.Facet.NewArrivals"; - default: - return null; - } - } - /// /// Gets the key / field name. /// diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs index 7003982f0d..67c3ad8337 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs @@ -10,10 +10,11 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Configuration; using SmartStore.Services.Localization; +using SmartStore.Services.Search.Extensions; namespace SmartStore.Services.Search.Modelling { - public class CatalogSearchQueryAliasMapper : ICatalogSearchQueryAliasMapper + public class CatalogSearchQueryAliasMapper : ICatalogSearchQueryAliasMapper { private const string ALL_ATTRIBUTE_ID_BY_ALIAS_KEY = "search.attribute.id.alias.mappings.all"; private const string ALL_ATTRIBUTE_ALIAS_BY_ID_KEY = "search.attribute.alias.id.mappings.all"; @@ -66,11 +67,6 @@ protected string CreateOptionKey(string prefix, int languageId, int optionId) return $"{prefix}.{languageId}.{optionId}"; } - protected string CreateSettingKey(FacetGroupKind kind, int languageId) - { - return $"FacetGroupKind-{kind.ToString()}-Alias-{languageId}"; - } - protected void CachedLocalizedAlias(string localeKeyGroup, Action caching) { _localizedPropertyRepository.TableUntracked @@ -464,7 +460,7 @@ protected virtual IDictionary GetCommonFacetAliasByGroupKindMapp { foreach (var groupKind in groupKinds) { - var key = CreateSettingKey(groupKind, language.Id); + var key = FacetUtility.GetFacetAliasSettingKey(groupKind, language.Id); var value = _settingService.GetSettingByKey(key); if (value.HasValue()) { @@ -486,7 +482,7 @@ public string GetCommonFacetAliasByGroupKind(FacetGroupKind kind, int languageId { var mappings = GetCommonFacetAliasByGroupKindMappings(); - return mappings.Get(CreateSettingKey(kind, languageId)); + return mappings.Get(FacetUtility.GetFacetAliasSettingKey(kind, languageId)); } #endregion diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs index 44788857bd..1ef4a2669e 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs @@ -11,6 +11,7 @@ using SmartStore.Services.Catalog; using SmartStore.Utilities; using SmartStore.Services.Security; +using SmartStore.Services.Search.Extensions; namespace SmartStore.Services.Search.Modelling { @@ -35,14 +36,12 @@ namespace SmartStore.Services.Search.Modelling public class CatalogSearchQueryFactory : SearchQueryFactoryBase, ICatalogSearchQueryFactory { - protected static readonly string[] _tokens = new string[] { "q", "i", "s", "o", "p", "c", "m", "r", "a", "n", "d", "v" }; protected static readonly string[] _instantSearchFields = new string[] { "manufacturer", "sku", "gtin", "mpn", "attrname", "variantname" }; protected readonly CatalogSettings _catalogSettings; protected readonly SearchSettings _searchSettings; protected readonly ICommonServices _services; protected readonly ICatalogSearchQueryAliasMapper _catalogSearchQueryAliasMapper; - private Multimap _aliases; public CatalogSearchQueryFactory( HttpContextBase httpContext, @@ -64,7 +63,9 @@ public CatalogSearchQueryFactory( public CatalogSearchQuery Current { get; private set; } - public CatalogSearchQuery CreateFromQuery() + protected override string[] Tokens => new string[] { "q", "i", "s", "o", "p", "c", "m", "r", "a", "n", "d", "v" }; + + public CatalogSearchQuery CreateFromQuery() { var ctx = _httpContext; @@ -88,7 +89,9 @@ public CatalogSearchQuery CreateFromQuery() foreach (var fieldName in _instantSearchFields) { if (_searchSettings.SearchFields.Contains(fieldName)) + { fields.Add(fieldName); + } } } else @@ -103,19 +106,19 @@ public CatalogSearchQuery CreateFromQuery() .VisibleIndividuallyOnly(true) .BuildFacetMap(!isInstantSearch); - // Visibility + // Visibility. query.VisibleOnly(!QuerySettings.IgnoreAcl ? _services.WorkContext.CurrentCustomer : null); - // Store + // Store. if (!QuerySettings.IgnoreMultiStore) { query.HasStoreId(_services.StoreContext.CurrentStore.Id); } - // Availability + // Availability. ConvertAvailability(query, routeData, origin); - // Instant-Search never uses these filter parameters + // Instant-Search never uses these filter parameters. if (!isInstantSearch) { ConvertPagingSorting(query, routeData, origin); @@ -143,14 +146,7 @@ protected virtual void ConvertPagingSorting(CatalogSearchQuery query, RouteData var orderBy = GetValueFor("o"); if (orderBy == null || orderBy == ProductSortingEnum.Initial) { - if(origin.Equals("Search/Search")) - { - orderBy = _searchSettings.DefaultSortOrder; - } - else - { - orderBy = _catalogSettings.DefaultSortOrder; - } + orderBy = origin.IsCaseInsensitiveEqual("Search/Search") ? _searchSettings.DefaultSortOrder : _catalogSettings.DefaultSortOrder; } query.CustomData["CurrentSortOrder"] = orderBy.Value; @@ -337,7 +333,7 @@ private void AddFacet( } var descriptor = new FacetDescriptor(fieldName); - descriptor.Label = _services.Localization.GetResource(FacetDescriptor.GetLabelResourceKey(kind) ?? kind.ToString()); + descriptor.Label = _services.Localization.GetResource(FacetUtility.GetLabelResourceKey(kind) ?? kind.ToString()); descriptor.IsMultiSelect = isMultiSelect; descriptor.DisplayOrder = displayOrder; descriptor.OrderBy = sorting; @@ -480,27 +476,6 @@ protected virtual void ConvertPrice(CatalogSearchQuery query, RouteData routeDat }); } - protected virtual bool TryParseRange(string query, out double? min, out double? max) - { - min = max = null; - - if (query.IsEmpty()) - { - return false; - } - - // Format: from~to || from[~] || ~to - var arr = query.Split('~').Select(x => x.Trim()).Take(2).ToArray(); - - CommonHelper.TryConvert(arr[0], out min); - if (arr.Length == 2) - { - CommonHelper.TryConvert(arr[1], out max); - } - - return min != null || max != null; - } - protected virtual void ConvertRating(CatalogSearchQuery query, RouteData routeData, string origin) { double? fromRate; @@ -622,46 +597,5 @@ protected virtual bool GetValueFor(CatalogSearchQuery query, string key, Face { return GetValueFor(_catalogSearchQueryAliasMapper.GetCommonFacetAliasByGroupKind(kind, query.LanguageId ?? 0) ?? key, out value); } - - protected virtual Multimap Aliases - { - get - { - if (_aliases == null) - { - _aliases = new Multimap(); - - if (_httpContext.Request != null) - { - var form = _httpContext.Request.Form; - var query = _httpContext.Request.QueryString; - - if (form != null) - { - foreach (var key in form.AllKeys) - { - if (key.HasValue() && !_tokens.Contains(key)) - { - _aliases.AddRange(key, form[key].SplitSafe(",")); - } - } - } - - if (query != null) - { - foreach (var key in query.AllKeys) - { - if (key.HasValue() && !_tokens.Contains(key)) - { - _aliases.AddRange(key, query[key].SplitSafe(",")); - } - } - } - } - } - - return _aliases; - } - } } } diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryModelBinder.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryModelBinder.cs index e92cf89e61..dce6a49f57 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryModelBinder.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryModelBinder.cs @@ -1,10 +1,9 @@ -using System; -using System.Web.Mvc; +using System.Web.Mvc; using Autofac.Integration.Mvc; namespace SmartStore.Services.Search.Modelling { - [ModelBinderType(typeof(CatalogSearchQuery))] + [ModelBinderType(typeof(CatalogSearchQuery))] public class CatalogSearchQueryModelBinder : IModelBinder { private readonly ICatalogSearchQueryFactory _factory; @@ -18,7 +17,7 @@ public object BindModel(ControllerContext controllerContext, ModelBindingContext { if (_factory.Current != null) { - // Dont' bind again for current request + // Don't bind again for current request. return _factory.Current; } @@ -31,16 +30,13 @@ public object BindModel(ControllerContext controllerContext, ModelBindingContext } var modelType = bindingContext.ModelType; - if (modelType != typeof(CatalogSearchQuery)) { return new CatalogSearchQuery(); } var query = _factory.CreateFromQuery(); - return query; - } } } diff --git a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs index 17c9d547c1..43eed05af3 100644 --- a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs +++ b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs @@ -170,6 +170,14 @@ public static string GetPublicName(Customer customer, bool userNamesEnabled) { name = GetPublicName(customer.FirstName, customer.LastName); } + else + { + var atSymbol = name.IndexOf('@'); + if (atSymbol > 1) + { + name = name.Substring(0, atSymbol); + } + } if (customer.BillingAddress != null && string.IsNullOrWhiteSpace(name)) { @@ -182,7 +190,8 @@ public static string GetPublicName(Customer customer, bool userNamesEnabled) public static IQueryable GetCustomersByNumberOfPosts( IRepository forumPostRepository, IRepository storeMappingRepository, - int storeId) + int storeId, + int minHitCount = 1) { var postQuery = forumPostRepository.TableUntracked .Expand(x => x.Customer) @@ -207,6 +216,10 @@ group p by p.CustomerId into grp grp.FirstOrDefault().Customer // Cannot be null. }; + groupQuery = minHitCount > 1 + ? groupQuery.Where(x => x.Count >= minHitCount) + : groupQuery; + var query = groupQuery .OrderByDescending(x => x.Count) .Select(x => x.Customer) @@ -214,5 +227,49 @@ group p by p.CustomerId into grp return query; } + + /// + /// Gets the string resource key for a facet group kind + /// + /// Facet group kind + /// Resource key + public static string GetLabelResourceKey(FacetGroupKind kind) + { + switch (kind) + { + case FacetGroupKind.Category: + return "Search.Facet.Category"; + case FacetGroupKind.Brand: + return "Search.Facet.Manufacturer"; + case FacetGroupKind.Price: + return "Search.Facet.Price"; + case FacetGroupKind.Rating: + return "Search.Facet.Rating"; + case FacetGroupKind.DeliveryTime: + return "Search.Facet.DeliveryTime"; + case FacetGroupKind.Availability: + return "Search.Facet.Availability"; + case FacetGroupKind.NewArrivals: + return "Search.Facet.NewArrivals"; + case FacetGroupKind.Forum: + return "Search.Facet.Forum"; + case FacetGroupKind.Customer: + return "Search.Facet.Customer"; + case FacetGroupKind.Date: + return "Search.Facet.Date"; + default: + return null; + } + } + + public static string GetFacetAliasSettingKey(FacetGroupKind kind, int languageId, string scope = null) + { + if (scope.HasValue()) + { + return $"FacetGroupKind-{kind.ToString()}-Alias-{languageId}-{scope}"; + } + + return $"FacetGroupKind-{kind.ToString()}-Alias-{languageId}"; + } } } diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs index 8ba89db311..a0d992ac99 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using SmartStore.Core.Domain.Forums; using SmartStore.Core.Search; namespace SmartStore.Services.Search @@ -6,7 +8,7 @@ namespace SmartStore.Services.Search public partial class ForumSearchQuery : SearchQuery, ICloneable { /// - /// Initializes a new instance of the class without a search term being set + /// Initializes a new instance of the class without a search term being set. /// public ForumSearchQuery() : base((string[])null, null) @@ -32,5 +34,67 @@ object ICloneable.Clone() { return this.MemberwiseClone(); } - } + + #region Fluent builder + + public ForumSearchQuery SortBy(ForumTopicSorting sort) + { + switch (sort) + { + case ForumTopicSorting.SubjectAsc: + case ForumTopicSorting.SubjectDesc: + return SortBy(SearchSort.ByStringField("subject", sort == ForumTopicSorting.SubjectDesc)); + + case ForumTopicSorting.CreatedOnAsc: + case ForumTopicSorting.CreatedOnDesc: + return SortBy(SearchSort.ByDateTimeField("createdon", sort == ForumTopicSorting.CreatedOnDesc)); + + case ForumTopicSorting.PostsAsc: + case ForumTopicSorting.PostsDesc: + return SortBy(SearchSort.ByIntField("numposts", sort == ForumTopicSorting.PostsDesc)); + + case ForumTopicSorting.ViewsAsc: + case ForumTopicSorting.ViewsDesc: + return SortBy(SearchSort.ByIntField("views", sort == ForumTopicSorting.ViewsDesc)); + + case ForumTopicSorting.Relevance: + return SortBy(SearchSort.ByRelevance()); + + default: + return this; + } + } + + public ForumSearchQuery WithForumIds(params int[] ids) + { + if (ids.Length == 0) + { + return this; + } + + return WithFilter(SearchFilter.Combined(ids.Select(x => SearchFilter.ByField("forumid", x).ExactMatch().NotAnalyzed()).ToArray())); + } + + public ForumSearchQuery WithCustomerIds(params int[] ids) + { + if (ids.Length == 0) + { + return this; + } + + return WithFilter(SearchFilter.Combined(ids.Select(x => SearchFilter.ByField("customerid", x).ExactMatch().NotAnalyzed()).ToArray())); + } + + public ForumSearchQuery CreatedBetween(DateTime? fromUtc, DateTime? toUtc) + { + if (fromUtc == null && toUtc == null) + { + return this; + } + + return WithFilter(SearchFilter.ByRange("createdon", fromUtc, toUtc, fromUtc.HasValue, toUtc.HasValue).Mandatory().ExactMatch().NotAnalyzed()); + } + + #endregion + } } diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs index c02f8aa3ef..a1c8f7a60e 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs @@ -61,10 +61,10 @@ protected virtual void ApplyFacetLabels(IDictionary facets) } // Apply "date" labels. - if (facets.TryGetValue("createdon", out var group)) + if (facets.TryGetValue("createdon", out var grp)) { var utcNow = DateTime.UtcNow; - foreach (var facet in group.Facets) + foreach (var facet in grp.Facets) { var dt = (DateTime)facet.Value.Value; var days = (utcNow - dt).TotalDays; @@ -94,6 +94,23 @@ protected virtual void ApplyFacetLabels(IDictionary facets) } } } + + // Ensure that there are no duplicate customer labels. + if (facets.TryGetValue("customerid", out grp)) + { + var groupedByLabel = + from x in grp.Facets + group x by x.Value.Label into g + select g; + + foreach (var item in groupedByLabel.Where(x => x.Count() > 1)) + { + foreach (var facet in item) + { + facet.Value.Label = $"{facet.Value.Value} ({facet.Value.Value.ToString()})"; + } + } + } } public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = false) diff --git a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs index 11fe757ef1..c84fbfccbf 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -214,7 +214,8 @@ protected virtual IDictionary GetFacets(ForumSearchQuery sea var customerQuery = FacetUtility.GetCustomersByNumberOfPosts( _forumPostRepository, _storeMappingRepository, - QuerySettings.IgnoreMultiStore ? 0 : storeId); + QuerySettings.IgnoreMultiStore ? 0 : storeId, + descriptor.MinHitCount); // Limit the result. Do not allow to get all customers. var maxChoices = descriptor.MaxChoicesCount > 0 ? descriptor.MaxChoicesCount : 20; diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryAliasMapper.cs new file mode 100644 index 0000000000..493bdcfbbb --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryAliasMapper.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core.Caching; +using SmartStore.Core.Search.Facets; +using SmartStore.Services.Configuration; +using SmartStore.Services.Localization; +using SmartStore.Services.Search.Extensions; + +namespace SmartStore.Services.Search.Modelling +{ + public class ForumSearchQueryAliasMapper : IForumSearchQueryAliasMapper + { + private const string ALL_FORUM_COMMONFACET_ALIAS_BY_KIND_KEY = "search.forum.commonfacet.alias.kind.mappings.all"; + + private readonly ICacheManager _cacheManager; + private readonly ISettingService _settingService; + private readonly ILanguageService _languageService; + + public ForumSearchQueryAliasMapper( + ICacheManager cacheManager, + ISettingService settingService, + ILanguageService languageService) + { + _cacheManager = cacheManager; + _settingService = settingService; + _languageService = languageService; + } + + protected virtual IDictionary GetCommonFacetAliasByGroupKindMappings() + { + return _cacheManager.Get(ALL_FORUM_COMMONFACET_ALIAS_BY_KIND_KEY, () => + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var groupKinds = new FacetGroupKind[] + { + FacetGroupKind.Forum, + FacetGroupKind.Customer, + FacetGroupKind.Date + }; + + foreach (var language in _languageService.GetAllLanguages()) + { + foreach (var groupKind in groupKinds) + { + var key = FacetUtility.GetFacetAliasSettingKey(groupKind, language.Id); + var value = _settingService.GetSettingByKey(key); + if (value.HasValue()) + { + result.Add(key, value); + } + } + } + + return result; + }); + } + + public void ClearCommonFacetCache() + { + _cacheManager.Remove(ALL_FORUM_COMMONFACET_ALIAS_BY_KIND_KEY); + } + + public string GetCommonFacetAliasByGroupKind(FacetGroupKind kind, int languageId) + { + var mappings = GetCommonFacetAliasByGroupKindMappings(); + + return mappings.Get(FacetUtility.GetFacetAliasSettingKey(kind, languageId)); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs index c77b07006d..1576b1c100 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs @@ -1,7 +1,13 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Web; using System.Web.Routing; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Forums; using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; +using SmartStore.Services.Search.Extensions; namespace SmartStore.Services.Search.Modelling { @@ -14,26 +20,38 @@ namespace SmartStore.Services.Search.Modelling o - Order by f - Forum c - Customer - p - Last post + d - Date */ public class ForumSearchQueryFactory : SearchQueryFactoryBase, IForumSearchQueryFactory { protected readonly ICommonServices _services; + protected readonly IForumSearchQueryAliasMapper _forumSearchQueryAliasMapper; protected readonly ForumSearchSettings _searchSettings; + protected readonly ForumSettings _forumSettings; public ForumSearchQueryFactory( ICommonServices services, HttpContextBase httpContext, - ForumSearchSettings searchSettings) + IForumSearchQueryAliasMapper forumSearchQueryAliasMapper, + ForumSearchSettings searchSettings, + ForumSettings forumSettings) : base(httpContext) { _services = services; + _forumSearchQueryAliasMapper = forumSearchQueryAliasMapper; _searchSettings = searchSettings; + _forumSettings = forumSettings; + + QuerySettings = DbQuerySettings.Default; } + public DbQuerySettings QuerySettings { get; set; } + public ForumSearchQuery Current { get; private set; } + protected override string[] Tokens => new string[] { "q", "i", "s", "o", "f", "c", "d" }; + public ForumSearchQuery CreateFromQuery() { var ctx = _httpContext; @@ -57,9 +75,19 @@ public ForumSearchQuery CreateFromQuery() .WithLanguage(_services.WorkContext.WorkingLanguage) .WithCurrency(_services.WorkContext.WorkingCurrency); + // Store. + if (!QuerySettings.IgnoreMultiStore) + { + query.HasStoreId(_services.StoreContext.CurrentStore.Id); + } + // Instant-Search never uses these filter parameters. if (!isInstantSearch) { + ConvertPagingSorting(query, routeData, origin); + ConvertForum(query, routeData, origin); + ConvertCustomer(query, routeData, origin); + ConvertDate(query, routeData, origin); } OnConverted(query, routeData, origin); @@ -68,8 +96,156 @@ public ForumSearchQuery CreateFromQuery() return query; } + protected virtual void ConvertPagingSorting(ForumSearchQuery query, RouteData routeData, string origin) + { + var index = Math.Max(1, GetValueFor("i") ?? 1); + var size = _forumSettings.SearchResultsPageSize; + var orderBy = GetValueFor("o"); + + query.Slice((index - 1) * size, size); + query.SortBy(orderBy.Value); + query.CustomData["CurrentSortOrder"] = orderBy.Value; + } + + private void AddFacet( + ForumSearchQuery query, + FacetGroupKind kind, + bool isMultiSelect, + FacetSorting sorting, + Action addValues) + { + string fieldName; + var displayOrder = 0; + + switch (kind) + { + case FacetGroupKind.Forum: + fieldName = "forumid"; + displayOrder = _searchSettings.ForumDisplayOrder; + break; + case FacetGroupKind.Customer: + fieldName = "customerid"; + displayOrder = _searchSettings.CustomerDisplayOrder; + break; + case FacetGroupKind.Date: + fieldName = "createdon"; + displayOrder = _searchSettings.DateDisplayOrder; + break; + default: + throw new SmartException($"Unknown field name for facet group '{kind.ToString()}'"); + } + + var descriptor = new FacetDescriptor(fieldName); + descriptor.Label = _services.Localization.GetResource(FacetUtility.GetLabelResourceKey(kind) ?? kind.ToString()); + descriptor.IsMultiSelect = isMultiSelect; + descriptor.DisplayOrder = displayOrder; + descriptor.OrderBy = sorting; + descriptor.MinHitCount = _searchSettings.FilterMinHitCount; + descriptor.MaxChoicesCount = _searchSettings.FilterMaxChoicesCount; + + addValues(descriptor); + query.WithFacet(descriptor); + } + + protected virtual void ConvertForum(ForumSearchQuery query, RouteData routeData, string origin) + { + if (origin == "Forum/Forum") + { + // We don't need forum facetting in forum pages. + return; + } + + List ids; + + if (GetValueFor(query, "f", FacetGroupKind.Forum, out ids) && ids != null && ids.Any()) + { + query.WithForumIds(ids.ToArray()); + } + + AddFacet(query, FacetGroupKind.Forum, true, FacetSorting.HitsDesc, descriptor => + { + if (ids != null) + { + foreach (var id in ids) + { + descriptor.AddValue(new FacetValue(id, IndexTypeCode.Int32) + { + IsSelected = true + }); + } + } + }); + } + + protected virtual void ConvertCustomer(ForumSearchQuery query, RouteData routeData, string origin) + { + List ids; + + if (GetValueFor(query, "c", FacetGroupKind.Customer, out ids) && ids != null && ids.Any()) + { + query.WithCustomerIds(ids.ToArray()); + } + + AddFacet(query, FacetGroupKind.Customer, true, FacetSorting.HitsDesc, descriptor => + { + if (ids != null) + { + foreach (var id in ids) + { + descriptor.AddValue(new FacetValue(id, IndexTypeCode.Int32) + { + IsSelected = true + }); + } + } + }); + } + + protected virtual void ConvertDate(ForumSearchQuery query, RouteData routeData, string origin) + { + string date; + DateTime? fromUtc = null; + DateTime? toUtc = null; + + if (GetValueFor(query, "d", FacetGroupKind.Date, out date) && TryParseRange(date, out fromUtc, out toUtc)) + { + if (fromUtc.HasValue && toUtc.HasValue && fromUtc > toUtc) + { + var tmp = fromUtc; + fromUtc = toUtc; + toUtc = tmp; + } + + if (fromUtc.HasValue || toUtc.HasValue) + { + query.CreatedBetween(fromUtc, toUtc); + } + } + + AddFacet(query, FacetGroupKind.Date, false, FacetSorting.DisplayOrder, descriptor => + { + if (fromUtc.HasValue || toUtc.HasValue) + { + descriptor.AddValue(new FacetValue( + fromUtc, + toUtc, + IndexTypeCode.DateTime, + fromUtc.HasValue, + toUtc.HasValue) + { + IsSelected = true + }); + } + }); + } + protected virtual void OnConverted(ForumSearchQuery query, RouteData routeData, string origin) { } + + protected virtual bool GetValueFor(ForumSearchQuery query, string key, FacetGroupKind kind, out T value) + { + return GetValueFor(_forumSearchQueryAliasMapper.GetCommonFacetAliasByGroupKind(kind, query.LanguageId ?? 0) ?? key, out value); + } } } diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryModelBinder.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryModelBinder.cs new file mode 100644 index 0000000000..ec47720bb5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryModelBinder.cs @@ -0,0 +1,42 @@ +using System.Web.Mvc; +using Autofac.Integration.Mvc; + +namespace SmartStore.Services.Search.Modelling +{ + [ModelBinderType(typeof(ForumSearchQuery))] + public class ForumSearchQueryModelBinder : IModelBinder + { + private readonly IForumSearchQueryFactory _factory; + + public ForumSearchQueryModelBinder(IForumSearchQueryFactory factory) + { + _factory = factory; + } + + public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) + { + if (_factory.Current != null) + { + // Don't bind again for current request. + return _factory.Current; + } + + if (controllerContext.IsChildAction) + { + // Never attempt to bind in child actions. We require the binding to happen + // in a parent action. If the child action is part of a request with an already bound + // 'ForumSearchQuery', good for you :-) You'll get an instance, but null otherwise. + return _factory.Current; + } + + var modelType = bindingContext.ModelType; + if (modelType != typeof(ForumSearchQuery)) + { + return new ForumSearchQuery(); + } + + var query = _factory.CreateFromQuery(); + return query; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryAliasMapper.cs new file mode 100644 index 0000000000..70b8785535 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryAliasMapper.cs @@ -0,0 +1,20 @@ +using SmartStore.Core.Search.Facets; + +namespace SmartStore.Services.Search.Modelling +{ + public interface IForumSearchQueryAliasMapper + { + /// + /// Clears all cached common facet mappings + /// + void ClearCommonFacetCache(); + + /// + /// Get the common facet alias by facet group kind + /// + /// Facet group kind + /// Language identifier + /// Common facet alias + string GetCommonFacetAliasByGroupKind(FacetGroupKind kind, int languageId); + } +} diff --git a/src/Libraries/SmartStore.Services/Search/SearchQueryFactoryBase.cs b/src/Libraries/SmartStore.Services/Search/SearchQueryFactoryBase.cs index b6dd78ea4e..998503180d 100644 --- a/src/Libraries/SmartStore.Services/Search/SearchQueryFactoryBase.cs +++ b/src/Libraries/SmartStore.Services/Search/SearchQueryFactoryBase.cs @@ -1,4 +1,7 @@ -using System.Web; +using System.Linq; +using System.Web; +using SmartStore.Collections; +using SmartStore.Utilities; namespace SmartStore.Services.Search { @@ -6,11 +9,57 @@ public abstract partial class SearchQueryFactoryBase { protected readonly HttpContextBase _httpContext; + private Multimap _aliases; + protected SearchQueryFactoryBase(HttpContextBase httpContext) { _httpContext = httpContext; } + protected abstract string[] Tokens { get; } + + protected virtual Multimap Aliases + { + get + { + if (_aliases == null) + { + _aliases = new Multimap(); + + if (_httpContext.Request != null) + { + var tokens = Tokens; + var form = _httpContext.Request.Form; + var query = _httpContext.Request.QueryString; + + if (form != null) + { + foreach (var key in form.AllKeys) + { + if (key.HasValue() && !tokens.Contains(key)) + { + _aliases.AddRange(key, form[key].SplitSafe(",")); + } + } + } + + if (query != null) + { + foreach (var key in query.AllKeys) + { + if (key.HasValue() && !tokens.Contains(key)) + { + _aliases.AddRange(key, query[key].SplitSafe(",")); + } + } + } + } + } + + return _aliases; + } + } + protected virtual T GetValueFor(string key) { T value; @@ -30,5 +79,26 @@ protected virtual bool GetValueFor(string key, out T value) value = default(T); return false; } + + protected virtual bool TryParseRange(string query, out T? min, out T? max) where T : struct + { + min = max = null; + + if (query.IsEmpty()) + { + return false; + } + + // Format: from~to || from[~] || ~to + var arr = query.Split('~').Select(x => x.Trim()).Take(2).ToArray(); + + CommonHelper.TryConvert(arr[0], out min); + if (arr.Length == 2) + { + CommonHelper.TryConvert(arr[1], out max); + } + + return min != null || max != null; + } } } diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index d824d6533c..54a0760a18 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -556,7 +556,10 @@ + + + diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 173587a4e8..01fee87d83 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -573,17 +573,23 @@ public class SearchModule : Module { protected override void Load(ContainerBuilder builder) { + // General. builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + + // Catalog. + builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().Named("linq").InstancePerRequest(); - builder.RegisterType().Named("linq").InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); - } + + // Forum. + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().Named("linq").InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + } } public class EventModule : Module diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index 65475453c5..0f82f9db9a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -32,6 +32,7 @@ using SmartStore.Services.Media; using SmartStore.Services.Media.Storage; using SmartStore.Services.Orders; +using SmartStore.Services.Search.Extensions; using SmartStore.Services.Search.Modelling; using SmartStore.Services.Security; using SmartStore.Services.Tax; @@ -77,6 +78,7 @@ public partial class SettingController : AdminControllerBase private readonly IPluginFinder _pluginFinder; private readonly Lazy _mediaMover; private readonly Lazy _catalogSearchQueryAliasMapper; + private readonly Lazy _forumSearchQueryAliasMapper; private readonly Lazy _siteMapService; private StoreDependingSettingHelper _storeDependingSettings; @@ -106,6 +108,7 @@ public SettingController( IPluginFinder pluginFinder, Lazy mediaMover, Lazy catalogSearchQueryAliasMapper, + Lazy forumSearchQueryAliasMapper, Lazy siteMapService) { _countryService = countryService; @@ -128,6 +131,7 @@ public SettingController( _pluginFinder = pluginFinder; _mediaMover = mediaMover; _catalogSearchQueryAliasMapper = catalogSearchQueryAliasMapper; + _forumSearchQueryAliasMapper = forumSearchQueryAliasMapper; _siteMapService = siteMapService; } @@ -154,21 +158,11 @@ private SelectListItem ResToSelectListItem(string resourceKey) return new SelectListItem { Text = value, Value = value }; } - private string CreateCommonFacetSettingKey(FacetGroupKind kind, int languageId, string scope = null) - { - if (scope.HasValue()) - { - return $"FacetGroupKind-{kind.ToString()}-Alias-{languageId}-{scope}"; - } - - return $"FacetGroupKind-{kind.ToString()}-Alias-{languageId}"; - } - private void UpdateLocalizedFacetSetting(CommonFacetSettingsModel model, FacetGroupKind kind, ref bool clearCache, string scope = null) { foreach (var localized in model.Locales) { - var key = CreateCommonFacetSettingKey(kind, localized.LanguageId, scope); + var key = FacetUtility.GetFacetAliasSettingKey(kind, localized.LanguageId, scope); var existingAlias = _services.Settings.GetSettingByKey(key); if (existingAlias.IsCaseInsensitiveEqual(localized.Alias)) @@ -1479,53 +1473,53 @@ public ActionResult Search() model.CategoryFacet.Locales.Add(new CommonFacetSettingsLocalizedModel { LanguageId = language.Id, - Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.Category, language.Id)) + Alias = _services.Settings.GetSettingByKey(FacetUtility.GetFacetAliasSettingKey(FacetGroupKind.Category, language.Id)) }); model.BrandFacet.Locales.Add(new CommonFacetSettingsLocalizedModel { LanguageId = language.Id, - Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.Brand, language.Id)) + Alias = _services.Settings.GetSettingByKey(FacetUtility.GetFacetAliasSettingKey(FacetGroupKind.Brand, language.Id)) }); model.PriceFacet.Locales.Add(new CommonFacetSettingsLocalizedModel { LanguageId = language.Id, - Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.Price, language.Id)) + Alias = _services.Settings.GetSettingByKey(FacetUtility.GetFacetAliasSettingKey(FacetGroupKind.Price, language.Id)) }); model.RatingFacet.Locales.Add(new CommonFacetSettingsLocalizedModel { LanguageId = language.Id, - Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.Rating, language.Id)) + Alias = _services.Settings.GetSettingByKey(FacetUtility.GetFacetAliasSettingKey(FacetGroupKind.Rating, language.Id)) }); model.DeliveryTimeFacet.Locales.Add(new CommonFacetSettingsLocalizedModel { LanguageId = language.Id, - Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.DeliveryTime, language.Id)) + Alias = _services.Settings.GetSettingByKey(FacetUtility.GetFacetAliasSettingKey(FacetGroupKind.DeliveryTime, language.Id)) }); model.AvailabilityFacet.Locales.Add(new CommonFacetSettingsLocalizedModel { LanguageId = language.Id, - Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.Availability, language.Id)) + Alias = _services.Settings.GetSettingByKey(FacetUtility.GetFacetAliasSettingKey(FacetGroupKind.Availability, language.Id)) }); model.NewArrivalsFacet.Locales.Add(new CommonFacetSettingsLocalizedModel { LanguageId = language.Id, - Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.NewArrivals, language.Id)) + Alias = _services.Settings.GetSettingByKey(FacetUtility.GetFacetAliasSettingKey(FacetGroupKind.NewArrivals, language.Id)) }); model.ForumSearchSettings.ForumFacet.Locales.Add(new CommonFacetSettingsLocalizedModel { LanguageId = language.Id, - Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.Forum, language.Id, "Forum")) + Alias = _services.Settings.GetSettingByKey(FacetUtility.GetFacetAliasSettingKey(FacetGroupKind.Forum, language.Id, "Forum")) }); model.ForumSearchSettings.CustomerFacet.Locales.Add(new CommonFacetSettingsLocalizedModel { LanguageId = language.Id, - Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.Customer, language.Id, "Forum")) + Alias = _services.Settings.GetSettingByKey(FacetUtility.GetFacetAliasSettingKey(FacetGroupKind.Customer, language.Id, "Forum")) }); model.ForumSearchSettings.DateFacet.Locales.Add(new CommonFacetSettingsLocalizedModel { LanguageId = language.Id, - Alias = _services.Settings.GetSettingByKey(CreateCommonFacetSettingKey(FacetGroupKind.Date, language.Id, "Forum")) + Alias = _services.Settings.GetSettingByKey(FacetUtility.GetFacetAliasSettingKey(FacetGroupKind.Date, language.Id, "Forum")) }); } @@ -1617,8 +1611,9 @@ public ActionResult Search(SearchSettingsModel model, FormCollection form) StoreDependingSettings.UpdateSettings(fsettings, form, storeScope, Services.Settings); } - var clearFacetCache = false; - using (Services.Settings.BeginScope()) + var clearCatalogFacetCache = false; + var clearForumFacetCache = false; + using (Services.Settings.BeginScope()) { _services.Settings.SaveSetting(settings, x => x.SearchFields, 0, false); _services.Settings.SaveSetting(fsettings, x => x.SearchFields, 0, false); @@ -1640,23 +1635,27 @@ public ActionResult Search(SearchSettingsModel model, FormCollection form) StoreDependingSettings.UpdateSetting("AvailabilityFacet.IncludeNotAvailable", "IncludeNotAvailable", settings, form, storeScope, Services.Settings); // Localized facet settings (CommonFacetSettingsLocalizedModel). - UpdateLocalizedFacetSetting(model.CategoryFacet, FacetGroupKind.Category, ref clearFacetCache); - UpdateLocalizedFacetSetting(model.BrandFacet, FacetGroupKind.Brand, ref clearFacetCache); - UpdateLocalizedFacetSetting(model.PriceFacet, FacetGroupKind.Price, ref clearFacetCache); - UpdateLocalizedFacetSetting(model.RatingFacet, FacetGroupKind.Rating, ref clearFacetCache); - UpdateLocalizedFacetSetting(model.DeliveryTimeFacet, FacetGroupKind.DeliveryTime, ref clearFacetCache); - UpdateLocalizedFacetSetting(model.AvailabilityFacet, FacetGroupKind.Availability, ref clearFacetCache); - UpdateLocalizedFacetSetting(model.NewArrivalsFacet, FacetGroupKind.NewArrivals, ref clearFacetCache); - - UpdateLocalizedFacetSetting(model.ForumSearchSettings.ForumFacet, FacetGroupKind.Forum, ref clearFacetCache, "Forum"); - UpdateLocalizedFacetSetting(model.ForumSearchSettings.CustomerFacet, FacetGroupKind.Customer, ref clearFacetCache, "Forum"); - UpdateLocalizedFacetSetting(model.ForumSearchSettings.DateFacet, FacetGroupKind.Date, ref clearFacetCache, "Forum"); + UpdateLocalizedFacetSetting(model.CategoryFacet, FacetGroupKind.Category, ref clearCatalogFacetCache); + UpdateLocalizedFacetSetting(model.BrandFacet, FacetGroupKind.Brand, ref clearCatalogFacetCache); + UpdateLocalizedFacetSetting(model.PriceFacet, FacetGroupKind.Price, ref clearCatalogFacetCache); + UpdateLocalizedFacetSetting(model.RatingFacet, FacetGroupKind.Rating, ref clearCatalogFacetCache); + UpdateLocalizedFacetSetting(model.DeliveryTimeFacet, FacetGroupKind.DeliveryTime, ref clearCatalogFacetCache); + UpdateLocalizedFacetSetting(model.AvailabilityFacet, FacetGroupKind.Availability, ref clearCatalogFacetCache); + UpdateLocalizedFacetSetting(model.NewArrivalsFacet, FacetGroupKind.NewArrivals, ref clearCatalogFacetCache); + + UpdateLocalizedFacetSetting(model.ForumSearchSettings.ForumFacet, FacetGroupKind.Forum, ref clearForumFacetCache, "Forum"); + UpdateLocalizedFacetSetting(model.ForumSearchSettings.CustomerFacet, FacetGroupKind.Customer, ref clearForumFacetCache, "Forum"); + UpdateLocalizedFacetSetting(model.ForumSearchSettings.DateFacet, FacetGroupKind.Date, ref clearForumFacetCache, "Forum"); } - if (clearFacetCache) + if (clearCatalogFacetCache) { _catalogSearchQueryAliasMapper.Value.ClearCommonFacetCache(); } + if (clearForumFacetCache) + { + _forumSearchQueryAliasMapper.Value.ClearCommonFacetCache(); + } return NotifyAndRedirect("Search"); } From 7fd944ecdb6427ffb77bd7ec36d72593371bbe0e Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 27 Aug 2018 15:16:01 +0200 Subject: [PATCH 051/932] Copy product method must consider all product downloads --- .../Catalog/CopyProductService.cs | 79 +++++++++++-------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs index c18e437254..52bb1b48e5 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs @@ -91,7 +91,6 @@ public virtual Product CopyProduct( var languages = _languageService.GetAllLanguages(true); // Media stuff - int? downloadId = null; int? sampleDownloadId = null; var clonedPictures = new Dictionary(); // Key = former ID, Value = cloned picture @@ -103,15 +102,12 @@ public virtual Product CopyProduct( forceNoTracking: true, hooksEnabled: false)) { - if (product.IsDownload) - { - downloadId = CopyDownload(product.Id, "Product")?.Id; - } - if (product.HasSampleDownload) { - sampleDownloadId = CopyDownload(product.SampleDownloadId.GetValueOrDefault())?.Id; - } + var sampleDownload = _downloadService.GetDownloadById((int)product.SampleDownloadId); + var sampleDownloadClone = CopyDownload(sampleDownload); + sampleDownloadId = sampleDownloadClone.Id; + } if (copyImages) { @@ -145,8 +141,7 @@ public virtual Product CopyProduct( RequiredProductIds = product.RequiredProductIds, AutomaticallyAddRequiredProducts = product.AutomaticallyAddRequiredProducts, IsDownload = product.IsDownload, - //DownloadId = downloadId ?? 0, - //UnlimitedDownloads = product.UnlimitedDownloads, + UnlimitedDownloads = product.UnlimitedDownloads, MaxNumberOfDownloads = product.MaxNumberOfDownloads, DownloadExpirationDays = product.DownloadExpirationDays, DownloadActivationType = product.DownloadActivationType, @@ -351,8 +346,11 @@ public virtual Product CopyProduct( // Bundle items ProcessBundleItems(product, clone); - // >>>>>>> Our final commit - Commit(); + // Downloads + CopyDownloads(product.Id, clone.Id, "Product"); + + // >>>>>>> Our final commit + Commit(); } return clone; @@ -364,36 +362,53 @@ private void Commit() _services.DbContext.SaveChanges(); } - private Download CopyDownload(int productId, string entityName = "") + private Dictionary CopyDownloads(int productId, int cloneId, string entityName = "") { - var download = _downloadService.GetDownloadsFor(productId, entityName).FirstOrDefault(); + var downloads = _downloadService.GetDownloadsFor(productId, entityName); + var clonedDownloads = new Dictionary(); - if (download == null) + if (!downloads.Any()) { return null; } - var clone = new Download - { - DownloadGuid = Guid.NewGuid(), - UseDownloadUrl = download.UseDownloadUrl, - DownloadUrl = download.DownloadUrl, - ContentType = download.ContentType, - Filename = download.Filename, - Extension = download.Extension, - IsNew = download.IsNew, - UpdatedOnUtc = DateTime.UtcNow - }; - - using (var scope = new DbContextScope(ctx: _productRepository.Context, autoCommit: true)) - { - _downloadService.InsertDownload(clone, download.MediaStorage?.Data); + using (var scope = new DbContextScope(ctx: _productRepository.Context, autoCommit: true)) + { + foreach (var download in downloads) + { + download.EntityId = cloneId; + var clone = CopyDownload(download); + clonedDownloads[clone.Id] = clone; + } } - return clone; + return clonedDownloads; } - private Dictionary CopyPictures(Product product, string newProductName) + private Download CopyDownload(Download download) + { + var clone = new Download + { + DownloadGuid = Guid.NewGuid(), + UseDownloadUrl = download.UseDownloadUrl, + DownloadUrl = download.DownloadUrl, + ContentType = download.ContentType, + Filename = download.Filename, + Extension = download.Extension, + IsNew = download.IsNew, + UpdatedOnUtc = DateTime.UtcNow, + EntityId = download.EntityId, + EntityName = download.EntityName, + Changelog = download.Changelog, + FileVersion = download.FileVersion + }; + + _downloadService.InsertDownload(clone, download.MediaStorage?.Data); + + return clone; + } + + private Dictionary CopyPictures(Product product, string newProductName) { var clonedPictures = new Dictionary(); var seoFilename = _pictureService.GetPictureSeName(newProductName); From 85290ae8986878e724fc89184e9d0f9388c76b38 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 24 Aug 2018 20:00:36 +0200 Subject: [PATCH 052/932] Resolves #1498: Remove the div element that is wrapped around widget topics --- .../SmartStore.Web/Views/Topic/Partials/TopicWidget.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Views/Topic/Partials/TopicWidget.cshtml b/src/Presentation/SmartStore.Web/Views/Topic/Partials/TopicWidget.cshtml index 964b0fd82c..2f28bcfe59 100644 --- a/src/Presentation/SmartStore.Web/Views/Topic/Partials/TopicWidget.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Topic/Partials/TopicWidget.cshtml @@ -6,7 +6,7 @@ @if (!Model.WrapContent) { -
@Html.Raw(Model.Html)
+ @Html.Raw(Model.Html) return; } From cecccb93463d18df7aeff14c9d49cea711465423 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Mon, 27 Aug 2018 19:16:21 +0200 Subject: [PATCH 053/932] Topic widgets should not check for empty html body --- .../SmartStore.Web/Infrastructure/DefaultWidgetSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Infrastructure/DefaultWidgetSelector.cs b/src/Presentation/SmartStore.Web/Infrastructure/DefaultWidgetSelector.cs index 214c6899c1..a27662ebfe 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/DefaultWidgetSelector.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/DefaultWidgetSelector.cs @@ -99,7 +99,7 @@ public virtual IEnumerable GetWidgets(string widgetZone, object .Select(t => { var locTitle = t.GetLocalized(x => t.Title); - var locBody = t.GetLocalized(x => t.Body, detectEmptyHtml: true); + var locBody = t.GetLocalized(x => t.Body, detectEmptyHtml: false); return new TopicWidgetStub { From 6f9cac926e9446ce57bd4787ac4332a4d5b081d3 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 27 Aug 2018 19:40:45 +0200 Subject: [PATCH 054/932] Changed tab name for product downloads --- .../SmartStore.Data/Migrations/MigrationsConfiguration.cs | 3 ++- .../Administration/Views/Product/_CreateOrUpdate.cshtml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 3354b5c9a2..5eb382a7d1 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -343,7 +343,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Common.Download.Versions", "Versions", "Versionen"); builder.AddOrUpdate("Common.Download.Version", "Version", "Version"); builder.AddOrUpdate("Common.Download.Delete", "Delete download", "Download löschen"); - + builder.AddOrUpdate("Common.Downloads", "Downloads", "Downloads"); + builder.AddOrUpdate("Admin.Catalog.Products.Fields.NewVersionDownloadId", "New download version", "Neue Version des Downloads", diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml index d1a6d4f419..043794089b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml @@ -116,7 +116,7 @@ .LinkHtmlAttributes(new { data_tab_name = "Stores" }) .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Stores" }) .Ajax(); - x.Add().Text(T("Common.Download").Text) + x.Add().Text(T("Common.Downloads").Text) .Icon("fa fa-download fa-lg fa-fw") .LinkHtmlAttributes(new { data_tab_name = "Downloads" }) .Content(Html.Partial("_CreateOrUpdate.Downloads", Model).ToHtmlString()); From c2990515a4a5b41a7db7e44d3710345ecfdce687 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 27 Aug 2018 19:53:16 +0200 Subject: [PATCH 055/932] Closes #1497 Add widget zone in customer orders list --- src/Presentation/SmartStore.Web/Views/Order/Details.cshtml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Presentation/SmartStore.Web/Views/Order/Details.cshtml b/src/Presentation/SmartStore.Web/Views/Order/Details.cshtml index b8e6111cd4..87f2a6846d 100644 --- a/src/Presentation/SmartStore.Web/Views/Order/Details.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Order/Details.cshtml @@ -449,6 +449,8 @@ @item.SubTotal + + Html.RenderWidget("orderdetails_page_after_item", item); } @helper BundleProducts(OrderDetailsModel.OrderItemModel parentItem) @@ -500,4 +502,6 @@ } } + + Html.RenderWidget("orderdetails_page_after_item", parentItem); } \ No newline at end of file From a8934c2a1c6290e2c43f9299f52a297f88fe4359 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 28 Aug 2018 11:00:11 +0200 Subject: [PATCH 056/932] Search: Finished forum search in service project --- .../Modelling/CatalogSearchQueryFactory.cs | 8 ++- .../Search/Extensions/FacetUrlHelper.cs | 54 ++++++++++++------- .../DefaultFacetTemplateSelector.cs | 14 ++--- .../Models/Search/ISearchResultModel.cs | 31 +++++++---- .../SmartStore.Web/SmartStore.Web.csproj | 1 + .../Views/Search/FacetTemplates/Date.cshtml | 49 +++++++++++++++++ 6 files changed, 116 insertions(+), 41 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/Date.cshtml diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs index 1ef4a2669e..4f4674d168 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs @@ -3,19 +3,17 @@ using System.Linq; using System.Web; using System.Web.Routing; -using SmartStore.Collections; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; using SmartStore.Services.Catalog; -using SmartStore.Utilities; -using SmartStore.Services.Security; using SmartStore.Services.Search.Extensions; +using SmartStore.Services.Security; namespace SmartStore.Services.Search.Modelling { - /* + /* TOKENS: =============================== q - Search term @@ -34,7 +32,7 @@ namespace SmartStore.Services.Search.Modelling * - Variants & attributes */ - public class CatalogSearchQueryFactory : SearchQueryFactoryBase, ICatalogSearchQueryFactory + public class CatalogSearchQueryFactory : SearchQueryFactoryBase, ICatalogSearchQueryFactory { protected static readonly string[] _instantSearchFields = new string[] { "manufacturer", "sku", "gtin", "mpn", "attrname", "variantname" }; diff --git a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUrlHelper.cs b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUrlHelper.cs index 19381fe342..d39648af11 100644 --- a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUrlHelper.cs +++ b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUrlHelper.cs @@ -12,12 +12,12 @@ namespace SmartStore.Services.Search.Extensions { - public class FacetUrlHelper + public class FacetUrlHelper { - private readonly ICatalogSearchQueryAliasMapper _mapper; - private readonly IWorkContext _workContext; + private readonly ICatalogSearchQueryAliasMapper _catalogAliasMapper; + private readonly IForumSearchQueryAliasMapper _forumAliasMapper; + private readonly IWorkContext _workContext; private readonly HttpRequestBase _httpRequest; - private readonly SearchSettings _searchSettings; private readonly int _languageId; private readonly string _url; @@ -25,25 +25,31 @@ public class FacetUrlHelper private readonly static IDictionary _queryNames = new Dictionary { + // Catalog. { FacetGroupKind.Brand, "m" }, { FacetGroupKind.Category, "c" }, { FacetGroupKind.Price, "p" }, { FacetGroupKind.Rating, "r" }, { FacetGroupKind.DeliveryTime, "d" }, { FacetGroupKind.Availability, "a" }, - { FacetGroupKind.NewArrivals, "n" } - }; + { FacetGroupKind.NewArrivals, "n" }, + + // Forum. + { FacetGroupKind.Forum, "f" }, + { FacetGroupKind.Customer, "c" }, + { FacetGroupKind.Date, "d" } + }; public FacetUrlHelper( - ICatalogSearchQueryAliasMapper mapper, - IWorkContext workContext, - HttpRequestBase httpRequest, - SearchSettings searchSettings) + ICatalogSearchQueryAliasMapper catalogAliasMapper, + IForumSearchQueryAliasMapper forumAliasMapper, + IWorkContext workContext, + HttpRequestBase httpRequest) { - _mapper = mapper; + _catalogAliasMapper = catalogAliasMapper; + _forumAliasMapper = forumAliasMapper; _workContext = workContext; _httpRequest = httpRequest; - _searchSettings = searchSettings; _languageId = _workContext.WorkingLanguage.Id; _url = _httpRequest.CurrentExecutionFilePath; @@ -111,6 +117,9 @@ public string Remove(params Facet[] facets) case FacetGroupKind.DeliveryTime: case FacetGroupKind.Availability: case FacetGroupKind.NewArrivals: + case FacetGroupKind.Forum: + case FacetGroupKind.Customer: + case FacetGroupKind.Date: qsName = _queryNames[facet.FacetGroup.Kind]; break; } @@ -173,15 +182,15 @@ protected virtual NameValueCollection GetQueryParts(Facet facet) else { entityId = val.Value.Convert(); - value = _mapper.GetAttributeOptionAliasById(entityId, _languageId) ?? "opt" + entityId; + value = _catalogAliasMapper.GetAttributeOptionAliasById(entityId, _languageId) ?? "opt" + entityId; } - name = _mapper.GetAttributeAliasById(val.ParentId, _languageId) ?? "attr" + val.ParentId; + name = _catalogAliasMapper.GetAttributeAliasById(val.ParentId, _languageId) ?? "attr" + val.ParentId; result.Add(name, value); break; case FacetGroupKind.Variant: entityId = val.Value.Convert(); - name = _mapper.GetVariantAliasById(val.ParentId, _languageId) ?? "vari" + val.ParentId; - value = _mapper.GetVariantOptionAliasById(entityId, _languageId) ?? "opt" + entityId; + name = _catalogAliasMapper.GetVariantAliasById(val.ParentId, _languageId) ?? "vari" + val.ParentId; + value = _catalogAliasMapper.GetVariantOptionAliasById(entityId, _languageId) ?? "opt" + entityId; result.Add(name, value); break; case FacetGroupKind.Category: @@ -194,11 +203,20 @@ protected virtual NameValueCollection GetQueryParts(Facet facet) value = val.ToString(); if (value.HasValue()) { - name = _mapper.GetCommonFacetAliasByGroupKind(group.Kind, _languageId) ?? _queryNames[group.Kind]; + name = _catalogAliasMapper.GetCommonFacetAliasByGroupKind(group.Kind, _languageId) ?? _queryNames[group.Kind]; result.Add(name, value); } - break; + case FacetGroupKind.Forum: + case FacetGroupKind.Customer: + case FacetGroupKind.Date: + value = val.ToString(); + if (value.HasValue()) + { + name = _forumAliasMapper.GetCommonFacetAliasByGroupKind(group.Kind, _languageId) ?? _queryNames[group.Kind]; + result.Add(name, value); + } + break; } return result; diff --git a/src/Presentation/SmartStore.Web/Infrastructure/DefaultFacetTemplateSelector.cs b/src/Presentation/SmartStore.Web/Infrastructure/DefaultFacetTemplateSelector.cs index 940157652c..078623c216 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/DefaultFacetTemplateSelector.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/DefaultFacetTemplateSelector.cs @@ -1,14 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Routing; +using System.Web.Routing; using SmartStore.Core.Search.Facets; using SmartStore.Services.Search.Rendering; namespace SmartStore.Web.Infrastructure { - public class DefaultFacetTemplateSelector : IFacetTemplateSelector + public class DefaultFacetTemplateSelector : IFacetTemplateSelector { public RouteInfo GetTemplateRoute(FacetGroup facetGroup) { @@ -41,12 +37,16 @@ private string GetTemplateName(FacetGroup group) case FacetGroupKind.Brand: case FacetGroupKind.Availability: case FacetGroupKind.NewArrivals: + case FacetGroupKind.Forum: + case FacetGroupKind.Customer: return prefix + (group.IsMultiSelect ? "MultiSelect" : "SingleSelect"); case FacetGroupKind.Price: return prefix + "Price"; case FacetGroupKind.Rating: return prefix + "Rating"; - } + case FacetGroupKind.Date: + return prefix + "Date"; + } return null; } diff --git a/src/Presentation/SmartStore.Web/Models/Search/ISearchResultModel.cs b/src/Presentation/SmartStore.Web/Models/Search/ISearchResultModel.cs index 8caea117de..3734355cb6 100644 --- a/src/Presentation/SmartStore.Web/Models/Search/ISearchResultModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Search/ISearchResultModel.cs @@ -1,29 +1,38 @@ -using System; -using System.Web.Mvc; +using System.Web.Mvc; using Autofac.Integration.Mvc; using SmartStore.Services.Search; namespace SmartStore.Web.Models.Search { - public interface ISearchResultModel + public interface ISearchResultModel { CatalogSearchResult SearchResult { get; } } - [ModelBinderType(typeof(ISearchResultModel))] + public interface IForumSearchResultModel + { + ForumSearchResult SearchResult { get; } + } + + [ModelBinderType(typeof(ISearchResultModel), typeof(IForumSearchResultModel))] public class SearchResultModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var modelType = bindingContext.ModelType; - if (!typeof(ISearchResultModel).IsAssignableFrom(modelType)) - { - return null; - } + if (typeof(ISearchResultModel).IsAssignableFrom(modelType)) + { + var model = controllerContext.GetMasterControllerContext().Controller.ViewData.Model as ISearchResultModel; + return model; + } + else if (typeof(IForumSearchResultModel).IsAssignableFrom(modelType)) + { + var model = controllerContext.GetMasterControllerContext().Controller.ViewData.Model as IForumSearchResultModel; + return model; + } - var model = controllerContext.GetMasterControllerContext().Controller.ViewData.Model as ISearchResultModel; - return model; - } + return null; + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index f70266f8f4..4ac6b8db9f 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -1760,6 +1760,7 @@ + Web.config diff --git a/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/Date.cshtml b/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/Date.cshtml new file mode 100644 index 0000000000..f931d519b6 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/Date.cshtml @@ -0,0 +1,49 @@ +@model FacetGroup + +@using SmartStore.Core.Search.Facets +@using SmartStore.Services.Search.Extensions; + +@{ + var customRangeFacet = Model.Facets.FirstOrDefault(x => x.Key == "custom"); +} + +
+ @foreach (var facet in Model.Facets) + { + if (facet.Key != "custom" && facet.Published) + { +
+ +
+ } + } + + @if (customRangeFacet != null) + { + string value = customRangeFacet.Value.Value != null ? customRangeFacet.Value.Value.ToString() : ""; + string upperValue = customRangeFacet.Value.UpperValue != null ? customRangeFacet.Value.UpperValue.ToString() : ""; + +
+
+ +
+
+ - +
+
+ +
+
+ +
+
+ } +
\ No newline at end of file From 818f5489b385d61065a8272c95eeb1d50023e9ef Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 28 Aug 2018 12:20:03 +0200 Subject: [PATCH 057/932] Minor change --- .../Administration/Views/Manufacturer/Edit.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Edit.cshtml index c083f79342..4a2c61cbd8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Edit.cshtml @@ -16,7 +16,7 @@ @T("Admin.Common.Preview") - From 84b8042284b35b7cd0c3b639e7fb3ec2864ee0a1 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 28 Aug 2018 15:19:30 +0200 Subject: [PATCH 058/932] Implemented DownloadService.GetDownloadByVersion --- .../Media/DownloadService.cs | 16 +++++++++++++++- .../Media/IDownloadService.cs | 19 ++++++++++++++----- .../Controllers/DownloadController.cs | 4 +--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Media/DownloadService.cs b/src/Libraries/SmartStore.Services/Media/DownloadService.cs index bcca316d5e..16a0feee63 100644 --- a/src/Libraries/SmartStore.Services/Media/DownloadService.cs +++ b/src/Libraries/SmartStore.Services/Media/DownloadService.cs @@ -104,7 +104,21 @@ public virtual IList GetDownloadsFor(int entityId, string entityName) return new List(); } - public virtual Multimap GetDownloadsByEntityIds(int[] entityIds, string entityName) + public virtual Download GetDownloadByVersion(int entityId, string entityName, string fileVersion) + { + if (entityId > 0 && fileVersion.HasValue() && entityName.HasValue()) + { + var download = (from x in _downloadRepository.Table + where x.EntityId == entityId && x.EntityName.Equals(entityName) && x.FileVersion.Equals(fileVersion) + select x).FirstOrDefault(); + + return download; + } + + return null; + } + + public virtual Multimap GetDownloadsByEntityIds(int[] entityIds, string entityName) { Guard.NotNull(entityIds, nameof(entityIds)); Guard.NotEmpty(entityName, nameof(entityName)); diff --git a/src/Libraries/SmartStore.Services/Media/IDownloadService.cs b/src/Libraries/SmartStore.Services/Media/IDownloadService.cs index 9a09d5ad28..245bfe9bb3 100644 --- a/src/Libraries/SmartStore.Services/Media/IDownloadService.cs +++ b/src/Libraries/SmartStore.Services/Media/IDownloadService.cs @@ -38,13 +38,22 @@ public partial interface IDownloadService /// List of download entities IList GetDownloadsFor(int entityId, string entityName); - /// - /// Gets downloads by entity identifiers + /// + /// Gets downloads by entity identifier & fileversion /// - /// Entity identifiers + /// Entity identifier /// Entity name - /// Multimap of download entities - Multimap GetDownloadsByEntityIds(int[] entityIds, string entityName); + /// File version + /// Download entity + Download GetDownloadByVersion(int entityId, string entityName, string fileVersion); + + /// + /// Gets downloads by entity identifiers + /// + /// Entity identifiers + /// Entity name + /// Multimap of download entities + Multimap GetDownloadsByEntityIds(int[] entityIds, string entityName); /// /// Gets a download by GUID diff --git a/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs b/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs index 73082683ee..ad68433f14 100644 --- a/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs @@ -119,9 +119,7 @@ public ActionResult GetDownload(Guid id, bool agree = false, string fileVersion if (fileVersion.HasValue()) { - download = _downloadService.GetDownloadsFor(product) - .Where(x => x.FileVersion.Equals(fileVersion)) - .FirstOrDefault(); + download = _downloadService.GetDownloadByVersion(product.Id, "Product", fileVersion); } else { From a66b849fd5a2520ef8893878c63899928ed93ee3 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 28 Aug 2018 15:21:04 +0200 Subject: [PATCH 059/932] Implemented OrderReportService.GetPurchaseCount --- .../Orders/IOrderReportService.cs | 9 ++++++++- .../Orders/OrderReportService.cs | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Orders/IOrderReportService.cs b/src/Libraries/SmartStore.Services/Orders/IOrderReportService.cs index b5f278c3d5..381a207c40 100644 --- a/src/Libraries/SmartStore.Services/Orders/IOrderReportService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IOrderReportService.cs @@ -56,7 +56,14 @@ IList BestSellersReport(int storeId, OrderStatus? os, PaymentStatus? ps, ShippingStatus? ss, int billingCountryId = 0, int recordsToReturn = 5, int orderBy = 1, bool showHidden = false); - + + /// + /// Gets a the count of purchases for a product + /// + /// Product identifier + /// Purchase count + int GetPurchaseCount(int productId); + /// /// Gets a list of product identifiers purchased by other customers who purchased the above /// diff --git a/src/Libraries/SmartStore.Services/Orders/OrderReportService.cs b/src/Libraries/SmartStore.Services/Orders/OrderReportService.cs index ba5fd412e0..6088986a46 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderReportService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderReportService.cs @@ -264,7 +264,20 @@ group orderItem by orderItem.ProductId into g return result; } - public virtual int[] GetAlsoPurchasedProductsIds(int storeId, int productId, int recordsToReturn = 5, bool showHidden = false) + public virtual int GetPurchaseCount(int productId) + { + if (productId == 0) + throw new ArgumentException("Product ID is not specified"); + + var query = from orderItem in _orderItemRepository.Table + where orderItem.ProductId == productId + group orderItem by orderItem.Id into g + select new { ProductsPurchased = g.Sum(x => x.Quantity) }; + + return query.Select(x => x.ProductsPurchased).FirstOrDefault(); + } + + public virtual int[] GetAlsoPurchasedProductsIds(int storeId, int productId, int recordsToReturn = 5, bool showHidden = false) { if (productId == 0) throw new ArgumentException("Product ID is not specified"); From 26afd067fd4c0dad09d9293ccd23d69bb7ba7e83 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 28 Aug 2018 15:21:34 +0200 Subject: [PATCH 060/932] Code readability --- .../Forums/ForumService.cs | 585 ++++++--------- .../Forums/IForumService.cs | 4 + .../Controllers/BoardsController.cs | 672 +++++++++--------- 3 files changed, 559 insertions(+), 702 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index 5e33010d1b..910f1c4c94 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -59,140 +59,80 @@ public ForumService( private void UpdateForumStats(int forumId) { - if (forumId == 0) - { - return; - } var forum = GetForumById(forumId); if (forum == null) { return; } - //number of topics - var queryNumTopics = from ft in _forumTopicRepository.Table - where ft.ForumId == forumId - select ft.Id; - int numTopics = queryNumTopics.Count(); - - //number of posts - var queryNumPosts = from ft in _forumTopicRepository.Table - join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId - where ft.ForumId == forumId - select fp.Id; - int numPosts = queryNumPosts.Count(); - - //last values - int lastTopicId = 0; - int lastPostId = 0; - int lastPostCustomerId = 0; - DateTime? lastPostTime = null; - var queryLastValues = from ft in _forumTopicRepository.Table - join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId - where ft.ForumId == forumId - orderby fp.CreatedOnUtc descending, ft.CreatedOnUtc descending - select new - { - LastTopicId = ft.Id, - LastPostId = fp.Id, - LastPostCustomerId = fp.CustomerId, - LastPostTime = fp.CreatedOnUtc - }; + var queryLastValues = + from ft in _forumTopicRepository.TableUntracked + join fp in _forumPostRepository.TableUntracked on ft.Id equals fp.TopicId + where ft.ForumId == forumId + orderby fp.CreatedOnUtc descending, ft.CreatedOnUtc descending + select new + { + LastTopicId = ft.Id, + LastPostId = fp.Id, + LastPostCustomerId = fp.CustomerId, + LastPostTime = fp.CreatedOnUtc + }; var lastValues = queryLastValues.FirstOrDefault(); - if (lastValues != null) - { - lastTopicId = lastValues.LastTopicId; - lastPostId = lastValues.LastPostId; - lastPostCustomerId = lastValues.LastPostCustomerId; - lastPostTime = lastValues.LastPostTime; - } - //update forum - forum.NumTopics = numTopics; - forum.NumPosts = numPosts; - forum.LastTopicId = lastTopicId; - forum.LastPostId = lastPostId; - forum.LastPostCustomerId = lastPostCustomerId; - forum.LastPostTime = lastPostTime; + forum.LastTopicId = lastValues?.LastTopicId ?? 0; + forum.LastPostId = lastValues?.LastPostId ?? 0; + forum.LastPostCustomerId = lastValues?.LastPostCustomerId ?? 0; + forum.LastPostTime = lastValues?.LastPostTime; + forum.NumTopics = _forumTopicRepository.Table.Where(x => x.ForumId == forumId).Count(); + forum.NumPosts = ( + from ft in _forumTopicRepository.Table + join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId + where ft.ForumId == forumId + select fp.Id).Count(); + UpdateForum(forum); } private void UpdateForumTopicStats(int forumTopicId) { - if (forumTopicId == 0) - { - return; - } var forumTopic = GetTopicById(forumTopicId); if (forumTopic == null) { return; } - //number of posts - var queryNumPosts = from fp in _forumPostRepository.Table - where fp.TopicId == forumTopicId - select fp.Id; - int numPosts = queryNumPosts.Count(); - - //last values - int lastPostId = 0; - int lastPostCustomerId = 0; - DateTime? lastPostTime = null; - var queryLastValues = from fp in _forumPostRepository.Table - where fp.TopicId == forumTopicId - orderby fp.CreatedOnUtc descending - select new - { - LastPostId = fp.Id, - LastPostCustomerId = fp.CustomerId, - LastPostTime = fp.CreatedOnUtc - }; + var queryLastValues = + from fp in _forumPostRepository.TableUntracked + where fp.TopicId == forumTopicId + orderby fp.CreatedOnUtc descending + select new + { + LastPostId = fp.Id, + LastPostCustomerId = fp.CustomerId, + LastPostTime = fp.CreatedOnUtc + }; var lastValues = queryLastValues.FirstOrDefault(); - if (lastValues != null) - { - lastPostId = lastValues.LastPostId; - lastPostCustomerId = lastValues.LastPostCustomerId; - lastPostTime = lastValues.LastPostTime; - } - //update topic - forumTopic.NumPosts = numPosts; - forumTopic.LastPostId = lastPostId; - forumTopic.LastPostCustomerId = lastPostCustomerId; - forumTopic.LastPostTime = lastPostTime; + forumTopic.LastPostId = lastValues?.LastPostId ?? 0; + forumTopic.LastPostCustomerId = lastValues?.LastPostCustomerId ?? 0; + forumTopic.LastPostTime = lastValues?.LastPostTime; + forumTopic.NumPosts = _forumPostRepository.Table.Where(x => x.TopicId == forumTopicId).Count(); + UpdateTopic(forumTopic); } private void UpdateCustomerStats(int customerId) { - if (customerId == 0) - { - return; - } - - var customer = _customerService.GetCustomerById(customerId); - - if (customer == null) + if (customerId != 0) { - return; - } - - var query = from fp in _forumPostRepository.Table - where fp.CustomerId == customerId - select fp.Id; - int numPosts = query.Count(); - - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ForumPostCount, numPosts); - } + var customer = _customerService.GetCustomerById(customerId); + if (customer != null) + { + var numPosts = _forumPostRepository.Table.Where(x => x.CustomerId == customerId).Count(); - private bool IsForumModerator(Customer customer) - { - if (customer.IsForumModerator()) - { - return true; + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ForumPostCount, numPosts); + } } - return false; } #region Group @@ -200,7 +140,9 @@ private bool IsForumModerator(Customer customer) public virtual ForumGroup GetForumGroupById(int forumGroupId) { if (forumGroupId == 0) + { return null; + } return _forumGroupRepository.GetById(forumGroupId); } @@ -230,36 +172,28 @@ orderby fgGroup.Key return query.ToListCached(); } - public virtual void DeleteForumGroup(ForumGroup forumGroup) - { - if (forumGroup == null) - { - throw new ArgumentNullException("forumGroup"); - } - - _forumGroupRepository.Delete(forumGroup); - } - public virtual void InsertForumGroup(ForumGroup forumGroup) { - if (forumGroup == null) - { - throw new ArgumentNullException("forumGroup"); - } + Guard.NotNull(forumGroup, nameof(forumGroup)); _forumGroupRepository.Insert(forumGroup); } public virtual void UpdateForumGroup(ForumGroup forumGroup) { - if (forumGroup == null) - { - throw new ArgumentNullException("forumGroup"); - } + Guard.NotNull(forumGroup, nameof(forumGroup)); _forumGroupRepository.Update(forumGroup); } + public virtual void DeleteForumGroup(ForumGroup forumGroup) + { + if (forumGroup != null) + { + _forumGroupRepository.Delete(forumGroup); + } + } + #endregion #region Forum @@ -267,7 +201,9 @@ public virtual void UpdateForumGroup(ForumGroup forumGroup) public virtual Forum GetForumById(int forumId) { if (forumId == 0) + { return null; + } return _forumRepository.GetById(forumId); } @@ -283,14 +219,28 @@ orderby f.DisplayOrder return forums; } + public virtual void InsertForum(Forum forum) + { + Guard.NotNull(forum, nameof(forum)); + + _forumRepository.Insert(forum); + } + + public virtual void UpdateForum(Forum forum) + { + Guard.NotNull(forum, nameof(forum)); + + _forumRepository.Update(forum); + } + public virtual void DeleteForum(Forum forum) { if (forum == null) { - throw new ArgumentNullException("forum"); + return; } - //delete forum subscriptions (topics) + // Delete forum subscriptions (topics). var queryTopicIds = from ft in _forumTopicRepository.Table where ft.ForumId == forum.Id select ft.Id; @@ -302,7 +252,7 @@ where queryTopicIds.Contains(fs.TopicId) _forumSubscriptionRepository.Delete(fs); } - //delete forum subscriptions (forum) + // Delete forum subscriptions (forum). var queryFs2 = from fs in _forumSubscriptionRepository.Table where fs.ForumId == forum.Id select fs; @@ -311,62 +261,14 @@ where queryTopicIds.Contains(fs.TopicId) _forumSubscriptionRepository.Delete(fs2); } - //delete forum + // Delete forum. _forumRepository.Delete(forum); } - public virtual void InsertForum(Forum forum) - { - if (forum == null) - { - throw new ArgumentNullException("forum"); - } - - _forumRepository.Insert(forum); - } - - public virtual void UpdateForum(Forum forum) - { - if (forum == null) - { - throw new ArgumentNullException("forum"); - } - - _forumRepository.Update(forum); - } - #endregion #region Topic - public virtual void DeleteTopic(ForumTopic forumTopic) - { - if (forumTopic == null) - { - throw new ArgumentNullException("forumTopic"); - } - - int customerId = forumTopic.CustomerId; - int forumId = forumTopic.ForumId; - - //delete topic - _forumTopicRepository.Delete(forumTopic); - - //delete forum subscriptions - var queryFs = from ft in _forumSubscriptionRepository.Table - where ft.TopicId == forumTopic.Id - select ft; - var forumSubscriptions = queryFs.ToList(); - foreach (var fs in forumSubscriptions) - { - _forumSubscriptionRepository.Delete(fs); - } - - //update stats - UpdateForumStats(forumId); - UpdateCustomerStats(customerId); - } - public virtual ForumTopic GetTopicById(int forumTopicId) { if (forumTopicId == 0) @@ -458,17 +360,12 @@ orderby ftGroup.Key public virtual void InsertTopic(ForumTopic forumTopic, bool sendNotifications) { - if (forumTopic == null) - { - throw new ArgumentNullException("forumTopic"); - } + Guard.NotNull(forumTopic, nameof(forumTopic)); _forumTopicRepository.Insert(forumTopic); - //update stats UpdateForumStats(forumTopic.ForumId); - //send notifications if (sendNotifications) { var forum = forumTopic.Forum; @@ -482,7 +379,7 @@ public virtual void InsertTopic(ForumTopic forumTopic, bool sendNotifications) continue; } - if (!String.IsNullOrEmpty(subscription.Customer.Email)) + if (subscription.Customer.Email.HasValue()) { _services.MessageFactory.SendNewForumTopicMessage(subscription.Customer, forumTopic, languageId); } @@ -491,48 +388,69 @@ public virtual void InsertTopic(ForumTopic forumTopic, bool sendNotifications) } public virtual void UpdateTopic(ForumTopic forumTopic) + { + Guard.NotNull(forumTopic, nameof(forumTopic)); + + _forumTopicRepository.Update(forumTopic); + } + + public virtual void DeleteTopic(ForumTopic forumTopic) { if (forumTopic == null) { - throw new ArgumentNullException("forumTopic"); + return; } - _forumTopicRepository.Update(forumTopic); + int customerId = forumTopic.CustomerId; + int forumId = forumTopic.ForumId; + + _forumTopicRepository.Delete(forumTopic); + + // Delete forum subscriptions. + var queryFs = from ft in _forumSubscriptionRepository.Table + where ft.TopicId == forumTopic.Id + select ft; + var forumSubscriptions = queryFs.ToList(); + foreach (var fs in forumSubscriptions) + { + _forumSubscriptionRepository.Delete(fs); + } + + UpdateForumStats(forumId); + UpdateCustomerStats(customerId); } public virtual ForumTopic MoveTopic(int forumTopicId, int newForumId) { var forumTopic = GetTopicById(forumTopicId); if (forumTopic == null) + { return forumTopic; + } - if (this.IsCustomerAllowedToMoveTopic(_services.WorkContext.CurrentCustomer, forumTopic)) + if (IsCustomerAllowedToMoveTopic(_services.WorkContext.CurrentCustomer, forumTopic)) { - int previousForumId = forumTopic.ForumId; + var previousForumId = forumTopic.ForumId; var newForum = GetForumById(newForumId); - - if (newForum != null) + if (newForum != null && previousForumId != newForumId) { - if (previousForumId != newForumId) - { - forumTopic.ForumId = newForum.Id; - UpdateTopic(forumTopic); + forumTopic.ForumId = newForum.Id; + UpdateTopic(forumTopic); - //update forum stats - UpdateForumStats(previousForumId); - UpdateForumStats(newForumId); - } + UpdateForumStats(previousForumId); + UpdateForumStats(newForumId); } } + return forumTopic; } public virtual int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId) { - int pageIndex = 0; + var pageIndex = 0; var forumPosts = GetAllPosts(forumTopicId, 0, string.Empty, true, 0, int.MaxValue); - for (int i = 0; i < forumPosts.TotalCount; i++) + for (var i = 0; i < forumPosts.TotalCount; i++) { if (forumPosts[i].Id == postId) { @@ -557,11 +475,7 @@ public virtual ForumPost GetPostById(int forumPostId) return null; } - var query = from fp in _forumPostRepository.Table - where fp.Id == forumPostId - select fp; - var forumPost = query.SingleOrDefault(); - + var forumPost = _forumPostRepository.Table.FirstOrDefault(x => x.Id == forumPostId); return forumPost; } @@ -576,7 +490,7 @@ public virtual IPagedList GetAllPosts(int forumTopicId, int customerI { query = query.Where(fp => customerId == fp.CustomerId); } - if (!String.IsNullOrEmpty(keywords)) + if (keywords.HasValue()) { query = query.Where(fp => fp.Text.Contains(keywords)); } @@ -596,39 +510,34 @@ public virtual IPagedList GetAllPosts(int forumTopicId, int customerI public virtual void InsertPost(ForumPost forumPost, bool sendNotifications) { - if (forumPost == null) - { - throw new ArgumentNullException("forumPost"); - } + Guard.NotNull(forumPost, nameof(forumPost)); _forumPostRepository.Insert(forumPost); - //update stats - int customerId = forumPost.CustomerId; - var forumTopic = this.GetTopicById(forumPost.TopicId); - int forumId = forumTopic.ForumId; + var forumTopic = GetTopicById(forumPost.TopicId); + UpdateForumTopicStats(forumPost.TopicId); - UpdateForumStats(forumId); - UpdateCustomerStats(customerId); + UpdateForumStats(forumTopic.ForumId); + UpdateCustomerStats(forumPost.CustomerId); - //notifications if (sendNotifications) { var forum = forumTopic.Forum; - var subscriptions = GetAllSubscriptions(0, 0, forumTopic.Id, 0, int.MaxValue); - var languageId = _services.WorkContext.WorkingLanguage.Id; + var subscriptions = GetAllSubscriptions(0, 0, forumTopic.Id, 0, int.MaxValue); + var friendlyTopicPageIndex = CalculateTopicPageIndex( + forumPost.TopicId, + _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 10, + forumPost.Id) + 1; - int friendlyTopicPageIndex = CalculateTopicPageIndex(forumPost.TopicId, _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 10, forumPost.Id) + 1; - - foreach (ForumSubscription subscription in subscriptions) + foreach (var subscription in subscriptions) { if (subscription.CustomerId == forumPost.CustomerId) { continue; } - if (!String.IsNullOrEmpty(subscription.Customer.Email)) + if (subscription.Customer.Email.HasValue()) { _services.MessageFactory.SendNewForumPostMessage(subscription.Customer, forumPost, friendlyTopicPageIndex, languageId); } @@ -638,11 +547,7 @@ public virtual void InsertPost(ForumPost forumPost, bool sendNotifications) public virtual void UpdatePost(ForumPost forumPost) { - //validation - if (forumPost == null) - { - throw new ArgumentNullException("forumPost"); - } + Guard.NotNull(forumPost, nameof(forumPost)); _forumPostRepository.Update(forumPost); } @@ -651,39 +556,37 @@ public virtual void DeletePost(ForumPost forumPost) { if (forumPost == null) { - throw new ArgumentNullException("forumPost"); + return; } - int forumTopicId = forumPost.TopicId; - int customerId = forumPost.CustomerId; - var forumTopic = this.GetTopicById(forumTopicId); - int forumId = forumTopic.ForumId; + var forumTopicId = forumPost.TopicId; + var customerId = forumPost.CustomerId; + var forumTopic = GetTopicById(forumTopicId); + var forumId = forumTopic.ForumId; - //delete topic if it was the first post - bool deleteTopic = false; + // Delete topic if it was the first post. + var deleteTopic = false; var firstPost = forumTopic.GetFirstPost(this); if (firstPost != null && firstPost.Id == forumPost.Id) { deleteTopic = true; } - //delete forum post + // Delete forum post. _forumPostRepository.Delete(forumPost); - //delete topic + // Delete topic. if (deleteTopic) { DeleteTopic(forumTopic); } - //update stats if (!deleteTopic) { UpdateForumTopicStats(forumTopicId); } UpdateForumStats(forumId); UpdateCustomerStats(customerId); - } #endregion @@ -697,11 +600,7 @@ public virtual PrivateMessage GetPrivateMessageById(int privateMessageId) return null; } - var query = from pm in _forumPrivateMessageRepository.Table - where pm.Id == privateMessageId - select pm; - var privateMessage = query.SingleOrDefault(); - + var privateMessage = _forumPrivateMessageRepository.Table.FirstOrDefault(x => x.Id == privateMessageId); return privateMessage; } @@ -718,20 +617,32 @@ public virtual IPagedList GetAllPrivateMessages( { var query = _forumPrivateMessageRepository.Table; - if (storeId > 0) - query = query.Where(pm => storeId == pm.StoreId); - if (fromCustomerId > 0) - query = query.Where(pm => fromCustomerId == pm.FromCustomerId); - if (toCustomerId > 0) - query = query.Where(pm => toCustomerId == pm.ToCustomerId); - if (isRead.HasValue) - query = query.Where(pm => isRead.Value == pm.IsRead); - if (isDeletedByAuthor.HasValue) - query = query.Where(pm => isDeletedByAuthor.Value == pm.IsDeletedByAuthor); - if (isDeletedByRecipient.HasValue) - query = query.Where(pm => isDeletedByRecipient.Value == pm.IsDeletedByRecipient); - - if (!String.IsNullOrEmpty(keywords)) + if (storeId > 0) + { + query = query.Where(pm => storeId == pm.StoreId); + } + if (fromCustomerId > 0) + { + query = query.Where(pm => fromCustomerId == pm.FromCustomerId); + } + if (toCustomerId > 0) + { + query = query.Where(pm => toCustomerId == pm.ToCustomerId); + } + if (isRead.HasValue) + { + query = query.Where(pm => isRead.Value == pm.IsRead); + } + if (isDeletedByAuthor.HasValue) + { + query = query.Where(pm => isDeletedByAuthor.Value == pm.IsDeletedByAuthor); + } + if (isDeletedByRecipient.HasValue) + { + query = query.Where(pm => isDeletedByRecipient.Value == pm.IsDeletedByRecipient); + } + + if (keywords.HasValue()) { query = query.Where(pm => pm.Subject.Contains(keywords)); query = query.Where(pm => pm.Text.Contains(keywords)); @@ -740,26 +651,12 @@ public virtual IPagedList GetAllPrivateMessages( query = query.OrderByDescending(pm => pm.CreatedOnUtc); var privateMessages = new PagedList(query, pageIndex, pageSize); - return privateMessages; } - public virtual void DeletePrivateMessage(PrivateMessage privateMessage) - { - if (privateMessage == null) - { - throw new ArgumentNullException("privateMessage"); - } - - _forumPrivateMessageRepository.Delete(privateMessage); - } - public virtual void InsertPrivateMessage(PrivateMessage privateMessage) { - if (privateMessage == null) - { - throw new ArgumentNullException("privateMessage"); - } + Guard.NotNull(privateMessage, nameof(privateMessage)); _forumPrivateMessageRepository.Insert(privateMessage); @@ -769,10 +666,8 @@ public virtual void InsertPrivateMessage(PrivateMessage privateMessage) throw new SmartException("Recipient could not be loaded"); } - //UI notification _genericAttributeService.SaveAttribute(customerTo, SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, false, privateMessage.StoreId); - //Email notification if (_forumSettings.NotifyAboutPrivateMessages) { _services.MessageFactory.SendPrivateMessageNotification(customerTo, privateMessage, _services.WorkContext.WorkingLanguage.Id); @@ -781,8 +676,7 @@ public virtual void InsertPrivateMessage(PrivateMessage privateMessage) public virtual void UpdatePrivateMessage(PrivateMessage privateMessage) { - if (privateMessage == null) - throw new ArgumentNullException("privateMessage"); + Guard.NotNull(privateMessage, nameof(privateMessage)); if (privateMessage.IsDeletedByAuthor && privateMessage.IsDeletedByRecipient) { @@ -794,6 +688,14 @@ public virtual void UpdatePrivateMessage(PrivateMessage privateMessage) } } + public virtual void DeletePrivateMessage(PrivateMessage privateMessage) + { + if (privateMessage != null) + { + _forumPrivateMessageRepository.Delete(privateMessage); + } + } + #endregion #region Subscription @@ -805,11 +707,7 @@ public virtual ForumSubscription GetSubscriptionById(int forumSubscriptionId) return null; } - var query = from fs in _forumSubscriptionRepository.Table - where fs.Id == forumSubscriptionId - select fs; - var forumSubscription = query.SingleOrDefault(); - + var forumSubscription = _forumSubscriptionRepository.Table.FirstOrDefault(x => x.Id == forumSubscriptionId); return forumSubscription; } @@ -837,44 +735,33 @@ where fsQuery.Contains(fs.SubscriptionGuid) public virtual void InsertSubscription(ForumSubscription forumSubscription) { - if (forumSubscription == null) - { - throw new ArgumentNullException("forumSubscription"); - } + Guard.NotNull(forumSubscription, nameof(forumSubscription)); _forumSubscriptionRepository.Insert(forumSubscription); } public virtual void UpdateSubscription(ForumSubscription forumSubscription) { - if (forumSubscription == null) - { - throw new ArgumentNullException("forumSubscription"); - } - + Guard.NotNull(forumSubscription, nameof(forumSubscription)); + _forumSubscriptionRepository.Update(forumSubscription); } public virtual void DeleteSubscription(ForumSubscription forumSubscription) { - if (forumSubscription == null) + if (forumSubscription != null) { - throw new ArgumentNullException("forumSubscription"); + _forumSubscriptionRepository.Delete(forumSubscription); } - - _forumSubscriptionRepository.Delete(forumSubscription); } #endregion + #region Customer + public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum forum) { - if (forum == null) - { - return false; - } - - if (customer == null) + if (forum == null || customer == null) { return false; } @@ -884,7 +771,7 @@ public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum foru return false; } - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } @@ -894,29 +781,19 @@ public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum foru public virtual bool IsCustomerAllowedToEditTopic(Customer customer, ForumTopic topic) { - if (topic == null) + if (topic == null || customer == null || customer.IsGuest()) { return false; } - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) - { - return false; - } - - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } if (_forumSettings.AllowCustomersToEditPosts) { - bool ownTopic = customer.Id == topic.CustomerId; + var ownTopic = customer.Id == topic.CustomerId; return ownTopic; } @@ -925,12 +802,7 @@ public virtual bool IsCustomerAllowedToEditTopic(Customer customer, ForumTopic t public virtual bool IsCustomerAllowedToMoveTopic(Customer customer, ForumTopic topic) { - if (topic == null) - { - return false; - } - - if (customer == null) + if (topic == null || customer == null) { return false; } @@ -940,7 +812,7 @@ public virtual bool IsCustomerAllowedToMoveTopic(Customer customer, ForumTopic t return false; } - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } @@ -950,29 +822,19 @@ public virtual bool IsCustomerAllowedToMoveTopic(Customer customer, ForumTopic t public virtual bool IsCustomerAllowedToDeleteTopic(Customer customer, ForumTopic topic) { - if (topic == null) - { - return false; - } - - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) + if (topic == null || customer == null || customer.IsGuest()) { return false; } - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } if (_forumSettings.AllowCustomersToDeletePosts) { - bool ownTopic = customer.Id == topic.CustomerId; + var ownTopic = customer.Id == topic.CustomerId; return ownTopic; } @@ -981,12 +843,7 @@ public virtual bool IsCustomerAllowedToDeleteTopic(Customer customer, ForumTopic public virtual bool IsCustomerAllowedToCreatePost(Customer customer, ForumTopic topic) { - if (topic == null) - { - return false; - } - - if (customer == null) + if (topic == null || customer == null) { return false; } @@ -1001,29 +858,19 @@ public virtual bool IsCustomerAllowedToCreatePost(Customer customer, ForumTopic public virtual bool IsCustomerAllowedToEditPost(Customer customer, ForumPost post) { - if (post == null) + if (post == null || customer == null || customer.IsGuest()) { return false; } - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) - { - return false; - } - - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } if (_forumSettings.AllowCustomersToEditPosts) { - bool ownPost = customer.Id == post.CustomerId; + var ownPost = customer.Id == post.CustomerId; return ownPost; } @@ -1032,29 +879,19 @@ public virtual bool IsCustomerAllowedToEditPost(Customer customer, ForumPost pos public virtual bool IsCustomerAllowedToDeletePost(Customer customer, ForumPost post) { - if (post == null) - { - return false; - } - - if (customer == null) + if (post == null || customer == null || customer.IsGuest()) { return false; } - if (customer.IsGuest()) - { - return false; - } - - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } if (_forumSettings.AllowCustomersToDeletePosts) { - bool ownPost = customer.Id == post.CustomerId; + var ownPost = customer.Id == post.CustomerId; return ownPost; } @@ -1063,17 +900,12 @@ public virtual bool IsCustomerAllowedToDeletePost(Customer customer, ForumPost p public virtual bool IsCustomerAllowedToSetTopicPriority(Customer customer) { - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) + if (customer == null || customer.IsGuest()) { return false; } - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } @@ -1083,17 +915,14 @@ public virtual bool IsCustomerAllowedToSetTopicPriority(Customer customer) public virtual bool IsCustomerAllowedToSubscribe(Customer customer) { - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) + if (customer == null || customer.IsGuest()) { return false; } return true; } + + #endregion } } diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index 9df126aa89..99bdf879b0 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -287,6 +287,8 @@ IPagedList GetAllSubscriptions(int customerId, int forumId, #endregion + #region Customer + /// /// Check whether customer is allowed to create new topics /// @@ -356,5 +358,7 @@ IPagedList GetAllSubscriptions(int customerId, int forumId, /// Customer /// True if allowed, otherwise false bool IsCustomerAllowedToSubscribe(Customer customer); + + #endregion } } diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 820ef0b51b..6b6fff51fd 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -59,6 +59,8 @@ public BoardsController( _breadcrumb = breadcrumb; } + #region Utilities + [NonAction] protected ForumTopicRowModel PrepareForumTopicRowModel(ForumTopic topic) { @@ -216,11 +218,15 @@ protected void CreateForumBreadcrumb(ForumGroup group = null, Forum forum = null } } + #endregion + + #region Forum group + public ActionResult Index() { if (!_forumSettings.ForumsEnabled) { - return HttpNotFound(); + return HttpNotFound(); } var store = Services.StoreContext.CurrentStore; @@ -240,103 +246,6 @@ public ActionResult Index() return View(model); } - [ChildActionOnly] - public ActionResult ActiveDiscussionsSmall() - { - if (!_forumSettings.ForumsEnabled) - { - return HttpNotFound(); - } - - int topicLimit = _forumSettings.HomePageActiveDiscussionsTopicCount; - var topics = _forumService.GetActiveTopics(0, topicLimit); - if (topics.Count == 0) - { - return new EmptyResult(); - } - - var model = new ActiveDiscussionsModel(); - - foreach (var topic in topics) - { - var topicModel = PrepareForumTopicRowModel(topic); - model.ForumTopics.Add(topicModel); - } - - model.ViewAllLinkEnabled = true; - model.ActiveDiscussionsFeedEnabled = _forumSettings.ActiveDiscussionsFeedEnabled; - model.PostsPageSize = _forumSettings.PostsPageSize; - - return PartialView("_ActiveTopics", model); - } - - public ActionResult ActiveDiscussions(int forumId = 0) - { - if (!_forumSettings.ForumsEnabled) - { - return HttpNotFound(); - } - - var topics = _forumService.GetActiveTopics(forumId, _forumSettings.ActiveDiscussionsPageTopicCount); - var model = new ActiveDiscussionsModel(); - - foreach (var topic in topics) - { - var topicModel = PrepareForumTopicRowModel(topic); - model.ForumTopics.Add(topicModel); - } - - model.ViewAllLinkEnabled = false; - model.ActiveDiscussionsFeedEnabled = _forumSettings.ActiveDiscussionsFeedEnabled; - model.PostsPageSize = _forumSettings.PostsPageSize; - - return View(model); - } - - [Compress] - public ActionResult ActiveDiscussionsRss(int forumId = 0) - { - if (!_forumSettings.ForumsEnabled) - { - return HttpNotFound(); - } - - var store = Services.StoreContext.CurrentStore; - var language = Services.WorkContext.WorkingLanguage; - var protocol = Services.WebHelper.IsCurrentConnectionSecured() ? "https" : "http"; - var selfLink = Url.Action("ActiveDiscussionsRSS", "Boards", null, protocol); - var discussionLink = Url.Action("ActiveDiscussions", "Boards", null, protocol); - - var title = "{0} - {1}".FormatInvariant(store.Name, T("Forum.ActiveDiscussionsFeedTitle")); - var feed = new SmartSyndicationFeed(new Uri(discussionLink), title, T("Forum.ActiveDiscussionsFeedDescription")); - - feed.AddNamespaces(false); - feed.Init(selfLink, language); - - if (!_forumSettings.ActiveDiscussionsFeedEnabled) - { - return new RssActionResult { Feed = feed }; - } - - var items = new List(); - var topics = _forumService.GetActiveTopics(forumId, _forumSettings.ActiveDiscussionsFeedCount); - var viewsText = T("Forum.Views"); - var repliesText = T("Forum.Replies"); - - foreach (var topic in topics) - { - var topicUrl = Url.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, protocol); - var synopsis = "{0}: {1}, {2}: {3}".FormatInvariant(repliesText, topic.NumReplies, viewsText, topic.Views); - - var item = feed.CreateItem(topic.Subject, synopsis, topicUrl, topic.LastPostTime ?? topic.UpdatedOnUtc); - items.Add(item); - } - - feed.Items = items; - - return new RssActionResult { Feed = feed }; - } - public ActionResult ForumGroup(int id) { if (!_forumSettings.ForumsEnabled) @@ -356,6 +265,10 @@ public ActionResult ForumGroup(int id) return View(model); } + #endregion + + #region Forum + public ActionResult Forum(int id, int page = 1) { if (!_forumSettings.ForumsEnabled) @@ -504,6 +417,111 @@ public ActionResult ForumWatch(int id) return Json(new { Subscribed = subscribed, Text = returnText, Error = false }); } + #endregion + + #region Active discussion + + [ChildActionOnly] + public ActionResult ActiveDiscussionsSmall() + { + if (!_forumSettings.ForumsEnabled) + { + return HttpNotFound(); + } + + int topicLimit = _forumSettings.HomePageActiveDiscussionsTopicCount; + var topics = _forumService.GetActiveTopics(0, topicLimit); + if (topics.Count == 0) + { + return new EmptyResult(); + } + + var model = new ActiveDiscussionsModel(); + + foreach (var topic in topics) + { + var topicModel = PrepareForumTopicRowModel(topic); + model.ForumTopics.Add(topicModel); + } + + model.ViewAllLinkEnabled = true; + model.ActiveDiscussionsFeedEnabled = _forumSettings.ActiveDiscussionsFeedEnabled; + model.PostsPageSize = _forumSettings.PostsPageSize; + + return PartialView("_ActiveTopics", model); + } + + public ActionResult ActiveDiscussions(int forumId = 0) + { + if (!_forumSettings.ForumsEnabled) + { + return HttpNotFound(); + } + + var topics = _forumService.GetActiveTopics(forumId, _forumSettings.ActiveDiscussionsPageTopicCount); + var model = new ActiveDiscussionsModel(); + + foreach (var topic in topics) + { + var topicModel = PrepareForumTopicRowModel(topic); + model.ForumTopics.Add(topicModel); + } + + model.ViewAllLinkEnabled = false; + model.ActiveDiscussionsFeedEnabled = _forumSettings.ActiveDiscussionsFeedEnabled; + model.PostsPageSize = _forumSettings.PostsPageSize; + + return View(model); + } + + [Compress] + public ActionResult ActiveDiscussionsRss(int forumId = 0) + { + if (!_forumSettings.ForumsEnabled) + { + return HttpNotFound(); + } + + var store = Services.StoreContext.CurrentStore; + var language = Services.WorkContext.WorkingLanguage; + var protocol = Services.WebHelper.IsCurrentConnectionSecured() ? "https" : "http"; + var selfLink = Url.Action("ActiveDiscussionsRSS", "Boards", null, protocol); + var discussionLink = Url.Action("ActiveDiscussions", "Boards", null, protocol); + + var title = "{0} - {1}".FormatInvariant(store.Name, T("Forum.ActiveDiscussionsFeedTitle")); + var feed = new SmartSyndicationFeed(new Uri(discussionLink), title, T("Forum.ActiveDiscussionsFeedDescription")); + + feed.AddNamespaces(false); + feed.Init(selfLink, language); + + if (!_forumSettings.ActiveDiscussionsFeedEnabled) + { + return new RssActionResult { Feed = feed }; + } + + var items = new List(); + var topics = _forumService.GetActiveTopics(forumId, _forumSettings.ActiveDiscussionsFeedCount); + var viewsText = T("Forum.Views"); + var repliesText = T("Forum.Replies"); + + foreach (var topic in topics) + { + var topicUrl = Url.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, protocol); + var synopsis = "{0}: {1}, {2}: {3}".FormatInvariant(repliesText, topic.NumReplies, viewsText, topic.Views); + + var item = feed.CreateItem(topic.Subject, synopsis, topicUrl, topic.LastPostTime ?? topic.UpdatedOnUtc); + items.Add(item); + } + + feed.Items = items; + + return new RssActionResult { Feed = feed }; + } + + #endregion + + #region Topic + public ActionResult Topic(int id, int page = 1) { if (!_forumSettings.ForumsEnabled) @@ -715,34 +733,6 @@ public ActionResult TopicMove(TopicMoveModel model) return RedirectToRoute("TopicSlug", new { id = forumTopic.Id, slug = forumTopic.GetSeName() }); } - public ActionResult TopicDelete(int id) - { - if (!_forumSettings.ForumsEnabled) - { - return HttpNotFound(); - } - - var forumTopic = _forumService.GetTopicById(id); - if (forumTopic != null) - { - if (!_forumService.IsCustomerAllowedToDeleteTopic(Services.WorkContext.CurrentCustomer, forumTopic)) - { - return new HttpUnauthorizedResult(); - } - - var forum = _forumService.GetForumById(forumTopic.ForumId); - - _forumService.DeleteTopic(forumTopic); - - if (forum != null) - { - return RedirectToRoute("ForumSlug", new { id = forum.Id, slug = forum.GetSeName() }); - } - } - - return RedirectToRoute("Boards"); - } - [GdprConsent] public ActionResult TopicCreate(int id) { @@ -1076,62 +1066,245 @@ public ActionResult TopicEdit(EditForumTopicModel model) return View(model); } - public ActionResult PostDelete(int id) + public ActionResult TopicDelete(int id) { if (!_forumSettings.ForumsEnabled) { - return HttpNotFound(); + return HttpNotFound(); } - var forumPost = _forumService.GetPostById(id); - if (forumPost != null) + var forumTopic = _forumService.GetTopicById(id); + if (forumTopic != null) { - if (!_forumService.IsCustomerAllowedToDeletePost(Services.WorkContext.CurrentCustomer, forumPost)) + if (!_forumService.IsCustomerAllowedToDeleteTopic(Services.WorkContext.CurrentCustomer, forumTopic)) { return new HttpUnauthorizedResult(); } - var forumTopic = forumPost.ForumTopic; - var forumId = forumTopic.Forum.Id; - var forumSlug = forumTopic.Forum.GetSeName(); - var url = string.Empty; + var forum = _forumService.GetForumById(forumTopic.ForumId); - _forumService.DeletePost(forumPost); + _forumService.DeleteTopic(forumTopic); - // Get topic one more time because it can be deleted (first or only post deleted). - forumTopic = _forumService.GetTopicById(forumPost.TopicId); - if (forumTopic == null) - { - return RedirectToRoute("ForumSlug", new { id = forumId, slug = forumSlug }); - } - else + if (forum != null) { - return RedirectToRoute("TopicSlug", new { id = forumTopic.Id, slug = forumTopic.GetSeName() }); + return RedirectToRoute("ForumSlug", new { id = forum.Id, slug = forum.GetSeName() }); } } return RedirectToRoute("Boards"); } - [GdprConsent] - public ActionResult PostCreate(int id, int? quote) + public ActionResult Search(string searchterms, bool? adv, string forumId, string within, string limitDays, int page = 1) { if (!_forumSettings.ForumsEnabled) { - return HttpNotFound(); + return HttpNotFound(); } - var customer = Services.WorkContext.CurrentCustomer; - var forumTopic = _forumService.GetTopicById(id); - if (forumTopic == null) - { - return RedirectToRoute("Boards"); - } + var pageSize = 10; + var store = Services.StoreContext.CurrentStore; + var model = new SearchModel(); - if (!_forumService.IsCustomerAllowedToCreatePost(customer, forumTopic)) + // Create the values for the "Limit results to previous" select list + var limitList = new List { - return new HttpUnauthorizedResult(); - } + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.AllResults"), + Value = "0" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.1day"), + Value = "1" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.7days"), + Value = "7" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.2weeks"), + Value = "14" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.1month"), + Value = "30" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.3months"), + Value = "92" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.6months"), + Value = "183" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.1year"), + Value = "365" + } + }; + + model.LimitList = limitList; + + // Create the values for the "Search in forum" select list. + var forumsSelectList = new List(); + forumsSelectList.Add(new SelectListItem + { + Text = T("Forum.Search.SearchInForum.All"), + Value = "0", + Selected = true, + }); + + var separator = "--"; + var forumGroups = _forumService.GetAllForumGroups(store.Id); + foreach (var fg in forumGroups) + { + // Add the forum group with value as '-' so it can't be used as a target forum id. + forumsSelectList.Add(new SelectListItem { Text = fg.GetLocalized(x => x.Name), Value = "-" }); + + var forums = _forumService.GetAllForumsByGroupId(fg.Id); + foreach (var f in forums) + { + forumsSelectList.Add(new SelectListItem + { + Text = string.Format("{0}{1}", separator, f.GetLocalized(x => x.Name)), + Value = f.Id.ToString() + }); + } + } + model.ForumList = forumsSelectList; + + // Create the values for "Search within" select list. + var withinList = new List + { + new SelectListItem + { + Value = ((int)ForumSearchType.All).ToString(), + Text = T("Forum.Search.SearchWithin.All") + }, + new SelectListItem + { + Value = ((int)ForumSearchType.TopicTitlesOnly).ToString(), + Text = T("Forum.Search.SearchWithin.TopicTitlesOnly") + }, + new SelectListItem + { + Value = ((int)ForumSearchType.PostTextOnly).ToString(), + Text = T("Forum.Search.SearchWithin.PostTextOnly") + } + }; + + model.WithinList = withinList; + + int.TryParse(forumId, out var forumIdSelected); + model.ForumIdSelected = forumIdSelected; + + int.TryParse(within, out var withinSelected); + model.WithinSelected = withinSelected; + + int.TryParse(limitDays, out var limitDaysSelected); + model.LimitDaysSelected = limitDaysSelected; + + model.ShowAdvancedSearch = adv.GetValueOrDefault(); + model.SearchResultsVisible = false; + model.NoResultsVisisble = false; + model.PostsPageSize = _forumSettings.PostsPageSize; + + try + { + if (String.IsNullOrWhiteSpace(searchterms) == false) + { + searchterms = searchterms.Trim(); + model.SearchTerms = searchterms; + + if (searchterms.Length < _forumSettings.ForumSearchTermMinimumLength) + { + throw new SmartException(T("Forum.SearchTermMinimumLengthIsNCharacters", _forumSettings.ForumSearchTermMinimumLength)); + } + + ForumSearchType searchWithin = 0; + var limitResultsToPrevious = 0; + if (adv.GetValueOrDefault() == true) + { + searchWithin = (ForumSearchType)withinSelected; + limitResultsToPrevious = limitDaysSelected; + } + + if (_forumSettings.SearchResultsPageSize > 0) + { + pageSize = _forumSettings.SearchResultsPageSize; + } + + var topics = _forumService.GetAllTopics(forumIdSelected, 0, searchterms, searchWithin, limitResultsToPrevious, page - 1, pageSize); + + model.TopicPageSize = topics.PageSize; + model.TopicTotalRecords = topics.TotalCount; + model.TopicPageIndex = topics.PageIndex; + + foreach (var topic in topics) + { + var topicModel = PrepareForumTopicRowModel(topic); + model.ForumTopics.Add(topicModel); + } + + model.SearchResultsVisible = (topics.Count > 0); + model.NoResultsVisisble = !(model.SearchResultsVisible); + + CreateForumBreadcrumb(); + _breadcrumb.Track(new MenuItem { Text = T("Forum.Search") }); + + return View(model); + } + else + { + model.SearchResultsVisible = false; + } + } + catch (Exception ex) + { + model.Error = ex.Message; + } + + // Some exception raised. + model.TopicPageSize = pageSize; + model.TopicTotalRecords = 0; + model.TopicPageIndex = page - 1; + + CreateForumBreadcrumb(); + _breadcrumb.Track(new MenuItem { Text = T("Forum.Search") }); + + return View(model); + } + + #endregion + + #region Forum post + + [GdprConsent] + public ActionResult PostCreate(int id, int? quote) + { + if (!_forumSettings.ForumsEnabled) + { + return HttpNotFound(); + } + + var customer = Services.WorkContext.CurrentCustomer; + var forumTopic = _forumService.GetTopicById(id); + if (forumTopic == null) + { + return RedirectToRoute("Boards"); + } + + if (!_forumService.IsCustomerAllowedToCreatePost(customer, forumTopic)) + { + return new HttpUnauthorizedResult(); + } var forum = forumTopic.Forum; if (forum == null) @@ -1468,192 +1641,41 @@ public ActionResult PostEdit(EditForumPostModel model) return View(model); } - public ActionResult Search(string searchterms, bool? adv, string forumId, string within, string limitDays, int page = 1) + public ActionResult PostDelete(int id) { if (!_forumSettings.ForumsEnabled) { - return HttpNotFound(); - } - - var pageSize = 10; - var store = Services.StoreContext.CurrentStore; - var model = new SearchModel(); - - // Create the values for the "Limit results to previous" select list - var limitList = new List - { - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.AllResults"), - Value = "0" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.1day"), - Value = "1" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.7days"), - Value = "7" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.2weeks"), - Value = "14" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.1month"), - Value = "30" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.3months"), - Value = "92" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.6months"), - Value = "183" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.1year"), - Value = "365" - } - }; - - model.LimitList = limitList; - - // Create the values for the "Search in forum" select list. - var forumsSelectList = new List(); - forumsSelectList.Add(new SelectListItem - { - Text = T("Forum.Search.SearchInForum.All"), - Value = "0", - Selected = true, - }); - - var separator = "--"; - var forumGroups = _forumService.GetAllForumGroups(store.Id); - foreach (var fg in forumGroups) - { - // Add the forum group with value as '-' so it can't be used as a target forum id. - forumsSelectList.Add(new SelectListItem { Text = fg.GetLocalized(x => x.Name), Value = "-" }); - - var forums = _forumService.GetAllForumsByGroupId(fg.Id); - foreach (var f in forums) - { - forumsSelectList.Add(new SelectListItem - { - Text = string.Format("{0}{1}", separator, f.GetLocalized(x => x.Name)), - Value = f.Id.ToString() - }); - } + return HttpNotFound(); } - model.ForumList = forumsSelectList; - // Create the values for "Search within" select list. - var withinList = new List + var forumPost = _forumService.GetPostById(id); + if (forumPost != null) { - new SelectListItem - { - Value = ((int)ForumSearchType.All).ToString(), - Text = T("Forum.Search.SearchWithin.All") - }, - new SelectListItem - { - Value = ((int)ForumSearchType.TopicTitlesOnly).ToString(), - Text = T("Forum.Search.SearchWithin.TopicTitlesOnly") - }, - new SelectListItem + if (!_forumService.IsCustomerAllowedToDeletePost(Services.WorkContext.CurrentCustomer, forumPost)) { - Value = ((int)ForumSearchType.PostTextOnly).ToString(), - Text = T("Forum.Search.SearchWithin.PostTextOnly") + return new HttpUnauthorizedResult(); } - }; - - model.WithinList = withinList; - int.TryParse(forumId, out var forumIdSelected); - model.ForumIdSelected = forumIdSelected; - - int.TryParse(within, out var withinSelected); - model.WithinSelected = withinSelected; - - int.TryParse(limitDays, out var limitDaysSelected); - model.LimitDaysSelected = limitDaysSelected; + var forumTopic = forumPost.ForumTopic; + var forumId = forumTopic.Forum.Id; + var forumSlug = forumTopic.Forum.GetSeName(); + var url = string.Empty; - model.ShowAdvancedSearch = adv.GetValueOrDefault(); - model.SearchResultsVisible = false; - model.NoResultsVisisble = false; - model.PostsPageSize = _forumSettings.PostsPageSize; + _forumService.DeletePost(forumPost); - try - { - if (String.IsNullOrWhiteSpace(searchterms) == false) + // Get topic one more time because it can be deleted (first or only post deleted). + forumTopic = _forumService.GetTopicById(forumPost.TopicId); + if (forumTopic == null) { - searchterms = searchterms.Trim(); - model.SearchTerms = searchterms; - - if (searchterms.Length < _forumSettings.ForumSearchTermMinimumLength) - { - throw new SmartException(T("Forum.SearchTermMinimumLengthIsNCharacters", _forumSettings.ForumSearchTermMinimumLength)); - } - - ForumSearchType searchWithin = 0; - var limitResultsToPrevious = 0; - if (adv.GetValueOrDefault() == true) - { - searchWithin = (ForumSearchType)withinSelected; - limitResultsToPrevious = limitDaysSelected; - } - - if (_forumSettings.SearchResultsPageSize > 0) - { - pageSize = _forumSettings.SearchResultsPageSize; - } - - var topics = _forumService.GetAllTopics(forumIdSelected, 0, searchterms, searchWithin, limitResultsToPrevious, page - 1, pageSize); - - model.TopicPageSize = topics.PageSize; - model.TopicTotalRecords = topics.TotalCount; - model.TopicPageIndex = topics.PageIndex; - - foreach (var topic in topics) - { - var topicModel = PrepareForumTopicRowModel(topic); - model.ForumTopics.Add(topicModel); - } - - model.SearchResultsVisible = (topics.Count > 0); - model.NoResultsVisisble = !(model.SearchResultsVisible); - - CreateForumBreadcrumb(); - _breadcrumb.Track(new MenuItem { Text = T("Forum.Search") }); - - return View(model); + return RedirectToRoute("ForumSlug", new { id = forumId, slug = forumSlug }); } else { - model.SearchResultsVisible = false; + return RedirectToRoute("TopicSlug", new { id = forumTopic.Id, slug = forumTopic.GetSeName() }); } } - catch (Exception ex) - { - model.Error = ex.Message; - } - // Some exception raised. - model.TopicPageSize = pageSize; - model.TopicTotalRecords = 0; - model.TopicPageIndex = page - 1; - - CreateForumBreadcrumb(); - _breadcrumb.Track(new MenuItem { Text = T("Forum.Search") }); - - return View(model); + return RedirectToRoute("Boards"); } [ChildActionOnly] @@ -1680,5 +1702,7 @@ public ActionResult LastPost(int forumPostId, bool showTopic) return PartialView(model); } + + #endregion } } From d0d0b79a27044812a1a179a4ed2999852ec817f8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 29 Aug 2018 10:21:00 +0200 Subject: [PATCH 061/932] Minor change --- src/Libraries/SmartStore.Core/Search/IndexInfo.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Search/IndexInfo.cs b/src/Libraries/SmartStore.Core/Search/IndexInfo.cs index 63291070c5..468754317f 100644 --- a/src/Libraries/SmartStore.Core/Search/IndexInfo.cs +++ b/src/Libraries/SmartStore.Core/Search/IndexInfo.cs @@ -15,12 +15,15 @@ public enum IndexingStatus public class IndexInfo { - public IndexInfo() + public IndexInfo(string scope) { + Guard.NotEmpty(scope, nameof(scope)); + + Scope = scope; Fields = new string[0]; } - public string Scope { get; set; } + public string Scope { get; private set; } public int DocumentCount { get; set; } public IEnumerable Fields { get; set; } @@ -52,9 +55,9 @@ public string ToXml() )).ToString(); } - public static IndexInfo FromXml(string xml) + public static IndexInfo FromXml(string xml, string scope) { - var info = new IndexInfo(); + var info = new IndexInfo(scope); try { From 6f9fdabdc51f2b2d54e81433dbf259299c273db8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 29 Aug 2018 15:01:08 +0200 Subject: [PATCH 062/932] Search: More on forum search --- .../Domain/Forums/ForumDateFilter.cs | 14 ++++++++++++ .../Domain/Forums/ForumTopicSorting.cs | 22 +++++++++---------- .../Search/Settings/ForumSearchSettings.cs | 2 +- .../SmartStore.Core/SmartStore.Core.csproj | 1 + .../Migrations/MigrationsConfiguration.cs | 19 +++++++++++----- .../Search/Forum/ForumSearchQuery.cs | 8 +++---- .../Search/Forum/LinqForumSearchService.cs | 14 ++++++------ .../Controllers/SettingController.cs | 3 ++- 8 files changed, 54 insertions(+), 29 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Domain/Forums/ForumDateFilter.cs diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumDateFilter.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumDateFilter.cs new file mode 100644 index 0000000000..4b6928e171 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumDateFilter.cs @@ -0,0 +1,14 @@ +namespace SmartStore.Core.Domain.Forums +{ + public enum ForumDateFilter + { + LastVisit = 1, + Yesterday, + LastWeek, + LastTwoWeeks, + LastMonth, + LastThreeMonths, + LastSixMonths, + LastYear + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs index 1038d4c969..014b2ac694 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs @@ -25,6 +25,16 @@ public enum ForumTopicSorting /// SubjectDesc, + /// + /// User name: A to Z + /// + UserNameAsc, + + /// + /// User name: Z to A + /// + UserNameDesc, + /// /// Creation date: Oldest first /// @@ -43,16 +53,6 @@ public enum ForumTopicSorting /// /// Number of posts: High to Low /// - PostsDesc, - - /// - /// Number of views: Low to High - /// - ViewsAsc, - - /// - /// Number of views: High to Low - /// - ViewsDesc + PostsDesc } } diff --git a/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs index 8a56d2901b..cb458c6df6 100644 --- a/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs +++ b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs @@ -9,7 +9,7 @@ public class ForumSearchSettings : ISettings public ForumSearchSettings() { SearchMode = SearchMode.Contains; - SearchFields = new List { "subject" }; + SearchFields = new List { "subject", "username" }; TopicSortOrder = ForumTopicSorting.Relevance; InstantSearchEnabled = true; InstantSearchNumberOfHits = 10; diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index 16c87dc91c..76f3ae488a 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -232,6 +232,7 @@ + diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 5eb382a7d1..02d2a2566a 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -373,17 +373,26 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.Initial", "Position", "Position"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.Relevance", "Relevance", "Beste Ergebnisse"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.SubjectAsc", "Subject: A to Z", "Thema: A bis Z"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.SubjectDesc", "Subject: Z to A", "Thema: Z bis A"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.SubjectAsc", "Title: A to Z", "Titel: A bis Z"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.SubjectDesc", "Title: Z to A", "Titel: Z bis A"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.UserNameAsc", "User name: A to Z", "Benutzername: A bis Z"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.UserNameDesc", "User name: Z to A", "Benutzername: Z bis A"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.CreatedOnAsc", "Created on: Oldest first", "Erstellt am: ältere zuerst"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.CreatedOnDesc", "Created on: Newest first", "Erstellt am: neuere zuerst"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.PostsAsc", "Post number: ascending", "Anzahl Beiträge: aufsteigend"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.PostsDesc", "Post number: descending", "Anzahl Beiträge: absteigend"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.ViewsAsc", "Hits number: ascending", "Trefferanzahl: aufsteigend"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.ViewsDesc", "Hits number: descending", "Trefferanzahl: absteigend"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastVisit", "Since last visit", "Seit dem letzten Besuch"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.Yesterday", "Yesterday", "Gestern"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastWeek", "Last week", "In der letzten Woche"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastTwoWeeks", "Last 2 weeks", "In den letzten 2 Wochen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastMonth", "Last month", "Im letzten Monat"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastThreeMonths", "Last 3 months", "In den letzten 3 Monaten"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastSixMonths", "Last 6 months", "In den letzten 6 Monaten"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastYear", "Last year", "Im letzten Jahr"); builder.AddOrUpdate("Search.Facet.Forum", "Forum", "Forum"); - builder.AddOrUpdate("Search.Facet.Customer", "Customer", "Kunde"); + builder.AddOrUpdate("Search.Facet.Customer", "Name", "Name"); builder.AddOrUpdate("Search.Facet.Date", "Date", "Datum"); builder.AddOrUpdate("Admin.Configuration.Settings.Search.WildcardSearchNote", diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs index a0d992ac99..bbf589b0eb 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs @@ -45,6 +45,10 @@ public ForumSearchQuery SortBy(ForumTopicSorting sort) case ForumTopicSorting.SubjectDesc: return SortBy(SearchSort.ByStringField("subject", sort == ForumTopicSorting.SubjectDesc)); + case ForumTopicSorting.UserNameAsc: + case ForumTopicSorting.UserNameDesc: + return SortBy(SearchSort.ByStringField("username", sort == ForumTopicSorting.UserNameDesc)); + case ForumTopicSorting.CreatedOnAsc: case ForumTopicSorting.CreatedOnDesc: return SortBy(SearchSort.ByDateTimeField("createdon", sort == ForumTopicSorting.CreatedOnDesc)); @@ -53,10 +57,6 @@ public ForumSearchQuery SortBy(ForumTopicSorting sort) case ForumTopicSorting.PostsDesc: return SortBy(SearchSort.ByIntField("numposts", sort == ForumTopicSorting.PostsDesc)); - case ForumTopicSorting.ViewsAsc: - case ForumTopicSorting.ViewsDesc: - return SortBy(SearchSort.ByIntField("views", sort == ForumTopicSorting.ViewsDesc)); - case ForumTopicSorting.Relevance: return SortBy(SearchSort.ByRelevance()); diff --git a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs index c84fbfccbf..760f7340ff 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -44,8 +44,6 @@ protected virtual IQueryable GetForumTopicQuery(ForumSearchQuery sea var filters = new List(); var query = baseQuery ?? _forumPostRepository.Table.Expand(x => x.ForumTopic); - //var searchType = ForumSearchType.All; //TODO, obsolete? - // Apply search term. if (term.HasValue() && fields != null && fields.Length != 0 && fields.Any(x => x.HasValue())) { @@ -53,12 +51,14 @@ protected virtual IQueryable GetForumTopicQuery(ForumSearchQuery sea { query = query.Where(x => (fields.Contains("subject") && x.ForumTopic.Subject.StartsWith(term)) || + (fields.Contains("username") && x.Customer.Username.StartsWith(term)) || (fields.Contains("text") && x.Text.StartsWith(term))); } else { query = query.Where(x => (fields.Contains("subject") && x.ForumTopic.Subject.Contains(term)) || + (fields.Contains("username") && x.Customer.Username.Contains(term)) || (fields.Contains("text") && x.Text.Contains(term))); } } @@ -100,7 +100,7 @@ protected virtual IQueryable GetForumTopicQuery(ForumSearchQuery sea } else if (filter.FieldName == "customerId") { - query = query.Where(x => x.ForumTopic.CustomerId == (int)filter.Term); + query = query.Where(x => x.CustomerId == (int)filter.Term); } else if (filter.FieldName == "createdon") { @@ -141,6 +141,10 @@ orderby grp.Key { topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.Subject, sort.Descending); } + else if (sort.FieldName == "username") + { + topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.Customer.Username, sort.Descending); + } else if (sort.FieldName == "createdon") { topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.CreatedOnUtc, sort.Descending); @@ -149,10 +153,6 @@ orderby grp.Key { topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.NumPosts, sort.Descending); } - else if (sort.FieldName == "views") - { - topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.Views, sort.Descending); - } } if (!ordered) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index 0f82f9db9a..587e67192c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -1442,7 +1442,8 @@ public ActionResult Search() model.ForumSearchSettings.AvailableSearchFields = new List { - new SelectListItem { Text = T("Forum.TopicTitle"), Value = "subject" } + new SelectListItem { Text = T("Forum.TopicTitle"), Value = "subject" }, + new SelectListItem { Text = T("Admin.Customers.Customers.Fields.Username"), Value = "username" } }; // Common facets. From 829663671f24ab7a4ecd9742bf110a8475dd771e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 30 Aug 2018 16:25:49 +0200 Subject: [PATCH 063/932] Search: More on forum search --- .../Customers/SystemCustomerAttributeNames.cs | 1 + .../Domain/Forums/ForumDateFilter.cs | 16 +- .../SmartStore.Core/Search/ISearchQuery.cs | 8 +- .../SmartStore.Core/Search/SearchQuery.cs | 17 +- .../Search/Settings/ForumSearchSettings.cs | 6 + .../Search/Extensions/FacetUtility.cs | 37 +- .../Search/Forum/ForumSearchService.cs | 42 +- .../Search/Forum/LinqForumSearchService.cs | 32 +- .../Modelling/ForumSearchQueryFactory.cs | 20 +- .../Controllers/BoardsController.cs | 507 +++++++++++------- .../DefaultFacetTemplateSelector.cs | 2 - .../Models/Boards/ForumSearchResultModel.cs | 33 ++ .../SmartStore.Web/SmartStore.Web.csproj | 3 +- .../Views/Boards/Partials/Filters.cshtml | 70 +++ .../Views/Boards/Partials/_SearchBox.cshtml | 6 +- .../SmartStore.Web/Views/Boards/Search.cshtml | 216 +++++++- .../Views/Search/FacetTemplates/Date.cshtml | 49 -- .../Public/Infrastructure/RoutesTests.cs | 3 +- 18 files changed, 735 insertions(+), 333 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs create mode 100644 src/Presentation/SmartStore.Web/Views/Boards/Partials/Filters.cshtml delete mode 100644 src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/Date.cshtml diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerAttributeNames.cs b/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerAttributeNames.cs index d7331453a6..445bc3b022 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerAttributeNames.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerAttributeNames.cs @@ -23,6 +23,7 @@ public static partial class SystemCustomerAttributeNames public static string CheckoutAttributes { get { return "CheckoutAttributes"; } } public static string AvatarPictureId { get { return "AvatarPictureId"; } } public static string ForumPostCount { get { return "ForumPostCount"; } } + public static string LastForumVisit { get { return "LastForumVisit"; } } public static string Signature { get { return "Signature"; } } public static string PasswordRecoveryToken { get { return "PasswordRecoveryToken"; } } public static string AccountActivationToken { get { return "AccountActivationToken"; } } diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumDateFilter.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumDateFilter.cs index 4b6928e171..47fe21281b 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumDateFilter.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumDateFilter.cs @@ -2,13 +2,13 @@ { public enum ForumDateFilter { - LastVisit = 1, - Yesterday, - LastWeek, - LastTwoWeeks, - LastMonth, - LastThreeMonths, - LastSixMonths, - LastYear + LastVisit = 0, + Yesterday = 1, + LastWeek = 7, + LastTwoWeeks = 14, + LastMonth = 30, + LastThreeMonths = 92, + LastSixMonths = 183, + LastYear = 365 } } diff --git a/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs b/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs index cf1bf1cca3..0b978dc2ec 100644 --- a/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs +++ b/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Search.Facets; namespace SmartStore.Core.Search @@ -9,10 +10,11 @@ public interface ISearchQuery int? LanguageId { get; } string LanguageCulture { get; } string CurrencyCode { get; } - int? StoreId { get; } + int? CustomerId { get; } + int? StoreId { get; } - // Search term - string[] Fields { get; } + // Search term + string[] Fields { get; } string Term { get; } SearchMode Mode { get; } bool EscapeTerm { get; } diff --git a/src/Libraries/SmartStore.Core/Search/SearchQuery.cs b/src/Libraries/SmartStore.Core/Search/SearchQuery.cs index 6cd4f78b9e..a166cc4846 100644 --- a/src/Libraries/SmartStore.Core/Search/SearchQuery.cs +++ b/src/Libraries/SmartStore.Core/Search/SearchQuery.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Search.Facets; @@ -65,10 +66,11 @@ protected SearchQuery(string[] fields, string term, SearchMode mode = SearchMode public int? LanguageId { get; protected set; } public string LanguageCulture { get; protected set; } public string CurrencyCode { get; protected set; } - public int? StoreId { get; protected set; } + public int? CustomerId { get; protected set; } + public int? StoreId { get; protected set; } - // Search term - public string[] Fields { get; set; } + // Search term + public string[] Fields { get; set; } public string Term { get; set; } public bool EscapeTerm { get; protected set; } public SearchMode Mode { get; protected set; } @@ -151,6 +153,15 @@ public TQuery WithCurrency(Currency currency) return (this as TQuery); } + public TQuery WithCustomer(Customer customer) + { + Guard.NotNull(customer, nameof(customer)); + + CustomerId = customer.Id; + + return (this as TQuery); + } + public TQuery Slice(int skip, int take) { Guard.NotNegative(skip, nameof(skip)); diff --git a/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs index cb458c6df6..e43da55e94 100644 --- a/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs +++ b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs @@ -16,6 +16,7 @@ public ForumSearchSettings() InstantSearchTermMinLength = 2; FilterMinHitCount = 1; FilterMaxChoicesCount = 20; + DefaultSortOrder = ForumTopicSorting.Relevance; ForumDisplayOrder = 1; CustomerDisplayOrder = 2; @@ -62,6 +63,11 @@ public ForumSearchSettings() /// public int FilterMaxChoicesCount { get; set; } + /// + /// Gets or sets the default sort order in search results + /// + public ForumTopicSorting DefaultSortOrder { get; set; } + #region Common facet settings public bool ForumDisabled { get; set; } diff --git a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs index 43eed05af3..723063308c 100644 --- a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs +++ b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs @@ -7,6 +7,7 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; +using SmartStore.Services.Common; namespace SmartStore.Services.Search.Extensions { @@ -140,18 +141,44 @@ public static IEnumerable GetRatings() } } - public static List GetForumDates() + public static List GetForumDates( + IGenericAttributeService genericAttributeService, + int customerId, + int storeId, + bool newer = true) { var count = 0; var utcNow = DateTime.UtcNow; var result = new List(); - foreach (var day in new int[] { 1, 7, 14, 30, 92, 183, 365 }) + foreach (ForumDateFilter filter in Enum.GetValues(typeof(ForumDateFilter))) { - result.Add(new FacetValue(utcNow.AddDays(-day), null, IndexTypeCode.DateTime, true, false) + var days = (int)filter; + if (filter == ForumDateFilter.LastVisit && customerId != 0) + { + var lastVisit = genericAttributeService.GetAttribute(nameof(Customer), customerId, SystemCustomerAttributeNames.LastForumVisit, storeId); + if (!lastVisit.HasValue) + { + continue; + } + + days = (int)(utcNow - lastVisit.Value).TotalDays; + } + + if (newer) + { + result.Add(new FacetValue(utcNow.AddDays(-days), null, IndexTypeCode.DateTime, true, false) + { + DisplayOrder = ++count + }); + } + else { - DisplayOrder = ++count - }); + result.Add(new FacetValue(null, utcNow.AddDays(-days), IndexTypeCode.DateTime, false, true) + { + DisplayOrder = ++count + }); + } } return result; diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs index a1c8f7a60e..2efbd734a9 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs @@ -55,7 +55,7 @@ protected virtual ForumSearchResult SearchDirect(ForumSearchQuery searchQuery) protected virtual void ApplyFacetLabels(IDictionary facets) { - if (facets == null || facets.Count == 0) + if (facets == null || !facets.Any()) { return; } @@ -63,34 +63,26 @@ protected virtual void ApplyFacetLabels(IDictionary facets) // Apply "date" labels. if (facets.TryGetValue("createdon", out var grp)) { + var foundLastVisit = false; var utcNow = DateTime.UtcNow; foreach (var facet in grp.Facets) { - var dt = (DateTime)facet.Value.Value; - var days = (utcNow - dt).TotalDays; - switch (days) + var dt = (DateTime)(facet.Value.IncludesUpper ? facet.Value.UpperValue : facet.Value.Value); + var days = (int)(utcNow - dt).TotalDays; + + foreach (ForumDateFilter filter in Enum.GetValues(typeof(ForumDateFilter))) { - case 1: - facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.1day"); - break; - case 7: - facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.7days"); - break; - case 14: - facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.2weeks"); - break; - case 30: - facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.1month"); - break; - case 92: - facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.3months"); - break; - case 183: - facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.6months"); - break; - case 365: - facet.Value.Label = T("Forum.Search.LimitResultsToPrevious.1year"); + if (days == (int)filter) + { + facet.Value.Label = T("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter." + filter.ToString()); break; + } + } + + if (!foundLastVisit && facet.Value.Label.IsEmpty()) + { + foundLastVisit = true; + facet.Value.Label = T("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastVisit"); } } } @@ -206,7 +198,7 @@ public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = fals return result; } - else if (searchQuery.Origin.IsCaseInsensitiveEqual("Search/Forum")) + else if (searchQuery.Origin.IsCaseInsensitiveEqual("Boards/Search")) { IndexingRequiredNotification(_services, _urlHelper); } diff --git a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs index 760f7340ff..f565ee263e 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -7,6 +7,7 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; +using SmartStore.Services.Common; using SmartStore.Services.Forums; using SmartStore.Services.Localization; using SmartStore.Services.Search.Extensions; @@ -18,17 +19,20 @@ public partial class LinqForumSearchService : SearchServiceBase, IForumSearchSer private readonly IRepository _forumPostRepository; private readonly IRepository _storeMappingRepository; private readonly IForumService _forumService; + private readonly IGenericAttributeService _genericAttributeService; private readonly ICommonServices _services; public LinqForumSearchService( IRepository forumPostRepository, IRepository storeMappingRepository, IForumService forumService, + IGenericAttributeService genericAttributeService, ICommonServices services) { _forumPostRepository = forumPostRepository; _storeMappingRepository = storeMappingRepository; _forumService = forumService; + _genericAttributeService = genericAttributeService; _services = services; QuerySettings = DbQuerySettings.Default; @@ -94,11 +98,11 @@ protected virtual IQueryable GetForumTopicQuery(ForumSearchQuery sea } } } - else if (filter.FieldName == "forumId") + else if (filter.FieldName == "forumid") { query = query.Where(x => x.ForumTopic.ForumId == (int)filter.Term); } - else if (filter.FieldName == "customerId") + else if (filter.FieldName == "customerid") { query = query.Where(x => x.CustomerId == (int)filter.Term); } @@ -241,37 +245,15 @@ protected virtual IDictionary GetFacets(ForumSearchQuery sea } else if (kind == FacetGroupKind.Date) { - var hasActivePredefinedFacet = false; - var dates = FacetUtility.GetForumDates(); + var dates = FacetUtility.GetForumDates(_genericAttributeService, searchQuery.CustomerId ?? 0, storeId, true); foreach (var date in dates) { var newFacet = new Facet(date); newFacet.Value.IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(date.Value)); - if (newFacet.Value.IsSelected) - { - hasActivePredefinedFacet = true; - } - facets.Add(newFacet); } - - // Add facet for custom date range. - var dateDescriptorValue = descriptor.Values.FirstOrDefault(); - var customDateValue = new FacetValue( - dateDescriptorValue != null && !hasActivePredefinedFacet ? dateDescriptorValue.Value : null, - dateDescriptorValue != null && !hasActivePredefinedFacet ? dateDescriptorValue.UpperValue : null, - IndexTypeCode.DateTime, - true, - true); - - customDateValue.IsSelected = customDateValue.Value != null || customDateValue.UpperValue != null; - - if (!(totalHits == 0 && !customDateValue.IsSelected)) - { - facets.Insert(0, new Facet("custom", customDateValue)); - } } if (facets.Any(x => x.Published)) diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs index 1576b1c100..adcb202f45 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs @@ -66,14 +66,16 @@ public ForumSearchQuery CreateFromQuery() var origin = "{0}{1}/{2}".FormatInvariant(area == null ? "" : area + "/", controller, action); var fields = new List { "text" }; var term = GetValueFor("q"); - var isInstantSearch = origin.IsCaseInsensitiveEqual("Search/InstantSearch"); + var isInstantSearch = origin.IsCaseInsensitiveEqual("Boards/InstantSearch"); fields.AddRange(_searchSettings.SearchFields); var query = new ForumSearchQuery(fields.ToArray(), term, _searchSettings.SearchMode) .OriginatesFrom(origin) .WithLanguage(_services.WorkContext.WorkingLanguage) - .WithCurrency(_services.WorkContext.WorkingCurrency); + .WithCurrency(_services.WorkContext.WorkingCurrency) + .WithCustomer(_services.WorkContext.CurrentCustomer) + .BuildFacetMap(!isInstantSearch); // Store. if (!QuerySettings.IgnoreMultiStore) @@ -100,9 +102,15 @@ protected virtual void ConvertPagingSorting(ForumSearchQuery query, RouteData ro { var index = Math.Max(1, GetValueFor("i") ?? 1); var size = _forumSettings.SearchResultsPageSize; - var orderBy = GetValueFor("o"); query.Slice((index - 1) * size, size); + + var orderBy = GetValueFor("o"); + if (orderBy == null || orderBy == ForumTopicSorting.Initial) + { + orderBy = _searchSettings.DefaultSortOrder; + } + query.SortBy(orderBy.Value); query.CustomData["CurrentSortOrder"] = orderBy.Value; } @@ -149,12 +157,6 @@ private void AddFacet( protected virtual void ConvertForum(ForumSearchQuery query, RouteData routeData, string origin) { - if (origin == "Forum/Forum") - { - // We don't need forum facetting in forum pages. - return; - } - List ids; if (GetValueFor(query, "f", FacetGroupKind.Forum, out ids) && ids != null && ids.Any()) diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 6b6fff51fd..4720d33576 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -8,6 +8,7 @@ using SmartStore.Core.Domain.Media; using SmartStore.Core.Html; using SmartStore.Core.Logging; +using SmartStore.Core.Search; using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.Directory; @@ -15,6 +16,8 @@ using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Media; +using SmartStore.Services.Search; +using SmartStore.Services.Search.Rendering; using SmartStore.Services.Seo; using SmartStore.Utilities; using SmartStore.Web.Framework; @@ -24,6 +27,7 @@ using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.UI; using SmartStore.Web.Models.Boards; +using SmartStore.Web.Models.Search; namespace SmartStore.Web.Controllers { @@ -33,30 +37,42 @@ public partial class BoardsController : PublicControllerBase private readonly IForumService _forumService; private readonly IPictureService _pictureService; private readonly ICountryService _countryService; + private readonly IForumSearchService _forumSearchService; + private readonly IGenericAttributeService _genericAttributeService; private readonly ForumSettings _forumSettings; + private readonly ForumSearchSettings _searchSettings; private readonly CustomerSettings _customerSettings; private readonly MediaSettings _mediaSettings; private readonly IDateTimeHelper _dateTimeHelper; private readonly IBreadcrumb _breadcrumb; + private readonly Lazy _templateProvider; - public BoardsController( + public BoardsController( IForumService forumService, IPictureService pictureService, ICountryService countryService, + IForumSearchService forumSearchService, + IGenericAttributeService genericAttributeService, ForumSettings forumSettings, + ForumSearchSettings searchSettings, CustomerSettings customerSettings, MediaSettings mediaSettings, IDateTimeHelper dateTimeHelper, - IBreadcrumb breadcrumb) + IBreadcrumb breadcrumb, + Lazy templateProvider) { _forumService = forumService; _pictureService = pictureService; _countryService = countryService; + _forumSearchService = forumSearchService; + _genericAttributeService = genericAttributeService; _forumSettings = forumSettings; + _searchSettings = searchSettings; _customerSettings = customerSettings; _mediaSettings = mediaSettings; _dateTimeHelper = dateTimeHelper; _breadcrumb = breadcrumb; + _templateProvider = templateProvider; } #region Utilities @@ -218,6 +234,25 @@ protected void CreateForumBreadcrumb(ForumGroup group = null, Forum forum = null } } + private void SaveLastForumVisit(Customer customer) + { + try + { + if (!customer.IsSystemAccount) + { + _genericAttributeService.SaveAttribute( + customer, + SystemCustomerAttributeNames.LastForumVisit, + DateTime.UtcNow, + Services.StoreContext.CurrentStore.Id); + } + } + catch (Exception ex) + { + Logger.Error(ex); + } + } + #endregion #region Forum group @@ -318,6 +353,7 @@ public ActionResult Forum(int id, int page = 1) model.PostsPageSize = _forumSettings.PostsPageSize; CreateForumBreadcrumb(forum: forum); + SaveLastForumVisit(customer); return View(model); } @@ -545,7 +581,7 @@ public ActionResult Topic(int id, int page = 1) // Update view count. try { - if (!customer.IsSearchEngineAccount()) + if (!customer.IsSystemAccount) { forumTopic.Views += 1; _forumService.UpdateTopic(forumTopic); @@ -633,8 +669,9 @@ public ActionResult Topic(int id, int page = 1) } CreateForumBreadcrumb(topic: forumTopic); + SaveLastForumVisit(customer); - return View(model); + return View(model); } return RedirectToRoute("Boards"); @@ -1094,194 +1131,6 @@ public ActionResult TopicDelete(int id) return RedirectToRoute("Boards"); } - public ActionResult Search(string searchterms, bool? adv, string forumId, string within, string limitDays, int page = 1) - { - if (!_forumSettings.ForumsEnabled) - { - return HttpNotFound(); - } - - var pageSize = 10; - var store = Services.StoreContext.CurrentStore; - var model = new SearchModel(); - - // Create the values for the "Limit results to previous" select list - var limitList = new List - { - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.AllResults"), - Value = "0" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.1day"), - Value = "1" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.7days"), - Value = "7" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.2weeks"), - Value = "14" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.1month"), - Value = "30" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.3months"), - Value = "92" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.6months"), - Value = "183" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.1year"), - Value = "365" - } - }; - - model.LimitList = limitList; - - // Create the values for the "Search in forum" select list. - var forumsSelectList = new List(); - forumsSelectList.Add(new SelectListItem - { - Text = T("Forum.Search.SearchInForum.All"), - Value = "0", - Selected = true, - }); - - var separator = "--"; - var forumGroups = _forumService.GetAllForumGroups(store.Id); - foreach (var fg in forumGroups) - { - // Add the forum group with value as '-' so it can't be used as a target forum id. - forumsSelectList.Add(new SelectListItem { Text = fg.GetLocalized(x => x.Name), Value = "-" }); - - var forums = _forumService.GetAllForumsByGroupId(fg.Id); - foreach (var f in forums) - { - forumsSelectList.Add(new SelectListItem - { - Text = string.Format("{0}{1}", separator, f.GetLocalized(x => x.Name)), - Value = f.Id.ToString() - }); - } - } - model.ForumList = forumsSelectList; - - // Create the values for "Search within" select list. - var withinList = new List - { - new SelectListItem - { - Value = ((int)ForumSearchType.All).ToString(), - Text = T("Forum.Search.SearchWithin.All") - }, - new SelectListItem - { - Value = ((int)ForumSearchType.TopicTitlesOnly).ToString(), - Text = T("Forum.Search.SearchWithin.TopicTitlesOnly") - }, - new SelectListItem - { - Value = ((int)ForumSearchType.PostTextOnly).ToString(), - Text = T("Forum.Search.SearchWithin.PostTextOnly") - } - }; - - model.WithinList = withinList; - - int.TryParse(forumId, out var forumIdSelected); - model.ForumIdSelected = forumIdSelected; - - int.TryParse(within, out var withinSelected); - model.WithinSelected = withinSelected; - - int.TryParse(limitDays, out var limitDaysSelected); - model.LimitDaysSelected = limitDaysSelected; - - model.ShowAdvancedSearch = adv.GetValueOrDefault(); - model.SearchResultsVisible = false; - model.NoResultsVisisble = false; - model.PostsPageSize = _forumSettings.PostsPageSize; - - try - { - if (String.IsNullOrWhiteSpace(searchterms) == false) - { - searchterms = searchterms.Trim(); - model.SearchTerms = searchterms; - - if (searchterms.Length < _forumSettings.ForumSearchTermMinimumLength) - { - throw new SmartException(T("Forum.SearchTermMinimumLengthIsNCharacters", _forumSettings.ForumSearchTermMinimumLength)); - } - - ForumSearchType searchWithin = 0; - var limitResultsToPrevious = 0; - if (adv.GetValueOrDefault() == true) - { - searchWithin = (ForumSearchType)withinSelected; - limitResultsToPrevious = limitDaysSelected; - } - - if (_forumSettings.SearchResultsPageSize > 0) - { - pageSize = _forumSettings.SearchResultsPageSize; - } - - var topics = _forumService.GetAllTopics(forumIdSelected, 0, searchterms, searchWithin, limitResultsToPrevious, page - 1, pageSize); - - model.TopicPageSize = topics.PageSize; - model.TopicTotalRecords = topics.TotalCount; - model.TopicPageIndex = topics.PageIndex; - - foreach (var topic in topics) - { - var topicModel = PrepareForumTopicRowModel(topic); - model.ForumTopics.Add(topicModel); - } - - model.SearchResultsVisible = (topics.Count > 0); - model.NoResultsVisisble = !(model.SearchResultsVisible); - - CreateForumBreadcrumb(); - _breadcrumb.Track(new MenuItem { Text = T("Forum.Search") }); - - return View(model); - } - else - { - model.SearchResultsVisible = false; - } - } - catch (Exception ex) - { - model.Error = ex.Message; - } - - // Some exception raised. - model.TopicPageSize = pageSize; - model.TopicTotalRecords = 0; - model.TopicPageIndex = page - 1; - - CreateForumBreadcrumb(); - _breadcrumb.Track(new MenuItem { Text = T("Forum.Search") }); - - return View(model); - } - #endregion #region Forum post @@ -1704,5 +1553,279 @@ public ActionResult LastPost(int forumPostId, bool showTopic) } #endregion + + #region Search + + public ActionResult Search_Old(string searchterms, bool? adv, string forumId, string within, string limitDays, int page = 1) + { + if (!_forumSettings.ForumsEnabled) + { + return HttpNotFound(); + } + + var pageSize = 10; + var store = Services.StoreContext.CurrentStore; + var model = new SearchModel(); + + // Create the values for the "Limit results to previous" select list + var limitList = new List + { + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.AllResults"), + Value = "0" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.1day"), + Value = "1" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.7days"), + Value = "7" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.2weeks"), + Value = "14" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.1month"), + Value = "30" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.3months"), + Value = "92" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.6months"), + Value = "183" + }, + new SelectListItem + { + Text = T("Forum.Search.LimitResultsToPrevious.1year"), + Value = "365" + } + }; + + model.LimitList = limitList; + + // Create the values for the "Search in forum" select list. + var forumsSelectList = new List(); + forumsSelectList.Add(new SelectListItem + { + Text = T("Forum.Search.SearchInForum.All"), + Value = "0", + Selected = true, + }); + + var separator = "--"; + var forumGroups = _forumService.GetAllForumGroups(store.Id); + foreach (var fg in forumGroups) + { + // Add the forum group with value as '-' so it can't be used as a target forum id. + forumsSelectList.Add(new SelectListItem { Text = fg.GetLocalized(x => x.Name), Value = "-" }); + + var forums = _forumService.GetAllForumsByGroupId(fg.Id); + foreach (var f in forums) + { + forumsSelectList.Add(new SelectListItem + { + Text = string.Format("{0}{1}", separator, f.GetLocalized(x => x.Name)), + Value = f.Id.ToString() + }); + } + } + model.ForumList = forumsSelectList; + + // Create the values for "Search within" select list. + var withinList = new List + { + new SelectListItem + { + Value = ((int)ForumSearchType.All).ToString(), + Text = T("Forum.Search.SearchWithin.All") + }, + new SelectListItem + { + Value = ((int)ForumSearchType.TopicTitlesOnly).ToString(), + Text = T("Forum.Search.SearchWithin.TopicTitlesOnly") + }, + new SelectListItem + { + Value = ((int)ForumSearchType.PostTextOnly).ToString(), + Text = T("Forum.Search.SearchWithin.PostTextOnly") + } + }; + + model.WithinList = withinList; + + int.TryParse(forumId, out var forumIdSelected); + model.ForumIdSelected = forumIdSelected; + + int.TryParse(within, out var withinSelected); + model.WithinSelected = withinSelected; + + int.TryParse(limitDays, out var limitDaysSelected); + model.LimitDaysSelected = limitDaysSelected; + + model.ShowAdvancedSearch = adv.GetValueOrDefault(); + model.SearchResultsVisible = false; + model.NoResultsVisisble = false; + model.PostsPageSize = _forumSettings.PostsPageSize; + + try + { + if (String.IsNullOrWhiteSpace(searchterms) == false) + { + searchterms = searchterms.Trim(); + model.SearchTerms = searchterms; + + if (searchterms.Length < _forumSettings.ForumSearchTermMinimumLength) + { + throw new SmartException(T("Forum.SearchTermMinimumLengthIsNCharacters", _forumSettings.ForumSearchTermMinimumLength)); + } + + ForumSearchType searchWithin = 0; + var limitResultsToPrevious = 0; + if (adv.GetValueOrDefault() == true) + { + searchWithin = (ForumSearchType)withinSelected; + limitResultsToPrevious = limitDaysSelected; + } + + if (_forumSettings.SearchResultsPageSize > 0) + { + pageSize = _forumSettings.SearchResultsPageSize; + } + + var topics = _forumService.GetAllTopics(forumIdSelected, 0, searchterms, searchWithin, limitResultsToPrevious, page - 1, pageSize); + + model.TopicPageSize = topics.PageSize; + model.TopicTotalRecords = topics.TotalCount; + model.TopicPageIndex = topics.PageIndex; + + foreach (var topic in topics) + { + var topicModel = PrepareForumTopicRowModel(topic); + model.ForumTopics.Add(topicModel); + } + + model.SearchResultsVisible = (topics.Count > 0); + model.NoResultsVisisble = !(model.SearchResultsVisible); + + CreateForumBreadcrumb(); + _breadcrumb.Track(new MenuItem { Text = T("Forum.Search") }); + + return View(model); + } + else + { + model.SearchResultsVisible = false; + } + } + catch (Exception ex) + { + model.Error = ex.Message; + } + + // Some exception raised. + model.TopicPageSize = pageSize; + model.TopicTotalRecords = 0; + model.TopicPageIndex = page - 1; + + CreateForumBreadcrumb(); + _breadcrumb.Track(new MenuItem { Text = T("Forum.Search") }); + + return View(model); + } + + [RequireHttpsByConfig(SslRequirement.No), ValidateInput(false)] + public ActionResult Search(ForumSearchQuery query/*, bool? adv*/) + { + if (!_forumSettings.ForumsEnabled) + { + return HttpNotFound(); + } + + ForumSearchResult result = null; + var model = new ForumSearchResultModel(query); + model.PostsPageSize = _forumSettings.PostsPageSize; + model.Term = query.Term; + + if (query.Term.HasValue() && query.Term.Length < _searchSettings.InstantSearchTermMinLength) + { + model.SearchResult = new ForumSearchResult(query); + model.Error = T("Search.SearchTermMinimumLengthIsNCharacters", _searchSettings.InstantSearchTermMinLength); + return View(model); + } + + try + { + if (query.Term.HasValue()) + { + result = _forumSearchService.Search(query); + + if (result.TotalHitsCount == 0 && result.SpellCheckerSuggestions.Any()) + { + // No matches, but spell checker made a suggestion. + // We implicitly search again with the first suggested term. + var oldSuggestions = result.SpellCheckerSuggestions; + var oldTerm = query.Term; + query.Term = oldSuggestions[0]; + + result = _forumSearchService.Search(query); + + if (result.TotalHitsCount > 0) + { + model.AttemptedTerm = oldTerm; + // Restore the original suggestions. + result.SpellCheckerSuggestions = oldSuggestions.Where(x => x != query.Term).ToArray(); + } + else + { + query.Term = oldTerm; + } + } + } + } + catch (Exception ex) + { + model.Error = ex.ToString(); + } + + model.SearchResult = result ?? new ForumSearchResult(query); + model.TotalCount = model.SearchResult.TotalHitsCount; + + foreach (var topic in model.SearchResult.Hits) + { + var topicModel = PrepareForumTopicRowModel(topic); + model.ForumTopics.Add(topicModel); + } + + CreateForumBreadcrumb(); + _breadcrumb.Track(new MenuItem { Text = T("Forum.Search") }); + + return View(model); + } + + [ChildActionOnly] + public ActionResult Filters(IForumSearchResultModel model) + { + if (model == null) + { + return new EmptyResult(); + } + + ViewBag.TemplateProvider = _templateProvider.Value; + + return PartialView(model); + } + + #endregion } } diff --git a/src/Presentation/SmartStore.Web/Infrastructure/DefaultFacetTemplateSelector.cs b/src/Presentation/SmartStore.Web/Infrastructure/DefaultFacetTemplateSelector.cs index 078623c216..e540956369 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/DefaultFacetTemplateSelector.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/DefaultFacetTemplateSelector.cs @@ -44,8 +44,6 @@ private string GetTemplateName(FacetGroup group) return prefix + "Price"; case FacetGroupKind.Rating: return prefix + "Rating"; - case FacetGroupKind.Date: - return prefix + "Date"; } return null; diff --git a/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs new file mode 100644 index 0000000000..f7e9440bcb --- /dev/null +++ b/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using SmartStore.Services.Search; +using SmartStore.Web.Framework.Modelling; +using SmartStore.Web.Models.Search; + +namespace SmartStore.Web.Models.Boards +{ + public class ForumSearchResultModel : ModelBase, IForumSearchResultModel + { + public ForumSearchResultModel(ForumSearchQuery query) + { + Query = query; + ForumTopics = new List(); + } + + public ForumSearchQuery Query { get; private set; } + + public ForumSearchResult SearchResult { get; set; } + + /// + /// Contains the original/misspelled search term, when the search did not match any results + /// and the spell checker suggested at least one term. + /// + public string AttemptedTerm { get; set; } + public string Term { get; set; } + + public int TotalCount { get; set; } + public int PostsPageSize { get; set; } + public string Error { get; set; } + + public IList ForumTopics { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index 4ac6b8db9f..de44ad6e31 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -371,6 +371,7 @@ + @@ -1760,7 +1761,7 @@ - + Web.config diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/Filters.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/Filters.cshtml new file mode 100644 index 0000000000..e31e646ab5 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/Filters.cshtml @@ -0,0 +1,70 @@ +@using SmartStore.Core.Search.Facets +@using SmartStore.Web.Models.Search +@using SmartStore.Services.Search.Rendering; +@using SmartStore.Services.Search.Extensions; +@model IForumSearchResultModel +@{ + Layout = ""; + + var templateProvider = ViewBag.TemplateProvider as IFacetTemplateProvider; + var facetGroups = Model.SearchResult.Facets.Select(x => x.Value).OrderBy(x => x.DisplayOrder); + var i = 0; +} + +@*@{ Html.RenderWidget("search_filters_before"); }*@ + +
+ +
+ +@*@{ Html.RenderWidget("search_filters_after"); }*@ diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_SearchBox.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_SearchBox.cshtml index 4fd356dea8..4ca8e36aa7 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_SearchBox.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_SearchBox.cshtml @@ -1,16 +1,12 @@ 
- @using (Html.BeginRouteForm("BoardSearch", FormMethod.Get, new { @class = "forum-search-box" })) { - var searchText = T("Forum.SearchForumsTooltip"); -
- @Html.TextBox("searchterms", searchText, new { @class = "forum-search-box-text form-control", placeholder = searchText, onclick = String.Format("if(this.value=='{0}')this.value=''", searchText), onblur = String.Format("if(this.value=='')this.value='{0}';", searchText) }) +
-
@Html.RouteLink(T("Forum.AdvancedSearch").Text, "BoardSearch", new { adv = true }, new { title = T("Forum.AdvancedSearch").Text }) diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml index 20b4c047ca..d7d3e92633 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml @@ -1,14 +1,220 @@ @using SmartStore.Core.Domain.Forums; @using SmartStore.Web.Models.Boards; -@using SmartStore.Web.Models.Common; @using SmartStore.Web.Framework.UI; -@model SearchModel +@model ForumSearchResultModel @{ - Layout = "_Layout"; - Html.AddTitleParts(T("Forum.PageTitle.Search").Text); + Layout = "_Layout"; + + if (Model.Term.HasValue()) + { + Html.AddTitleParts(T("Search.PageTitle", "\"" + Model.Term + "\"")); + } + else + { + Html.AddTitleParts(T("Forum.PageTitle.Search")); + } + + var firstItemIndex = 0; + var lastItemIndex = 0; + var suggestions = new string[0]; + if (Model.TotalCount > 0) + { + firstItemIndex = Model.SearchResult.Hits.FirstItemIndex; + lastItemIndex = Model.SearchResult.Hits.LastItemIndex; + suggestions = Model.SearchResult.SpellCheckerSuggestions; + } + + var pager = "TODO: pager"; + //var pager = Html.SmartStore().Pager(Model.SearchResult.Hits.PageIndex, Model.SearchResult.Hits.PageSize, Model.TotalCount) + // .Name("paginator") + // .Alignment(PagerAlignment.Right) + // .Route("BoardSearch"); } + + + +@**@ diff --git a/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/Date.cshtml b/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/Date.cshtml deleted file mode 100644 index f931d519b6..0000000000 --- a/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/Date.cshtml +++ /dev/null @@ -1,49 +0,0 @@ -@model FacetGroup - -@using SmartStore.Core.Search.Facets -@using SmartStore.Services.Search.Extensions; - -@{ - var customRangeFacet = Model.Facets.FirstOrDefault(x => x.Key == "custom"); -} - -
- @foreach (var facet in Model.Facets) - { - if (facet.Key != "custom" && facet.Published) - { -
- -
- } - } - - @if (customRangeFacet != null) - { - string value = customRangeFacet.Value.Value != null ? customRangeFacet.Value.Value.ToString() : ""; - string upperValue = customRangeFacet.Value.UpperValue != null ? customRangeFacet.Value.UpperValue.ToString() : ""; - -
-
- -
-
- - -
-
- -
-
- -
-
- } -
\ No newline at end of file diff --git a/src/Tests/SmartStore.Web.MVC.Tests/Public/Infrastructure/RoutesTests.cs b/src/Tests/SmartStore.Web.MVC.Tests/Public/Infrastructure/RoutesTests.cs index f5c325dcf3..a92d7b3f43 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/Public/Infrastructure/RoutesTests.cs +++ b/src/Tests/SmartStore.Web.MVC.Tests/Public/Infrastructure/RoutesTests.cs @@ -54,7 +54,8 @@ public void Boards_routes() "~/boards/forum/14/test-forum-slug/page/2".ShouldMapTo(c => c.Forum(14, 2)); "~/boards/forumgroup/15/".ShouldMapTo(c => c.ForumGroup(15)); "~/boards/forumgroup/15/test-forumgroup-slug/".ShouldMapTo(c => c.ForumGroup(15)); - "~/boards/search/".ShouldMapTo(c => c.Search(null, null, null, null, null, 1)); + //"~/boards/search/".ShouldMapTo(c => c.Search(null, null, null, null, null, 1)); + "~/boards/search/".ShouldMapTo(c => c.Search(null)); } [Test] From 2d18c8134a9896ddd5d0587fdd77a8eafb771f15 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 31 Aug 2018 18:42:41 +0200 Subject: [PATCH 064/932] Fixed SQLCE migration issues with indexes, locking and truncating --- .../Migrations/201609201852449_log4net.cs | 9 +++++++-- .../Migrations/201807051830375_MoveCustomerFields.cs | 5 +---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs index a1ddb8c90a..4dda98a5f8 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs @@ -14,9 +14,14 @@ public override void Up() // Custom START if (DataSettings.Current.IsSqlServer) { - //DropIndex("dbo.Log", "IX_Log_ContentHash"); Sql("IF EXISTS (SELECT * FROM sys.indexes WHERE name='IX_Log_ContentHash' AND object_id = OBJECT_ID('[dbo].[Log]')) DROP INDEX [IX_Log_ContentHash] ON [dbo].[Log];"); - Sql(@"Truncate Table [Log]"); + Sql(@"TRUNCATE Table [Log]"); + } + else + { + Sql(@"SET LOCK_TIMEOUT 20000;"); + DropIndex("Log", "IX_Log_ContentHash"); + Sql(@"DELETE FROM Log;"); } // Custom END diff --git a/src/Libraries/SmartStore.Data/Migrations/201807051830375_MoveCustomerFields.cs b/src/Libraries/SmartStore.Data/Migrations/201807051830375_MoveCustomerFields.cs index 510712cde8..a780ddb08a 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201807051830375_MoveCustomerFields.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201807051830375_MoveCustomerFields.cs @@ -1,11 +1,8 @@ namespace SmartStore.Data.Migrations { using System; - using System.Linq; - using System.Data.Entity; using System.Data.Entity.Migrations; using SmartStore.Data.Setup; - using System.Collections.Generic; using SmartStore.Data.Utilities; public partial class MoveCustomerFields : DbMigration, ILocaleResourcesProvider, IDataSeeder @@ -22,7 +19,7 @@ public override void Up() AddColumn("dbo.Customer", "BirthDate", c => c.DateTime()); CreateIndex("dbo.Customer", "FullName", name: "IX_Customer_FullName"); CreateIndex("dbo.Customer", "Company", name: "IX_Customer_Company"); - CreateIndex("dbo.Customer", "CustomerNumber", name: "IX_Customer_CustomerNumber"); + CreateIndex("dbo.Customer", "CustomerNumber", name: "IX_Customer_CustomerNumber", unique: false); CreateIndex("dbo.Customer", "BirthDate", name: "IX_Customer_BirthDate"); } From 81f0978398487c9d8f6c667ec2c84df33bfb4346 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 31 Aug 2018 20:10:34 +0200 Subject: [PATCH 065/932] Moved Html and Liquid editor templates to frontend web project so they can be used by plugins --- .../Administration/Content/_admin.scss | 113 +------------- .../Administration/Content/theme.scss | 1 + .../Administration/SmartStore.Admin.csproj | 4 - .../Content/skinning/_summernote.scss | 144 ++++++++++++++++++ .../SmartStore.Web/SmartStore.Web.csproj | 3 + .../Themes/Flex/Content/theme.scss | 1 + .../Views/Shared/EditorTemplates/Html.cshtml | 0 .../Shared/EditorTemplates/Liquid.cshtml | 0 8 files changed, 150 insertions(+), 116 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Content/skinning/_summernote.scss rename src/Presentation/SmartStore.Web/{Administration => }/Views/Shared/EditorTemplates/Html.cshtml (100%) rename src/Presentation/SmartStore.Web/{Administration => }/Views/Shared/EditorTemplates/Liquid.cshtml (100%) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss b/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss index daca930998..8374987c71 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss +++ b/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss @@ -1378,121 +1378,10 @@ table.payment-method-features td { // -// HTML editors +// Misc // -------------------------------------------------------------- .cke_chrome.cke_focus { border-color: $input-focus-border-color; box-shadow: none; -} - -.note-editor-preview { - border: 1px solid $input-border-color; - min-height: 100px; - max-height: 350px; - overflow-x: hidden; - overflow-y: auto; - padding: $input-padding-y $input-padding-x; - cursor: default; - - &.empty { - min-height: initial; - //color: $text-muted; - } -} - -.note-editor { - border-color: $input-border-color !important; - border-radius: $input-border-radius !important; - - &.focus { - border-color: $input-focus-border-color !important; - } - - .note-toolbar { - display: flex; - flex-wrap: wrap; - - > .note-btn-group { - border: 0 solid rgba(#000, 0.12); - border-right-width: 1px; - padding-right: 5px; - } - - > .note-btn-group:last-of-type { - border-right-width: 0; - padding-right: 0; - //margin-left: auto !important; - } - - .dropdown-toggle:after { - margin-left: 0.5em; - opacity: 0.6; - } - - .dropdown-toggle:empty:after { - margin-left: 0; - } - - .note-para > .btn-group:last-child .dropdown-menu.show { - // Fix: the para popover button groups should not wrap - display: flex !important; - } - - .dropdown-style > .dropdown-item > * { - // Fix: Summernote does not remove the BS4 bottom margin of para style dropdown items - margin-bottom: 0; - } - - .note-color + .btn-group > .btn { - // Fix: the fontsize button width should not change (toolbar flickers) - padding-left: 0.5rem; - padding-right: 0.5rem; - min-width: 50px; - } - - .scrollable-menu { - height: auto; - max-height: 340px; - width: 360px; - overflow-x: hidden; - padding: 0 !important - } - - .btn-group-cssclass .dropdown-menu { - &.show { - display: flex; - flex-direction: column; - } - - .dropdown-item { - position: relative; - - &.checked { - order: -1; - - &:before { - position: absolute; - content: ''; - left: 0; - top: 0; - bottom: 0; - width: 3px; - background-color: $warning; - } - } - } - } - } - - .note-editable { - table:not(.table-bordered):not([border]), - table:not(.table-bordered)[border='0'] { - border-collapse: collapse; - - td, th { - border: 1px dashed #bbb; - } - } - } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Content/theme.scss b/src/Presentation/SmartStore.Web/Administration/Content/theme.scss index af8e772156..cc26df11e8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/theme.scss +++ b/src/Presentation/SmartStore.Web/Administration/Content/theme.scss @@ -104,6 +104,7 @@ @import "../../Content/skinning/_select2.scss"; @import "../../Content/skinning/_pnotify.scss"; @import "../../Content/skinning/_fileupload.scss"; +@import "../../Content/skinning/_summernote.scss"; // diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index 3200cae08c..7e582d9899 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -585,7 +585,6 @@ - @@ -1230,9 +1229,6 @@ - - - diff --git a/src/Presentation/SmartStore.Web/Content/skinning/_summernote.scss b/src/Presentation/SmartStore.Web/Content/skinning/_summernote.scss new file mode 100644 index 0000000000..bc9cd36c50 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Content/skinning/_summernote.scss @@ -0,0 +1,144 @@ +/// +/// + +// +// HTML editors +// -------------------------------------------------------------- + +.cke_chrome.cke_focus { + border-color: $input-focus-border-color; + box-shadow: none; +} + +.note-editor-preview { + border: 1px solid $input-border-color; + min-height: 100px; + max-height: 350px; + overflow-x: hidden; + overflow-y: auto; + padding: $input-padding-y $input-padding-x; + cursor: default; + + &.empty { + min-height: initial; + //color: $text-muted; + } +} + +.note-editor { + border-color: $input-border-color !important; + border-radius: $input-border-radius !important; + + &:focus-within, + &:focus, + &.focus { + z-index: 1001; + border-color: $input-focus-border-color !important; + } + + &.focus { + // Redundant, but "Edge" will skip :focus-within selector + border-color: $input-focus-border-color !important; + } + + .note-toolbar-wrapper { + position: relative; + } + + .note-toolbar { + display: flex; + flex-wrap: wrap; + + .note-btn-group { + //z-index: $zindex-dropdown - 1; + } + + > .note-btn-group { + border: 0 solid rgba(#000, 0.12); + border-right-width: 1px; + padding-right: 5px; + } + + > .note-btn-group:last-of-type { + border-right-width: 0; + padding-right: 0; + //margin-left: auto !important; + } + + .btn.note-btn { + // overwrite any theme-specific button skinning + transform: none !important; + box-shadow: none; + } + + .dropdown-toggle:after { + margin-left: 0.5em; + opacity: 0.6; + } + + .dropdown-toggle:empty:after { + margin-left: 0; + } + + .note-para > .btn-group:last-child .dropdown-menu.show { + // Fix: the para popover button groups should not wrap + display: flex !important; + } + + .dropdown-style > .dropdown-item > * { + // Fix: Summernote does not remove the BS4 bottom margin of para style dropdown items + margin-bottom: 0; + } + + .note-color + .btn-group > .btn { + // Fix: the fontsize button width should not change (toolbar flickers) + padding-left: 0.5rem; + padding-right: 0.5rem; + min-width: 50px; + } + + .scrollable-menu { + height: auto; + max-height: 340px; + width: 360px; + overflow-x: hidden; + padding: 0 !important + } + + .btn-group-cssclass .dropdown-menu { + &.show { + display: flex; + flex-direction: column; + } + + .dropdown-item { + position: relative; + + &.checked { + order: -1; + + &:before { + position: absolute; + content: ''; + left: 0; + top: 0; + bottom: 0; + width: 3px; + background-color: $warning; + } + } + } + } + } + + .note-editable { + table:not(.table-bordered):not([border]), + table:not(.table-bordered)[border='0'] { + border-collapse: collapse; + + td, th { + border: 1px dashed #bbb; + } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index de44ad6e31..d65daccd38 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -1002,6 +1002,7 @@ + @@ -1762,6 +1763,8 @@ + + Web.config diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Content/theme.scss b/src/Presentation/SmartStore.Web/Themes/Flex/Content/theme.scss index 93ab6e3820..9747edc81e 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Content/theme.scss +++ b/src/Presentation/SmartStore.Web/Themes/Flex/Content/theme.scss @@ -89,6 +89,7 @@ @import "../../../Content/skinning/_slick.scss"; @import "../../../Content/skinning/_drift.scss"; @import "../../../Content/skinning/_fileupload.scss"; +@import "../../../Content/skinning/_summernote.scss"; // diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Html.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Html.cshtml similarity index 100% rename from src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Html.cshtml rename to src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Html.cshtml diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Liquid.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Liquid.cshtml similarity index 100% rename from src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Liquid.cshtml rename to src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Liquid.cshtml From f60b9dc81894266772a5469db602a7cfc11bd8fa Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 31 Aug 2018 20:15:21 +0200 Subject: [PATCH 066/932] Moved Picture editor template to frontend web project so it can be used by plugins --- .../SmartStore.Web/Administration/SmartStore.Admin.csproj | 1 - src/Presentation/SmartStore.Web/SmartStore.Web.csproj | 1 + .../Views/Shared/EditorTemplates/Picture.cshtml | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename src/Presentation/SmartStore.Web/{Administration => }/Views/Shared/EditorTemplates/Picture.cshtml (100%) diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index 7e582d9899..d12a93a5f4 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -614,7 +614,6 @@ - diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index d65daccd38..7aaaccf9e1 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -1765,6 +1765,7 @@ + Web.config diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Picture.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Picture.cshtml similarity index 100% rename from src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Picture.cshtml rename to src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Picture.cshtml From d13ccac74a137413ee5a530225ef04910864d620 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 31 Aug 2018 23:30:27 +0200 Subject: [PATCH 067/932] Checkout confirm page: links for term pages can be opened in new browser tabs --- .../Views/Checkout/Confirm.cshtml | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml index 699331d415..2a0a072e6f 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml @@ -8,13 +8,14 @@ Html.AddTitleParts(T("PageTitle.Checkout").Text); Html.AddBodyCssClass("no-offcanvas-cart"); - string termsLink = ""; - string disclaimerLink = ""; - string privacyLink = ""; - string terms = string.Format(T("Checkout.TermsOfService.IAccept"), termsLink, "", disclaimerLink, privacyLink); - - string consentZoneContent = Html.Widget("checkout_submit_data_consent", false).ToHtmlString(); - bool consentZoneHasValue = consentZoneContent.HasValue(); + var termsLinkTemplate = ""; + var termsLink = termsLinkTemplate.FormatInvariant(Url.TopicUrl("conditionsofuse", true)); + var disclaimerLink = termsLinkTemplate.FormatInvariant(Url.TopicUrl("disclaimer", true)); + var privacyLink = termsLinkTemplate.FormatInvariant(Url.TopicUrl("privacyinfo", true)); + var terms = string.Format(T("Checkout.TermsOfService.IAccept"), termsLink, "", disclaimerLink, privacyLink); + + var consentZoneContent = Html.Widget("checkout_submit_data_consent", false).ToHtmlString(); + var consentZoneHasValue = consentZoneContent.HasValue(); } @section orderProgress{ @@ -46,20 +47,12 @@ @if (Model.TermsOfServiceEnabled) { - From abeb217a9609f5d370955266ffb50a78532e5b4b Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 31 Aug 2018 23:39:23 +0200 Subject: [PATCH 068/932] Minor CSS fix --- .../Administration/Views/Product/_Create.SaveFirst.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_Create.SaveFirst.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_Create.SaveFirst.cshtml index 6b11b4ac3f..fcac81fbca 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_Create.SaveFirst.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_Create.SaveFirst.cshtml @@ -2,6 +2,6 @@

@T("Admin.SaveBeforeEdit")

- \ No newline at end of file From 22c6d881ec24bc7cd9aff3466967155ac705d18f Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 31 Aug 2018 23:48:24 +0200 Subject: [PATCH 069/932] Cannot create category without picture assignment --- .../Administration/Models/Catalog/CategoryModel.cs | 2 +- src/Presentation/SmartStore.Web/Global.asax | 2 +- src/Presentation/SmartStore.Web/Web.config | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryModel.cs index a1f5b20e97..a6c12464b7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryModel.cs @@ -76,7 +76,7 @@ public CategoryModel() [UIHint("Picture")] [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.Picture")] - public int PictureId { get; set; } + public int? PictureId { get; set; } [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.PageSize")] public int? PageSize { get; set; } diff --git a/src/Presentation/SmartStore.Web/Global.asax b/src/Presentation/SmartStore.Web/Global.asax index ebaeaa3f40..612debf2a2 100644 --- a/src/Presentation/SmartStore.Web/Global.asax +++ b/src/Presentation/SmartStore.Web/Global.asax @@ -1 +1 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="SmartStore.Web.MvcApplication" Language="C#" %> +<%@ Application Codebehind="Global.asax.cs" Inherits="SmartStore.Web.MvcApplication" Language="C#" %> diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index 1e3eac9981..9bee792763 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -79,7 +79,7 @@ - + From 2a4cc4b016e1dfa3c68454399605d9d01aa64312 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 1 Sep 2018 03:21:20 +0200 Subject: [PATCH 070/932] Fixed summernote modal backdrop issue --- .../SmartStore.Web/Content/editors/summernote/globalinit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Content/editors/summernote/globalinit.js b/src/Presentation/SmartStore.Web/Content/editors/summernote/globalinit.js index 87486ba383..cf990eb64a 100644 --- a/src/Presentation/SmartStore.Web/Content/editors/summernote/globalinit.js +++ b/src/Presentation/SmartStore.Web/Content/editors/summernote/globalinit.js @@ -26,7 +26,7 @@ var summernote_image_upload_url; summernote_global_config = { disableDragAndDrop: false, - dialogsInBody: false, + dialogsInBody: true, dialogsFade: true, height: 300, prettifyHtml: true, From 26ddf036d67187d671a27bab5e35a58fd78ff9a2 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 3 Sep 2018 09:33:56 +0200 Subject: [PATCH 071/932] MegaSearch: More on forum search --- .../Domain/Forums/ForumSearchType.cs | 21 -- .../Domain/Forums/ForumSettings.cs | 6 - .../Domain/Forums/ForumTopic.cs | 6 + .../Search/Facets/FacetGroup.cs | 11 +- .../Search/Facets/FacetValue.cs | 34 ++- .../SmartStore.Core/Search/ISearchQuery.cs | 4 +- .../SmartStore.Core/Search/SearchQuery.cs | 13 +- .../Search/Settings/ForumSearchSettings.cs | 2 +- .../SmartStore.Core/SmartStore.Core.csproj | 1 - .../Mapping/Forums/ForumTopicMap.cs | 14 +- .../Migrations/MigrationsConfiguration.cs | 30 ++- .../SmartStore.Services/Customers/GdprTool.cs | 2 +- .../Forums/ForumExtensions.cs | 2 +- .../Forums/ForumService.cs | 52 +--- .../Forums/IForumService.cs | 23 +- .../Search/Extensions/FacetUtility.cs | 44 ---- .../Search/Forum/ForumSearchQuery.cs | 19 ++ .../Search/Forum/ForumSearchService.cs | 56 ++--- .../Search/Forum/LinqForumSearchService.cs | 153 ++++++++---- .../Modelling/ForumSearchQueryFactory.cs | 67 ++++-- .../Controllers/SettingController.cs | 4 +- .../Infrastructure/AutoMapperAdminProfile.cs | 3 +- .../Controllers/BoardsController.cs | 207 +--------------- .../Controllers/CommonController.cs | 3 +- .../Controllers/CustomerController.cs | 3 +- .../Controllers/PrivateMessagesController.cs | 14 +- .../Controllers/ProfileController.cs | 11 +- .../Models/Boards/ForumTopicRowModel.cs | 23 +- .../Models/Boards/SearchModel.cs | 52 ---- .../SmartStore.Web/Scripts/public.search.js | 38 ++- .../SmartStore.Web/SmartStore.Web.csproj | 1 - .../Views/Boards/Partials/Filters.cshtml | 9 + .../SmartStore.Web/Views/Boards/Search.cshtml | 227 ++---------------- .../Search/FacetTemplates/MultiSelect.cshtml | 2 +- .../Search/FacetTemplates/SingleSelect.cshtml | 2 +- 35 files changed, 408 insertions(+), 751 deletions(-) delete mode 100644 src/Libraries/SmartStore.Core/Domain/Forums/ForumSearchType.cs delete mode 100644 src/Presentation/SmartStore.Web/Models/Boards/SearchModel.cs diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSearchType.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSearchType.cs deleted file mode 100644 index 169754cd58..0000000000 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSearchType.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace SmartStore.Core.Domain.Forums -{ - /// - /// Represents a forum search type - /// - public enum ForumSearchType - { - /// - /// Topic titles and post text - /// - All = 0, - /// - /// Topic titles only - /// - TopicTitlesOnly = 10, - /// - /// Post text only - /// - PostTextOnly = 20, - } -} diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs index c8b28c4197..455b0c1fbf 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs @@ -26,7 +26,6 @@ public ForumSettings() ActiveDiscussionsPageTopicCount = 50; ActiveDiscussionsFeedCount = 25; ForumFeedCount = 10; - ForumSearchTermMinimumLength = 3; } /// @@ -183,10 +182,5 @@ public ForumSettings() /// Gets or sets the number of items to display for Forum RSS Feed /// public int ForumFeedCount { get; set; } - - /// - /// Gets or sets the minimum length for search term - /// - public int ForumSearchTermMinimumLength { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs index 50a3ab730a..4b30ac9b02 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs @@ -38,6 +38,12 @@ public partial class ForumTopic : BaseEntity, IAuditable /// public int Views { get; set; } + /// + /// Gets or sets the first post identifier, for example of the first search hit. + /// This property is not a data member. + /// + public int FirstPostId { get; set; } + /// /// Gets or sets the last post identifier /// diff --git a/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs b/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs index b9c10ef0fa..e6d9ffc48e 100644 --- a/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs @@ -51,6 +51,7 @@ public FacetGroup( Label = label; IsMultiSelect = isMultiSelect; DisplayOrder = displayOrder; + IsScrollable = true; _facets = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -131,7 +132,7 @@ public bool IsMultiSelect private set; } - public bool HasChildren + public bool HasChildren { get; private set; @@ -143,7 +144,13 @@ public int DisplayOrder private set; } - public IEnumerable Facets + public bool IsScrollable + { + get; + set; + } + + public IEnumerable Facets { get { diff --git a/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs b/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs index 0cb8d6d3e1..e3e451cab7 100644 --- a/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs @@ -155,20 +155,38 @@ public override bool Equals(object obj) return this.Equals(obj as FacetValue); } + protected virtual string ConvertToString(object value) + { + if (value != null) + { + if (TypeCode == IndexTypeCode.DateTime) + { + // The default conversion is not pretty enough. + var dt = (DateTime)value; + if (dt.Hour == 0 && dt.Minute == 0 && dt.Second == 0) + { + return dt.ToString("yyyy-MM-dd"); + } + else + { + return dt.ToString("yyyy-MM-dd HH:mm:ss"); + } + } + + return Convert.ToString(value, CultureInfo.InvariantCulture); + } + + return string.Empty; + } + public override string ToString() { var result = string.Empty; - - var valueStr = Value != null - ? Convert.ToString(Value, CultureInfo.InvariantCulture) - : string.Empty; + var valueStr = ConvertToString(Value); if (IsRange) { - var upperValueStr = UpperValue != null - ? Convert.ToString(UpperValue, CultureInfo.InvariantCulture) - : string.Empty; - + var upperValueStr = ConvertToString(UpperValue); if (upperValueStr.HasValue()) { result = string.Concat(valueStr, "~", upperValueStr); diff --git a/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs b/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs index 0b978dc2ec..28ba070494 100644 --- a/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs +++ b/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs @@ -1,16 +1,14 @@ using System.Collections.Generic; -using SmartStore.Core.Domain.Customers; using SmartStore.Core.Search.Facets; namespace SmartStore.Core.Search { - public interface ISearchQuery + public interface ISearchQuery { // Language, Currency & Store int? LanguageId { get; } string LanguageCulture { get; } string CurrencyCode { get; } - int? CustomerId { get; } int? StoreId { get; } // Search term diff --git a/src/Libraries/SmartStore.Core/Search/SearchQuery.cs b/src/Libraries/SmartStore.Core/Search/SearchQuery.cs index a166cc4846..af1f59465f 100644 --- a/src/Libraries/SmartStore.Core/Search/SearchQuery.cs +++ b/src/Libraries/SmartStore.Core/Search/SearchQuery.cs @@ -1,14 +1,13 @@ using System; using System.Collections.Generic; using System.Text; -using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Search.Facets; namespace SmartStore.Core.Search { - public enum SearchResultFlags + public enum SearchResultFlags { WithHits = 1 << 0, WithFacets = 1 << 1, @@ -66,7 +65,6 @@ protected SearchQuery(string[] fields, string term, SearchMode mode = SearchMode public int? LanguageId { get; protected set; } public string LanguageCulture { get; protected set; } public string CurrencyCode { get; protected set; } - public int? CustomerId { get; protected set; } public int? StoreId { get; protected set; } // Search term @@ -153,15 +151,6 @@ public TQuery WithCurrency(Currency currency) return (this as TQuery); } - public TQuery WithCustomer(Customer customer) - { - Guard.NotNull(customer, nameof(customer)); - - CustomerId = customer.Id; - - return (this as TQuery); - } - public TQuery Slice(int skip, int take) { Guard.NotNegative(skip, nameof(skip)); diff --git a/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs index e43da55e94..c8e7e17af5 100644 --- a/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs +++ b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs @@ -9,7 +9,7 @@ public class ForumSearchSettings : ISettings public ForumSearchSettings() { SearchMode = SearchMode.Contains; - SearchFields = new List { "subject", "username" }; + SearchFields = new List { "username", "text" }; TopicSortOrder = ForumTopicSorting.Relevance; InstantSearchEnabled = true; InstantSearchNumberOfHits = 10; diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index 76f3ae488a..92b84a5c53 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -524,7 +524,6 @@ - diff --git a/src/Libraries/SmartStore.Data/Mapping/Forums/ForumTopicMap.cs b/src/Libraries/SmartStore.Data/Mapping/Forums/ForumTopicMap.cs index e6fc71352b..1a8b46113b 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Forums/ForumTopicMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/Forums/ForumTopicMap.cs @@ -7,16 +7,18 @@ public partial class ForumTopicMap : EntityTypeConfiguration { public ForumTopicMap() { - this.ToTable("Forums_Topic"); - this.HasKey(ft => ft.Id); - this.Property(ft => ft.Subject).IsRequired().HasMaxLength(450); - this.Ignore(ft => ft.ForumTopicType); + ToTable("Forums_Topic"); + HasKey(ft => ft.Id); + Property(ft => ft.Subject).IsRequired().HasMaxLength(450); - this.HasRequired(ft => ft.Forum) + Ignore(ft => ft.ForumTopicType); + Ignore(ft => ft.FirstPostId); + + HasRequired(ft => ft.Forum) .WithMany() .HasForeignKey(ft => ft.ForumId); - this.HasRequired(ft => ft.Customer) + HasRequired(ft => ft.Customer) .WithMany(c => c.ForumTopics) .HasForeignKey(ft => ft.CustomerId) .WillCascadeOnDelete(false); diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 02d2a2566a..1751ea62f1 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -392,8 +392,12 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastYear", "Last year", "Im letzten Jahr"); builder.AddOrUpdate("Search.Facet.Forum", "Forum", "Forum"); - builder.AddOrUpdate("Search.Facet.Customer", "Name", "Name"); - builder.AddOrUpdate("Search.Facet.Date", "Date", "Datum"); + builder.AddOrUpdate("Search.Facet.Customer", "User name", "Benutzername"); + builder.AddOrUpdate("Search.Facet.Date", "Period", "Zeitraum"); + builder.AddOrUpdate("Search.Facet.Date.Newer", "and newer", "und neuer"); + builder.AddOrUpdate("Search.Facet.Date.Older", "and older", "und älter"); + + builder.AddOrUpdate("Forum.PostText", "Post text", "Beitragstext"); builder.AddOrUpdate("Admin.Configuration.Settings.Search.WildcardSearchNote", "The wildcard mode can slow down the search for a large number of objects.", @@ -408,8 +412,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Settings.Search.Forum.SearchFields", "Search fields", "Suchfelder", - "Specifies additional search fields. The post text is always searched.", - "Legt zusätzlich zu durchsuchende Felder fest. Der Beitragstext wird grundsätzlich immer durchsucht."); + "Specifies additional search fields. The topic title is always searched.", + "Legt zusätzlich zu durchsuchende Felder fest. Der Thementitel wird grundsätzlich immer durchsucht."); builder.AddOrUpdate("Admin.Configuration.Settings.Search.DefaultSortOrder", "Default sort order", @@ -425,7 +429,23 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.Delete( "Admin.Configuration.Settings.Search.DefaultSortOrderMode", - "Admin.Configuration.Settings.Search.InstantSearchNumberOfProducts"); + "Admin.Configuration.Settings.Search.InstantSearchNumberOfProducts", + "Forum.Search.LimitResultsToPrevious.AllResults", + "Forum.Search.LimitResultsToPrevious.1day", + "Forum.Search.LimitResultsToPrevious.7days", + "Forum.Search.LimitResultsToPrevious.2weeks", + "Forum.Search.LimitResultsToPrevious.1month", + "Forum.Search.LimitResultsToPrevious.3months", + "Forum.Search.LimitResultsToPrevious.6months", + "Forum.Search.LimitResultsToPrevious.1year", + "Forum.Search.SearchInForum.All", + "Forum.Search.SearchWithin.All", + "Forum.Search.SearchWithin.TopicTitlesOnly", + "Forum.Search.SearchWithin.PostTextOnly", + "Forum.SearchTermMinimumLengthIsNCharacters", + "Enums.SmartStore.Core.Domain.Forums.ForumSearchType.All", + "Enums.SmartStore.Core.Domain.Forums.ForumSearchType.PostTextOnly", + "Enums.SmartStore.Core.Domain.Forums.ForumSearchType.TopicTitlesOnly"); } } } diff --git a/src/Libraries/SmartStore.Services/Customers/GdprTool.cs b/src/Libraries/SmartStore.Services/Customers/GdprTool.cs index 1ed40229e2..b8a52c9666 100644 --- a/src/Libraries/SmartStore.Services/Customers/GdprTool.cs +++ b/src/Libraries/SmartStore.Services/Customers/GdprTool.cs @@ -275,7 +275,7 @@ public virtual void AnonymizeCustomer(Customer customer, bool pseudomyzeContent) // Private messages if (pseudomyzeContent) { - var privateMessages = _forumService.GetAllPrivateMessages(0, customer.Id, 0, null, null, null, null, 0, int.MaxValue); + var privateMessages = _forumService.GetAllPrivateMessages(0, customer.Id, 0, null, null, null, 0, int.MaxValue); foreach (var msg in privateMessages) { AnonymizeData(msg, x => x.Subject, IdentifierDataType.Text, language); diff --git a/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs b/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs index db1ac72671..d6d4fe8b19 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs @@ -154,7 +154,7 @@ public static ForumPost GetFirstPost(this ForumTopic forumTopic, IForumService f if (forumTopic == null) throw new ArgumentNullException("forumTopic"); - var forumPosts = forumService.GetAllPosts(forumTopic.Id, 0, string.Empty, true, 0, 1); + var forumPosts = forumService.GetAllPosts(forumTopic.Id, 0, true, 0, 1); if (forumPosts.Count > 0) return forumPosts[0]; diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index 910f1c4c94..703231be4c 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using SmartStore.Core; using SmartStore.Core.Data; @@ -293,35 +292,20 @@ public virtual IList GetTopicsByIds(int[] topicIds) return query.OrderBySequence(topicIds).ToList(); } - public virtual IPagedList GetAllTopics(int forumId, int customerId, string keywords, ForumSearchType searchType, int limitDays, int pageIndex, int pageSize) + public virtual IPagedList GetAllTopics(int forumId, int pageIndex, int pageSize) { - DateTime? limitDate = null; - - if (limitDays > 0) + var query = _forumTopicRepository.TableUntracked; + if (forumId != 0) { - limitDate = DateTime.UtcNow.AddDays(-limitDays); + query = query.Where(x => x.ForumId == forumId); } - var query1 = - from ft in _forumTopicRepository.Table - join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId - where - (forumId == 0 || ft.ForumId == forumId) && - (customerId == 0 || ft.CustomerId == customerId) && - (!limitDate.HasValue || limitDate.Value <= ft.LastPostTime) && - ( - ((searchType == ForumSearchType.All || searchType == ForumSearchType.TopicTitlesOnly) && ft.Subject.Contains(keywords)) || - ((searchType == ForumSearchType.All || searchType == ForumSearchType.PostTextOnly) && fp.Text.Contains(keywords)) - ) - select ft.Id; - - var query2 = - from ft in _forumTopicRepository.Table - where query1.Contains(ft.Id) - orderby ft.TopicTypeId descending, ft.LastPostTime descending, ft.Id descending - select ft; + query = query + .OrderByDescending(x => x.TopicTypeId) + .ThenByDescending(x => x.LastPostTime) + .ThenByDescending(x => x.Id); - var topics = new PagedList(query2, pageIndex, pageSize); + var topics = new PagedList(query, pageIndex, pageSize); return topics; } @@ -448,7 +432,7 @@ public virtual ForumTopic MoveTopic(int forumTopicId, int newForumId) public virtual int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId) { var pageIndex = 0; - var forumPosts = GetAllPosts(forumTopicId, 0, string.Empty, true, 0, int.MaxValue); + var forumPosts = GetAllPosts(forumTopicId, 0, true, 0, int.MaxValue); for (var i = 0; i < forumPosts.TotalCount; i++) { @@ -479,7 +463,7 @@ public virtual ForumPost GetPostById(int forumPostId) return forumPost; } - public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, string keywords, bool ascSort, int pageIndex, int pageSize) + public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, bool ascSort, int pageIndex, int pageSize) { var query = _forumPostRepository.Table; if (forumTopicId > 0) @@ -490,10 +474,6 @@ public virtual IPagedList GetAllPosts(int forumTopicId, int customerI { query = query.Where(fp => customerId == fp.CustomerId); } - if (keywords.HasValue()) - { - query = query.Where(fp => fp.Text.Contains(keywords)); - } if (ascSort) { query = query.OrderBy(fp => fp.CreatedOnUtc).ThenBy(fp => fp.Id); @@ -504,7 +484,6 @@ public virtual IPagedList GetAllPosts(int forumTopicId, int customerI } var forumPosts = new PagedList(query, pageIndex, pageSize); - return forumPosts; } @@ -611,7 +590,6 @@ public virtual IPagedList GetAllPrivateMessages( bool? isRead, bool? isDeletedByAuthor, bool? isDeletedByRecipient, - string keywords, int pageIndex, int pageSize) { @@ -642,12 +620,6 @@ public virtual IPagedList GetAllPrivateMessages( query = query.Where(pm => isDeletedByRecipient.Value == pm.IsDeletedByRecipient); } - if (keywords.HasValue()) - { - query = query.Where(pm => pm.Subject.Contains(keywords)); - query = query.Where(pm => pm.Text.Contains(keywords)); - } - query = query.OrderByDescending(pm => pm.CreatedOnUtc); var privateMessages = new PagedList(query, pageIndex, pageSize); diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index 99bdf879b0..b6fabc8225 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -102,14 +102,10 @@ public partial interface IForumService /// Gets all forum topics /// /// The forum identifier - /// The customer identifier - /// Keywords - /// Search type - /// Limit by the last number days; 0 to load all topics /// Page index /// Page size /// Forum Topics - IPagedList GetAllTopics(int forumId, int customerId, string keywords, ForumSearchType searchType, int limitDays, int pageIndex, int pageSize); + IPagedList GetAllTopics(int forumId, int pageIndex, int pageSize); /// /// Gets active forum topics @@ -171,13 +167,11 @@ public partial interface IForumService /// /// The forum topic identifier /// The customer identifier - /// Keywords /// Sort order /// Page index /// Page size /// Forum Posts - IPagedList GetAllPosts(int forumTopicId, int customerId, - string keywords, bool ascSort, int pageIndex, int pageSize); + IPagedList GetAllPosts(int forumTopicId, int customerId, bool ascSort, int pageIndex, int pageSize); /// /// Deletes a forum post @@ -218,13 +212,18 @@ IPagedList GetAllPosts(int forumTopicId, int customerId, /// A value indicating whether loaded messages are read. false - to load not read messages only, 1 to load read messages only, null to load all messages /// A value indicating whether loaded messages are deleted by author. false - messages are not deleted by author, null to load all messages /// A value indicating whether loaded messages are deleted by recipient. false - messages are not deleted by recipient, null to load all messages - /// Keywords /// Page index /// Page size /// Private messages - IPagedList GetAllPrivateMessages(int storeId, int fromCustomerId, - int toCustomerId, bool? isRead, bool? isDeletedByAuthor, bool? isDeletedByRecipient, - string keywords, int pageIndex, int pageSize); + IPagedList GetAllPrivateMessages( + int storeId, + int fromCustomerId, + int toCustomerId, + bool? isRead, + bool? isDeletedByAuthor, + bool? isDeletedByRecipient, + int pageIndex, + int pageSize); /// /// Deletes a private message diff --git a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs index 723063308c..44974917b5 100644 --- a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs +++ b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs @@ -7,7 +7,6 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; -using SmartStore.Services.Common; namespace SmartStore.Services.Search.Extensions { @@ -141,49 +140,6 @@ public static IEnumerable GetRatings() } } - public static List GetForumDates( - IGenericAttributeService genericAttributeService, - int customerId, - int storeId, - bool newer = true) - { - var count = 0; - var utcNow = DateTime.UtcNow; - var result = new List(); - - foreach (ForumDateFilter filter in Enum.GetValues(typeof(ForumDateFilter))) - { - var days = (int)filter; - if (filter == ForumDateFilter.LastVisit && customerId != 0) - { - var lastVisit = genericAttributeService.GetAttribute(nameof(Customer), customerId, SystemCustomerAttributeNames.LastForumVisit, storeId); - if (!lastVisit.HasValue) - { - continue; - } - - days = (int)(utcNow - lastVisit.Value).TotalDays; - } - - if (newer) - { - result.Add(new FacetValue(utcNow.AddDays(-days), null, IndexTypeCode.DateTime, true, false) - { - DisplayOrder = ++count - }); - } - else - { - result.Add(new FacetValue(null, utcNow.AddDays(-days), IndexTypeCode.DateTime, false, true) - { - DisplayOrder = ++count - }); - } - } - - return result; - } - public static string GetPublicName(Customer customer, bool userNamesEnabled) { if (customer == null) diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs index bbf589b0eb..a6b761cec1 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs @@ -65,6 +65,25 @@ public ForumSearchQuery SortBy(ForumTopicSorting sort) } } + public override ForumSearchQuery HasStoreId(int id) + { + base.HasStoreId(id); + + if (id == 0) + { + WithFilter(SearchFilter.ByField("storeid", 0).ExactMatch().NotAnalyzed()); + } + else + { + WithFilter(SearchFilter.Combined( + SearchFilter.ByField("storeid", 0).ExactMatch().NotAnalyzed(), + SearchFilter.ByField("storeid", id).ExactMatch().NotAnalyzed()) + ); + } + + return this; + } + public ForumSearchQuery WithForumIds(params int[] ids) { if (ids.Length == 0) diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs index 2efbd734a9..804ba59d23 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs @@ -60,35 +60,8 @@ protected virtual void ApplyFacetLabels(IDictionary facets) return; } - // Apply "date" labels. - if (facets.TryGetValue("createdon", out var grp)) - { - var foundLastVisit = false; - var utcNow = DateTime.UtcNow; - foreach (var facet in grp.Facets) - { - var dt = (DateTime)(facet.Value.IncludesUpper ? facet.Value.UpperValue : facet.Value.Value); - var days = (int)(utcNow - dt).TotalDays; - - foreach (ForumDateFilter filter in Enum.GetValues(typeof(ForumDateFilter))) - { - if (days == (int)filter) - { - facet.Value.Label = T("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter." + filter.ToString()); - break; - } - } - - if (!foundLastVisit && facet.Value.Label.IsEmpty()) - { - foundLastVisit = true; - facet.Value.Label = T("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastVisit"); - } - } - } - // Ensure that there are no duplicate customer labels. - if (facets.TryGetValue("customerid", out grp)) + if (facets.TryGetValue("customerid", out var grp)) { var groupedByLabel = from x in grp.Facets @@ -148,8 +121,31 @@ public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = fals { using (_services.Chronometer.Step(stepPrefix + "Collect")) { - var ids = searchHits.Select(x => x.GetInt("topicid")).Distinct().ToArray(); - hitsFactory = () => _forumService.Value.GetTopicsByIds(ids); + var postIds = searchHits + .Select(x => new + { + TopicId = x.GetInt("topicid"), + PostId = x.GetInt("id") + }) + .ToMultimap(x => x.TopicId, x => x.PostId); + + var topicIds = postIds.Select(x => x.Key).ToArray(); + + hitsFactory = () => + { + var hits = _forumService.Value.GetTopicsByIds(topicIds); + + // Provide the id of the first hit, so that we can jump directly to the post when opening the topic. + foreach (var topic in hits) + { + if (postIds.ContainsKey(topic.Id)) + { + topic.FirstPostId = postIds[topic.Id].OrderBy(x => x).FirstOrDefault(); + } + } + + return hits; + }; } } diff --git a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs index f565ee263e..7ef1d2d6b5 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -7,7 +7,6 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; -using SmartStore.Services.Common; using SmartStore.Services.Forums; using SmartStore.Services.Localization; using SmartStore.Services.Search.Extensions; @@ -19,20 +18,17 @@ public partial class LinqForumSearchService : SearchServiceBase, IForumSearchSer private readonly IRepository _forumPostRepository; private readonly IRepository _storeMappingRepository; private readonly IForumService _forumService; - private readonly IGenericAttributeService _genericAttributeService; private readonly ICommonServices _services; public LinqForumSearchService( IRepository forumPostRepository, IRepository storeMappingRepository, IForumService forumService, - IGenericAttributeService genericAttributeService, ICommonServices services) { _forumPostRepository = forumPostRepository; _storeMappingRepository = storeMappingRepository; _forumService = forumService; - _genericAttributeService = genericAttributeService; _services = services; QuerySettings = DbQuerySettings.Default; @@ -40,13 +36,14 @@ public LinqForumSearchService( public DbQuerySettings QuerySettings { get; set; } - protected virtual IQueryable GetForumTopicQuery(ForumSearchQuery searchQuery, IQueryable baseQuery) - { + protected virtual IQueryable GetTopicQuery(ForumSearchQuery searchQuery, IQueryable baseQuery) + { + // Post query. var ordered = false; var term = searchQuery.Term; var fields = searchQuery.Fields; var filters = new List(); - var query = baseQuery ?? _forumPostRepository.Table.Expand(x => x.ForumTopic); + var query = baseQuery ?? _forumPostRepository.TableUntracked.Expand(x => x.ForumTopic); // Apply search term. if (term.HasValue() && fields != null && fields.Length != 0 && fields.Any(x => x.HasValue())) @@ -130,44 +127,71 @@ protected virtual IQueryable GetForumTopicQuery(ForumSearchQuery sea } } } + else if (filter.FieldName == "storeid") + { + if (!QuerySettings.IgnoreMultiStore) + { + var storeId = (int)filter.Term; + if (storeId != 0) + { + query = + from p in query + join sm in _storeMappingRepository.TableUntracked on new { eid = p.ForumTopic.Forum.ForumGroupId, ename = "ForumGroup" } + equals new { eid = sm.EntityId, ename = sm.EntityName } into gsm + from sm in gsm.DefaultIfEmpty() + where !p.ForumTopic.Forum.ForumGroup.LimitedToStores || sm.StoreId == storeId + select p; + } + } + } } - var topicsQuery = - from ft in query.Select(x => x.ForumTopic) - group ft by ft.Id into grp - orderby grp.Key - select grp.FirstOrDefault(); + var sortCreatedOn = searchQuery.Sorting.FirstOrDefault(x => x.FieldName == "createdon"); + if (sortCreatedOn != null) + { + query = OrderBy(ref ordered, query, x => x.CreatedOnUtc, sortCreatedOn.Descending); + } + + // Topic query. + var topicQuery = + from fp in query + group fp by fp.TopicId into grp + select new LinqSearchTopic + { + Topic = grp.Select(x => x.ForumTopic).FirstOrDefault(), + FirstPostId = grp.OrderBy(x => x.Id).Select(x => x.Id).FirstOrDefault() + }; - // Sorting of topics. + // Sorting. foreach (var sort in searchQuery.Sorting) { if (sort.FieldName == "subject") { - topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.Subject, sort.Descending); + topicQuery = OrderBy(ref ordered, topicQuery, x => x.Topic.Subject, sort.Descending); } else if (sort.FieldName == "username") { - topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.Customer.Username, sort.Descending); + topicQuery = OrderBy(ref ordered, topicQuery, x => x.Topic.Customer.Username, sort.Descending); } else if (sort.FieldName == "createdon") { - topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.CreatedOnUtc, sort.Descending); + // Skip, already processed. We want to sort by ForumPost.CreatedOnUtc, not ForumTopic.CreatedOnUtc. } else if (sort.FieldName == "posts") { - topicsQuery = OrderBy(ref ordered, topicsQuery, x => x.NumPosts, sort.Descending); + topicQuery = OrderBy(ref ordered, topicQuery, x => x.Topic.NumPosts, sort.Descending); } } - if (!ordered) + if (!ordered && !searchQuery.Sorting.Any(x => x.FieldName == "createdon")) { - topicsQuery = topicsQuery - .OrderByDescending(x => x.TopicTypeId) - .ThenByDescending(x => x.LastPostTime) - .ThenByDescending(x => x.Id); + topicQuery = topicQuery + .OrderByDescending(x => x.Topic.TopicTypeId) + .ThenByDescending(x => x.Topic.LastPostTime) + .ThenByDescending(x => x.Topic.Id); } - return topicsQuery; + return topicQuery; } protected virtual IDictionary GetFacets(ForumSearchQuery searchQuery, int totalHits) @@ -245,14 +269,9 @@ protected virtual IDictionary GetFacets(ForumSearchQuery sea } else if (kind == FacetGroupKind.Date) { - var dates = FacetUtility.GetForumDates(_genericAttributeService, searchQuery.CustomerId ?? 0, storeId, true); - - foreach (var date in dates) + foreach (var value in descriptor.Values) { - var newFacet = new Facet(date); - newFacet.Value.IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(date.Value)); - - facets.Add(newFacet); + facets.Add(new Facet(value)); } } @@ -260,14 +279,19 @@ protected virtual IDictionary GetFacets(ForumSearchQuery sea { //facets.Each(x => $"{key} {x.Value.ToString()}".Dump()); - result.Add(key, new FacetGroup( + var group = new FacetGroup( "Forum", key, descriptor.Label, descriptor.IsMultiSelect, false, descriptor.DisplayOrder, - facets.OrderBy(descriptor))); + facets.OrderBy(descriptor)) + { + IsScrollable = facets.Count > 14 + }; + + result.Add(key, group); } } @@ -284,24 +308,47 @@ public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = fals if (searchQuery.Take > 0) { - var query = GetForumTopicQuery(searchQuery, null); - totalHits = query.Count(); + var query = GetTopicQuery(searchQuery, null); + totalHits = query.Count(); - // Fix paging boundaries - if (searchQuery.Skip > 0 && searchQuery.Skip >= totalHits) - { - searchQuery.Slice((totalHits / searchQuery.Take) * searchQuery.Take, searchQuery.Take); - } + // Fix paging boundaries. + if (searchQuery.Skip > 0 && searchQuery.Skip >= totalHits) + { + searchQuery.Slice((totalHits / searchQuery.Take) * searchQuery.Take, searchQuery.Take); + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithHits)) + { + query = query + .Skip(searchQuery.PageIndex * searchQuery.Take) + .Take(searchQuery.Take); + + var idQuery = query + .Select(x => new + { + x.Topic.Id, + x.FirstPostId + }); + + // Topic.Id -> first post id. + var ids = idQuery.ToList().ToDictionarySafe(x => x.Id, x => x.FirstPostId); + + hitsFactory = () => + { + var hits = _forumService.GetTopicsByIds(ids.Select(x => x.Key).ToArray()); - if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithHits)) - { - query = query - .Skip(searchQuery.PageIndex * searchQuery.Take) - .Take(searchQuery.Take); + // Provide the id of the first hit, so that we can jump directly to the post when opening the topic. + foreach (var topic in hits) + { + if (ids.TryGetValue(topic.Id, out var firstPostId)) + { + topic.FirstPostId = firstPostId; + } + } - var ids = query.Select(x => x.Id).Distinct().ToArray(); - hitsFactory = () => _forumService.GetTopicsByIds(ids); - } + return hits; + }; + } if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithFacets) && searchQuery.FacetDescriptors.Any()) { @@ -309,7 +356,7 @@ public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = fals } } - var result = new ForumSearchResult( + var result = new ForumSearchResult( null, searchQuery, totalHits, @@ -324,7 +371,15 @@ public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = fals public IQueryable PrepareQuery(ForumSearchQuery searchQuery, IQueryable baseQuery = null) { - return GetForumTopicQuery(searchQuery, baseQuery); + var query = GetTopicQuery(searchQuery, baseQuery); + return query.Select(x => x.Topic); } } + + + public class LinqSearchTopic + { + public ForumTopic Topic { get; set; } + public int FirstPostId { get; set; } + } } diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs index adcb202f45..156fcdfb2d 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs @@ -4,9 +4,12 @@ using System.Web; using System.Web.Routing; using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Localization; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; +using SmartStore.Services.Common; using SmartStore.Services.Search.Extensions; namespace SmartStore.Services.Search.Modelling @@ -27,6 +30,7 @@ public class ForumSearchQueryFactory : SearchQueryFactoryBase, IForumSearchQuery { protected readonly ICommonServices _services; protected readonly IForumSearchQueryAliasMapper _forumSearchQueryAliasMapper; + protected readonly IGenericAttributeService _genericAttributeService; protected readonly ForumSearchSettings _searchSettings; protected readonly ForumSettings _forumSettings; @@ -34,18 +38,22 @@ public ForumSearchQueryFactory( ICommonServices services, HttpContextBase httpContext, IForumSearchQueryAliasMapper forumSearchQueryAliasMapper, + IGenericAttributeService genericAttributeService, ForumSearchSettings searchSettings, ForumSettings forumSettings) : base(httpContext) { _services = services; _forumSearchQueryAliasMapper = forumSearchQueryAliasMapper; + _genericAttributeService = genericAttributeService; _searchSettings = searchSettings; _forumSettings = forumSettings; + T = NullLocalizer.Instance; QuerySettings = DbQuerySettings.Default; } + public Localizer T { get; set; } public DbQuerySettings QuerySettings { get; set; } public ForumSearchQuery Current { get; private set; } @@ -64,7 +72,7 @@ public ForumSearchQuery CreateFromQuery() var controller = routeData.GetRequiredString("controller"); var action = routeData.GetRequiredString("action"); var origin = "{0}{1}/{2}".FormatInvariant(area == null ? "" : area + "/", controller, action); - var fields = new List { "text" }; + var fields = new List { "subject" }; var term = GetValueFor("q"); var isInstantSearch = origin.IsCaseInsensitiveEqual("Boards/InstantSearch"); @@ -74,7 +82,6 @@ public ForumSearchQuery CreateFromQuery() .OriginatesFrom(origin) .WithLanguage(_services.WorkContext.WorkingLanguage) .WithCurrency(_services.WorkContext.WorkingCurrency) - .WithCustomer(_services.WorkContext.CurrentCustomer) .BuildFacetMap(!isInstantSearch); // Store. @@ -226,18 +233,7 @@ protected virtual void ConvertDate(ForumSearchQuery query, RouteData routeData, AddFacet(query, FacetGroupKind.Date, false, FacetSorting.DisplayOrder, descriptor => { - if (fromUtc.HasValue || toUtc.HasValue) - { - descriptor.AddValue(new FacetValue( - fromUtc, - toUtc, - IndexTypeCode.DateTime, - fromUtc.HasValue, - toUtc.HasValue) - { - IsSelected = true - }); - } + AddDates(descriptor, fromUtc, toUtc); }); } @@ -249,5 +245,48 @@ protected virtual bool GetValueFor(ForumSearchQuery query, string key, FacetG { return GetValueFor(_forumSearchQueryAliasMapper.GetCommonFacetAliasByGroupKind(kind, query.LanguageId ?? 0) ?? key, out value); } + + protected virtual void AddDates(FacetDescriptor descriptor, DateTime? selectedFrom, DateTime? selectedTo) + { + var store = _services.StoreContext.CurrentStore; + var customer = _services.WorkContext.CurrentCustomer; + var count = 0; + var utcNow = DateTime.UtcNow; + utcNow = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, 0, 0, 0); + + foreach (ForumDateFilter filter in Enum.GetValues(typeof(ForumDateFilter))) + { + var dt = utcNow.AddDays(-((int)filter)); + + if (filter == ForumDateFilter.LastVisit) + { + var lastVisit = _genericAttributeService.GetAttribute(nameof(Customer), customer.Id, SystemCustomerAttributeNames.LastForumVisit, store.Id); + if (!lastVisit.HasValue) + { + continue; + } + + dt = lastVisit.Value; + } + + var value = selectedTo.HasValue + ? new FacetValue(null, dt, IndexTypeCode.DateTime, false, true) + : new FacetValue(dt, null, IndexTypeCode.DateTime, true, false); + + value.DisplayOrder = ++count; + value.Label = T("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter." + filter.ToString()); + + if (selectedFrom.HasValue) + { + value.IsSelected = dt == selectedFrom.Value; + } + else if (selectedTo.HasValue) + { + value.IsSelected = dt == selectedTo.Value; + } + + descriptor.AddValue(value); + } + } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index 587e67192c..64a538abce 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -1442,8 +1442,8 @@ public ActionResult Search() model.ForumSearchSettings.AvailableSearchFields = new List { - new SelectListItem { Text = T("Forum.TopicTitle"), Value = "subject" }, - new SelectListItem { Text = T("Admin.Customers.Customers.Fields.Username"), Value = "username" } + new SelectListItem { Text = T("Admin.Customers.Customers.Fields.Username"), Value = "username" }, + new SelectListItem { Text = T("Forum.PostText"), Value = "text" }, }; // Common facets. diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs index 7c7557d5e4..8d8256a982 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs @@ -641,8 +641,7 @@ public AutoMapperAdminProfile() .ForMember(dest => dest.PMSubjectMaxLength, mo => mo.Ignore()) .ForMember(dest => dest.PMTextMaxLength, mo => mo.Ignore()) .ForMember(dest => dest.HomePageActiveDiscussionsTopicCount, mo => mo.Ignore()) - .ForMember(dest => dest.ActiveDiscussionsPageTopicCount, mo => mo.Ignore()) - .ForMember(dest => dest.ForumSearchTermMinimumLength, mo => mo.Ignore()); + .ForMember(dest => dest.ActiveDiscussionsPageTopicCount, mo => mo.Ignore()); CreateMap(); CreateMap(); CreateMap() diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 4720d33576..12c49f00d7 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -31,7 +31,7 @@ namespace SmartStore.Web.Controllers { - [RequireHttpsByConfigAttribute(SslRequirement.No)] + [RequireHttpsByConfig(SslRequirement.No)] public partial class BoardsController : PublicControllerBase { private readonly IForumService _forumService; @@ -85,6 +85,7 @@ protected ForumTopicRowModel PrepareForumTopicRowModel(ForumTopic topic) Id = topic.Id, Subject = topic.Subject, SeName = topic.GetSeName(), + FirstPostId = topic.FirstPostId, LastPostId = topic.LastPostId, NumPosts = topic.NumPosts, Views = topic.Views, @@ -93,12 +94,10 @@ protected ForumTopicRowModel PrepareForumTopicRowModel(ForumTopic topic) CustomerId = topic.CustomerId, AllowViewingProfiles = _customerSettings.AllowViewingProfiles, CustomerName = topic.Customer.FormatUserName(true), - IsCustomerGuest = topic.Customer.IsGuest() + IsCustomerGuest = topic.Customer.IsGuest(), + PostsPageSize = _forumSettings.PostsPageSize }; - var forumPosts = _forumService.GetAllPosts(topic.Id, 0, string.Empty, true, 1, _forumSettings.PostsPageSize); - topicModel.TotalPostPages = forumPosts.TotalPages; - return topicModel; } @@ -238,7 +237,7 @@ private void SaveLastForumVisit(Customer customer) { try { - if (!customer.IsSystemAccount) + if (!customer.Deleted && customer.Active && !customer.IsSystemAccount) { _genericAttributeService.SaveAttribute( customer, @@ -336,7 +335,7 @@ public ActionResult Forum(int id, int page = 1) } var pageSize = _forumSettings.TopicsPageSize > 0 ? _forumSettings.TopicsPageSize : 10; - var topics = _forumService.GetAllTopics(forum.Id, 0, string.Empty, ForumSearchType.All, 0, (page - 1), pageSize); + var topics = _forumService.GetAllTopics(forum.Id, (page - 1), pageSize); model.TopicPageSize = topics.PageSize; model.TopicTotalRecords = topics.TotalCount; @@ -393,7 +392,7 @@ public ActionResult ForumRss(int id = 0) feed.Title = new TextSyndicationContent("{0} - {1}".FormatInvariant(store.Name, forum.GetLocalized(x => x.Name, language))); var items = new List(); - var topics = _forumService.GetAllTopics(id, 0, string.Empty, ForumSearchType.All, 0, 0, _forumSettings.ForumFeedCount); + var topics = _forumService.GetAllTopics(id, 0, _forumSettings.ForumFeedCount); var viewsText = T("Forum.Views"); var repliesText = T("Forum.Replies"); @@ -570,7 +569,7 @@ public ActionResult Topic(int id, int page = 1) if (forumTopic != null) { - var posts = _forumService.GetAllPosts(forumTopic.Id, 0, string.Empty, true, page - 1, _forumSettings.PostsPageSize); + var posts = _forumService.GetAllPosts(forumTopic.Id, 0, true, page - 1, _forumSettings.PostsPageSize); // If no posts area loaded, redirect to the first page. if (posts.Count == 0 && page > 1) @@ -581,7 +580,7 @@ public ActionResult Topic(int id, int page = 1) // Update view count. try { - if (!customer.IsSystemAccount) + if (!customer.Deleted && customer.Active && !customer.IsSystemAccount) { forumTopic.Views += 1; _forumService.UpdateTopic(forumTopic); @@ -1556,194 +1555,6 @@ public ActionResult LastPost(int forumPostId, bool showTopic) #region Search - public ActionResult Search_Old(string searchterms, bool? adv, string forumId, string within, string limitDays, int page = 1) - { - if (!_forumSettings.ForumsEnabled) - { - return HttpNotFound(); - } - - var pageSize = 10; - var store = Services.StoreContext.CurrentStore; - var model = new SearchModel(); - - // Create the values for the "Limit results to previous" select list - var limitList = new List - { - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.AllResults"), - Value = "0" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.1day"), - Value = "1" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.7days"), - Value = "7" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.2weeks"), - Value = "14" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.1month"), - Value = "30" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.3months"), - Value = "92" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.6months"), - Value = "183" - }, - new SelectListItem - { - Text = T("Forum.Search.LimitResultsToPrevious.1year"), - Value = "365" - } - }; - - model.LimitList = limitList; - - // Create the values for the "Search in forum" select list. - var forumsSelectList = new List(); - forumsSelectList.Add(new SelectListItem - { - Text = T("Forum.Search.SearchInForum.All"), - Value = "0", - Selected = true, - }); - - var separator = "--"; - var forumGroups = _forumService.GetAllForumGroups(store.Id); - foreach (var fg in forumGroups) - { - // Add the forum group with value as '-' so it can't be used as a target forum id. - forumsSelectList.Add(new SelectListItem { Text = fg.GetLocalized(x => x.Name), Value = "-" }); - - var forums = _forumService.GetAllForumsByGroupId(fg.Id); - foreach (var f in forums) - { - forumsSelectList.Add(new SelectListItem - { - Text = string.Format("{0}{1}", separator, f.GetLocalized(x => x.Name)), - Value = f.Id.ToString() - }); - } - } - model.ForumList = forumsSelectList; - - // Create the values for "Search within" select list. - var withinList = new List - { - new SelectListItem - { - Value = ((int)ForumSearchType.All).ToString(), - Text = T("Forum.Search.SearchWithin.All") - }, - new SelectListItem - { - Value = ((int)ForumSearchType.TopicTitlesOnly).ToString(), - Text = T("Forum.Search.SearchWithin.TopicTitlesOnly") - }, - new SelectListItem - { - Value = ((int)ForumSearchType.PostTextOnly).ToString(), - Text = T("Forum.Search.SearchWithin.PostTextOnly") - } - }; - - model.WithinList = withinList; - - int.TryParse(forumId, out var forumIdSelected); - model.ForumIdSelected = forumIdSelected; - - int.TryParse(within, out var withinSelected); - model.WithinSelected = withinSelected; - - int.TryParse(limitDays, out var limitDaysSelected); - model.LimitDaysSelected = limitDaysSelected; - - model.ShowAdvancedSearch = adv.GetValueOrDefault(); - model.SearchResultsVisible = false; - model.NoResultsVisisble = false; - model.PostsPageSize = _forumSettings.PostsPageSize; - - try - { - if (String.IsNullOrWhiteSpace(searchterms) == false) - { - searchterms = searchterms.Trim(); - model.SearchTerms = searchterms; - - if (searchterms.Length < _forumSettings.ForumSearchTermMinimumLength) - { - throw new SmartException(T("Forum.SearchTermMinimumLengthIsNCharacters", _forumSettings.ForumSearchTermMinimumLength)); - } - - ForumSearchType searchWithin = 0; - var limitResultsToPrevious = 0; - if (adv.GetValueOrDefault() == true) - { - searchWithin = (ForumSearchType)withinSelected; - limitResultsToPrevious = limitDaysSelected; - } - - if (_forumSettings.SearchResultsPageSize > 0) - { - pageSize = _forumSettings.SearchResultsPageSize; - } - - var topics = _forumService.GetAllTopics(forumIdSelected, 0, searchterms, searchWithin, limitResultsToPrevious, page - 1, pageSize); - - model.TopicPageSize = topics.PageSize; - model.TopicTotalRecords = topics.TotalCount; - model.TopicPageIndex = topics.PageIndex; - - foreach (var topic in topics) - { - var topicModel = PrepareForumTopicRowModel(topic); - model.ForumTopics.Add(topicModel); - } - - model.SearchResultsVisible = (topics.Count > 0); - model.NoResultsVisisble = !(model.SearchResultsVisible); - - CreateForumBreadcrumb(); - _breadcrumb.Track(new MenuItem { Text = T("Forum.Search") }); - - return View(model); - } - else - { - model.SearchResultsVisible = false; - } - } - catch (Exception ex) - { - model.Error = ex.Message; - } - - // Some exception raised. - model.TopicPageSize = pageSize; - model.TopicTotalRecords = 0; - model.TopicPageIndex = page - 1; - - CreateForumBreadcrumb(); - _breadcrumb.Track(new MenuItem { Text = T("Forum.Search") }); - - return View(model); - } - [RequireHttpsByConfig(SslRequirement.No), ValidateInput(false)] public ActionResult Search(ForumSearchQuery query/*, bool? adv*/) { diff --git a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs index 0090cefcbd..d54963f455 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs @@ -750,8 +750,7 @@ protected int GetUnreadPrivateMessages() var customer = _services.WorkContext.CurrentCustomer; if (_forumSettings.AllowPrivateMessages && !customer.IsGuest()) { - var privateMessages = _forumservice.Value.GetAllPrivateMessages(_services.StoreContext.CurrentStore.Id, 0, customer.Id, false, null, false, string.Empty, 0, 1); - + var privateMessages = _forumservice.Value.GetAllPrivateMessages(_services.StoreContext.CurrentStore.Id, 0, customer.Id, false, null, false, 0, 1); if (privateMessages.TotalCount > 0) { result = privateMessages.TotalCount; diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index 681130ac4b..246eb0124a 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -185,8 +185,7 @@ protected int GetUnreadPrivateMessages() var customer = _services.WorkContext.CurrentCustomer; if (_forumSettings.AllowPrivateMessages && !customer.IsGuest()) { - var privateMessages = _forumService.GetAllPrivateMessages(_services.StoreContext.CurrentStore.Id, 0, customer.Id, false, null, false, string.Empty, 0, 1); - + var privateMessages = _forumService.GetAllPrivateMessages(_services.StoreContext.CurrentStore.Id, 0, customer.Id, false, null, false, 0, 1); if (privateMessages.TotalCount > 0) { result = privateMessages.TotalCount; diff --git a/src/Presentation/SmartStore.Web/Controllers/PrivateMessagesController.cs b/src/Presentation/SmartStore.Web/Controllers/PrivateMessagesController.cs index d6a9e186c7..1d7f7ce196 100644 --- a/src/Presentation/SmartStore.Web/Controllers/PrivateMessagesController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/PrivateMessagesController.cs @@ -122,14 +122,7 @@ public ActionResult Inbox(int page, string tab) } var pageSize = _forumSettings.PrivateMessagesPageSize; - - var list = _forumService.GetAllPrivateMessages( - _storeContext.CurrentStore.Id, - 0, - _workContext.CurrentCustomer.Id, - null, null, false, string.Empty, - page, pageSize); - + var list = _forumService.GetAllPrivateMessages(_storeContext.CurrentStore.Id, 0, _workContext.CurrentCustomer.Id, null, null, false, page, pageSize); var inbox = new List(); foreach (var pm in list) @@ -170,10 +163,7 @@ public ActionResult SentItems(int page, string tab) } var pageSize = _forumSettings.PrivateMessagesPageSize; - - var list = _forumService.GetAllPrivateMessages(_storeContext.CurrentStore.Id, - _workContext.CurrentCustomer.Id, 0, null, false, null, string.Empty, page, pageSize); - + var list = _forumService.GetAllPrivateMessages(_storeContext.CurrentStore.Id, _workContext.CurrentCustomer.Id, 0, null, false, null, page, pageSize); var sentItems = new List(); foreach (var pm in list) diff --git a/src/Presentation/SmartStore.Web/Controllers/ProfileController.cs b/src/Presentation/SmartStore.Web/Controllers/ProfileController.cs index 66648903a2..2d4329e5b8 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProfileController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProfileController.cs @@ -212,13 +212,10 @@ public ActionResult Posts(int customerProfileId, int page) page -= 1; } - var pageSize = _forumSettings.LatestCustomerPostsPageSize; - - var list = _forumService.GetAllPosts(0, customer.Id, string.Empty, false, page, pageSize); - + var posts = _forumService.GetAllPosts(0, customer.Id, false, page, _forumSettings.LatestCustomerPostsPageSize); var latestPosts = new List(); - foreach (var forumPost in list) + foreach (var forumPost in posts) { var posted = string.Empty; if (_forumSettings.RelativeDateTimeFormattingEnabled) @@ -230,7 +227,7 @@ public ActionResult Posts(int customerProfileId, int page) posted = _dateTimeHelper.ConvertToUserTime(forumPost.CreatedOnUtc, DateTimeKind.Utc).ToString("f"); } - latestPosts.Add(new PostsModel() + latestPosts.Add(new PostsModel { ForumTopicId = forumPost.TopicId, ForumTopicTitle = forumPost.ForumTopic.Subject, @@ -242,7 +239,7 @@ public ActionResult Posts(int customerProfileId, int page) ViewData["PagerRouteValues"] = new RouteValues { page = page, id = customerProfileId }; - var model = new ProfilePostsModel(list) + var model = new ProfilePostsModel(posts) { Posts = latestPosts, }; diff --git a/src/Presentation/SmartStore.Web/Models/Boards/ForumTopicRowModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/ForumTopicRowModel.cs index 7b5f442cea..2e7a5c1ea0 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/ForumTopicRowModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/ForumTopicRowModel.cs @@ -7,19 +7,36 @@ public partial class ForumTopicRowModel public int Id { get; set; } public string Subject { get; set; } public string SeName { get; set; } + public int FirstPostId { get; set; } public int LastPostId { get; set; } + public ForumTopicType ForumTopicType { get; set; } public int NumPosts { get; set; } public int Views { get; set; } public int NumReplies { get; set; } - public ForumTopicType ForumTopicType { get; set; } + + public int PostsPageSize { get; set; } + public int TotalPostPages + { + get + { + return PostsPageSize != 0 + ? (NumPosts / PostsPageSize) + 1 + : 1; + } + } public int CustomerId { get; set; } public bool AllowViewingProfiles { get; set; } public string CustomerName { get; set; } public bool IsCustomerGuest { get; set; } - //posts - public int TotalPostPages { get; set; } + public string AnchorTag + { + get + { + return FirstPostId == 0 ? string.Empty : string.Concat("#", FirstPostId); + } + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Boards/SearchModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/SearchModel.cs deleted file mode 100644 index dcb6352ce6..0000000000 --- a/src/Presentation/SmartStore.Web/Models/Boards/SearchModel.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Collections.Generic; -using System.Web.Mvc; - -namespace SmartStore.Web.Models.Boards -{ - public partial class SearchModel - { - public SearchModel() - { - LimitList = new List(); - ForumList = new List(); - WithinList = new List(); - ForumTopics = new List(); - } - - public bool ShowAdvancedSearch { get; set; } - - [AllowHtml] - public string SearchTerms { get; set; } - - public int? ForumId { get; set; } - - public int? Within { get; set; } - - public int? LimitDays { get; set; } - - public IList ForumTopics { get; set; } - public int TopicPageSize { get; set; } - public int TopicTotalRecords { get; set; } - public int TopicPageIndex { get; set; } - - public List LimitList { get; set; } - - public List ForumList { get; set; } - - public List WithinList { get; set; } - - public int ForumIdSelected { get; set; } - - public int WithinSelected { get; set; } - - public int LimitDaysSelected { get; set; } - - public bool SearchResultsVisible { get; set; } - - public bool NoResultsVisisble { get; set; } - - public string Error { get; set; } - - public int PostsPageSize { get; set; } - } -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Scripts/public.search.js b/src/Presentation/SmartStore.Web/Scripts/public.search.js index 6beb04418b..7bd1274d49 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.search.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.search.js @@ -257,7 +257,43 @@ if (idx > 0 && otherIdx > 0 && ((isMin && idx >= otherIdx) || (!isMin && idx <= otherIdx))) { validateRangeControls(); } - }); + }); + + // Switch range value to upper or back. + widget.on('change', 'select.facet-switch-range', function (e, recursive) { + if (recursive) + return; + + var select = $(this), + selectedUrl = null, + toUpper = select.val() === 'upper', + qname = select.data('qname'); + + // Update all url and input values. + select.closest('.facet-group').find('.facet-item').each(function (index) { + var item = $(this), + url = item.attr('data-href'), + input = item.find('input.facet-control-native'), + val = input.val().replace('~', ''); + + if (toUpper) { + val = '~' + val; + } + url = modifyUrl(url, qname, val); + + input.val(val); + item.attr('data-href', url); + + if (input.is(':checked')) { + selectedUrl = url; + } + }); + + // Update location for selected filter. + if (selectedUrl) { + setLocation(selectedUrl); + } + }); })(); diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index 7aaaccf9e1..5512a93144 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -339,7 +339,6 @@ - diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/Filters.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/Filters.cshtml index e31e646ab5..1777ee2dca 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/Filters.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/Filters.cshtml @@ -62,6 +62,15 @@ }
+ @if (group.Kind == FacetGroupKind.Date && group.Facets.Any()) + { +
+ +
+ }
}
diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml index d7d3e92633..c08f1e5ea5 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml @@ -24,7 +24,7 @@ suggestions = Model.SearchResult.SpellCheckerSuggestions; } - var pager = "TODO: pager"; + var pager = "TODO: pager etc."; //var pager = Html.SmartStore().Pager(Model.SearchResult.Hits.PageIndex, Model.SearchResult.Hits.PageSize, Model.TotalCount) // .Name("paginator") // .Alignment(PagerAlignment.Right) @@ -51,21 +51,20 @@
+ @if (Model.SearchResult.Facets.Any()) + { +
+ @{ Html.RenderAction("Filters", new { model = Model }); } +
+ } + @using (Html.BeginRouteForm("BoardSearch", FormMethod.Get)) { -
- @if (Model.SearchResult.Facets.Any()) - { -
- @{ Html.RenderAction("Filters", new { model = Model }); } -
- } -
- -
- -
-
+
+ +
+ +
} @@ -162,7 +161,9 @@ { @Html.Raw(topicText) } - @Html.RouteLink(topic.Subject, "TopicSlug", new { id = topic.Id, slug = topic.SeName }, new { @class = "topic-title" }) + + @Html.Raw(Model.SearchResult.Highlight(topic.Subject, "", "", false)) + @if (topic.NumPosts > Model.PostsPageSize) { @(Html.SmartStore().Pager(topic.TotalPostPages) @@ -211,199 +212,3 @@ @pager
- - - -@**@ diff --git a/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/MultiSelect.cshtml b/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/MultiSelect.cshtml index 1692c7b781..92219da7e7 100644 --- a/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/MultiSelect.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/MultiSelect.cshtml @@ -43,7 +43,7 @@
} -
+
@foreach (var facet in facets) {
diff --git a/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/SingleSelect.cshtml b/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/SingleSelect.cshtml index fdfb0b9daf..fc6005b991 100644 --- a/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/SingleSelect.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Search/FacetTemplates/SingleSelect.cshtml @@ -3,7 +3,7 @@ @using SmartStore.Core.Search.Facets @using SmartStore.Services.Search.Extensions; -
+
@foreach (var facet in Model.Facets) { if (facet.Published) From 9fcbdaaf9aeab774dc91d98b1a4b09bac0e5c251 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 3 Sep 2018 09:40:39 +0200 Subject: [PATCH 072/932] Removed an unused view model --- .../Models/CheckoutBillingAddressModel.cs | 24 ------------------- .../SmartStore.Web/SmartStore.Web.csproj | 1 - 2 files changed, 25 deletions(-) delete mode 100644 src/Presentation/SmartStore.Web/Models/CheckoutBillingAddressModel.cs diff --git a/src/Presentation/SmartStore.Web/Models/CheckoutBillingAddressModel.cs b/src/Presentation/SmartStore.Web/Models/CheckoutBillingAddressModel.cs deleted file mode 100644 index d07e120dff..0000000000 --- a/src/Presentation/SmartStore.Web/Models/CheckoutBillingAddressModel.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using SmartStore.Web.Framework.Modelling; -using SmartStore.Web.Models.Common; - -namespace SmartStore.Web.Models.Checkout -{ - public partial class CheckoutBillingAddressModel2 : ModelBase - { - public CheckoutBillingAddressModel2() - { - ExistingAddresses = new List(); - NewAddress = new AddressModel(); - } - - public IList ExistingAddresses { get; set; } - - public AddressModel NewAddress { get; set; } - - /// - /// Used on one-page checkout page - /// - public bool NewAddressPreselected { get; set; } - } -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index 5512a93144..dff3e3d3c5 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -357,7 +357,6 @@ - From 429a67e2f9db9154fd40b9441c31477abfcfeaf9 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 3 Sep 2018 12:13:04 +0200 Subject: [PATCH 073/932] Fixed fadeInUp animation --- src/Presentation/SmartStore.Web/Content/shared/_animate.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Content/shared/_animate.scss b/src/Presentation/SmartStore.Web/Content/shared/_animate.scss index 3617766864..083ca3753b 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_animate.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_animate.scss @@ -144,8 +144,8 @@ body { } } - .fadeIn { - animation-name: fadeIn; + .fadeInUp { + animation-name: fadeInUp; } } From ad131b5062686a2e7fb9bd0e65e7f7854a1a18c6 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 3 Sep 2018 12:17:20 +0200 Subject: [PATCH 074/932] Minor change --- .../Controllers/PluginController.cs | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs index f780a022b9..5627bacdc2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs @@ -11,6 +11,7 @@ using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Tax; using SmartStore.Core.Html; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Licensing; using SmartStore.Services; @@ -73,13 +74,29 @@ public PluginController(IPluginFinder pluginFinder, this._services = services; } - #endregion + #endregion + + #region Utilities - #region Utilities + private bool IsLicensable(PluginDescriptor pluginDescriptor) + { + var result = false; - private LicensingData PrepareLicenseLabelModel(LicenseLabelModel model, PluginDescriptor pluginDescriptor, string url = null) + try + { + result = LicenseChecker.IsLicensablePlugin(pluginDescriptor); + } + catch (Exception ex) + { + Logger.Error(ex); + } + + return result; + } + + private LicensingData PrepareLicenseLabelModel(LicenseLabelModel model, PluginDescriptor pluginDescriptor, string url = null) { - if (LicenseChecker.IsLicensablePlugin(pluginDescriptor)) + if (IsLicensable(pluginDescriptor)) { // We always show license button to serve ability to delete a key. model.IsLicensable = true; @@ -421,16 +438,22 @@ public ActionResult LicensePlugin(string systemName, string licenseKey) [HttpPost] public ActionResult LicensePlugin(string systemName, LicensePluginModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManagePlugins)) - return AccessDeniedView(); + if (!_permissionService.Authorize(StandardPermissionProvider.ManagePlugins)) + { + return AccessDeniedView(); + } var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(systemName); - if (descriptor == null || !descriptor.Installed) - return HttpNotFound(); + if (descriptor == null || !descriptor.Installed) + { + return HttpNotFound(); + } - var isLicensable = LicenseChecker.IsLicensablePlugin(descriptor); - if (!isLicensable) - return HttpNotFound(); + var isLicensable = IsLicensable(descriptor); + if (!isLicensable) + { + return HttpNotFound(); + } if (model.StoreLicenses != null) { From ace7c58c02f9474b566d18c66c79412d1214ea60 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 4 Sep 2018 02:48:11 +0200 Subject: [PATCH 075/932] Minor CSS issue --- .../SmartStore.Web/Content/skinning/_summernote.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Content/skinning/_summernote.scss b/src/Presentation/SmartStore.Web/Content/skinning/_summernote.scss index bc9cd36c50..d77738b43e 100644 --- a/src/Presentation/SmartStore.Web/Content/skinning/_summernote.scss +++ b/src/Presentation/SmartStore.Web/Content/skinning/_summernote.scss @@ -67,8 +67,7 @@ .btn.note-btn { // overwrite any theme-specific button skinning - transform: none !important; - box-shadow: none; + } .dropdown-toggle:after { From 06d5dfe2fcdc634907531a6277aa9593b8ba808f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 4 Sep 2018 12:31:53 +0200 Subject: [PATCH 076/932] Search: Show highlighted text fragments if a search hit was found in the first post. Consider CustomerSettings.CustomerNameFormat when searching forum by user name. --- .../SmartStore.Core/Search/ISearchEngine.cs | 30 ++-- .../Customers/CustomerExtensions.cs | 135 +++++++++++------- .../Search/Catalog/CatalogSearchResult.cs | 8 +- .../Search/Extensions/FacetUtility.cs | 34 +---- .../Search/Forum/ForumSearchResult.cs | 39 +++-- .../Search/Forum/ForumSearchService.cs | 36 +---- .../Search/Forum/LinqForumSearchService.cs | 42 ++++-- .../SmartStore.Web/Views/Boards/Search.cshtml | 6 + 8 files changed, 177 insertions(+), 153 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs b/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs index 70e9dcb508..2a17f673b0 100644 --- a/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs +++ b/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs @@ -47,13 +47,25 @@ public interface ISearchEngine /// Suggestions/corrections or an empty array string[] CheckSpelling(); - /// - /// Highlights chosen terms in a text, extracting the most relevant sections - /// - /// Text to highlight terms in - /// Text/HTML to prepend to matched keyword - /// Text/HTML to append to matched keyword - /// Highlighted text fragments - string Highlight(string input, string preMatch, string postMatch); - } + /// + /// Highlights chosen terms in a text, extracting the most relevant sections + /// + /// Text to highlight terms in + /// Field name + /// Text/HTML to prepend to matched keyword + /// Text/HTML to append to matched keyword + /// Highlighted text fragments + string Highlight(string input, string fieldName, string preMatch, string postMatch); + + /// + /// Gets highlighted text fragments for an entity identifier. + /// + /// Entity identifier. + /// Field name. + /// Text/HTML to prepend to matched keyword. + /// Text/HTML to append to matched keyword. + /// Maximum number of returned text fragments. + /// Highlighted text fragments. + string Highlight(int id, string fieldName, string preMatch, string postMatch, int maxNumFragments); + } } diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerExtensions.cs b/src/Libraries/SmartStore.Services/Customers/CustomerExtensions.cs index 72123f9920..8c5c061837 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerExtensions.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerExtensions.cs @@ -8,6 +8,7 @@ using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Infrastructure; +using SmartStore.Core.Localization; using SmartStore.Services.Common; using SmartStore.Services.Localization; using SmartStore.Services.Orders; @@ -163,33 +164,62 @@ public static string GetFullName(this Customer customer) } /// - /// Formats the customer name + /// Formats the customer name. /// - /// Source - /// Formatted text + /// Customer entity. + /// Formatted customer name. public static string FormatUserName(this Customer customer) { return FormatUserName(customer, false); } /// - /// Formats the customer name + /// Formats the customer name. /// - /// Source - /// Strip too long customer name - /// Formatted text + /// Customer entity. + /// Whether to strip too long customer name. + /// Formatted customer name. public static string FormatUserName(this Customer customer, bool stripTooLong) { + var engine = EngineContext.Current; + + var userName = customer.FormatUserName( + engine.Resolve(), + engine.Resolve(), + stripTooLong); + + return userName; + } + + /// + /// Formats the customer name. + /// + /// Customer entity. + /// Customer settings. + /// Localizer. + /// Whether to strip too long customer name. + /// Formatted customer name. + public static string FormatUserName( + this Customer customer, + CustomerSettings customerSettings, + Localizer T, + bool stripTooLong) + { + Guard.NotNull(customerSettings, nameof(customerSettings)); + Guard.NotNull(T, nameof(T)); + if (customer == null) + { return string.Empty; - + } if (customer.IsGuest()) { - return EngineContext.Current.Resolve().GetResource("Customer.Guest"); + return T("Customer.Guest"); } - string result = string.Empty; - switch (EngineContext.Current.Resolve().CustomerNameFormat) + var result = string.Empty; + + switch (customerSettings.CustomerNameFormat) { case CustomerNameFormat.ShowEmails: result = customer.Email; @@ -200,59 +230,56 @@ public static string FormatUserName(this Customer customer, bool stripTooLong) case CustomerNameFormat.ShowUsernames: result = customer.Username; break; - case CustomerNameFormat.ShowFirstName: - result = customer.FirstName; - break; - case CustomerNameFormat.ShowNameAndCity: - { - var firstName = customer.FirstName; - var lastName = customer.LastName; - var city = customer.GetAttribute(SystemCustomerAttributeNames.City); - - if (firstName.IsEmpty()) - { - var address = customer.Addresses.FirstOrDefault(); - if (address != null) - { - firstName = address.FirstName; - lastName = address.LastName; - city = address.City; - } - } - - result = firstName; - if (lastName.HasValue()) - { - result = "{0} {1}.".FormatWith(result, lastName.First()); - } - - if (city.HasValue()) - { - var from = EngineContext.Current.Resolve().GetResource("Common.ComingFrom"); - result = "{0} {1} {2}".FormatWith(result, from, city); - } - } - break; + case CustomerNameFormat.ShowFirstName: + result = customer.FirstName; + break; + case CustomerNameFormat.ShowNameAndCity: + { + var firstName = customer.FirstName; + var lastName = customer.LastName; + var city = customer.GetAttribute(SystemCustomerAttributeNames.City); + + if (firstName.IsEmpty()) + { + var address = customer.Addresses.FirstOrDefault(); + if (address != null) + { + firstName = address.FirstName; + lastName = address.LastName; + city = address.City; + } + } + + result = firstName; + if (lastName.HasValue()) + { + result = "{0} {1}.".FormatInvariant(result, lastName.First()); + } + + if (city.HasValue()) + { + var from = T("Common.ComingFrom"); + result = "{0} {1} {2}".FormatInvariant(result, from, city); + } + } + break; default: break; } - if (stripTooLong && result.HasValue()) + var maxLength = customerSettings.CustomerNameFormatMaxLength; + if (stripTooLong && maxLength > 0 && result != null && result.Length > maxLength) { - int maxLength = EngineContext.Current.Resolve().CustomerNameFormatMaxLength; - if (maxLength > 0 && result.Length > maxLength) - { - result = result.Truncate(maxLength, "..."); - } + result = result.Truncate(maxLength, "..."); } return result; } - /// - /// Find any email address of customer - /// - public static string FindEmail(this Customer customer) + /// + /// Find any email address of customer + /// + public static string FindEmail(this Customer customer) { if (customer != null) { diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchResult.cs b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchResult.cs index e37c1f3c7f..c130ae6b46 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchResult.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchResult.cs @@ -103,8 +103,10 @@ public ISearchEngine Engine /// Highlighted text fragments public string Highlight(string input, string preMatch = "", string postMatch = "", bool useSearchEngine = true) { - if (Query?.Term == null || input.IsEmpty()) - return input; + if (Query?.Term == null || input.IsEmpty()) + { + return input; + } string hilite = null; @@ -112,7 +114,7 @@ public string Highlight(string input, string preMatch = "", string postM { try { - hilite = Engine.Highlight(input, preMatch, postMatch); + hilite = Engine.Highlight(input, "name", preMatch, postMatch); } catch { } } diff --git a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs index 44974917b5..26bb723fba 100644 --- a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs +++ b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs @@ -140,36 +140,6 @@ public static IEnumerable GetRatings() } } - public static string GetPublicName(Customer customer, bool userNamesEnabled) - { - if (customer == null) - { - return null; - } - - var name = userNamesEnabled ? customer.Username : null; - - if (string.IsNullOrWhiteSpace(name)) - { - name = GetPublicName(customer.FirstName, customer.LastName); - } - else - { - var atSymbol = name.IndexOf('@'); - if (atSymbol > 1) - { - name = name.Substring(0, atSymbol); - } - } - - if (customer.BillingAddress != null && string.IsNullOrWhiteSpace(name)) - { - name = GetPublicName(customer.BillingAddress.FirstName, customer.BillingAddress.LastName); - } - - return name; - } - public static IQueryable GetCustomersByNumberOfPosts( IRepository forumPostRepository, IRepository storeMappingRepository, @@ -178,7 +148,9 @@ public static IQueryable GetCustomersByNumberOfPosts( { var postQuery = forumPostRepository.TableUntracked .Expand(x => x.Customer) - .Expand(x => x.Customer.BillingAddress); + .Expand(x => x.Customer.BillingAddress) + .Expand(x => x.Customer.ShippingAddress) + .Expand(x => x.Customer.Addresses); if (storeId > 0) { diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs index 4e9a3a3e5a..c68b0ec5fc 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using SmartStore.Core; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Search; @@ -76,15 +77,17 @@ public IPagedList Hits public ISearchEngine Engine { get; private set; } - /// - /// Highlights chosen terms in a text, extracting the most relevant sections - /// - /// Text to highlight terms in - /// Highlighted text fragments - public string Highlight(string input, string preMatch = "", string postMatch = "", bool useSearchEngine = true) + /// + /// Highlights chosen terms in a text, extracting the most relevant sections + /// + /// Text to highlight terms in + /// Highlighted text fragments + public string Highlight(string input, string preMatch = "", string postMatch = "", bool useSearchEngine = true) { - if (Query?.Term == null || input.IsEmpty()) - return input; + if (Query?.Term == null || input.IsEmpty()) + { + return input; + } string hilite = null; @@ -92,7 +95,7 @@ public string Highlight(string input, string preMatch = "", string postM { try { - hilite = Engine.Highlight(input, preMatch, postMatch); + hilite = Engine.Highlight(input, "subject", preMatch, postMatch); } catch { } } @@ -104,5 +107,21 @@ public string Highlight(string input, string preMatch = "", string postM return input.HighlightKeywords(Query.Term, preMatch, postMatch); } - } + + public bool CanHighlightPostFor(int postId) + { + if (postId != 0 && Engine != null && Query.Fields != null && Query.Fields.Contains("text")) + { + return true; + } + + return false; + } + + public string Highlight(int postId, string preMatch = "", string postMatch = "") + { + var fraqment = Engine.Highlight(postId, "text", preMatch, postMatch, 3); + return fraqment; + } + } } diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs index 804ba59d23..f4d534d34f 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs @@ -4,7 +4,6 @@ using System.Web.Mvc; using Autofac; using SmartStore.Core.Domain.Forums; -using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; @@ -32,12 +31,8 @@ public ForumSearchService( _forumService = forumService; _logger = logger; _urlHelper = urlHelper; - - T = NullLocalizer.Instance; } - public Localizer T { get; set; } - /// /// Bypasses the index provider and directly searches in the database /// @@ -48,36 +43,10 @@ protected virtual ForumSearchResult SearchDirect(ForumSearchQuery searchQuery) // Fallback to linq search. var linqForumSearchService = _services.Container.ResolveNamed("linq"); var result = linqForumSearchService.Search(searchQuery, true); - ApplyFacetLabels(result.Facets); return result; } - protected virtual void ApplyFacetLabels(IDictionary facets) - { - if (facets == null || !facets.Any()) - { - return; - } - - // Ensure that there are no duplicate customer labels. - if (facets.TryGetValue("customerid", out var grp)) - { - var groupedByLabel = - from x in grp.Facets - group x by x.Value.Label into g - select g; - - foreach (var item in groupedByLabel.Where(x => x.Count() > 1)) - { - foreach (var facet in item) - { - facet.Value.Label = $"{facet.Value.Value} ({facet.Value.Value.ToString()})"; - } - } - } - } - public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = false) { Guard.NotNull(searchQuery, nameof(searchQuery)); @@ -129,10 +98,9 @@ public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = fals }) .ToMultimap(x => x.TopicId, x => x.PostId); - var topicIds = postIds.Select(x => x.Key).ToArray(); - hitsFactory = () => { + var topicIds = postIds.Select(x => x.Key).ToArray(); var hits = _forumService.Value.GetTopicsByIds(topicIds); // Provide the id of the first hit, so that we can jump directly to the post when opening the topic. @@ -140,6 +108,7 @@ public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = fals { if (postIds.ContainsKey(topic.Id)) { + // Unified order: we only sort by ForumPost.Id to save an additional database query here. topic.FirstPostId = postIds[topic.Id].OrderBy(x => x).FirstOrDefault(); } } @@ -156,7 +125,6 @@ public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = fals using (_services.Chronometer.Step(stepPrefix + "Facets")) { facets = searchEngine.GetFacetMap(); - ApplyFacetLabels(facets); } } catch (Exception ex) diff --git a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs index 7ef1d2d6b5..3bc7f8166c 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -5,8 +5,10 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Localization; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; +using SmartStore.Services.Customers; using SmartStore.Services.Forums; using SmartStore.Services.Localization; using SmartStore.Services.Search.Extensions; @@ -19,48 +21,64 @@ public partial class LinqForumSearchService : SearchServiceBase, IForumSearchSer private readonly IRepository _storeMappingRepository; private readonly IForumService _forumService; private readonly ICommonServices _services; + private readonly CustomerSettings _customerSettings; - public LinqForumSearchService( + public LinqForumSearchService( IRepository forumPostRepository, IRepository storeMappingRepository, IForumService forumService, - ICommonServices services) + ICommonServices services, + CustomerSettings customerSettings) { _forumPostRepository = forumPostRepository; _storeMappingRepository = storeMappingRepository; _forumService = forumService; _services = services; + _customerSettings = customerSettings; + T = NullLocalizer.Instance; QuerySettings = DbQuerySettings.Default; } + public Localizer T { get; set; } public DbQuerySettings QuerySettings { get; set; } protected virtual IQueryable GetTopicQuery(ForumSearchQuery searchQuery, IQueryable baseQuery) { // Post query. var ordered = false; - var term = searchQuery.Term; + var t = searchQuery.Term; + var f = _customerSettings.CustomerNameFormat; var fields = searchQuery.Fields; var filters = new List(); var query = baseQuery ?? _forumPostRepository.TableUntracked.Expand(x => x.ForumTopic); // Apply search term. - if (term.HasValue() && fields != null && fields.Length != 0 && fields.Any(x => x.HasValue())) + if (t.HasValue() && fields != null && fields.Length != 0 && fields.Any(x => x.HasValue())) { if (searchQuery.Mode == SearchMode.StartsWith) { query = query.Where(x => - (fields.Contains("subject") && x.ForumTopic.Subject.StartsWith(term)) || - (fields.Contains("username") && x.Customer.Username.StartsWith(term)) || - (fields.Contains("text") && x.Text.StartsWith(term))); + (fields.Contains("subject") && x.ForumTopic.Subject.StartsWith(t)) || + (fields.Contains("text") && x.Text.StartsWith(t)) || + (fields.Contains("username") && ( + f == CustomerNameFormat.ShowEmails ? x.Customer.Email.StartsWith(t) : + f == CustomerNameFormat.ShowUsernames ? x.Customer.Username.StartsWith(t) : + f == CustomerNameFormat.ShowFirstName ? x.Customer.FirstName.StartsWith(t) : + x.Customer.FullName.StartsWith(t)) + )); } else { query = query.Where(x => - (fields.Contains("subject") && x.ForumTopic.Subject.Contains(term)) || - (fields.Contains("username") && x.Customer.Username.Contains(term)) || - (fields.Contains("text") && x.Text.Contains(term))); + (fields.Contains("subject") && x.ForumTopic.Subject.Contains(t)) || + (fields.Contains("text") && x.Text.Contains(t)) || + (fields.Contains("username") && ( + f == CustomerNameFormat.ShowEmails ? x.Customer.Email.Contains(t) : + f == CustomerNameFormat.ShowUsernames ? x.Customer.Username.Contains(t) : + f == CustomerNameFormat.ShowFirstName ? x.Customer.FirstName.Contains(t) : + x.Customer.FullName.Contains(t)) + )); } } @@ -153,6 +171,7 @@ from sm in gsm.DefaultIfEmpty() } // Topic query. + // Unified order: we only sort by ForumPost.Id to save an additional database query in ForumSearchService.Search. var topicQuery = from fp in query group fp by fp.TopicId into grp @@ -199,7 +218,6 @@ protected virtual IDictionary GetFacets(ForumSearchQuery sea var result = new Dictionary(); var storeId = searchQuery.StoreId ?? _services.StoreContext.CurrentStore.Id; var languageId = searchQuery.LanguageId ?? _services.WorkContext.WorkingLanguage.Id; - var userNamesEnabled = _services.Settings.LoadSetting().UsernamesEnabled; foreach (var key in searchQuery.FacetDescriptors.Keys) { @@ -251,7 +269,7 @@ protected virtual IDictionary GetFacets(ForumSearchQuery sea foreach (var customer in customers) { - var name = FacetUtility.GetPublicName(customer, userNamesEnabled); + var name = customer.FormatUserName(_customerSettings, T, true); if (name.HasValue()) { facets.Add(new Facet(new FacetValue(customer.Id, IndexTypeCode.Int32) diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml index c08f1e5ea5..78fbd7a209 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml @@ -191,6 +191,12 @@ @topic.CustomerName }
+ if (Model.SearchResult.CanHighlightPostFor(topic.FirstPostId)) + { +
+ @Html.Raw(Model.SearchResult.Highlight(topic.FirstPostId, "", "")) +
+ } }
+ @Html.SmartLabelFor(model => model.AllowSorting) + + @Html.SettingEditorFor(model => model.AllowSorting) + @Html.ValidationMessageFor(model => model.AllowSorting) +
@Html.SmartLabelFor(model => model.ForumEditor) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Search.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Search.cshtml index ab4a349074..1251dcdfff 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Search.cshtml @@ -223,11 +223,11 @@
- @Html.SmartLabelFor(x => x.ForumSearchSettings.TopicSortOrder) + @Html.SmartLabelFor(x => x.ForumSearchSettings.DefaultSortOrder) - @Html.SettingEditorFor(x => x.ForumSearchSettings.TopicSortOrder, Html.DropDownListFor(x => x.ForumSearchSettings.TopicSortOrder, Model.ForumSearchSettings.AvailableTopicSortOrders)) - @Html.ValidationMessageFor(x => x.ForumSearchSettings.TopicSortOrder) + @Html.SettingEditorFor(x => x.ForumSearchSettings.DefaultSortOrder, Html.DropDownListFor(x => x.ForumSearchSettings.DefaultSortOrder, Model.ForumSearchSettings.AvailableDefaultSortOrders)) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.DefaultSortOrder)
+ + + + + + + + + + + diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 5681d8d48f..bbbd1b0b58 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -3,6 +3,7 @@ using System.Linq; using System.ServiceModel.Syndication; using System.Web.Mvc; +using SmartStore.Core; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.Media; @@ -27,6 +28,7 @@ using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.UI; using SmartStore.Web.Models.Boards; +using SmartStore.Web.Models.Common; using SmartStore.Web.Models.Search; namespace SmartStore.Web.Controllers @@ -1638,11 +1640,25 @@ public ActionResult Search(ForumSearchQuery query/*, bool? adv*/) model.SearchResult = result ?? new ForumSearchResult(query); model.TotalCount = model.SearchResult.TotalHitsCount; + + var pagedList = new PagedList( + model.SearchResult.Hits.Select(x => PrepareForumTopicRowModel(x)), + model.SearchResult.Hits.PageIndex, + model.SearchResult.Hits.PageSize, + model.SearchResult.TotalHitsCount); + + model.PagedList = new PagedListModel(pagedList); - foreach (var topic in model.SearchResult.Hits) + if (_forumSettings.AllowCustomersToSelectPageSize) { - var topicModel = PrepareForumTopicRowModel(topic); - model.ForumTopics.Add(topicModel); + try + { + model.PagedList.AvailablePageSizes = _forumSettings.DefaultPageSizeOptions.Convert>(); + } + catch + { + model.PagedList.AvailablePageSizes = new int[] { 12, 24, 36, 48, 72, 120 }; + } } CreateForumBreadcrumb(); diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.MapProduct.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.MapProduct.cs index e2dc1b6939..bf83a4b94d 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.MapProduct.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.MapProduct.cs @@ -79,11 +79,11 @@ public void MapListActions(ProductSummaryModel model, IPagingOptions entity, str { try { - model.AvailablePageSizes = (entity?.PageSizeOptions.NullEmpty() ?? defaultPageSizeOptions).Convert>(); + model.PagedList.AvailablePageSizes = (entity?.PageSizeOptions.NullEmpty() ?? defaultPageSizeOptions).Convert>(); } catch { - model.AvailablePageSizes = new int[] { 12, 24, 36, 48, 72, 120 }; + model.PagedList.AvailablePageSizes = new int[] { 12, 24, 36, 48, 72, 120 }; } } diff --git a/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs index b038ce2599..2028c88741 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs @@ -1,20 +1,22 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using SmartStore.Core.Domain.Catalog; using SmartStore.Services.Search; using SmartStore.Web.Framework.Modelling; +using SmartStore.Web.Models.Catalog; +using SmartStore.Web.Models.Common; using SmartStore.Web.Models.Search; namespace SmartStore.Web.Models.Boards { - public class ForumSearchResultModel : ModelBase, IForumSearchResultModel + public class ForumSearchResultModel : ModelBase, IForumSearchResultModel, IListActions { public ForumSearchResultModel(ForumSearchQuery query) { Query = query; - ForumTopics = new List(); } public ForumSearchQuery Query { get; private set; } - public ForumSearchResult SearchResult { get; set; } /// @@ -28,11 +30,21 @@ public ForumSearchResultModel(ForumSearchQuery query) public int PostsPageSize { get; set; } public string Error { get; set; } + #region IListActions + + public bool AllowViewModeChanging => throw new NotImplementedException(); + public ProductSummaryViewMode ViewMode => throw new NotImplementedException(); + public GridColumnSpan GridColumnSpan => throw new NotImplementedException(); + + public bool AllowFiltering => true; + public bool AllowSorting { get; set; } public int? CurrentSortOrder { get; set; } public string CurrentSortOrderName { get; set; } public IDictionary AvailableSortOptions { get; set; } - public IList ForumTopics { get; set; } + public PagedListModel PagedList { get; set; } + + #endregion } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/IListActions.cs b/src/Presentation/SmartStore.Web/Models/Catalog/IListActions.cs index 0395cc1fd3..76a3340003 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/IListActions.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/IListActions.cs @@ -1,11 +1,10 @@ -using System; -using System.Collections.Generic; -using SmartStore.Core; +using System.Collections.Generic; using SmartStore.Core.Domain.Catalog; +using SmartStore.Web.Models.Common; namespace SmartStore.Web.Models.Catalog { - public interface IListActions + public interface IListActions { ProductSummaryViewMode ViewMode { get; } GridColumnSpan GridColumnSpan { get; } @@ -18,7 +17,6 @@ public interface IListActions string CurrentSortOrderName { get; } IDictionary AvailableSortOptions { get; } - IPageable PagedList { get; } - IEnumerable AvailablePageSizes { get; } + PagedListModel PagedList { get; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ProductSummaryModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ProductSummaryModel.cs index 00205320ec..d4b3ab1566 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ProductSummaryModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ProductSummaryModel.cs @@ -5,6 +5,7 @@ using SmartStore.Services.Localization; using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.UI; +using SmartStore.Web.Models.Common; using SmartStore.Web.Models.Media; namespace SmartStore.Web.Models.Catalog @@ -19,8 +20,7 @@ public ProductSummaryModel(IPagedList products) Items = new List(); AvailableSortOptions = new Dictionary(); - AvailablePageSizes = new int[0]; - PagedList = products; + PagedList = new PagedListModel(products); } public int? ThumbSize { get; set; } @@ -60,8 +60,7 @@ public ProductSummaryModel(IPagedList products) public string RelevanceSortOrderName { get; set; } public IDictionary AvailableSortOptions { get; set; } - public IEnumerable AvailablePageSizes { get; set; } - public IPageable PagedList { get; set; } + public PagedListModel PagedList { get; set; } public void Dispose() { diff --git a/src/Presentation/SmartStore.Web/Models/Common/PagedListModel.cs b/src/Presentation/SmartStore.Web/Models/Common/PagedListModel.cs new file mode 100644 index 0000000000..ac9b67ad6b --- /dev/null +++ b/src/Presentation/SmartStore.Web/Models/Common/PagedListModel.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using SmartStore.Core; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Web.Models.Common +{ + public partial class PagedListModel : ModelBase + { + public PagedListModel(IPageable pagedList) + { + Guard.NotNull(pagedList, nameof(pagedList)); + + PagedList = pagedList; + AvailablePageSizes = new int[0]; + } + + public IPageable PagedList { get; private set; } + public IEnumerable AvailablePageSizes { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index dff3e3d3c5..c618da7334 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -367,6 +367,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml index a7dc66bb74..a0f2212522 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml @@ -24,11 +24,8 @@ suggestions = Model.SearchResult.SpellCheckerSuggestions; } - //"TODO: pager - //var pager = Html.SmartStore().Pager(Model.SearchResult.Hits.PageIndex, Model.SearchResult.Hits.PageSize, Model.TotalCount) - // .Name("paginator") - // .Alignment(PagerAlignment.Right) - // .Route("BoardSearch"); + string pagerHtml = null; + pagerHtml = Html.Partial("Product.List.Pager", Model.PagedList).ToHtmlString(); } - @foreach (var topic in Model.ForumTopics) + @foreach (ForumTopicRowModel topic in Model.PagedList.PagedList) { + + + + + + + + - - - - - - - - } - @foreach (var topic in @Model.ForumTopics) + @foreach (var topic in Model.ForumTopics) {
+ @Html.SmartLabelFor(model => model.SignaturesEnabled) + + @Html.SettingEditorFor(model => model.SignaturesEnabled) + @Html.ValidationMessageFor(model => model.SignaturesEnabled) +
@Html.SmartLabelFor(model => model.RelativeDateTimeFormattingEnabled) @@ -99,56 +108,70 @@
- @Html.SmartLabelFor(model => model.TopicsPageSize) + @Html.SmartLabelFor(model => model.ForumEditor) - @Html.SettingEditorFor(model => model.TopicsPageSize) - @Html.ValidationMessageFor(model => model.TopicsPageSize) + @Html.EnumSettingEditorFor(model => model.ForumEditor) + @Html.ValidationMessageFor(model => model.ForumEditor) +
+
- @Html.SmartLabelFor(model => model.PostsPageSize) + @Html.SmartLabelFor(model => model.AllowSorting) - @Html.SettingEditorFor(model => model.PostsPageSize) - @Html.ValidationMessageFor(model => model.PostsPageSize) + @Html.SettingEditorFor(model => model.AllowSorting) + @Html.ValidationMessageFor(model => model.AllowSorting)
- @Html.SmartLabelFor(model => model.SearchResultsPageSize) + @Html.SmartLabelFor(model => model.AllowCustomersToSelectPageSize) - @Html.SettingEditorFor(model => model.SearchResultsPageSize) - @Html.ValidationMessageFor(model => model.SearchResultsPageSize) + @Html.SettingEditorFor(model => model.AllowCustomersToSelectPageSize) + @Html.ValidationMessageFor(model => model.AllowCustomersToSelectPageSize)
- @Html.SmartLabelFor(model => model.AllowSorting) + @Html.SmartLabelFor(model => model.DefaultPageSizeOptions) - @Html.SettingEditorFor(model => model.AllowSorting) - @Html.ValidationMessageFor(model => model.AllowSorting) + @Html.SettingEditorFor(model => model.DefaultPageSizeOptions) + @Html.ValidationMessageFor(model => model.DefaultPageSizeOptions)
- @Html.SmartLabelFor(model => model.ForumEditor) + @Html.SmartLabelFor(model => model.TopicsPageSize) - @Html.EnumSettingEditorFor(model => model.ForumEditor) - @Html.ValidationMessageFor(model => model.ForumEditor) + @Html.SettingEditorFor(model => model.TopicsPageSize) + @Html.ValidationMessageFor(model => model.TopicsPageSize)
- @Html.SmartLabelFor(model => model.SignaturesEnabled) + @Html.SmartLabelFor(model => model.PostsPageSize) - @Html.SettingEditorFor(model => model.SignaturesEnabled) - @Html.ValidationMessageFor(model => model.SignaturesEnabled) + @Html.SettingEditorFor(model => model.PostsPageSize) + @Html.ValidationMessageFor(model => model.PostsPageSize) +
+ @Html.SmartLabelFor(model => model.SearchResultsPageSize) + + @Html.SettingEditorFor(model => model.SearchResultsPageSize) + @Html.ValidationMessageFor(model => model.SearchResultsPageSize)
@@ -204,7 +208,6 @@ .Size(PagerSize.Mini) .Route("TopicSlug", new { id = topic.Id, slug = topic.SeName })) } -
@if (topic.CustomerId > 0) { @@ -244,14 +247,22 @@
+ @if (Model.TotalCount > 0 && pagerHtml.HasValue()) + { +
+
+ @Html.Raw(pagerHtml) +
+
+ }
- @{ Html.RenderPartial("_SearchBox"); } - +
+ @{ Html.RenderAction("SearchBox", "Boards"); } +
diff --git a/src/Presentation/SmartStore.Web/Views/Boards/ForumGroup.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/ForumGroup.cshtml index 3c11979206..c96972078f 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/ForumGroup.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/ForumGroup.cshtml @@ -1,15 +1,14 @@ -@model ForumGroupModel -@using SmartStore.Web.Models.Boards; +@using SmartStore.Web.Models.Boards; +@model ForumGroupModel @{ Layout = "_Layout"; Html.AddTitleParts(Model.Name); }
- @{ - Html.RenderPartial("_SearchBox"); - } - +
+ @{ Html.RenderAction("SearchBox", "Boards"); } +
@{ Html.RenderPartial("_ForumGroupPartial", Model); }
diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Index.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Index.cshtml index 4cd7084e56..d156ae5b45 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Index.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Index.cshtml @@ -1,5 +1,5 @@ -@model BoardsIndexModel -@using SmartStore.Web.Models.Boards; +@using SmartStore.Web.Models.Boards; +@model BoardsIndexModel @{ Layout = "_Layout"; @@ -10,7 +10,9 @@
@Model.CurrentTime.ToNativeString("F")
- @{ Html.RenderPartial("_SearchBox"); } +
+ @{ Html.RenderAction("SearchBox", "Boards"); } +
@{ Html.RenderAction("TopicBlock", "Topic", new { systemName = "ForumWelcomeMessage" }); } diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml index 68257de90a..f03ed52d74 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml @@ -1,14 +1,15 @@ -@model ActiveDiscussionsModel -@using SmartStore.Web.Models.Boards; +@using SmartStore.Web.Models.Boards; @using SmartStore.Web.Framework.UI; - +@model ActiveDiscussionsModel @{ var isPartial = Model.ViewAllLinkEnabled; } @if (!isPartial) { - Html.RenderPartial("_SearchBox"); +
+ @{ Html.RenderAction("SearchBox", "Boards"); } +
}
diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_SearchBox.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_SearchBox.cshtml deleted file mode 100644 index 4ca8e36aa7..0000000000 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_SearchBox.cshtml +++ /dev/null @@ -1,16 +0,0 @@ -
-
- @using (Html.BeginRouteForm("BoardSearch", FormMethod.Get, new { @class = "forum-search-box" })) - { -
- -
- -
-
-
- @Html.RouteLink(T("Forum.AdvancedSearch").Text, "BoardSearch", new { adv = true }, new { title = T("Forum.AdvancedSearch").Text }) -
- } -
-
\ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml index b3c8c43105..7a4d204d48 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml @@ -55,15 +55,9 @@
} - @using (Html.BeginRouteForm("BoardSearch", FormMethod.Get)) - { -
- -
- -
-
- } +
+ @{ Html.RenderAction("SearchBox", "Boards"); } +
@if (Model.AttemptedTerm.HasValue()) { diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml index 64255f5970..08c8b03b72 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml @@ -1,7 +1,7 @@ -@model ForumTopicPageModel -@using SmartStore.Web.Models.Boards; +@using SmartStore.Web.Models.Boards; @using SmartStore.Web.Models.Common; @using SmartStore.Web.Framework.UI; +@model ForumTopicPageModel @{ Layout = "_Layout"; Html.AddTitleParts(Model.Subject); @@ -15,8 +15,9 @@
- @{ Html.RenderPartial("_SearchBox"); } - +
+ @{ Html.RenderAction("SearchBox", "Boards"); } +

@Model.Subject

diff --git a/src/Presentation/SmartStore.Web/Views/Search/Partials/SearchBox.cshtml b/src/Presentation/SmartStore.Web/Views/Search/Partials/SearchBox.cshtml index 316922858f..378c4d3240 100644 --- a/src/Presentation/SmartStore.Web/Views/Search/Partials/SearchBox.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Search/Partials/SearchBox.cshtml @@ -1,4 +1,4 @@ -@using SmartStore.Web.Models.Catalog; +@using SmartStore.Web.Models.Search; @model SearchBoxModel @{ if (IsStoreClosed) @@ -12,28 +12,26 @@ labelStyle += " btn-flat"; } } - -@using (Html.BeginRouteForm("Search", FormMethod.Get, new { @class = "instasearch-form" })) -{ +
-
+
- Html.RenderWidget("searchbox"); -} \ No newline at end of file + @{ Html.RenderWidget("searchbox"); } + \ No newline at end of file From c72cccaece26c5058322263ea2abc2924b38fee0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 7 Sep 2018 15:55:35 +0200 Subject: [PATCH 087/932] Minor change --- .../SmartStore.Services/Search/Catalog/CatalogSearchService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchService.cs index d3b02a0b53..a1e40b18cc 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchService.cs @@ -184,7 +184,7 @@ public CatalogSearchResult Search( { using (_services.Chronometer.Step(stepPrefix + "Collect")) { - var productIds = searchHits.Select(x => x.EntityId).Distinct().ToArray(); + var productIds = searchHits.Select(x => x.EntityId).ToArray(); hitsFactory = () => _productService.Value.GetProductsByIds(productIds, loadFlags); } } From 2c4d75b472394ac8863b4f8b5154acdd82c267ba Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 7 Sep 2018 22:01:33 +0200 Subject: [PATCH 088/932] Minor improvement --- .../SmartStore.Web/Controllers/BoardsController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index f19039be9a..fd4a2e125f 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -1592,7 +1592,7 @@ public ActionResult InstantSearch(ForumSearchQuery query) .Slice(0, Math.Min(16, _searchSettings.InstantSearchNumberOfHits)) .SortBy(ForumTopicSorting.Relevance); - var result = _forumSearchService.Search(query); + var result = _forumSearchService.Search(query) ?? new ForumSearchResult(query); var model = new ForumSearchResultModel(query) { @@ -1729,7 +1729,7 @@ public ActionResult Search(ForumSearchQuery query/*, bool? adv*/) } } - model.AddSpellCheckerSuggestions(result.SpellCheckerSuggestions, T, x => Url.RouteUrl("BoardSearch", new { q = x })); + model.AddSpellCheckerSuggestions(model.SearchResult.SpellCheckerSuggestions, T, x => Url.RouteUrl("BoardSearch", new { q = x })); return View(model); } From f886c7ef3d5447c27ea7d0e1074a72d42b582daa Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 7 Sep 2018 22:10:52 +0200 Subject: [PATCH 089/932] Topic grid filter mask was always hidden --- .../Theming/CssHttpHandler.cs | 2 +- .../Administration/Views/Topic/List.cshtml | 56 ++++++++++--------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/CssHttpHandler.cs b/src/Presentation/SmartStore.Web.Framework/Theming/CssHttpHandler.cs index c7a96144a9..3a1cc2ac7c 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/CssHttpHandler.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/CssHttpHandler.cs @@ -126,7 +126,7 @@ protected override IAsset TranslateAsset(IAsset asset, ITransformer transformer, _context.Response.StatusCode = 500; _context.Response.End(); } - + throw; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml index 30fb3f032e..70bd28caf6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml @@ -20,38 +20,42 @@
-@if (Model.AvailableStores.Count > 1) -{ -
-
+
+ @if (Model.AvailableStores.Count > 1) + { +
@Html.SmartLabelFor(model => model.SearchStoreId) @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All"))
-
- @Html.SmartLabelFor(model => model.SystemName) - @Html.TextBoxFor(model => Model.SystemName, new { @class = "form-control" }) -
-
- @Html.SmartLabelFor(model => model.Title) - @Html.TextBoxFor(model => Model.Title, new { @class = "form-control" }) -
-
- @Html.SmartLabelFor(model => model.WidgetZone) - @Html.TextBoxFor(model => Model.WidgetZone, new { @class = "form-control" }) -
-
- @Html.SmartLabelFor(model => model.RenderAsWidget) - @Html.EditorFor(model => Model.RenderAsWidget, new { @class = "form-control" }) -
+ } +
+ @Html.SmartLabelFor(model => model.SystemName) + @Html.TextBoxFor(model => Model.SystemName, new { @class = "form-control" }) +
+
+ @Html.SmartLabelFor(model => model.Title) + @Html.TextBoxFor(model => Model.Title, new { @class = "form-control" }) +
+
+ @Html.SmartLabelFor(model => model.WidgetZone) + @Html.TextBoxFor(model => Model.WidgetZone, new { @class = "form-control" }) +
+
+ @Html.SmartLabelFor(model => model.RenderAsWidget) + @Html.EditorFor(model => Model.RenderAsWidget, new { @class = "form-control" }) +
-
+
+ @if (Model.AvailableStores.Count > 1) + { - -
+ } + +
-} +
@(Html.Telerik().Grid() From 0b993d6bc5f0ad470a20300914e6c26d903628fc Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Mon, 10 Sep 2018 21:10:38 +0200 Subject: [PATCH 090/932] Fix IIS 404 with localized URLs on very first request --- .../Controllers/PublicControllerBase.cs | 2 +- .../Localization/LanguageSeoCodeAttribute.cs | 9 +++------ .../Localization/LocalizedRoute.cs | 5 +++-- src/Presentation/SmartStore.Web/Web.config | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/PublicControllerBase.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/PublicControllerBase.cs index 73cb98b079..bdd6a5a50c 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/PublicControllerBase.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/PublicControllerBase.cs @@ -15,7 +15,7 @@ namespace SmartStore.Web.Framework.Controllers [StoreIpAddress(Order = 100)] [StoreLastVisitedPage(Order = 100)] [CheckAffiliate(Order = 100)] - public abstract partial class PublicControllerBase : SmartController + public abstract partial class PublicControllerBase : SmartController { } } diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/LanguageSeoCodeAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Localization/LanguageSeoCodeAttribute.cs index 3c9bef7810..28055079b9 100644 --- a/src/Presentation/SmartStore.Web.Framework/Localization/LanguageSeoCodeAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Localization/LanguageSeoCodeAttribute.cs @@ -18,16 +18,13 @@ public class LanguageSeoCodeAttribute : FilterAttribute, IActionFilter public Lazy LanguageService { get; set; } public Lazy LocalizationSettings { get; set; } - public virtual void OnActionExecuting(ActionExecutingContext filterContext) + public virtual void OnActionExecuting(ActionExecutingContext filterContext) { - if (filterContext == null || filterContext.HttpContext == null) - return; - - HttpRequestBase request = filterContext.HttpContext.Request; + var request = filterContext?.HttpContext?.Request; if (request == null) return; - //don't apply filter to child methods + // Don't apply filter to child methods if (filterContext.IsChildAction) return; diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedRoute.cs b/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedRoute.cs index 6ab96f7117..c29c145a8c 100644 --- a/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedRoute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedRoute.cs @@ -161,8 +161,9 @@ protected internal static bool SeoFriendlyUrlsForLanguagesEnabled } catch { } } - - return _seoFriendlyUrlsForLanguagesEnabled ?? false; + + // Assume is enabled on very first request to prevent IIS 404 with localized URLs + return _seoFriendlyUrlsForLanguagesEnabled ?? true; } } diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index 9bee792763..2a6a78e3f7 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -79,7 +79,7 @@ - + From f81dd675bc77e4cf6aca0e6e045d4449ece2063b Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 11 Sep 2018 12:54:20 +0200 Subject: [PATCH 091/932] Added options for alternating price display (in badges) --- changelog.md | 3 +- .../Domain/Catalog/CatalogSettings.cs | 26 ++++-- .../Domain/Catalog/PriceDisplayStyle.cs | 26 ++++++ .../SmartStore.Core/SmartStore.Core.csproj | 1 + .../Migrations/MigrationsConfiguration.cs | 16 ++++ .../Models/Settings/CatalogSettingsModel.cs | 10 ++- .../Views/Setting/Catalog.cshtml | 18 +++++ .../Controllers/CatalogHelper.MapProduct.cs | 19 ++--- .../Controllers/CatalogHelper.cs | 6 +- .../Controllers/ProductController.cs | 5 +- .../Models/Catalog/ProductDetailsModel.cs | 6 +- .../Models/Catalog/ProductSummaryModel.cs | 4 +- .../Themes/Flex/Content/_artlist.scss | 3 + .../Themes/Flex/Content/_product.scss | 4 + .../Partials/Product.Offer.Price.cshtml | 80 +++++++++++++------ .../Partials/Product.List.Item.Price.cshtml | 36 ++++++++- 16 files changed, 206 insertions(+), 57 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Domain/Catalog/PriceDisplayStyle.cs diff --git a/changelog.md b/changelog.md index 9475057678..9732000836 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -# Release Notes +# Release Notes ## SmartStore.NET 3.2 @@ -14,6 +14,7 @@ * Wallet: Allow customer to choose whether refund should be submitted to his wallet. * Added option to display preview pictures in product lists * Added option to add multiple file versions to product download section +* Added options for alternating price display (in badges) ### Improvements * (Perf) Significantly increased query performance for products with a lot of category assignments (> 10). diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs index eb62b12e43..0be6f2d778 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs @@ -401,15 +401,25 @@ public CatalogSettings() public bool ShowDiscountSign { get; set; } - /// - /// Gets or sets the available customer selectable default page size options - /// - public string DefaultPageSizeOptions { get; set; } + /// + /// Gets or sets the price display style for prices + /// + public PriceDisplayStyle PriceDisplayStyle { get; set; } - /// - /// Gets or sets the price display type for prices in product lists - /// - public PriceDisplayType PriceDisplayType { get; set; } + /// + /// Displays a textual resources instead of the decimal value when prices are 0 + /// + public bool DisplayTextForZeroPrices { get; set; } + + /// + /// Gets or sets the available customer selectable default page size options + /// + public string DefaultPageSizeOptions { get; set; } + + /// + /// Gets or sets the price display type for prices in product lists + /// + public PriceDisplayType PriceDisplayType { get; set; } /// /// Gets or sets a value indicating whether to include "Short description" in compare products diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/PriceDisplayStyle.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/PriceDisplayStyle.cs new file mode 100644 index 0000000000..adee8aa61f --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/PriceDisplayStyle.cs @@ -0,0 +1,26 @@ +using System; + +namespace SmartStore.Core.Domain.Catalog +{ + /// + /// Represents the style in which prices are displayed + /// + [Flags] + public enum PriceDisplayStyle + { + /// + /// Display prices without badges + /// + Default = 1, + + /// + /// Display all prices within badges + /// + BadgeAll = 2, + + /// + /// Display prices of free products within badges + /// + BadgeFreeProductsOnly = 4 + } +} diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index 92b84a5c53..98f3bb0cb0 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -210,6 +210,7 @@ + diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index cc8549881c..b2dcd8f4f5 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -467,6 +467,22 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Enums.SmartStore.Core.Domain.Forums.ForumSearchType.TopicTitlesOnly", "Forum.AdvancedSearch", "Forum.SearchButton"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.PriceDisplayStyle", + "Price display style", + "Preisdarstellung", + "Specifies the form in which prices are displayed in product lists and on the product detail page.", + "Bestimmt die Darstellungform von Preisen in Produktlisten und auf der Produktdetailseite."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.DisplayTextForZeroPrices", + "Display text when prices are 0,00", + "Zeige Text wenn Preise 0,00 sind", + "Specifies whether to display a textual resource (free) instead of the value 0.00.", + "Bestimmt, ob statt dem Wert 0,00 eine textuelle Resource (kostenlos) angezeigt werden soll."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayStyle.Default", "Default", "Standard"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayStyle.BadgeAll", "In bagdes", "Markiert"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayStyle.BadgeFreeProductsOnly", "Badge free products only", "Nur kostenlose Produkte markieren"); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs index 81c787bcb8..d7f56e0c9b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs @@ -36,6 +36,12 @@ public CatalogSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowDiscountSign")] public bool ShowDiscountSign { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.PriceDisplayStyle")] + public PriceDisplayStyle PriceDisplayStyle { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.DisplayTextForZeroPrices")] + public bool DisplayTextForZeroPrices { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.IgnoreDiscounts")] public bool IgnoreDiscounts { get; set; } @@ -157,8 +163,8 @@ public CatalogSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.PriceDisplayType")] public PriceDisplayType PriceDisplayType { get; set; } public SelectList AvailablePriceDisplayTypes { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.GridStyleListColumnSpan")] + + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.GridStyleListColumnSpan")] public GridColumnSpan GridStyleListColumnSpan { get; set; } #endregion diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml index 95e2e7cc4a..efcb02b519 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml @@ -178,6 +178,24 @@
+ @Html.SmartLabelFor(model => model.DisplayTextForZeroPrices) + + @Html.SettingEditorFor(model => model.DisplayTextForZeroPrices) + @Html.ValidationMessageFor(model => model.DisplayTextForZeroPrices) +
+ @Html.SmartLabelFor(model => model.PriceDisplayStyle) + + @Html.EnumSettingEditorFor(model => model.PriceDisplayStyle) + @Html.ValidationMessageFor(model => model.PriceDisplayStyle) +
@Html.SmartLabelFor(model => model.IgnoreDiscounts) diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.MapProduct.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.MapProduct.cs index e2dc1b6939..e40bbc131c 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.MapProduct.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.MapProduct.cs @@ -524,7 +524,10 @@ private void MapProductSummaryItem(Product product, MapProductSummaryItemContext { item.TransportSurcharge = ctx.Resources["Common.AdditionalShippingSurcharge"].Text.FormatCurrent(_priceFormatter.FormatPrice(addShippingPrice, true, false)); } - } + + item.PriceDisplayStyle = _catalogSettings.PriceDisplayStyle; + item.DisplayTextForZeroPrices = _catalogSettings.DisplayTextForZeroPrices; + } if (model.ShowWeight && contextProduct.Weight > 0) { @@ -540,18 +543,8 @@ private void MapProductSummaryItem(Product product, MapProductSummaryItemContext Style = BadgeStyle.Success }); } - - // free product badge - if (item.Price.PriceValue == 0) - { - item.Badges.Add(new ProductSummaryModel.Badge - { - Label = T("Products.Free"), - Style = BadgeStyle.Success - }); - } - - model.Items.Add(item); + + model.Items.Add(item); } /// The product or the first associated product of a group. diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index 81f43911c3..e877e13b3d 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -209,8 +209,10 @@ public ProductDetailsModel PrepareProductDetailsPageModel( IsAssociatedProduct = isAssociatedProduct, CompareEnabled = !isAssociatedProduct && _catalogSettings.CompareProductsEnabled, TellAFriendEnabled = !isAssociatedProduct && _catalogSettings.EmailAFriendEnabled, - AskQuestionEnabled = !isAssociatedProduct && _catalogSettings.AskQuestionEnabled - }; + AskQuestionEnabled = !isAssociatedProduct && _catalogSettings.AskQuestionEnabled, + PriceDisplayStyle = _catalogSettings.PriceDisplayStyle, + DisplayTextForZeroPrices = _catalogSettings.DisplayTextForZeroPrices + }; // Social share code if (_catalogSettings.ShowShareButton && _catalogSettings.PageShareCode.HasValue()) diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index 8ccf44f84f..b48f76ff53 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -589,7 +589,10 @@ public ActionResult UpdateProductDetails(int productId, string itemType, int bun galleryStartIndex = pictureModel.GalleryStartIndex; galleryHtml = this.RenderPartialViewToString("Product.Picture", pictureModel); } - } + + m.PriceDisplayStyle = _catalogSettings.PriceDisplayStyle; + m.DisplayTextForZeroPrices = _catalogSettings.DisplayTextForZeroPrices; + } object partials = null; diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs index 3b7adf4911..0eca3e49ef 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs @@ -112,8 +112,10 @@ public string GiftCardFieldPrefix public bool BundlePerItemShipping { get; set; } public bool BundlePerItemPricing { get; set; } public bool BundlePerItemShoppingCart { get; set; } - - public ProductVariantAttributeCombination SelectedCombination { get; set; } + public bool DisplayTextForZeroPrices { get; set; } + public PriceDisplayStyle PriceDisplayStyle { get; set; } + + public ProductVariantAttributeCombination SelectedCombination { get; set; } public IList Manufacturers { get; set; } public int ReviewCount { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ProductSummaryModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ProductSummaryModel.cs index 49c9bac154..24380641e4 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ProductSummaryModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ProductSummaryModel.cs @@ -122,8 +122,10 @@ public ProductSummaryModel Parent public bool DisplayDeliveryTimeAccordingToStock { get; set; } public string StockAvailablity { get; set; } public string BasePriceInfo { get; set; } + public PriceDisplayStyle PriceDisplayStyle { get; set; } + public bool DisplayTextForZeroPrices { get; set; } - public int MinPriceProductId { get; set; } // Internal + public int MinPriceProductId { get; set; } // Internal public ManufacturerOverviewModel Manufacturer { get; set; } public PriceModel Price { get; set; } diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_artlist.scss b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_artlist.scss index 59bedcbb44..ffdf3c740f 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_artlist.scss +++ b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_artlist.scss @@ -305,6 +305,9 @@ $art-border-color: #ddd; &.art-price--offer { color: $art-price-offer-color; } + &.badge { + color: $white; + } } .art-oldprice { diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_product.scss b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_product.scss index f340f76248..21e796258f 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_product.scss +++ b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_product.scss @@ -210,6 +210,10 @@ $pd-offer-spacing: $grid-gutter-width / 2; color: $text-muted; font-weight: 400; } + .badge { + font-size: 100%; + font-weight: initial; + } } .pd-price-block + .pd-price-block { diff --git a/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.Price.cshtml b/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.Price.cshtml index a3efa6f7e2..082bb6b0e5 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.Price.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.Price.cshtml @@ -3,36 +3,65 @@ @using System.Globalization; @using SmartStore.Services.Localization; @using SmartStore.Web.Models.Catalog; +@using SmartStore.Core.Domain.Catalog; @{ - if (Model.AddToCart.DisableBuyButton && Model.AddToCart.DisableWishlistButton) - { - return; - } + if (Model.AddToCart.DisableBuyButton && Model.AddToCart.DisableWishlistButton) + { + return; + } - var isBundleItem = Model.IsBundlePart; - var priceModel = Model.ProductPrice; + var isBundleItem = Model.IsBundlePart; + var priceModel = Model.ProductPrice; - var currentPrice = priceModel.PriceWithDiscount.NullEmpty() ?? priceModel.Price; - var currentPriceValue = priceModel.PriceWithDiscountValue > 0 ? priceModel.PriceWithDiscountValue : priceModel.PriceValue; - var currentPriceLabel = priceModel.NoteWithDiscount; + var currentPrice = priceModel.PriceWithDiscount.NullEmpty() ?? priceModel.Price; + var currentPriceValue = priceModel.PriceWithDiscountValue > 0 ? priceModel.PriceWithDiscountValue : priceModel.PriceValue; + var currentPriceLabel = priceModel.NoteWithDiscount; - var oldPrice1 = priceModel.OldPrice; - var oldPrice1Label = T("Products.Price.OldPrice").Text; + var oldPrice1 = priceModel.OldPrice; + var oldPrice1Label = T("Products.Price.OldPrice").Text; - var oldPrice2 = priceModel.PriceWithDiscountValue < priceModel.PriceValue ? priceModel.Price : ""; - var oldPrice2Label = priceModel.NoteWithoutDiscount; + var oldPrice2 = priceModel.PriceWithDiscountValue < priceModel.PriceValue ? priceModel.Price : ""; + var oldPrice2Label = priceModel.NoteWithoutDiscount; - var basePriceEnabled = isBundleItem ? priceModel.BundleItemShowBasePrice : Model.IsBasePriceEnabled; + var basePriceEnabled = isBundleItem ? priceModel.BundleItemShowBasePrice : Model.IsBasePriceEnabled; - if (oldPrice2.HasValue() && !oldPrice1.HasValue()) - { - oldPrice1 = oldPrice2; - oldPrice2 = null; - oldPrice2Label = null; - } + if (oldPrice2.HasValue() && !oldPrice1.HasValue()) + { + oldPrice1 = oldPrice2; + oldPrice2 = null; + oldPrice2Label = null; + } - var blocksHaveLabel = currentPriceLabel.HasValue() && oldPrice2Label.HasValue(); + if (priceModel.PriceValue == 0 && Model.DisplayTextForZeroPrices) + { + currentPrice = T("Products.Free").Text; + oldPrice1 = null; + oldPrice2 = null; + oldPrice2Label = null; + } + + var blocksHaveLabel = currentPriceLabel.HasValue() && oldPrice2Label.HasValue(); + + var artPriceClasses = "pd-price"; + if (isBundleItem) + { + artPriceClasses += " pd-price-sm"; + } + if (priceModel.OldPrice.HasValue()) + { + artPriceClasses += " pd-price--offer"; + } + if (priceModel.CallForPrice) + { + artPriceClasses += " pd-price--call"; + } + + var badgeClasses = ""; + if (Model.PriceDisplayStyle == PriceDisplayStyle.BadgeAll || (Model.PriceDisplayStyle == PriceDisplayStyle.BadgeFreeProductsOnly && priceModel.PriceValue == 0)) + { + badgeClasses = Model.PriceDisplayStyle == PriceDisplayStyle.BadgeAll && priceModel.SavingAmount.HasValue() ? "text-danger" : "text-success"; + } }
@@ -54,12 +83,15 @@ {
@currentPriceLabel
} -
+
@if (!priceModel.CallForPrice && !isBundleItem) { } - @Html.Raw(currentPrice) + + + @Html.Raw(currentPrice) +
@if (oldPrice2.HasValue()) @@ -120,7 +152,7 @@ @Html.Raw(LocalizedValue.FixBrackets(Model.BasePriceInfo, WorkContext.WorkingLanguage))
} - @if (Model.ShowLegalInfo && Model.LegalInfo.HasValue()) + @if (Model.ShowLegalInfo && Model.LegalInfo.HasValue() && currentPriceValue != 0) {
@Html.Raw(Model.LegalInfo) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Partials/Product.List.Item.Price.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Partials/Product.List.Item.Price.cshtml index 15fd2de006..75e93739b0 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Partials/Product.List.Item.Price.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Partials/Product.List.Item.Price.cshtml @@ -2,9 +2,38 @@ @using SmartStore.Services.Localization; @using SmartStore.Web.Models.Catalog; +@using SmartStore.Core.Domain.Catalog; @{ - var list = Model.Parent; + var list = Model.Parent; + var artPriceClasses = "art-price"; + + if (Model.PriceDisplayStyle == PriceDisplayStyle.BadgeAll || (Model.PriceDisplayStyle == PriceDisplayStyle.BadgeFreeProductsOnly && Model.Price.PriceValue == 0)) + { + if (Model.Price.SavingAmount.HasValue()) + { + artPriceClasses += " badge badge-danger"; + } + else + { + artPriceClasses += " badge badge-success"; + } + } + + if (Model.DisplayTextForZeroPrices && Model.Price.PriceValue == 0) + { + Model.Price.Price = T("Products.Free").Text; + } + + if (Model.Price.RegularPrice.HasValue()) + { + artPriceClasses += " art-price--offer"; + } + + if (Model.Price.CallForPrice) + { + artPriceClasses += " art-price--call"; + } } @if (list.ShowDeliveryTimes) @@ -23,9 +52,10 @@ @if (list.ShowPrice) { +
- - @Model.Price.Price + + @(Model.Price.SavingAmount.HasValue() ? "% " : "")@Model.Price.Price @if (Model.Price.RegularPrice.HasValue()) { From 1210cae30ee05a7f502f5629781dfbcac465d817 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 12 Sep 2018 17:18:08 +0200 Subject: [PATCH 092/932] Search: Started to change forum search result paging --- .../Domain/Forums/ForumSettings.cs | 18 ++------- .../Migrations/MigrationsConfiguration.cs | 4 +- .../Modelling/ForumSearchQueryFactory.cs | 28 +------------ .../Models/Settings/ForumSettingsModel.cs | 6 --- .../Administration/Views/Setting/Forum.cshtml | 18 --------- .../Controllers/BoardsController.cs | 12 ------ .../Models/Boards/ForumSearchResultModel.cs | 3 +- .../Boards/Partials/InstantSearch.cshtml | 17 +++----- .../SmartStore.Web/Views/Boards/Search.cshtml | 39 +++++-------------- 9 files changed, 22 insertions(+), 123 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs index 116811da15..89406566f7 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs @@ -13,10 +13,8 @@ public ForumSettings() StrippedTopicMaxLength = 45; TopicsPageSize = 10; PostsPageSize = 10; + SearchResultsPageSize = 12; AllowSorting = true; - SearchResultsPageSize = 12; - DefaultPageSizeOptions = "12,24,36,48,72,120"; - AllowCustomersToSelectPageSize = true; LatestCustomerPostsPageSize = 10; ShowCustomersPostCount = true; ForumEditor = EditorType.BBCodeEditor; @@ -91,25 +89,15 @@ public ForumSettings() /// public int PostsPageSize { get; set; } - /// - /// Gets or sets a value indicating whether sorting is enabled. - /// - public bool AllowSorting { get; set; } - /// /// Gets or sets the page size for search result /// public int SearchResultsPageSize { get; set; } /// - /// Gets or sets the available customer selectable default page size options - /// - public string DefaultPageSizeOptions { get; set; } - - /// - /// Gets or sets a value indicating whether customers can select page size in product listings + /// Gets or sets a value indicating whether sorting is enabled. /// - public bool AllowCustomersToSelectPageSize { get; set; } + public bool AllowSorting { get; set; } /// /// Gets or sets the page size for latest customer posts diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index b2dcd8f4f5..9df81502aa 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -398,6 +398,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Search.Facet.Date.Older", "and older", "und älter"); builder.AddOrUpdate("Forum.PostText", "Post text", "Beitragstext"); + builder.AddOrUpdate("Search.HitsFor", "{0} hits for {1}", "{0} Treffer für {1}"); builder.AddOrUpdate("Admin.Configuration.Settings.Search.WildcardSearchNote", "The wildcard mode can slow down the search for a large number of objects.", @@ -466,7 +467,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Enums.SmartStore.Core.Domain.Forums.ForumSearchType.PostTextOnly", "Enums.SmartStore.Core.Domain.Forums.ForumSearchType.TopicTitlesOnly", "Forum.AdvancedSearch", - "Forum.SearchButton"); + "Forum.SearchButton", + "Forum.PageTitle.Search"); builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.PriceDisplayStyle", "Price display style", diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs index a16cc31743..50918174c6 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs @@ -19,7 +19,6 @@ namespace SmartStore.Services.Search.Modelling =============================== q - Search term i - Page index - s - Page size o - Order by f - Forum c - Customer @@ -58,7 +57,7 @@ public ForumSearchQueryFactory( public ForumSearchQuery Current { get; private set; } - protected override string[] Tokens => new string[] { "q", "i", "s", "o", "f", "c", "d" }; + protected override string[] Tokens => new string[] { "q", "i", "o", "f", "c", "d" }; public ForumSearchQuery CreateFromQuery() { @@ -126,31 +125,6 @@ protected virtual void ConvertPagingSorting(ForumSearchQuery query, RouteData ro protected virtual int GetPageSize(ForumSearchQuery query, RouteData routeData, string origin) { - var sessionKey = "PageSize:" + origin; - - if (_forumSettings.AllowCustomersToSelectPageSize) - { - var selectedSize = GetValueFor("s"); - if (selectedSize.HasValue) - { - if (_httpContext.Session != null) - { - _httpContext.Session[sessionKey] = selectedSize.Value; - } - - return selectedSize.Value; - } - } - - if (_httpContext.Session != null) - { - var sessionSize = _httpContext.Session[sessionKey].Convert(); - if (sessionSize.HasValue) - { - return sessionSize.Value; - } - } - return _forumSettings.SearchResultsPageSize; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs index afebec56cc..455f58204d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs @@ -41,12 +41,6 @@ public class ForumSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.SearchResultsPageSize")] public int SearchResultsPageSize { get; set; } - [SmartResourceDisplayName("Admin.Common.DefaultPageSizeOptions")] - public string DefaultPageSizeOptions { get; set; } - - [SmartResourceDisplayName("Admin.Common.AllowCustomersToSelectPageSize")] - public bool AllowCustomersToSelectPageSize { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.ForumEditor")] public EditorType ForumEditor { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml index 7b745dbb93..85efaac0ef 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml @@ -129,24 +129,6 @@ @Html.ValidationMessageFor(model => model.AllowSorting)
- @Html.SmartLabelFor(model => model.AllowCustomersToSelectPageSize) - - @Html.SettingEditorFor(model => model.AllowCustomersToSelectPageSize) - @Html.ValidationMessageFor(model => model.AllowCustomersToSelectPageSize) -
- @Html.SmartLabelFor(model => model.DefaultPageSizeOptions) - - @Html.SettingEditorFor(model => model.DefaultPageSizeOptions) - @Html.ValidationMessageFor(model => model.DefaultPageSizeOptions) -
@Html.SmartLabelFor(model => model.TopicsPageSize) diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index fd4a2e125f..38f672dfae 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -1717,18 +1717,6 @@ public ActionResult Search(ForumSearchQuery query/*, bool? adv*/) model.SearchResult.Hits.PageSize, model.SearchResult.TotalHitsCount); - if (_forumSettings.AllowCustomersToSelectPageSize) - { - try - { - model.AvailablePageSizes = _forumSettings.DefaultPageSizeOptions.Convert>(); - } - catch - { - model.AvailablePageSizes = new int[] { 12, 24, 36, 48, 72, 120 }; - } - } - model.AddSpellCheckerSuggestions(model.SearchResult.SpellCheckerSuggestions, T, x => Url.RouteUrl("BoardSearch", new { q = x })); return View(model); diff --git a/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs index a8a4df9655..85d515a9fe 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/ForumSearchResultModel.cs @@ -13,7 +13,6 @@ public class ForumSearchResultModel : SearchResultModelBase, IForumSearchResultM public ForumSearchResultModel(ForumSearchQuery query) { Query = query; - AvailablePageSizes = new int[0]; } public ForumSearchQuery Query { get; private set; } @@ -47,7 +46,7 @@ public ForumSearchResultModel(ForumSearchQuery query) public IDictionary AvailableSortOptions { get; set; } public IPageable PagedList { get; set; } - public IEnumerable AvailablePageSizes { get; set; } + public IEnumerable AvailablePageSizes => throw new NotImplementedException(); #endregion } diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/InstantSearch.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/InstantSearch.cshtml index 4366242839..82fe80c3f2 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/InstantSearch.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/InstantSearch.cshtml @@ -16,18 +16,11 @@ {
  • - @if (hit.NoHighlight) - { - @hit.Label - } - else - { - @Html.Raw(group.Parent.Highlight(hit.Label, "subject", parentModel.Query, null, "", "")) - } - @if (hit.Tag.HasValue()) - { - @Html.Raw(hit.Tag) - } + + + @Html.Raw(group.Parent.Highlight(hit.Label, "subject", parentModel.Query, null, "", "")) + +
  • } diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml index 7a4d204d48..e12935c023 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml @@ -11,21 +11,14 @@ } else { - Html.AddTitleParts(T("Forum.PageTitle.Search")); + Html.AddTitleParts(T("Forum.Search")); } - var firstItemIndex = 0; - var lastItemIndex = 0; var suggestions = new string[0]; if (Model.TotalCount > 0) { - firstItemIndex = Model.SearchResult.Hits.FirstItemIndex; - lastItemIndex = Model.SearchResult.Hits.LastItemIndex; suggestions = Model.SearchResult.SpellCheckerSuggestions; } - - string pagerHtml = null; - pagerHtml = Html.Partial("Product.List.Pager", Model).ToHtmlString(); }
    @@ -55,7 +44,7 @@
    } -
    +
    @{ Html.RenderAction("SearchBox", "Boards"); }
    @@ -101,12 +90,11 @@
    } - @if (pagerHtml.HasValue()) - { -
    - @Html.Raw(pagerHtml) -
    - } +
    + @T("Search.HitsFor", + "{1}+".FormatInvariant(Model.SearchResult.Hits.Count, Model.SearchResult.Hits.Count.ToString("N0")), + "{0}".FormatInvariant(Model.Term.NaIfEmpty())) +
    } @@ -223,23 +211,14 @@ } - - @if (Model.TotalCount > 0 && pagerHtml.HasValue()) - { -
    -
    - @Html.Raw(pagerHtml) -
    -
    - } \ No newline at end of file From 7c74d973731d0eda8a1ed0b32443f2eac9f53324 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 14 Sep 2018 17:42:38 +0200 Subject: [PATCH 106/932] Allow SMTP connection without authentication --- src/Libraries/SmartStore.Core/Email/SmtpContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Core/Email/SmtpContext.cs b/src/Libraries/SmartStore.Core/Email/SmtpContext.cs index c9b308736d..e1fde3c68a 100644 --- a/src/Libraries/SmartStore.Core/Email/SmtpContext.cs +++ b/src/Libraries/SmartStore.Core/Email/SmtpContext.cs @@ -81,7 +81,7 @@ public SmtpClient ToSmtpClient() } else { - if(!String.IsNullOrEmpty(this.Username)) + if (!String.IsNullOrEmpty(this.Username)) smtpClient.Credentials = new NetworkCredential(this.Username, this.Password); } From 53bc18e24c870ae5ecab24cbd9382a5a5f02f548 Mon Sep 17 00:00:00 2001 From: Raphael Hinterndorfer Date: Sat, 15 Sep 2018 12:03:36 +0200 Subject: [PATCH 107/932] Changes to offcanvas closer; Use times character instead of X character. Place closer link outside nav menu. --- .../Catalog/Partials/OffCanvasMenu.cshtml | 22 ++++++++----------- .../Partials/OffCanvasCart.cshtml | 10 +++------ 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/Partials/OffCanvasMenu.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/Partials/OffCanvasMenu.cshtml index f355c01f80..e5c11c7fb6 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/Partials/OffCanvasMenu.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/Partials/OffCanvasMenu.cshtml @@ -5,15 +5,16 @@
    + ×
    diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Partials/OffCanvasCart.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Partials/OffCanvasCart.cshtml index 5fd45b106a..93d28aa186 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Partials/OffCanvasCart.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Partials/OffCanvasCart.cshtml @@ -7,13 +7,9 @@ }
    + ×
    - @{ - var topicType = "post"; - var topicText = String.Empty; - var topicIcon = "fa fa-comment-o"; - switch (topic.ForumTopicType) - { - case ForumTopicType.Normal: - topicType = "post"; - topicIcon = "fa fa-comment-o"; - break; - case ForumTopicType.Sticky: - topicType = "poststicky"; - topicIcon = "fa fa-thumb-tack"; - topicText = string.Format("", @T("Forum.Sticky").ToString()); - break; - case ForumTopicType.Announcement: - topicType = "post-annoucement"; - topicIcon = "fa fa-bullhorn"; - topicText = string.Format("", @T("Forum.Announcement").ToString()); - break; - default: - topicType = "post"; - topicIcon = "fa fa-comment-o"; - break; - } -
    - -
    - } + @{ Html.RenderPartial("Customer.Avatar", topic.Avatar); }
    - @if (!String.IsNullOrEmpty(topicText)) + @if (topic.ForumTopicType == ForumTopicType.Sticky) + { + + } + else if (topic.ForumTopicType == ForumTopicType.Announcement) { - @Html.Raw(topicText) + } - @Html.RouteLink(topic.Subject, "TopicSlug", new { id = topic.Id, slug = topic.SeName }, new { @class = "topic-title" }) - + @Html.RouteLink(topic.Subject, "TopicSlug", new { id = topic.Id, slug = topic.SeName }, new { @class = "topic-title" })
    @if (topic.CustomerId > 0) diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml index b5c0965b04..4cc9c4135a 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml @@ -12,40 +12,16 @@ {
    - @{ - var topicType = "post"; - var topicText = string.Empty; - var topicIcon = "fa fa-comment-o"; - switch (topic.ForumTopicType) - { - case ForumTopicType.Normal: - topicType = "post"; - topicIcon = "fa fa-comment-o"; - break; - case ForumTopicType.Sticky: - topicType = "poststicky"; - topicIcon = "fa fa-thumb-tack"; - topicText = string.Format("", T("Forum.Sticky").ToString()); - break; - case ForumTopicType.Announcement: - topicType = "post-annoucement"; - topicIcon = "fa fa-bullhorn"; - topicText = string.Format("", T("Forum.Announcement").ToString()); - break; - default: - topicType = "post"; - topicIcon = "fa fa-comment-o"; - break; - } -
    - -
    - } + @{ Html.RenderPartial("Customer.Avatar", topic.Avatar); }
    - @if (!string.IsNullOrEmpty(topicText)) + @if (topic.ForumTopicType == ForumTopicType.Sticky) + { + + } + else if (topic.ForumTopicType == ForumTopicType.Announcement) { - @Html.Raw(topicText) + } @Html.Raw(Model.Highlight(topic.Subject, "subject", Model.Query, null, "", "")) @@ -65,7 +41,7 @@
    @if (topic.CustomerId > 0) { -
    +
    @T("Forum.Author"): @if (topic.AllowViewingProfiles && !topic.IsCustomerGuest) { diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml index 0a962246cf..ed406257d6 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml @@ -65,7 +65,7 @@ {
    - + @{ Html.RenderPartial("Customer.Avatar", topic.Avatar); } @Html.RouteLink(topic.Subject, "TopicSlug", new { id = topic.Id, slug = topic.SeName }, new { @class = "topic-title" }) diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml index b0d286bd23..a05c41b657 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml @@ -42,15 +42,11 @@
    - -
    +
    @T("Forum.Posted"): @Model.PostCreatedOnStr diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml index 3b705bb245..b01c7edc85 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml @@ -103,7 +103,8 @@ - + + diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml index ed406257d6..f59ca9deb5 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml @@ -30,10 +30,10 @@ @if (isPartial) { - + } diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml index 48d3d6a5df..e578941f00 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml @@ -2,6 +2,13 @@ @using SmartStore.Web; @using SmartStore.Web.Models.Boards; @model EditForumPostModel +@{ + var cancelUrl = Url.RouteUrl("TopicSlug", new { id = Model.ForumTopicId, slug = Model.ForumTopicSeName }); + if (Model.Id != 0) + { + cancelUrl = string.Concat(cancelUrl, "#", Model.Id); + } +} @using (Html.BeginForm()) {
    @@ -86,7 +93,7 @@ @T("Forum.Submit") - + @T("Forum.Cancel") diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml index a05c41b657..e257858c2c 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml @@ -2,7 +2,7 @@ @model ForumPostModel @Html.Raw("".FormatInvariant(Model.Id)) -
    +
    @if (Model.CustomerId > 0) { @@ -17,7 +17,6 @@ }
    - @if (Model.IsCurrentCustomerAllowedToEditPost) { @@ -41,7 +40,7 @@
    -
    +
    diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml index 08c8b03b72..cd633b0952 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml @@ -52,14 +52,13 @@ @ButtonBar()
    - @foreach (var post in @Model.ForumPostModels) + @foreach (var post in Model.ForumPostModels) { Html.RenderPartial("_ForumPost", post); }
    @ButtonBar() -
    @helper ButtonBar() @@ -72,12 +71,11 @@
    - @if (Model.IsCustomerAllowedToSubscribe) { @@ -85,7 +83,7 @@ @Ajax.ActionLink(Model.WatchTopicText, "TopicWatch", - new { id = @Model.Id }, + new { id = Model.Id }, new AjaxOptions { HttpMethod = "Post", OnSuccess = "handleTopicWatch" }, new { @class = "watch-topic-link-button" })
    diff --git a/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.cshtml index be35d61a84..15e314e63c 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.cshtml @@ -11,10 +11,8 @@

    @T("Forum.MoveTopic")

    -

    @T("Forum.SelectTheForumToMoveTopic")

    -
    @Html.DropDownList("ForumSelected", new SelectList(Model.ForumList, "Value", "Text"), new { @class = "form-control" })
    @@ -23,9 +21,9 @@ - +
    From 7924067fbd0b0a6856491786be38e7e03fc50d77 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 27 Sep 2018 01:08:01 +0200 Subject: [PATCH 139/932] Implemented FastInvoker for speeding up dynamic method invocation --- .../ComponentModel/FastActivator.cs | 4 +- .../ComponentModel/FastInvoker.cs | 228 ++++++++++++++++++ .../ComponentModel/FastProperty.cs | 2 +- .../SmartStore.Core/SmartStore.Core.csproj | 1 + .../UI/Blocks/BlockHandlerBase.cs | 2 +- 5 files changed, 233 insertions(+), 4 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/ComponentModel/FastInvoker.cs diff --git a/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs b/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs index d37ec07015..dea60d7a62 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs @@ -44,6 +44,8 @@ public object Activate(params object[] parameters) return Invoker(parameters); } + #region Static + /// /// Creates a single fast constructor invoker. The result is not cached. /// @@ -78,8 +80,6 @@ public static Func MakeFastInvoker(ConstructorInfo constructor return lambda.Compile(); } - #region Static - /// /// Creates and caches fast constructor invokers /// diff --git a/src/Libraries/SmartStore.Core/ComponentModel/FastInvoker.cs b/src/Libraries/SmartStore.Core/ComponentModel/FastInvoker.cs new file mode 100644 index 0000000000..d88dbc43ae --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/FastInvoker.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Linq.Expressions; +using System.Collections.Concurrent; +using SmartStore.Utilities; + +namespace SmartStore.ComponentModel +{ + public class FastInvoker + { + private static readonly ConcurrentDictionary _invokersCache = new ConcurrentDictionary(); + + public FastInvoker(MethodInfo methodInfo) + { + Guard.NotNull(methodInfo, nameof(methodInfo)); + + Method = methodInfo; + Invoker = MakeFastInvoker(methodInfo); + ParameterTypes = methodInfo.GetParameters().Select(p => p.ParameterType).ToArray(); + } + + /// + /// Gets the backing . + /// + public MethodInfo Method { get; private set; } + + /// + /// Gets the parameter types from the backing + /// + public Type[] ParameterTypes { get; private set; } + + /// + /// Gets the method invoker. + /// + public Func Invoker { get; private set; } + + /// + /// Invokes the method using the specified parameters. + /// + /// The method invocation result. + public object Invoke(object obj, params object[] parameters) + { + return Invoker(obj, parameters); + } + + #region Static + + /// + /// Creates a single fast method invoker. The result is not cached. + /// + /// Method to create invoker for. + /// The fast method invoker delegate. + public static Func MakeFastInvoker(MethodInfo method) + { + Guard.NotNull(method, nameof(method)); + + var instanceParameterExpression = Expression.Parameter(typeof(object), "instance"); + var argumentsParameterExpression = Expression.Parameter(typeof(object[]), "args"); + var index = 0; + + var argumentExtractionExpressions = + method + .GetParameters() + .Select(parameter => + Expression.Convert( + Expression.ArrayAccess( + argumentsParameterExpression, + Expression.Constant(index++) + ), + parameter.ParameterType + ) + ).ToList(); + + var callExpression = method.IsStatic + ? Expression.Call(method, argumentExtractionExpressions) + : Expression.Call( + Expression.Convert( + instanceParameterExpression, + method.DeclaringType + ), + method, + argumentExtractionExpressions + ); + + var endLabel = Expression.Label(typeof(object)); + + var finalExpression = method.ReturnType == typeof(void) + ? (Expression)Expression.Block( + callExpression, + Expression.Return(endLabel, Expression.Constant(null)), + Expression.Label(endLabel, Expression.Constant(null)) + ) + : Expression.Convert(callExpression, typeof(object)); + + var lambdaExpression = Expression.Lambda>( + finalExpression, + instanceParameterExpression, + argumentsParameterExpression + ); + + var lamdba = lambdaExpression.Compile(); + return lamdba; + } + + /// + /// Invokes a method using the specified object and parameter instances. + /// + /// The objectinstance + /// Method name + /// Argument types of the matching method overload (in exact order) + /// Parameter instances to pass to invocation + /// The method invocation result. + public static object Invoke(object obj, string methodName, Type[] parameterTypes, object[] parameters) + { + Guard.NotNull(obj, nameof(obj)); + + FastInvoker invoker; + + if (parameterTypes == null || parameterTypes.Length == 0) + { + invoker = GetInvoker(obj.GetType(), methodName); + } + else + { + invoker = GetInvoker(obj.GetType(), methodName, parameterTypes); + } + + //var hasAnyNullParam = parameters.Any(x => x == null); + //if (hasAnyNullParam) + //{ + // throw new ArgumentException("When invoking a method with parameter instances, no instance must be null.", nameof(parameters)); + //} + + return invoker.Invoke(obj, parameters ?? Array.Empty()); + } + + /// + /// Creates and caches a fast method invoker. + /// + /// Method info instance to create an invoker for. + /// The fast method invoker. + public static FastInvoker GetInvoker(MethodInfo methodInfo) + { + Guard.NotNull(methodInfo, nameof(methodInfo)); + + return GetInvoker( + methodInfo.DeclaringType, + methodInfo.Name, + methodInfo.GetParameters().Select(x => x.ParameterType).ToArray()); + } + + /// + /// Creates and caches a fast method invoker. + /// + /// Name of method to create an invoker for. + /// Argument types of method to create an invoker for. + /// The fast method invoker. + public static FastInvoker GetInvoker(string methodName, params Type[] argTypes) + { + return GetInvoker(typeof(T), methodName, argTypes); + } + + /// + /// Creates and caches a fast method invoker. + /// + /// The type to extract fast method invoker for. + /// Name of method to create an invoker for. + /// Argument types of method to create an invoker for. + /// The fast method invoker. + public static FastInvoker GetInvoker(Type type, string methodName, params Type[] argTypes) + { + Guard.NotNull(type, nameof(type)); + Guard.NotEmpty(methodName, nameof(methodName)); + + var cacheKey = MethodKey.Create(type, methodName, argTypes); + + if (!_invokersCache.TryGetValue(cacheKey, out var invoker)) + { + var method = FindMatchingMethod(type, methodName, argTypes); + + if (method == null) + { + throw new MethodAccessException("Could not find a matching method '{0}' in type {1}.".FormatInvariant(methodName, type)); + } + + invoker = new FastInvoker(method); + _invokersCache.TryAdd(cacheKey, invoker); + } + + return invoker; + } + + private static MethodInfo FindMatchingMethod(Type type, string methodName, Type[] argTypes) + { + var method = argTypes == null || argTypes.Length == 0 + ? type.GetMethod(methodName) + : type.GetMethod( + methodName, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, + null, + argTypes ?? new Type[0], + null); + + return method; + } + + #endregion + + class MethodKey : Tuple + { + public MethodKey(Type type, string methodName, int parametersHash) + : base(type, methodName, parametersHash) + { + } + public Type Type { get { return base.Item1; } } + public string MethodName { get { return base.Item2; } } + public int ParameterHash { get { return base.Item3; } } + + public static MethodKey Create(Type type, string methodName, IEnumerable parameterTypes) + { + var hashCombiner = HashCodeCombiner.Start().Add(parameterTypes); + return new MethodKey(type, methodName, hashCombiner.CombinedHash); + } + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs b/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs index 4ad5aebee4..7aafa809cd 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs @@ -543,7 +543,7 @@ protected static IDictionary GetVisibleProperties( { return result; } - + // The simple and common case, this is normal POCO object - no need to allocate. var allPropertiesDefinedOnType = true; var allProperties = GetProperties(type, createPropertyHelper, allPropertiesCache); diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index 98f3bb0cb0..2a4eafc78f 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -181,6 +181,7 @@ + diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs index 93c100e955..0d395920de 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs @@ -89,7 +89,7 @@ protected virtual void RenderCore(IBlockContainer element, string[] templates, H throw new InvalidOperationException("The return value of the 'GetRoute()' method cannot be NULL."); } - routeInfo.RouteValues["model"] = element.Block; + //routeInfo.RouteValues["model"] = element.Block; var originalWriter = htmlHelper.ViewContext.Writer; htmlHelper.ViewContext.Writer = textWriter; From 9668c0a404b737155455c6b0c0e62290d7584f25 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 27 Sep 2018 03:14:41 +0200 Subject: [PATCH 140/932] CSS enhancements in admin theme --- .../Administration/Content/_admin.scss | 65 ++++++++++--------- .../Scripts/admin.globalinit.js | 2 +- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss b/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss index 6c00126382..c0180abd6b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss +++ b/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss @@ -3,6 +3,17 @@ /// /// +$section-header-height: 70px; + +:root { + --content-padding-x: $content-padding-x / 2; + --content-padding-y: $content-padding-y; + + @include media-breakpoint-up(lg) { + --content-padding-x: $content-padding-x; + } +} + // // Legacy BS elements // -------------------------------------------------- @@ -122,22 +133,19 @@ body { #content { position: relative; - margin: 0 ($content-padding-x / 2); - padding: $content-padding-y ($content-padding-x / 2); - padding-bottom: $content-padding-x * 2; + margin: 0 var(--content-padding-x); + padding-left: var(--content-padding-x); + padding-right: var(--content-padding-x); + padding-top: $section-header-height; + padding-bottom: $content-padding-x * 2; + box-shadow: 0 8px 16px rgba(#32325d, .1), 0 -2px 8px rgba(#000, .09); border-radius: $border-radius-lg $border-radius-lg 0 0; background-color: #fff; grid-column: 1 / -1; grid-row: 2 / -1; z-index: 9; - - @include media-breakpoint-up(lg) { - margin-left: $content-padding-x; - margin-right: $content-padding-x; - padding-left: $content-padding-x; - padding-right: $content-padding-x; - } + overflow-x: hidden; .popup & { margin: 0; @@ -149,7 +157,6 @@ body { } .cph { - position: relative; display: flex; flex-flow: column; } @@ -167,9 +174,16 @@ body { } .section-header { - padding-bottom: 0.75rem; - transition: background-color 0.1s ease-in-out; - + position: absolute; + left: 0; + top: 0; + right: 0; + padding-left: var(--content-padding-x); + padding-right: var(--content-padding-x); + padding-top: var(--content-padding-y); + padding-bottom: var(--content-padding-y); + transition: box-shadow 0.25s ease; + @include make-row(); flex-wrap: nowrap; margin-left: 0; @@ -245,29 +259,18 @@ body { &.sticky { position: fixed; z-index: 600; - left: $content-padding-x / 2; - right: $content-padding-x / 2; - top: 0; - padding-left: $content-padding-x / 2; - padding-right: $content-padding-x / 2; - padding-top: $content-padding-y; - margin-left: 0 !important; - margin-right: 0 !important; - background: $gray-100; + left: var(--content-padding-x); + right: var(--content-padding-x); + padding-left: var(--content-padding-x); + padding-right: var(--content-padding-x); background: #fff; - border-bottom: 1px solid rgba(0,0,0, 0.08); + //border-bottom: 1px solid rgba(0,0,0, 0.08); + box-shadow: 0 8px 16px 0 rgba(#32325d, .1), 0 2px 7px 0 rgba(#000, .07); .modal-open & { // add the killed scrollbar width margin-right: 17px !important; } - - @include media-breakpoint-up(lg) { - left: $content-padding-x; - right: $content-padding-x; - padding-left: $content-padding-x; - padding-right: $content-padding-x; - } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js index f5197676da..07c3abeb5f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js +++ b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js @@ -123,7 +123,7 @@ // sticky section-header var navbar = $("#navbar"); - var navbarHeight = navbar.height() || 0; + var navbarHeight = navbar.height() || 1; var sectionHeader = $('.section-header'); var sectionHeaderHasButtons = undefined; From 4df91fd42f9fbbf2121ad8590ebc88dee2123a2c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 27 Sep 2018 09:30:15 +0200 Subject: [PATCH 141/932] Removed default avatar setting (obsolete) --- .../Domain/Customers/CustomerSettings.cs | 6 ------ .../SmartStore.Core/Domain/Media/PictureType.cs | 5 +---- .../Migrations/MigrationsConfiguration.cs | 1 + .../SmartStore.Services/Media/PictureService.cs | 10 ++-------- .../Models/Settings/CustomerUserSettingsModel.cs | 5 +---- .../Views/Setting/CustomerUser.cshtml | 11 +---------- .../Content/Images/default-avatar.jpg | Bin 901 -> 0 bytes .../Extensions/MappingExtensions.cs | 14 +------------- .../SmartStore.Web/SmartStore.Web.csproj | 1 - 9 files changed, 7 insertions(+), 46 deletions(-) delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/default-avatar.jpg diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs index 74beb552c2..7c51a9ced5 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs @@ -14,7 +14,6 @@ public CustomerSettings() PasswordMinLength = 6; UserRegistrationType = UserRegistrationType.Standard; AvatarMaximumSizeBytes = 512000; - DefaultAvatarEnabled = false; CustomerNameFormat = CustomerNameFormat.ShowFirstName; CustomerNameFormatMaxLength = 64; GenderEnabled = true; @@ -82,11 +81,6 @@ public CustomerSettings() /// public int AvatarMaximumSizeBytes { get; set; } - /// - /// Gets or sets a value indicating whether to display default user avatar. - /// - public bool DefaultAvatarEnabled { get; set; } - /// /// Gets or sets a value indicating whether customers location is shown /// diff --git a/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs b/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs index 84d245f3be..94eb05184c 100644 --- a/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs +++ b/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs @@ -6,14 +6,11 @@ namespace SmartStore.Core.Domain.Media public enum FallbackPictureType { NoFallback = 0, + /// /// Entities (products, categories, manufacturers) /// Entity = 1, - /// - /// Avatar - /// - Avatar = 10, } public enum ThumbnailScaleMode diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 7deca315ce..b656f275f9 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -453,6 +453,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.Delete( "Admin.Configuration.Settings.Search.DefaultSortOrderMode", "Admin.Configuration.Settings.Search.InstantSearchNumberOfProducts", + "Admin.Configuration.Settings.CustomerUser.DefaultAvatarEnabled", "Forum.Search.LimitResultsToPrevious.AllResults", "Forum.Search.LimitResultsToPrevious.1day", "Forum.Search.LimitResultsToPrevious.7days", diff --git a/src/Libraries/SmartStore.Services/Media/PictureService.cs b/src/Libraries/SmartStore.Services/Media/PictureService.cs index 4f033c9ec7..e15b780795 100644 --- a/src/Libraries/SmartStore.Services/Media/PictureService.cs +++ b/src/Libraries/SmartStore.Services/Media/PictureService.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Data.Entity; using System.Drawing; @@ -25,7 +24,7 @@ namespace SmartStore.Services.Media { - [Serializable] + [Serializable] public class PictureInfo { public int Id { get; set; } @@ -53,7 +52,6 @@ public partial class PictureService : IPictureService private readonly IImageProcessor _imageProcessor; private readonly IImageCache _imageCache; private readonly Provider _storageProvider; - private readonly IStoreContext _storeContext; private readonly HttpContextBase _httpContext; private readonly ICacheManager _cacheManager; @@ -78,7 +76,7 @@ public PictureService( IImageProcessor imageProcessor, IImageCache imageCache, IProviderManager providerManager, - IStoreContext storeContext, + IStoreContext storeContext, HttpContextBase httpContext, ICacheManager cacheManager) { @@ -89,7 +87,6 @@ public PictureService( _mediaSettings = mediaSettings; _imageProcessor = imageProcessor; _imageCache = imageCache; - _storeContext = storeContext; _httpContext = httpContext; _cacheManager = cacheManager; @@ -143,9 +140,6 @@ protected virtual string GetFallbackImageFileName(FallbackPictureType defaultPic case FallbackPictureType.Entity: defaultImageFileName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.png"); break; - case FallbackPictureType.Avatar: - defaultImageFileName = _settingService.GetSettingByKey("Media.Customer.DefaultAvatarImageName", "default-avatar.jpg"); - break; default: defaultImageFileName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.png"); break; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs index d3537e1464..e2052ea7e6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs @@ -7,7 +7,7 @@ namespace SmartStore.Admin.Models.Settings { - public partial class CustomerUserSettingsModel : ModelBase, ILocalizedModel + public partial class CustomerUserSettingsModel : ModelBase, ILocalizedModel { public CustomerUserSettingsModel() { @@ -57,9 +57,6 @@ public partial class CustomerSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.AllowCustomersToUploadAvatars")] public bool AllowCustomersToUploadAvatars { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.DefaultAvatarEnabled")] - public bool DefaultAvatarEnabled { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.ShowCustomersLocation")] public bool ShowCustomersLocation { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml index 65a1bf4f4a..be6dea2058 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml @@ -146,19 +146,10 @@
    - - - - + From 75cfe5c40edcfd8f5ab6e48f8a0af1098283e521 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 27 Sep 2018 17:07:37 +0200 Subject: [PATCH 143/932] Several performance improvements at forum service --- .../Forums/ForumService.cs | 398 +++++++++--------- .../Forums/IForumService.cs | 76 ++-- 2 files changed, 239 insertions(+), 235 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index 2e55625acf..cef6e4adb3 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -26,7 +26,7 @@ public partial class ForumService : IForumService private readonly IRepository _customerRepository; private readonly IGenericAttributeService _genericAttributeService; private readonly ICustomerService _customerService; - private readonly ICommonServices _services; + private readonly ICommonServices _services; public ForumService( IRepository forumGroupRepository, @@ -41,7 +41,7 @@ public ForumService( IRepository customerRepository, IGenericAttributeService genericAttributeService, ICustomerService customerService, - ICommonServices services) + ICommonServices services) { _forumGroupRepository = forumGroupRepository; _forumRepository = forumRepository; @@ -55,23 +55,22 @@ public ForumService( _customerRepository = customerRepository; _genericAttributeService = genericAttributeService; _customerService = customerService; - _services = services; + _services = services; } - public DbQuerySettings QuerySettings { get; set; } + public DbQuerySettings QuerySettings { get; set; } - private void UpdateForumStats(int forumId) + protected virtual void UpdateForumStats(Forum forum) { - var forum = GetForumById(forumId); if (forum == null) { return; } - var queryLastValues = + var queryLastValues = from ft in _forumTopicRepository.TableUntracked join fp in _forumPostRepository.TableUntracked on ft.Id equals fp.TopicId - where ft.ForumId == forumId && ft.Published && fp.Published + where ft.ForumId == forum.Id && ft.Published && fp.Published orderby fp.CreatedOnUtc descending, ft.CreatedOnUtc descending select new { @@ -86,27 +85,26 @@ join fp in _forumPostRepository.TableUntracked on ft.Id equals fp.TopicId forum.LastPostId = lastValues?.LastPostId ?? 0; forum.LastPostCustomerId = lastValues?.LastPostCustomerId ?? 0; forum.LastPostTime = lastValues?.LastPostTime; - forum.NumTopics = _forumTopicRepository.Table.Where(x => x.ForumId == forumId && x.Published).Count(); + forum.NumTopics = _forumTopicRepository.Table.Where(x => x.ForumId == forum.Id && x.Published).Count(); forum.NumPosts = ( from ft in _forumTopicRepository.Table join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId - where ft.ForumId == forumId && ft.Published && fp.Published + where ft.ForumId == forum.Id && ft.Published && fp.Published select fp.Id).Count(); UpdateForum(forum); } - private void UpdateForumTopicStats(int forumTopicId) + protected virtual void UpdateForumTopicStats(ForumTopic topic) { - var forumTopic = GetTopicById(forumTopicId); - if (forumTopic == null) + if (topic == null) { return; } - var queryLastValues = + var queryLastValues = from fp in _forumPostRepository.TableUntracked - where fp.TopicId == forumTopicId && fp.Published + where fp.TopicId == topic.Id && fp.Published orderby fp.CreatedOnUtc descending select new { @@ -116,38 +114,36 @@ orderby fp.CreatedOnUtc descending }; var lastValues = queryLastValues.FirstOrDefault(); - forumTopic.LastPostId = lastValues?.LastPostId ?? 0; - forumTopic.LastPostCustomerId = lastValues?.LastPostCustomerId ?? 0; - forumTopic.LastPostTime = lastValues?.LastPostTime; - forumTopic.NumPosts = _forumPostRepository.Table.Where(x => x.TopicId == forumTopicId && x.Published).Count(); + topic.LastPostId = lastValues?.LastPostId ?? 0; + topic.LastPostCustomerId = lastValues?.LastPostCustomerId ?? 0; + topic.LastPostTime = lastValues?.LastPostTime; + topic.NumPosts = _forumPostRepository.Table.Where(x => x.TopicId == topic.Id && x.Published).Count(); - UpdateTopic(forumTopic); + UpdateTopic(topic); } - private void UpdateCustomerStats(int customerId) + protected virtual void UpdateCustomerStats(Customer customer) { - if (customerId != 0) + if (customer != null) { - var customer = _customerService.GetCustomerById(customerId); - if (customer != null) - { - var numPosts = _forumPostRepository.Table.Where(x => x.CustomerId == customerId && x.Published).Count(); + var numPosts = _forumPostRepository.Table + .Where(x => x.CustomerId == customer.Id && x.Published) + .Count(); - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ForumPostCount, numPosts); - } + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ForumPostCount, numPosts); } } #region Group - public virtual ForumGroup GetForumGroupById(int forumGroupId) + public virtual ForumGroup GetForumGroupById(int groupId) { - if (forumGroupId == 0) + if (groupId == 0) { return null; } - return _forumGroupRepository.GetById(forumGroupId); + return _forumGroupRepository.GetById(groupId); } public virtual IList GetAllForumGroups(int storeId = 0, bool showHidden = false) @@ -171,7 +167,7 @@ from sm in fg_sm.DefaultIfEmpty() { var allowedCustomerRolesIds = _services.WorkContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); - query = + query = from fg in query join a in _aclRepository.Table on new { a1 = fg.Id, a2 = "ForumGroup" } equals new { a1 = a.EntityId, a2 = a.EntityName } into fg_acl from a in fg_acl.DefaultIfEmpty() @@ -195,26 +191,26 @@ orderby fgGroup.Key return query.ToListCached(); } - public virtual void InsertForumGroup(ForumGroup forumGroup) + public virtual void InsertForumGroup(ForumGroup group) { - Guard.NotNull(forumGroup, nameof(forumGroup)); + Guard.NotNull(group, nameof(group)); - _forumGroupRepository.Insert(forumGroup); + _forumGroupRepository.Insert(group); } - public virtual void UpdateForumGroup(ForumGroup forumGroup) + public virtual void UpdateForumGroup(ForumGroup group) { - Guard.NotNull(forumGroup, nameof(forumGroup)); + Guard.NotNull(group, nameof(group)); - _forumGroupRepository.Update(forumGroup); + _forumGroupRepository.Update(group); } - public virtual void DeleteForumGroup(ForumGroup forumGroup) + public virtual void DeleteForumGroup(ForumGroup group) { - if (forumGroup != null) + if (group != null) { - _forumGroupRepository.Delete(forumGroup); - } + _forumGroupRepository.Delete(group); + } } #endregion @@ -285,9 +281,9 @@ where queryTopicIds.Contains(fs.TopicId) #region Topic - public virtual ForumTopic GetTopicById(int forumTopicId) + public virtual ForumTopic GetTopicById(int topicId) { - if (forumTopicId == 0) + if (topicId == 0) { return null; } @@ -295,7 +291,7 @@ public virtual ForumTopic GetTopicById(int forumTopicId) var entity = _forumTopicRepository.Table .Expand(x => x.Forum) .Expand(x => x.Forum.ForumGroup) - .FirstOrDefault(x => x.Id == forumTopicId); + .FirstOrDefault(x => x.Id == topicId); return entity; } @@ -343,30 +339,30 @@ public virtual IList GetActiveTopics(int forumId, int count, bool sh var customer = _services.WorkContext.CurrentCustomer; var query = - from ft in _forumTopicRepository.Table - where (forumId == 0 || ft.ForumId == forumId) && ft.LastPostTime.HasValue - select ft; + from ft in _forumTopicRepository.Table + where (forumId == 0 || ft.ForumId == forumId) && ft.LastPostTime.HasValue + select ft; if (!showHidden && !customer.IsForumModerator()) { query = query.Where(x => x.Published || x.CustomerId == customer.Id); } - if (!QuerySettings.IgnoreMultiStore) - { - var currentStoreId = _services.StoreContext.CurrentStore.Id; + if (!QuerySettings.IgnoreMultiStore) + { + var currentStoreId = _services.StoreContext.CurrentStore.Id; - query = - from ft in query - join ff in _forumRepository.Table on ft.ForumId equals ff.Id - join fg in _forumGroupRepository.Table on ff.ForumGroupId equals fg.Id - join sm in _storeMappingRepository.Table on new { c1 = fg.Id, c2 = "ForumGroup" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into fg_sm - from sm in fg_sm.DefaultIfEmpty() - where !fg.LimitedToStores || currentStoreId == sm.StoreId - select ft; + query = + from ft in query + join ff in _forumRepository.Table on ft.ForumId equals ff.Id + join fg in _forumGroupRepository.Table on ff.ForumGroupId equals fg.Id + join sm in _storeMappingRepository.Table on new { c1 = fg.Id, c2 = "ForumGroup" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into fg_sm + from sm in fg_sm.DefaultIfEmpty() + where !fg.LimitedToStores || currentStoreId == sm.StoreId + select ft; joinApplied = true; - } + } if (!showHidden && !QuerySettings.IgnoreAcl) { @@ -399,119 +395,155 @@ orderby ftGroup.Key return forumTopics; } - public virtual void InsertTopic(ForumTopic forumTopic, bool sendNotifications) + public virtual void InsertTopic(ForumTopic topic, bool sendNotifications) { - Guard.NotNull(forumTopic, nameof(forumTopic)); + Guard.NotNull(topic, nameof(topic)); + + _forumTopicRepository.Insert(topic); - _forumTopicRepository.Insert(forumTopic); + var forum = topic.Forum ?? GetForumById(topic.ForumId); - UpdateForumStats(forumTopic.ForumId); + UpdateForumStats(forum); if (sendNotifications) { - var forum = forumTopic.Forum; var subscriptions = GetAllSubscriptions(0, forum.Id, 0, 0, int.MaxValue); var languageId = _services.WorkContext.WorkingLanguage.Id; foreach (var subscription in subscriptions) { - if (subscription.CustomerId == forumTopic.CustomerId) + if (subscription.CustomerId == topic.CustomerId) { continue; } if (subscription.Customer.Email.HasValue()) { - _services.MessageFactory.SendNewForumTopicMessage(subscription.Customer, forumTopic, languageId); + _services.MessageFactory.SendNewForumTopicMessage(subscription.Customer, topic, languageId); } } } } - public virtual void UpdateTopic(ForumTopic forumTopic) + public virtual void UpdateTopic(ForumTopic topic) { - Guard.NotNull(forumTopic, nameof(forumTopic)); + Guard.NotNull(topic, nameof(topic)); - _forumTopicRepository.Update(forumTopic); + _forumTopicRepository.Update(topic); } - public virtual void DeleteTopic(ForumTopic forumTopic) + public virtual void DeleteTopic(ForumTopic topic) { - if (forumTopic == null) + if (topic == null) { return; } - int customerId = forumTopic.CustomerId; - int forumId = forumTopic.ForumId; + var forum = topic.Forum ?? GetForumById(topic.ForumId); + var customer = topic.Customer ?? _customerService.GetCustomerById(topic.CustomerId); - _forumTopicRepository.Delete(forumTopic); + _forumTopicRepository.Delete(topic); // Delete forum subscriptions. - var queryFs = from ft in _forumSubscriptionRepository.Table - where ft.TopicId == forumTopic.Id - select ft; - var forumSubscriptions = queryFs.ToList(); - foreach (var fs in forumSubscriptions) + var subscriptions = _forumSubscriptionRepository.Table.Where(x => x.TopicId == topic.Id).ToList(); + foreach (var subscription in subscriptions) { - _forumSubscriptionRepository.Delete(fs); + _forumSubscriptionRepository.Delete(subscription); } - UpdateForumStats(forumId); - UpdateCustomerStats(customerId); + UpdateForumStats(forum); + UpdateCustomerStats(customer); } - public virtual ForumTopic MoveTopic(int forumTopicId, int newForumId) + public virtual ForumTopic MoveTopic(int topicId, int newForumId) { - var forumTopic = GetTopicById(forumTopicId); - if (forumTopic == null) + var topic = GetTopicById(topicId); + if (topic == null) { - return forumTopic; + return topic; } - if (IsCustomerAllowedToMoveTopic(_services.WorkContext.CurrentCustomer, forumTopic)) + if (IsCustomerAllowedToMoveTopic(_services.WorkContext.CurrentCustomer, topic)) { - var previousForumId = forumTopic.ForumId; + var previousForumId = topic.ForumId; var newForum = GetForumById(newForumId); if (newForum != null && previousForumId != newForumId) { - forumTopic.ForumId = newForum.Id; - UpdateTopic(forumTopic); + topic.ForumId = newForum.Id; + UpdateTopic(topic); - UpdateForumStats(previousForumId); - UpdateForumStats(newForumId); + var previousForum = topic.Forum ?? GetForumById(topic.ForumId); + UpdateForumStats(previousForum); + UpdateForumStats(newForum); } } - return forumTopic; + return topic; } - public virtual int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId) + public virtual int CalculateTopicPageIndex(int topicId, int pageSize, int postId) { - var pageIndex = 0; - var forumPosts = GetAllPosts(forumTopicId, 0, true, 0, int.MaxValue); + if (pageSize <= 0 || postId == 0) + { + return 0; + } + + var query = GetPostQuery(topicId, 0, true, true); + var postIds = query.Select(x => x.Id).ToList(); - for (var i = 0; i < forumPosts.TotalCount; i++) + for (var i = 0; i < postIds.Count; ++i) { - if (forumPosts[i].Id == postId) + if (postIds[i] == postId) { - if (pageSize > 0) - { - pageIndex = i / pageSize; - } + return i / pageSize; } } - return pageIndex; + return 0; } #endregion #region Post - public virtual ForumPost GetPostById(int forumPostId) + protected virtual IQueryable GetPostQuery( + int topicId, + int customerId, + bool ascSort, + bool showHidden = false, + bool untracked = false) { - if (forumPostId == 0) + var customer = _services.WorkContext.CurrentCustomer; + var query = untracked ? _forumPostRepository.TableUntracked : _forumPostRepository.Table; + + if (topicId > 0) + { + query = query.Where(x => topicId == x.TopicId); + } + if (customerId > 0) + { + query = query.Where(x => customerId == x.CustomerId); + } + if (!showHidden && !customer.IsForumModerator()) + { + query = query.Where(x => x.Published || x.CustomerId == customer.Id); + } + + if (ascSort) + { + query = query.OrderBy(x => x.CreatedOnUtc).ThenBy(x => x.Id); + } + else + { + query = query.OrderByDescending(x => x.CreatedOnUtc).ThenBy(x => x.Id); + } + + return query; + } + + public virtual ForumPost GetPostById(int postId) + { + if (postId == 0) { return null; } @@ -520,7 +552,7 @@ public virtual ForumPost GetPostById(int forumPostId) .Expand(x => x.ForumTopic) .Expand(x => x.ForumTopic.Forum) .Expand(x => x.ForumTopic.Forum.ForumGroup) - .FirstOrDefault(x => x.Id == forumPostId); + .FirstOrDefault(x => x.Id == postId); return forumPost; } @@ -539,135 +571,107 @@ public virtual IList GetPostsByIds(int[] postIds) } public virtual IPagedList GetAllPosts( - int forumTopicId, + int topicId, int customerId, bool ascSort, int pageIndex, int pageSize, bool showHidden = false) { - var customer = _services.WorkContext.CurrentCustomer; - var query = _forumPostRepository.Table; - - if (forumTopicId > 0) - { - query = query.Where(fp => forumTopicId == fp.TopicId); - } - if (customerId > 0) - { - query = query.Where(fp => customerId == fp.CustomerId); - } - if (!showHidden && !customer.IsForumModerator()) - { - query = query.Where(fp => fp.Published || fp.CustomerId == customer.Id); - } - - if (ascSort) - { - query = query.OrderBy(fp => fp.CreatedOnUtc).ThenBy(fp => fp.Id); - } - else - { - query = query.OrderByDescending(fp => fp.CreatedOnUtc).ThenBy(fp => fp.Id); - } - + var query = GetPostQuery(topicId, customerId, ascSort, showHidden); var forumPosts = new PagedList(query, pageIndex, pageSize); return forumPosts; } - public virtual void InsertPost(ForumPost forumPost, bool sendNotifications) + public virtual void InsertPost(ForumPost post, bool sendNotifications) { - Guard.NotNull(forumPost, nameof(forumPost)); + Guard.NotNull(post, nameof(post)); - _forumPostRepository.Insert(forumPost); + _forumPostRepository.Insert(post); - var forumTopic = GetTopicById(forumPost.TopicId); + var topic = post.ForumTopic ?? GetTopicById(post.TopicId); + var forum = topic.Forum ?? GetForumById(topic.ForumId); + var customer = post.Customer ?? _customerService.GetCustomerById(post.CustomerId); - UpdateForumTopicStats(forumPost.TopicId); - UpdateForumStats(forumTopic.ForumId); - UpdateCustomerStats(forumPost.CustomerId); + UpdateForumTopicStats(topic); + UpdateForumStats(forum); + UpdateCustomerStats(customer); if (sendNotifications) { - var forum = forumTopic.Forum; var languageId = _services.WorkContext.WorkingLanguage.Id; - var subscriptions = GetAllSubscriptions(0, 0, forumTopic.Id, 0, int.MaxValue); + var subscriptions = GetAllSubscriptions(0, 0, topic.Id, 0, int.MaxValue); var friendlyTopicPageIndex = CalculateTopicPageIndex( - forumPost.TopicId, + post.TopicId, _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 20, - forumPost.Id) + 1; + post.Id) + 1; foreach (var subscription in subscriptions) { - if (subscription.CustomerId == forumPost.CustomerId) + if (subscription.CustomerId == post.CustomerId) { continue; } if (subscription.Customer.Email.HasValue()) { - _services.MessageFactory.SendNewForumPostMessage(subscription.Customer, forumPost, friendlyTopicPageIndex, languageId); + _services.MessageFactory.SendNewForumPostMessage(subscription.Customer, post, friendlyTopicPageIndex, languageId); } } } } - public virtual void UpdatePost(ForumPost forumPost) + public virtual void UpdatePost(ForumPost post) { - Guard.NotNull(forumPost, nameof(forumPost)); + Guard.NotNull(post, nameof(post)); - _forumPostRepository.Update(forumPost); + _forumPostRepository.Update(post); } - public virtual void DeletePost(ForumPost forumPost) + public virtual void DeletePost(ForumPost post) { - if (forumPost == null) + if (post == null) { return; } - var forumTopicId = forumPost.TopicId; - var customerId = forumPost.CustomerId; - var forumTopic = GetTopicById(forumTopicId); - var forumId = forumTopic.ForumId; + var topic = post.ForumTopic ?? GetTopicById(post.TopicId); + var forum = topic.Forum ?? GetForumById(topic.ForumId); + var customer = post.Customer ?? _customerService.GetCustomerById(post.CustomerId); // Delete topic if it was the first post. - var deleteTopic = false; - var firstPost = forumTopic.GetFirstPost(this); - if (firstPost != null && firstPost.Id == forumPost.Id) - { - deleteTopic = true; - } + var firstPost = topic.GetFirstPost(this); + var deleteTopic = firstPost != null && firstPost.Id == post.Id; // Delete forum post. - _forumPostRepository.Delete(forumPost); + _forumPostRepository.Delete(post); // Delete topic. if (deleteTopic) { - DeleteTopic(forumTopic); + DeleteTopic(topic); } - - if (!deleteTopic) + else { - UpdateForumTopicStats(forumTopicId); + UpdateForumTopicStats(topic); } - UpdateForumStats(forumId); - UpdateCustomerStats(customerId); + + UpdateForumStats(forum); + UpdateCustomerStats(customer); } #endregion #region Private message - public virtual PrivateMessage GetPrivateMessageById(int privateMessageId) + public virtual PrivateMessage GetPrivateMessageById(int messageId) { - if (privateMessageId == 0) + if (messageId == 0) { return null; } - var privateMessage = _forumPrivateMessageRepository.Table.FirstOrDefault(x => x.Id == privateMessageId); + var privateMessage = _forumPrivateMessageRepository.Table.FirstOrDefault(x => x.Id == messageId); return privateMessage; } @@ -714,45 +718,45 @@ public virtual IPagedList GetAllPrivateMessages( return privateMessages; } - public virtual void InsertPrivateMessage(PrivateMessage privateMessage) + public virtual void InsertPrivateMessage(PrivateMessage message) { - Guard.NotNull(privateMessage, nameof(privateMessage)); + Guard.NotNull(message, nameof(message)); - _forumPrivateMessageRepository.Insert(privateMessage); + _forumPrivateMessageRepository.Insert(message); - var customerTo = _customerService.GetCustomerById(privateMessage.ToCustomerId); + var customerTo = _customerService.GetCustomerById(message.ToCustomerId); if (customerTo == null) { throw new SmartException("Recipient could not be loaded"); } - _genericAttributeService.SaveAttribute(customerTo, SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, false, privateMessage.StoreId); + _genericAttributeService.SaveAttribute(customerTo, SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, false, message.StoreId); if (_forumSettings.NotifyAboutPrivateMessages) { - _services.MessageFactory.SendPrivateMessageNotification(customerTo, privateMessage, _services.WorkContext.WorkingLanguage.Id); + _services.MessageFactory.SendPrivateMessageNotification(customerTo, message, _services.WorkContext.WorkingLanguage.Id); } } - public virtual void UpdatePrivateMessage(PrivateMessage privateMessage) + public virtual void UpdatePrivateMessage(PrivateMessage message) { - Guard.NotNull(privateMessage, nameof(privateMessage)); + Guard.NotNull(message, nameof(message)); - if (privateMessage.IsDeletedByAuthor && privateMessage.IsDeletedByRecipient) + if (message.IsDeletedByAuthor && message.IsDeletedByRecipient) { - _forumPrivateMessageRepository.Delete(privateMessage); + _forumPrivateMessageRepository.Delete(message); } else { - _forumPrivateMessageRepository.Update(privateMessage); + _forumPrivateMessageRepository.Update(message); } } - public virtual void DeletePrivateMessage(PrivateMessage privateMessage) + public virtual void DeletePrivateMessage(PrivateMessage message) { - if (privateMessage != null) + if (message != null) { - _forumPrivateMessageRepository.Delete(privateMessage); + _forumPrivateMessageRepository.Delete(message); } } @@ -760,14 +764,14 @@ public virtual void DeletePrivateMessage(PrivateMessage privateMessage) #region Subscription - public virtual ForumSubscription GetSubscriptionById(int forumSubscriptionId) + public virtual ForumSubscription GetSubscriptionById(int subscriptionId) { - if (forumSubscriptionId == 0) + if (subscriptionId == 0) { return null; } - var forumSubscription = _forumSubscriptionRepository.Table.FirstOrDefault(x => x.Id == forumSubscriptionId); + var forumSubscription = _forumSubscriptionRepository.Table.FirstOrDefault(x => x.Id == subscriptionId); return forumSubscription; } @@ -793,25 +797,25 @@ where fsQuery.Contains(fs.SubscriptionGuid) return forumSubscriptions; } - public virtual void InsertSubscription(ForumSubscription forumSubscription) + public virtual void InsertSubscription(ForumSubscription subscription) { - Guard.NotNull(forumSubscription, nameof(forumSubscription)); + Guard.NotNull(subscription, nameof(subscription)); - _forumSubscriptionRepository.Insert(forumSubscription); + _forumSubscriptionRepository.Insert(subscription); } - public virtual void UpdateSubscription(ForumSubscription forumSubscription) + public virtual void UpdateSubscription(ForumSubscription subscription) { - Guard.NotNull(forumSubscription, nameof(forumSubscription)); + Guard.NotNull(subscription, nameof(subscription)); - _forumSubscriptionRepository.Update(forumSubscription); + _forumSubscriptionRepository.Update(subscription); } - public virtual void DeleteSubscription(ForumSubscription forumSubscription) + public virtual void DeleteSubscription(ForumSubscription subscription) { - if (forumSubscription != null) + if (subscription != null) { - _forumSubscriptionRepository.Delete(forumSubscription); + _forumSubscriptionRepository.Delete(subscription); } } diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index 361e1720fb..9f26ab3e6c 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -15,9 +15,9 @@ public partial interface IForumService /// /// Gets a forum group /// - /// The forum group identifier + /// The forum group identifier /// Forum group - ForumGroup GetForumGroupById(int forumGroupId); + ForumGroup GetForumGroupById(int groupId); /// /// Gets all forum groups @@ -30,20 +30,20 @@ public partial interface IForumService /// /// Deletes a forum group /// - /// Forum group - void DeleteForumGroup(ForumGroup forumGroup); + /// Forum group + void DeleteForumGroup(ForumGroup group); /// /// Inserts a forum group /// - /// Forum group - void InsertForumGroup(ForumGroup forumGroup); + /// Forum group + void InsertForumGroup(ForumGroup group); /// /// Updates the forum group /// - /// Forum group - void UpdateForumGroup(ForumGroup forumGroup); + /// Forum group + void UpdateForumGroup(ForumGroup group); #endregion @@ -81,9 +81,9 @@ public partial interface IForumService /// /// Gets a forum topic /// - /// The forum topic identifier + /// The forum topic identifier /// Forum Topic - ForumTopic GetTopicById(int forumTopicId); + ForumTopic GetTopicById(int topicId); /// /// Gets forum topics by identifiers @@ -114,38 +114,38 @@ public partial interface IForumService /// /// Deletes a forum topic /// - /// Forum topic - void DeleteTopic(ForumTopic forumTopic); + /// Forum topic + void DeleteTopic(ForumTopic topic); /// /// Inserts a forum topic /// - /// Forum topic + /// Forum topic /// A value indicating whether to send notifications to subscribed customers - void InsertTopic(ForumTopic forumTopic, bool sendNotifications); + void InsertTopic(ForumTopic topic, bool sendNotifications); /// /// Updates the forum topic /// - /// Forum topic - void UpdateTopic(ForumTopic forumTopic); + /// Forum topic + void UpdateTopic(ForumTopic topic); /// /// Moves the forum topic /// - /// The forum topic identifier + /// The forum topic identifier /// New forum identifier /// Moved forum topic - ForumTopic MoveTopic(int forumTopicId, int newForumId); + ForumTopic MoveTopic(int topicId, int newForumId); /// /// Calculates topic page index by post identifier /// - /// Topic identifier + /// Topic identifier /// Page size /// Post identifier /// Page index - int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId); + int CalculateTopicPageIndex(int topicId, int pageSize, int postId); #endregion @@ -154,9 +154,9 @@ public partial interface IForumService /// /// Gets a forum post /// - /// The forum post identifier + /// The forum post identifier /// Forum Post - ForumPost GetPostById(int forumPostId); + ForumPost GetPostById(int postId); /// /// Gets forum posts by identifiers. @@ -168,33 +168,33 @@ public partial interface IForumService /// /// Gets all forum posts /// - /// The forum topic identifier + /// The forum topic identifier /// The customer identifier /// Sort order /// Page index /// Page size /// Whether to load hidden records /// Forum Posts - IPagedList GetAllPosts(int forumTopicId, int customerId, bool ascSort, int pageIndex, int pageSize, bool showHidden = false); + IPagedList GetAllPosts(int topicId, int customerId, bool ascSort, int pageIndex, int pageSize, bool showHidden = false); /// /// Deletes a forum post /// - /// Forum post - void DeletePost(ForumPost forumPost); + /// Forum post + void DeletePost(ForumPost post); /// /// Inserts a forum post /// - /// The forum post + /// The forum post /// A value indicating whether to send notifications to subscribed customers - void InsertPost(ForumPost forumPost, bool sendNotifications); + void InsertPost(ForumPost post, bool sendNotifications); /// /// Updates the forum post /// - /// Forum post - void UpdatePost(ForumPost forumPost); + /// Forum post + void UpdatePost(ForumPost post); #endregion @@ -203,9 +203,9 @@ public partial interface IForumService /// /// Gets a private message /// - /// The private message identifier + /// The private message identifier /// Private message - PrivateMessage GetPrivateMessageById(int privateMessageId); + PrivateMessage GetPrivateMessageById(int messageId); /// /// Gets private messages @@ -232,20 +232,20 @@ IPagedList GetAllPrivateMessages( /// /// Deletes a private message /// - /// Private message - void DeletePrivateMessage(PrivateMessage privateMessage); + /// Private message + void DeletePrivateMessage(PrivateMessage message); /// /// Inserts a private message /// - /// Private message - void InsertPrivateMessage(PrivateMessage privateMessage); + /// Private message + void InsertPrivateMessage(PrivateMessage message); /// /// Updates the private message /// - /// Private message - void UpdatePrivateMessage(PrivateMessage privateMessage); + /// Private message + void UpdatePrivateMessage(PrivateMessage message); #endregion From 00a35732cd48e5024d927fe36719915c161b0863 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 27 Sep 2018 20:59:05 +0200 Subject: [PATCH 144/932] Published property and updating forum counter --- .../Domain/Forums/ForumTopic.cs | 8 +- .../Forums/ForumService.cs | 121 +++++++++++------- .../Forums/IForumService.cs | 6 +- .../Controllers/BoardsController.cs | 20 +-- .../SmartStore.Web/Views/Boards/Forum.cshtml | 1 - 5 files changed, 95 insertions(+), 61 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs index c31e029a50..63d6134530 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs @@ -114,10 +114,12 @@ public int NumReplies { get { - int result = 0; if (NumPosts > 0) - result = NumPosts - 1; - return result; + { + return NumPosts - 1; + } + + return 0; } } } diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index cef6e4adb3..4c0b56e800 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -60,7 +60,7 @@ public ForumService( public DbQuerySettings QuerySettings { get; set; } - protected virtual void UpdateForumStats(Forum forum) + protected virtual void UpdateForumStatistics(Forum forum) { if (forum == null) { @@ -85,7 +85,11 @@ join fp in _forumPostRepository.TableUntracked on ft.Id equals fp.TopicId forum.LastPostId = lastValues?.LastPostId ?? 0; forum.LastPostCustomerId = lastValues?.LastPostCustomerId ?? 0; forum.LastPostTime = lastValues?.LastPostTime; - forum.NumTopics = _forumTopicRepository.Table.Where(x => x.ForumId == forum.Id && x.Published).Count(); + + forum.NumTopics = _forumTopicRepository + .Table.Where(x => x.ForumId == forum.Id && x.Published) + .Count(); + forum.NumPosts = ( from ft in _forumTopicRepository.Table join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId @@ -95,7 +99,7 @@ join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId UpdateForum(forum); } - protected virtual void UpdateForumTopicStats(ForumTopic topic) + protected virtual void UpdateTopicStatistics(ForumTopic topic) { if (topic == null) { @@ -117,17 +121,20 @@ orderby fp.CreatedOnUtc descending topic.LastPostId = lastValues?.LastPostId ?? 0; topic.LastPostCustomerId = lastValues?.LastPostCustomerId ?? 0; topic.LastPostTime = lastValues?.LastPostTime; - topic.NumPosts = _forumPostRepository.Table.Where(x => x.TopicId == topic.Id && x.Published).Count(); - UpdateTopic(topic); + topic.NumPosts = topic.Published + ? _forumPostRepository.Table.Where(x => x.TopicId == topic.Id && x.Published).Count() + : 0; + + UpdateTopic(topic, false); } - protected virtual void UpdateCustomerStats(Customer customer) + protected virtual void UpdateCustomerStatistics(Customer customer) { if (customer != null) { var numPosts = _forumPostRepository.Table - .Where(x => x.CustomerId == customer.Id && x.Published) + .Where(x => x.CustomerId == customer.Id && x.ForumTopic.Published && x.Published) .Count(); _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ForumPostCount, numPosts); @@ -253,21 +260,27 @@ public virtual void DeleteForum(Forum forum) } // Delete forum subscriptions (topics). - var queryTopicIds = from ft in _forumTopicRepository.Table - where ft.ForumId == forum.Id - select ft.Id; - var queryFs1 = from fs in _forumSubscriptionRepository.Table - where queryTopicIds.Contains(fs.TopicId) - select fs; + var queryTopicIds = + from ft in _forumTopicRepository.Table + where ft.ForumId == forum.Id + select ft.Id; + + var queryFs1 = + from fs in _forumSubscriptionRepository.Table + where queryTopicIds.Contains(fs.TopicId) + select fs; + foreach (var fs in queryFs1.ToList()) { _forumSubscriptionRepository.Delete(fs); } // Delete forum subscriptions (forum). - var queryFs2 = from fs in _forumSubscriptionRepository.Table - where fs.ForumId == forum.Id - select fs; + var queryFs2 = + from fs in _forumSubscriptionRepository.Table + where fs.ForumId == forum.Id + select fs; + foreach (var fs2 in queryFs2.ToList()) { _forumSubscriptionRepository.Delete(fs2); @@ -403,7 +416,7 @@ public virtual void InsertTopic(ForumTopic topic, bool sendNotifications) var forum = topic.Forum ?? GetForumById(topic.ForumId); - UpdateForumStats(forum); + UpdateForumStatistics(forum); if (sendNotifications) { @@ -425,11 +438,21 @@ public virtual void InsertTopic(ForumTopic topic, bool sendNotifications) } } - public virtual void UpdateTopic(ForumTopic topic) + public virtual void UpdateTopic(ForumTopic topic, bool updateStatistics) { Guard.NotNull(topic, nameof(topic)); _forumTopicRepository.Update(topic); + + if (updateStatistics) + { + var forum = topic.Forum ?? GetForumById(topic.ForumId); + var customer = topic.Customer ?? _customerService.GetCustomerById(topic.CustomerId); + + UpdateForumStatistics(forum); + UpdateTopicStatistics(topic); + UpdateCustomerStatistics(customer); + } } public virtual void DeleteTopic(ForumTopic topic) @@ -451,30 +474,25 @@ public virtual void DeleteTopic(ForumTopic topic) _forumSubscriptionRepository.Delete(subscription); } - UpdateForumStats(forum); - UpdateCustomerStats(customer); + UpdateForumStatistics(forum); + UpdateCustomerStatistics(customer); } public virtual ForumTopic MoveTopic(int topicId, int newForumId) { var topic = GetTopicById(topicId); - if (topic == null) - { - return topic; - } - - if (IsCustomerAllowedToMoveTopic(_services.WorkContext.CurrentCustomer, topic)) + if (topic != null && IsCustomerAllowedToMoveTopic(_services.WorkContext.CurrentCustomer, topic)) { var previousForumId = topic.ForumId; var newForum = GetForumById(newForumId); + if (newForum != null && previousForumId != newForumId) { topic.ForumId = newForum.Id; - UpdateTopic(topic); + UpdateTopic(topic, false); - var previousForum = topic.Forum ?? GetForumById(topic.ForumId); - UpdateForumStats(previousForum); - UpdateForumStats(newForum); + UpdateForumStatistics(GetForumById(previousForumId)); + UpdateForumStatistics(newForum); } } @@ -483,19 +501,17 @@ public virtual ForumTopic MoveTopic(int topicId, int newForumId) public virtual int CalculateTopicPageIndex(int topicId, int pageSize, int postId) { - if (pageSize <= 0 || postId == 0) + if (pageSize > 0 && postId != 0) { - return 0; - } - - var query = GetPostQuery(topicId, 0, true, true); - var postIds = query.Select(x => x.Id).ToList(); + var query = GetPostQuery(topicId, 0, true, true); + var postIds = query.Select(x => x.Id).ToList(); - for (var i = 0; i < postIds.Count; ++i) - { - if (postIds[i] == postId) + for (var i = 0; i < postIds.Count; ++i) { - return i / pageSize; + if (postIds[i] == postId) + { + return i / pageSize; + } } } @@ -593,9 +609,9 @@ public virtual void InsertPost(ForumPost post, bool sendNotifications) var forum = topic.Forum ?? GetForumById(topic.ForumId); var customer = post.Customer ?? _customerService.GetCustomerById(post.CustomerId); - UpdateForumTopicStats(topic); - UpdateForumStats(forum); - UpdateCustomerStats(customer); + UpdateTopicStatistics(topic); + UpdateForumStatistics(forum); + UpdateCustomerStatistics(customer); if (sendNotifications) { @@ -621,11 +637,22 @@ public virtual void InsertPost(ForumPost post, bool sendNotifications) } } - public virtual void UpdatePost(ForumPost post) + public virtual void UpdatePost(ForumPost post, bool updateStatistics) { Guard.NotNull(post, nameof(post)); _forumPostRepository.Update(post); + + if (updateStatistics) + { + var topic = post.ForumTopic ?? GetTopicById(post.TopicId); + var forum = topic.Forum ?? GetForumById(topic.ForumId); + var customer = post.Customer ?? _customerService.GetCustomerById(post.CustomerId); + + UpdateForumStatistics(forum); + UpdateTopicStatistics(topic); + UpdateCustomerStatistics(customer); + } } public virtual void DeletePost(ForumPost post) @@ -653,11 +680,11 @@ public virtual void DeletePost(ForumPost post) } else { - UpdateForumTopicStats(topic); + UpdateTopicStatistics(topic); } - UpdateForumStats(forum); - UpdateCustomerStats(customer); + UpdateForumStatistics(forum); + UpdateCustomerStatistics(customer); } #endregion diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index 9f26ab3e6c..22706359df 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -128,7 +128,8 @@ public partial interface IForumService /// Updates the forum topic /// /// Forum topic - void UpdateTopic(ForumTopic topic); + /// A value indicating whether to update counter. + void UpdateTopic(ForumTopic topic, bool updateStatistics); /// /// Moves the forum topic @@ -194,7 +195,8 @@ public partial interface IForumService /// Updates the forum post /// /// Forum post - void UpdatePost(ForumPost post); + /// A value indicating whether to update counter. + void UpdatePost(ForumPost post, bool updateStatistics); #endregion diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 96003cf4f5..14faac192d 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -686,7 +686,7 @@ public ActionResult Topic(int id, int page = 1) if (!customer.Deleted && customer.Active && !customer.IsSystemAccount) { topic.Views += 1; - _forumService.UpdateTopic(topic); + _forumService.UpdateTopic(topic, false); } } catch (Exception ex) @@ -956,8 +956,8 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) if (customer.IsForumModerator()) { - model.Published = model.Published; - model.TopicTypeId = model.TopicTypeId; + topic.Published = model.Published; + topic.TopicTypeId = model.TopicTypeId; } topic.Subject = _forumSettings.TopicSubjectMaxLength > 0 && model.Subject.Length > _forumSettings.TopicSubjectMaxLength @@ -980,12 +980,12 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) _forumService.InsertPost(post, false); - topic.NumPosts = 1; + topic.NumPosts = topic.Published ? 1 : 0; topic.LastPostId = post.Id; topic.LastPostCustomerId = post.CustomerId; topic.LastPostTime = post.CreatedOnUtc; - _forumService.UpdateTopic(topic); + _forumService.UpdateTopic(topic, false); // Subscription. if (_forumService.IsCustomerAllowedToSubscribe(customer)) @@ -1112,8 +1112,10 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) { try { + var updateStatistics = false; if (customer.IsForumModerator()) { + updateStatistics = topic.Published != model.Published; topic.Published = model.Published; topic.TopicTypeId = model.TopicTypeId; } @@ -1122,7 +1124,7 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) ? model.Subject.Substring(0, _forumSettings.TopicSubjectMaxLength) : model.Subject; - _forumService.UpdateTopic(topic); + _forumService.UpdateTopic(topic, updateStatistics); var text = _forumSettings.PostMaxLength > 0 && model.Text.Length > _forumSettings.PostMaxLength ? model.Text.Substring(0, _forumSettings.PostMaxLength) @@ -1132,7 +1134,7 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) if (firstPost != null) { firstPost.Text = text; - _forumService.UpdatePost(firstPost); + _forumService.UpdatePost(firstPost, false); } else { @@ -1496,8 +1498,10 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) { try { + var updateStatistics = false; if (customer.IsForumModerator()) { + updateStatistics = post.Published != model.Published; post.Published = model.Published; } @@ -1505,7 +1509,7 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) ? model.Text.Substring(0, _forumSettings.PostMaxLength) : model.Text; - _forumService.UpdatePost(post); + _forumService.UpdatePost(post, updateStatistics); // Subscription. if (_forumService.IsCustomerAllowedToSubscribe(customer)) diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml index ac5022ded4..6ff88f5941 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml @@ -144,7 +144,6 @@ .Route("TopicSlug", new { id = topic.Id, slug = topic.SeName })) } - - - - - + if (Model.PagedList.TotalPages > 1) + { + + + + + + } } else if (Model.Error.HasValue()) { diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml index 7e6cf51acc..6425113e07 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml @@ -139,26 +139,28 @@ \ No newline at end of file From 0b33722bd581eee640345b640ab32aad38953d50 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 29 Sep 2018 04:06:21 +0200 Subject: [PATCH 152/932] Minor code fix --- .../SmartStore.Core/Search/Facets/FacetValue.cs | 4 ++-- .../Hooks/UpdateCustomerFullNameHook.cs | 9 ++++----- .../Templating/Liquid/LiquidTemplateEngine.cs | 13 +++++-------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs b/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs index e3e451cab7..80e54b44af 100644 --- a/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs @@ -100,8 +100,8 @@ public override int GetHashCode() { var combiner = HashCodeCombiner .Start() - .Add(Value.GetHashCode()) - .Add(UpperValue.GetHashCode()); + .Add(Value) + .Add(UpperValue); return combiner.CombinedHash; } diff --git a/src/Libraries/SmartStore.Services/Hooks/UpdateCustomerFullNameHook.cs b/src/Libraries/SmartStore.Services/Hooks/UpdateCustomerFullNameHook.cs index 662659d2dd..ba90b19cc8 100644 --- a/src/Libraries/SmartStore.Services/Hooks/UpdateCustomerFullNameHook.cs +++ b/src/Libraries/SmartStore.Services/Hooks/UpdateCustomerFullNameHook.cs @@ -5,7 +5,6 @@ using SmartStore.Core.Data; using SmartStore.Core.Data.Hooks; using SmartStore.Core.Domain.Customers; -using SmartStore.Utilities; namespace SmartStore.Services.Hooks { @@ -14,10 +13,10 @@ public class UpdateCustomerFullNameHook : DbSaveHook { private static readonly HashSet _candidateProps = new HashSet(new string[] { - TypeHelper.NameOf(x => x.Title), - TypeHelper.NameOf(x => x.Salutation), - TypeHelper.NameOf(x => x.FirstName), - TypeHelper.NameOf(x => x.LastName) + nameof(Customer.Title), + nameof(Customer.Salutation), + nameof(Customer.FirstName), + nameof(Customer.LastName) }); private readonly IComponentContext _ctx; diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplateEngine.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplateEngine.cs index 9f3b03baca..a229d69208 100644 --- a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplateEngine.cs +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplateEngine.cs @@ -1,18 +1,15 @@ using System; using System.IO; using System.Web; -using System.Web.Hosting; using DotLiquid; using DotLiquid.FileSystems; using DotLiquid.NamingConventions; using SmartStore.Core; -using SmartStore.Core.Events; using SmartStore.Core.Infrastructure.DependencyManagement; using SmartStore.Core.IO; using SmartStore.Core.Localization; using SmartStore.Core.Themes; using SmartStore.Services; -using SmartStore.Utilities; namespace SmartStore.Templating.Liquid { @@ -37,11 +34,11 @@ public LiquidTemplateEngine( // Register Value type transformers var allowedMoneyProps = new[] { - TypeHelper.NameOf(x => x.Amount), - TypeHelper.NameOf(x => x.RoundedAmount), - TypeHelper.NameOf(x => x.TruncatedAmount), - TypeHelper.NameOf(x => x.Formatted), - TypeHelper.NameOf(x => x.DecimalDigits) + nameof(Money.Amount), + nameof(Money.RoundedAmount), + nameof(Money.TruncatedAmount), + nameof(Money.Formatted), + nameof(Money.DecimalDigits) }; Template.RegisterSafeType(typeof(Money), allowedMoneyProps, x => x); From efe8fe2b25c5bb91a2a808d52744ec6253eb0514 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 1 Oct 2018 10:54:55 +0200 Subject: [PATCH 153/932] Import: Validate picture parameter modifies file size thus subsequent search for equal images fails --- .../Catalog/Importer/CategoryImporter.cs | 9 ++++----- .../Catalog/Importer/ProductImporter.cs | 11 ++++------- .../Customers/Importer/CustomerImporter.cs | 9 ++++----- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 504c4d91a2..57d484a6b6 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -221,15 +221,14 @@ protected virtual int ProcessPictures( currentPictures.Add(picture); } } - - var size = Size.Empty; - pictureBinary = _pictureService.ValidatePicture(pictureBinary, image.MimeType, out size); + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); if (pictureBinary != null && pictureBinary.Length > 0) { - var picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, size.Width, size.Height, false); - if (picture != null) + //var picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, size.Width, size.Height, false); + var picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false); ; + if (picture != null) { category.PictureId = picture.Id; _categoryRepository.Update(category); diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index d15e4ed20d..b12d3c2d44 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -547,7 +547,6 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu if (pictureBinary != null && pictureBinary.Length > 0) { var currentProductPictures = _productPictureRepository.TableUntracked - .Expand(x => x.Picture) .Expand(x => x.Picture.MediaStorage) .Where(x => x.ProductId == row.Entity.Id) .ToList(); @@ -560,16 +559,14 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu { displayOrder = (currentProductPictures.Any() ? currentProductPictures.Select(x => x.DisplayOrder).Max() : 0); } - - var size = Size.Empty; - pictureBinary = _pictureService.ValidatePicture(pictureBinary, image.MimeType, out size); + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); if (pictureBinary != null && pictureBinary.Length > 0) { - // no equal picture found in sequence - var newPicture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, size.Width, size.Height, false); - if (newPicture != null) + var newPicture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false); ; + + if (newPicture != null) { var mapping = new ProductPicture { diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 15b0cdcf13..62836732ce 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -606,15 +606,14 @@ protected virtual int ProcessAvatars( currentPictures.Add(picture); } } - - var size = Size.Empty; - pictureBinary = _pictureService.ValidatePicture(pictureBinary, image.MimeType, out size); + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); if (pictureBinary != null && pictureBinary.Length > 0) { - var picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, size.Width, size.Height, false); - if (picture != null) + var picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false); + + if (picture != null) { SaveAttribute(row, SystemCustomerAttributeNames.AvatarPictureId, picture.Id); } From 95499debea88deeb3afa30b17f3b330d6a08a501 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 1 Oct 2018 11:15:31 +0200 Subject: [PATCH 154/932] Avoid "No route in the route table matches the supplied values" (rare case of a grouped product) --- .../SmartStore.Web/Controllers/ProductController.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index b48f76ff53..a69302a416 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -160,12 +160,15 @@ public ActionResult ProductDetails(int productId, string attributes, ProductVari { // Find parent grouped product. var parentGroupedProduct = _productService.GetProductById(product.ParentGroupedProductId); - if (parentGroupedProduct == null) return HttpNotFound(); - var routeValues = new RouteValueDictionary(); - routeValues.Add("SeName", parentGroupedProduct.GetSeName()); + var seName = parentGroupedProduct.GetSeName(); + if (seName.IsEmpty()) + return HttpNotFound(); + + var routeValues = new RouteValueDictionary(); + routeValues.Add("SeName", seName); // Add query string parameters. Request.QueryString.AllKeys.Each(x => routeValues.Add(x, Request.QueryString[x])); From 80d70feec9d442c2291b70a965f4e622c73a36dc Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 1 Oct 2018 13:04:53 +0200 Subject: [PATCH 155/932] Avoid exception "Failed to parse email address for variable..." if parsed email address is an empty string --- src/Libraries/SmartStore.Services/Messages/MessageFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs b/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs index 14d42ddb6d..284269020b 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs @@ -187,7 +187,7 @@ private EmailAddress RenderEmailAddress(string email, MessageContext ctx, bool r { parsed = RenderTemplate(email, ctx, required); - if (required || parsed != null) + if (required || parsed.HasValue()) { return parsed.Convert(); } From 141c665f3821a9c19ac7661c9844139a52e3a38a Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 1 Oct 2018 14:37:34 +0200 Subject: [PATCH 156/932] Added CustomerLoggedInEvent --- .../Customers/Events/CustomerLogedInEvent.cs | 17 +++++++++++++++++ .../Events/CustomerRegisteredEvent.cs | 2 +- .../SmartStore.Services.csproj | 1 + .../Controllers/CustomerController.cs | 19 +++++++++++-------- 4 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/Customers/Events/CustomerLogedInEvent.cs diff --git a/src/Libraries/SmartStore.Services/Customers/Events/CustomerLogedInEvent.cs b/src/Libraries/SmartStore.Services/Customers/Events/CustomerLogedInEvent.cs new file mode 100644 index 0000000000..b224692e64 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Customers/Events/CustomerLogedInEvent.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using SmartStore.Core.Domain.Customers; + +namespace SmartStore.Services.Customers +{ + /// + /// An event message, which will be published after customer has logged in + /// + public class CustomerLogedInEvent + { + public Customer Customer + { + get; + set; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Customers/Events/CustomerRegisteredEvent.cs b/src/Libraries/SmartStore.Services/Customers/Events/CustomerRegisteredEvent.cs index 76bc89b1de..920c08fca6 100644 --- a/src/Libraries/SmartStore.Services/Customers/Events/CustomerRegisteredEvent.cs +++ b/src/Libraries/SmartStore.Services/Customers/Events/CustomerRegisteredEvent.cs @@ -4,7 +4,7 @@ namespace SmartStore.Services.Customers { /// - /// An event message, which gets published after customer was registered + /// An event message, which will be published after customer has registered /// public class CustomerRegisteredEvent { diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 54a0760a18..a26437105d 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -211,6 +211,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index 81b9d388e4..21fdd3e78d 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -39,6 +39,7 @@ using SmartStore.Web.Framework.UI; using SmartStore.Web.Models.Common; using SmartStore.Web.Models.Customer; +using SmartStore.Core.Events; namespace SmartStore.Web.Controllers { @@ -82,18 +83,18 @@ public partial class CustomerController : PublicControllerBase private readonly IWebHelper _webHelper; private readonly ICustomerActivityService _customerActivityService; private readonly ProductUrlHelper _productUrlHelper; - - private readonly MediaSettings _mediaSettings; + private readonly MediaSettings _mediaSettings; private readonly LocalizationSettings _localizationSettings; private readonly CaptchaSettings _captchaSettings; private readonly ExternalAuthenticationSettings _externalAuthenticationSettings; private readonly PluginMediator _pluginMediator; + private readonly IEventPublisher _eventPublisher; - #endregion + #endregion - #region Ctor + #region Ctor - public CustomerController( + public CustomerController( ICommonServices services, IAuthenticationService authenticationService, IDateTimeHelper dateTimeHelper, @@ -122,7 +123,7 @@ public CustomerController( MediaSettings mediaSettings, LocalizationSettings localizationSettings, CaptchaSettings captchaSettings, ExternalAuthenticationSettings externalAuthenticationSettings, - PluginMediator pluginMediator) + PluginMediator pluginMediator, IEventPublisher eventPublisher) { _services = services; _authenticationService = authenticationService; @@ -160,13 +161,13 @@ public CustomerController( _webHelper = webHelper; _customerActivityService = customerActivityService; _productUrlHelper = productUrlHelper; - _mediaSettings = mediaSettings; _localizationSettings = localizationSettings; _captchaSettings = captchaSettings; _externalAuthenticationSettings = externalAuthenticationSettings; _pluginMediator = pluginMediator; - } + _eventPublisher = eventPublisher; + } #endregion @@ -485,6 +486,8 @@ public ActionResult Login(LoginModel model, string returnUrl, bool captchaValid) _customerActivityService.InsertActivity("PublicStore.Login", _localizationService.GetResource("ActivityLog.PublicStore.Login"), customer); + _eventPublisher.Publish(new CustomerLogedInEvent { Customer = customer }); + // Redirect home where redirect to referrer would be confusing. if (returnUrl.IsEmpty() || returnUrl.Contains(@"/login?") || returnUrl.Contains(@"/passwordrecoveryconfirm")) { From c9fc0ea5fdeebc7f8d345ce1f1b78f7294d4fe85 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 1 Oct 2018 15:44:51 +0200 Subject: [PATCH 157/932] Updated changelog --- changelog.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 759bbdbfc1..1975b7b336 100644 --- a/changelog.md +++ b/changelog.md @@ -1,8 +1,12 @@ -# Release Notes +# Release Notes ## SmartStore.NET 3.2 ### New Features +* **EmailReminder**: + * Reminders for open shopping carts + * Reminders for product reviews + * Reminders to visit the shop after a long absence * #1144 Enable multi server search index * Made Topic ACL enabled * Implemented paging & filtering for Topic grid @@ -25,12 +29,18 @@ * #1172 Option to display related search terms on search page. * Customer avatar: Letter with colored background if no avatar image was uploaded. * Viveum: Supports payment via "Virtual Account Brands" like PayPal. +* Added options for alternating price display (in badges) ### Improvements * (Perf) Significantly increased query performance for products with a lot of category assignments (> 10). * Debitoor: Partially update customer instead of full update to avoid all fields being overwritten * #1479 Show in messages the delivery time at the time of purchase * #1184 Sort Current shopping carts & Current wishlists by ShoppingCartItem.CreatedOn. +* #1106 BMECat: import & export support for product keywords +* #1499 Add hint to forms indicating that fields with an asterisk (*) +* Added filter for newsletter subscriber export by working language +* Refactored download section +* Enhanced EntityPicker to pick from customers, manufacturers & categories ### Bugfixes * In a multi-store environment, multiple topics with the same system name cannot be resolved reliably. @@ -61,6 +71,9 @@ * #1504 Cart item price calculation wrong if attribute combinations with text types are involved. * #1485 Dropdown list for product sorting does not work with Internet Explorer 11. * #1468 Twitter authentication not working anymore. +* Newsletter subscription didn't work when customer privacy setting DisplayGdprConsentOnForms was turned off +* Fixed social media image detection +* Fixed redirection of bots when several languages were active ## SmartStore.NET 3.1.5 From 680c2175db716108629d3b5c683ecff9e589ba0c Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 1 Oct 2018 15:44:51 +0200 Subject: [PATCH 158/932] Updated changelog --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 1975b7b336..44d18966c1 100644 --- a/changelog.md +++ b/changelog.md @@ -37,7 +37,7 @@ * #1479 Show in messages the delivery time at the time of purchase * #1184 Sort Current shopping carts & Current wishlists by ShoppingCartItem.CreatedOn. * #1106 BMECat: import & export support for product keywords -* #1499 Add hint to forms indicating that fields with an asterisk (*) +* #1499 Added hint to forms indicating that fields with an asterisk (*) * Added filter for newsletter subscriber export by working language * Refactored download section * Enhanced EntityPicker to pick from customers, manufacturers & categories From 9e88ffbf2f57228eda137279312f0df616c6c4c7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 1 Oct 2018 22:00:19 +0200 Subject: [PATCH 159/932] Getting started with voting on forum posts --- .../Catalog/ProductReviewHelpfulness.cs | 9 +- .../Domain/Forums/ForumPost.cs | 12 ++ .../Domain/Forums/ForumPostVote.cs | 25 ++++ .../Domain/Forums/ForumSettings.cs | 13 +- .../SmartStore.Core/SmartStore.Core.csproj | 1 + .../Mapping/Forums/ForumPostVoteMap.cs | 18 +++ .../201810011954195_ForumPostVote.Designer.cs | 29 ++++ .../201810011954195_ForumPostVote.cs | 35 +++++ .../201810011954195_ForumPostVote.resx | 126 ++++++++++++++++++ .../Migrations/MigrationsConfiguration.cs | 14 ++ .../SmartStore.Data/SmartStore.Data.csproj | 8 ++ .../Controllers/BoardsController.cs | 81 ++++++++++- .../Models/Boards/ForumPostModel.cs | 3 + .../Views/Boards/Partials/_ForumPost.cshtml | 13 +- .../SmartStore.Web/Views/Boards/Topic.cshtml | 29 +++- 15 files changed, 401 insertions(+), 15 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Domain/Forums/ForumPostVote.cs create mode 100644 src/Libraries/SmartStore.Data/Mapping/Forums/ForumPostVoteMap.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.resx diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductReviewHelpfulness.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductReviewHelpfulness.cs index e6b612e382..7d9cd545d1 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductReviewHelpfulness.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductReviewHelpfulness.cs @@ -1,25 +1,24 @@ - using SmartStore.Core.Domain.Customers; namespace SmartStore.Core.Domain.Catalog { /// - /// Represents a product review helpfulness + /// Represents a product review helpfulness. /// public partial class ProductReviewHelpfulness : CustomerContent { /// - /// Gets or sets the product review identifier + /// Gets or sets the product review identifier. /// public int ProductReviewId { get; set; } /// - /// A value indicating whether a review a helpful + /// A value indicating whether a review is helpful. /// public bool WasHelpful { get; set; } /// - /// Gets the product + /// Gets the product review. /// public virtual ProductReview ProductReview { get; set; } } diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs index 292615a8d3..e65c80573d 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using SmartStore.Core.Domain.Customers; @@ -9,6 +10,8 @@ namespace SmartStore.Core.Domain.Forums /// public partial class ForumPost : BaseEntity, IAuditable { + private ICollection _forumPostVotes; + /// /// Gets or sets the forum topic identifier /// @@ -55,5 +58,14 @@ public partial class ForumPost : BaseEntity, IAuditable /// Gets the customer /// public virtual Customer Customer { get; set; } + + /// + /// Forum post votes. + /// + public virtual ICollection ForumPostVotes + { + get { return _forumPostVotes ?? (_forumPostVotes = new HashSet()); } + protected set { _forumPostVotes = value; } + } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumPostVote.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumPostVote.cs new file mode 100644 index 0000000000..1799d22dd3 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumPostVote.cs @@ -0,0 +1,25 @@ +using SmartStore.Core.Domain.Customers; + +namespace SmartStore.Core.Domain.Forums +{ + /// + /// Represents a vote for a forum post. + /// + public partial class ForumPostVote : CustomerContent + { + /// + /// Forum post identifier. + /// + public int ForumPostId { get; set; } + + /// + /// A value indicating whether the customer voted for or against a forum post. + /// + public bool Vote { get; set; } + + /// + /// Forum post entity. + /// + public virtual ForumPost ForumPost { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs index ed484dbb4f..86a2b5e423 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs @@ -27,7 +27,8 @@ public ForumSettings() HomePageActiveDiscussionsTopicCount = 5; ActiveDiscussionsPageTopicCount = 50; ActiveDiscussionsFeedCount = 25; - } + AllowCustomersToVoteOnPosts = true; + } /// /// Gets or sets a value indicating whether forums are enabled @@ -64,6 +65,16 @@ public ForumSettings() /// public bool AllowCustomersToDeletePosts { get; set; } + /// + /// Gets or sets a value indicating whether customer can vote on posts + /// + public bool AllowCustomersToVoteOnPosts { get; set; } + + /// + /// Gets or sets a value indicating whether guests are allowed to vote on posts + /// + public bool AllowGuestsToVoteOnPosts { get; set; } + /// /// Gets or sets maximum length of topic subject /// diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index 2a4eafc78f..ddb7fbe127 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -235,6 +235,7 @@ + diff --git a/src/Libraries/SmartStore.Data/Mapping/Forums/ForumPostVoteMap.cs b/src/Libraries/SmartStore.Data/Mapping/Forums/ForumPostVoteMap.cs new file mode 100644 index 0000000000..40d9e9d8bb --- /dev/null +++ b/src/Libraries/SmartStore.Data/Mapping/Forums/ForumPostVoteMap.cs @@ -0,0 +1,18 @@ +using System.Data.Entity.ModelConfiguration; +using SmartStore.Core.Domain.Forums; + +namespace SmartStore.Data.Mapping.Forums +{ + public partial class ForumPostVoteMap : EntityTypeConfiguration + { + public ForumPostVoteMap() + { + ToTable("ForumPostVote"); + + HasRequired(fpl => fpl.ForumPost) + .WithMany(fp => fp.ForumPostVotes) + .HasForeignKey(fpl => fpl.ForumPostId) + .WillCascadeOnDelete(true); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.Designer.cs new file mode 100644 index 0000000000..df1f55332e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class ForumPostVote : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ForumPostVote)); + + string IMigrationMetadata.Id + { + get { return "201810011954195_ForumPostVote"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.cs b/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.cs new file mode 100644 index 0000000000..843ad1b291 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.cs @@ -0,0 +1,35 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class ForumPostVote : DbMigration + { + public override void Up() + { + CreateTable( + "dbo.ForumPostVote", + c => new + { + Id = c.Int(nullable: false), + ForumPostId = c.Int(nullable: false), + Vote = c.Boolean(nullable: false), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.CustomerContent", t => t.Id) + .ForeignKey("dbo.Forums_Post", t => t.ForumPostId, cascadeDelete: true) + .Index(t => t.Id) + .Index(t => t.ForumPostId); + + } + + public override void Down() + { + DropForeignKey("dbo.ForumPostVote", "ForumPostId", "dbo.Forums_Post"); + DropForeignKey("dbo.ForumPostVote", "Id", "dbo.CustomerContent"); + DropIndex("dbo.ForumPostVote", new[] { "ForumPostId" }); + DropIndex("dbo.ForumPostVote", new[] { "Id" }); + DropTable("dbo.ForumPostVote"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.resx b/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.resx new file mode 100644 index 0000000000..1363d329af --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index b656f275f9..b949a4779a 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -527,6 +527,20 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "* Input elements with asterisk are required and have to be filled out.", "* Eingabefelder mit Sternchen sind Pflichfelder und müssen ausgefüllt werden."); + builder.AddOrUpdate("Forum.Post.Vote.OnlyRegistered", + "Only registered users can vote for posts.", + "Nur registrierte Benutzer können Beiträge bewerten."); + + builder.AddOrUpdate("Forum.Post.Vote.OwnPostNotAllowed", + "You cannot vote for your own post.", + "Sie können nicht Ihren eigenen Beitrag bewerten."); + + builder.AddOrUpdate("Forum.Post.Vote.SuccessfullyVoted", + "Thank you for your vote.", + "Danke für Ihre Bewertung."); + + builder.AddOrUpdate("Common.LikeIt", "I like it", "Gefällt mir"); + builder.AddOrUpdate("Common.DoNotLikeIt", "I do not like it anymore", "Gefällt mir nicht mehr"); } } } diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index cc9da1dd58..b0d59e0d11 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -147,6 +147,7 @@ + @@ -627,6 +628,10 @@ 201809261026134_ForumGroupAcl.cs + + + 201810011954195_ForumPostVote.cs + @@ -1127,6 +1132,9 @@ 201809261026134_ForumGroupAcl.cs + + 201810011954195_ForumPostVote.cs + diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 51bb2e5537..7eac114bea 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -45,6 +45,7 @@ public partial class BoardsController : PublicControllerBase private readonly IGenericAttributeService _genericAttributeService; private readonly IStoreMappingService _storeMappingService; private readonly IAclService _aclService; + private readonly ICustomerContentService _customerContentService; private readonly ForumSettings _forumSettings; private readonly ForumSearchSettings _searchSettings; private readonly CustomerSettings _customerSettings; @@ -63,6 +64,7 @@ public BoardsController( IGenericAttributeService genericAttributeService, IStoreMappingService storeMappingService, IAclService aclService, + ICustomerContentService customerContentService, ForumSettings forumSettings, ForumSearchSettings searchSettings, CustomerSettings customerSettings, @@ -80,6 +82,7 @@ public BoardsController( _genericAttributeService = genericAttributeService; _storeMappingService = storeMappingService; _aclService = aclService; + _customerContentService = customerContentService; _forumSettings = forumSettings; _searchSettings = searchSettings; _customerSettings = customerSettings; @@ -696,7 +699,7 @@ public ActionResult Topic(int id, int page = 1) foreach (var post in posts) { - var forumPostModel = new ForumPostModel + var postModel = new ForumPostModel { Id = post.Id, Published = post.Published, @@ -717,26 +720,32 @@ public ActionResult Topic(int id, int page = 1) AllowPrivateMessages = _forumSettings.AllowPrivateMessages, SignaturesEnabled = _forumSettings.SignaturesEnabled, FormattedSignature = post.Customer.GetAttribute(SystemCustomerAttributeNames.Signature).FormatForumSignatureText(), + AllowVoting = _forumSettings.AllowCustomersToVoteOnPosts && post.CustomerId != customer.Id }; - forumPostModel.PostCreatedOnStr = _forumSettings.RelativeDateTimeFormattingEnabled + if (postModel.AllowVoting) + { + postModel.Vote = post.ForumPostVotes.FirstOrDefault(x => x.CustomerId == customer.Id)?.Vote ?? false; + } + + postModel.PostCreatedOnStr = _forumSettings.RelativeDateTimeFormattingEnabled ? post.CreatedOnUtc.RelativeFormat(true, "f") : _dateTimeHelper.ConvertToUserTime(post.CreatedOnUtc, DateTimeKind.Utc).ToString("f"); - forumPostModel.Avatar = post.Customer.ToAvatarModel(_genericAttributeService, _pictureService, _customerSettings, _mediaSettings, Url, forumPostModel.CustomerName, true); + postModel.Avatar = post.Customer.ToAvatarModel(_genericAttributeService, _pictureService, _customerSettings, _mediaSettings, Url, postModel.CustomerName, true); // Location. - forumPostModel.ShowCustomersLocation = _customerSettings.ShowCustomersLocation; + postModel.ShowCustomersLocation = _customerSettings.ShowCustomersLocation; if (_customerSettings.ShowCustomersLocation) { var countryId = post.Customer.GetAttribute(SystemCustomerAttributeNames.CountryId); var country = _countryService.GetCountryById(countryId); - forumPostModel.CustomerLocation = country != null ? country.GetLocalized(x => x.Name) : string.Empty; + postModel.CustomerLocation = country != null ? country.GetLocalized(x => x.Name) : string.Empty; } // Page number is needed for creating post link in _ForumPost partial view. - forumPostModel.CurrentTopicPage = page; - model.ForumPostModels.Add(forumPostModel); + postModel.CurrentTopicPage = page; + model.ForumPostModels.Add(postModel); } CreateForumBreadcrumb(topic: topic); @@ -1605,6 +1614,64 @@ public ActionResult PostDelete(int id) } } + [HttpPost] + public ActionResult PostVote(int id, bool vote) + { + if (!_forumSettings.ForumsEnabled || !_forumSettings.AllowCustomersToVoteOnPosts) + { + return HttpNotFound(); + } + + var customer = Services.WorkContext.CurrentCustomer; + var post = _forumService.GetPostById(id); + + if (post == null || !_storeMappingService.Authorize(post.ForumTopic.Forum.ForumGroup) || !_aclService.Authorize(post.ForumTopic.Forum.ForumGroup)) + { + return HttpNotFound(); + } + + if (!_forumSettings.AllowGuestsToVoteOnPosts && customer.IsGuest()) + { + return Json(new { success = false, message = T("Forum.Post.Vote.OnlyRegistered").Text }); + } + + // Do not allow to vote for own posts. + if (post.CustomerId == customer.Id) + { + return Json(new { success = false, message = T("Forum.Post.Vote.OwnPostNotAllowed").Text }); + } + + var voteEntity = post.ForumPostVotes.FirstOrDefault(x => x.CustomerId == customer.Id); + if (vote) + { + if (voteEntity == null) + { + voteEntity = new ForumPostVote + { + ForumPostId = post.Id, + Vote = true, + CustomerId = customer.Id, + IpAddress = Services.WebHelper.GetCurrentIpAddress() + }; + _customerContentService.InsertCustomerContent(voteEntity); + } + else + { + voteEntity.Vote = true; + _customerContentService.UpdateCustomerContent(voteEntity); + } + } + else + { + if (voteEntity != null) + { + _customerContentService.DeleteCustomerContent(voteEntity); + } + } + + return Json(new { success = true, message = T("Forum.Post.Vote.SuccessfullyVoted").Text }); + } + #endregion #region Search diff --git a/src/Presentation/SmartStore.Web/Models/Boards/ForumPostModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/ForumPostModel.cs index b7c7806ab8..57ba6d5db5 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/ForumPostModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/ForumPostModel.cs @@ -39,6 +39,9 @@ public partial class ForumPostModel : EntityModelBase public int CurrentTopicPage { get; set; } + public bool AllowVoting { get; set; } + public bool Vote { get; set; } + public CustomerAvatarModel Avatar { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml index b271913bc1..411d957ad8 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml @@ -2,7 +2,7 @@ @model ForumPostModel @Html.Raw("".FormatInvariant(Model.Id)) -
    +
    @if (Model.CustomerId > 0) { @@ -114,6 +114,17 @@ @T("Forum.QuotePost") + @if (Model.AllowVoting) + { + + + }
    diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml index ab9b4acda1..a670fe2f87 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml @@ -92,4 +92,31 @@ @pager -} \ No newline at end of file +} + + From 7c836ab2659f1740635d5a24794355f378bb4579 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 2 Oct 2018 02:51:21 +0200 Subject: [PATCH 160/932] Added object-fit css util classes --- src/Presentation/SmartStore.Web/Content/shared/_utils.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Presentation/SmartStore.Web/Content/shared/_utils.scss b/src/Presentation/SmartStore.Web/Content/shared/_utils.scss index dc11f76300..fe8e282617 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_utils.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_utils.scss @@ -183,6 +183,11 @@ height: auto !important; } +.fit-fill { object-fit: fill; } +.fit-contain { object-fit: contain; } +.fit-cover { object-fit: cover; } +.fit-scale-down { object-fit: scale-down; } +.fit-none { object-fit: none; } // // Bootstrap overrides and tweaks From b9307cafa3ea114be85f9fd178e8701e88d01504 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 2 Oct 2018 10:45:49 +0200 Subject: [PATCH 161/932] Fixes region cannot be selected in checkout when entering a billing or shipping address --- .../Shared/Partials/_CreateOrUpdateAddress.cshtml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Partials/_CreateOrUpdateAddress.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Partials/_CreateOrUpdateAddress.cshtml index f5ac141f36..ec96afef12 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Partials/_CreateOrUpdateAddress.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Partials/_CreateOrUpdateAddress.cshtml @@ -81,17 +81,18 @@ @if (Model.CountryEnabled) { + var fieldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix.NullEmpty() ?? "Address";
    @Html.LabelFor(model => model.CountryId, new { @class = "col-{0}-3 col-form-label{1}".FormatInvariant(breakpoint, Model.CountryRequired ? " required" : "") })
    @Html.DropDownList("CountryId", Model.AvailableCountries, - new - { - @class = "form-control country-input country-selector", - data_region_control_selector = "#Address_StateProvinceId", - data_states_ajax_url = Url.Action("GetStatesByCountryId", "Country"), - data_addEmptyStateIfRequired = "true" - }) + new + { + @class = "form-control country-input country-selector", + data_region_control_selector = "#{0}_StateProvinceId".FormatInvariant(fieldPrefix), + data_states_ajax_url = Url.Action("GetStatesByCountryId", "Country"), + data_addEmptyStateIfRequired = "true" + }) @Html.ValidationMessageFor(model => model.CountryId)
    From 4743fd650b22ccd28381c93c0b1eded74c44a2f4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 2 Oct 2018 10:54:10 +0200 Subject: [PATCH 162/932] Updated change log --- changelog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 44d18966c1..d79f588e5b 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -# Release Notes +# Release Notes ## SmartStore.NET 3.2 @@ -74,6 +74,7 @@ * Newsletter subscription didn't work when customer privacy setting DisplayGdprConsentOnForms was turned off * Fixed social media image detection * Fixed redirection of bots when several languages were active +* Region cannot be selected in checkout when entering a billing or shipping address ## SmartStore.NET 3.1.5 From 249008882e604d24b6685e92a2d5bd2d7739621c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 2 Oct 2018 12:41:28 +0200 Subject: [PATCH 163/932] More on forum post voting --- .../Migrations/MigrationsConfiguration.cs | 13 +++++++++++ .../Models/Settings/ForumSettingsModel.cs | 10 +++++++-- .../Administration/Views/Setting/Forum.cshtml | 18 +++++++++++++++ .../Controllers/BoardsController.cs | 22 +++++++++++++++++-- .../Models/Boards/ForumPostModel.cs | 1 + .../Themes/Flex/Content/_forum.scss | 12 +--------- .../Views/Boards/Partials/_ForumPost.cshtml | 13 ++++++++--- .../SmartStore.Web/Views/Boards/Topic.cshtml | 12 ++++++++-- 8 files changed, 81 insertions(+), 20 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index b949a4779a..980c0bb4fa 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -539,8 +539,21 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Thank you for your vote.", "Danke für Ihre Bewertung."); + builder.AddOrUpdate("Common.Liked", "Liked", "Gefällt"); builder.AddOrUpdate("Common.LikeIt", "I like it", "Gefällt mir"); builder.AddOrUpdate("Common.DoNotLikeIt", "I do not like it anymore", "Gefällt mir nicht mehr"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Forums.AllowCustomersToVoteOnPosts", + "Allow customers to vote on posts", + "Benutzer können Beiträge bewerten", + "Specifies whether customers can vote on posts.", + "Legt fest, ob Benutzer Beiträge bewerten können."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Forums.AllowGuestsToVoteOnPosts", + "Allow guests to vote on posts", + "Gäste können Beiträge bewerten", + "Specifies whether guests can vote on posts.", + "Legt fest, ob Gäste Beiträge bewerten können."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs index 455f58204d..cb1a9ec8ab 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ForumSettingsModel.cs @@ -35,11 +35,17 @@ public class ForumSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.PostsPageSize")] public int PostsPageSize { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.SearchResultsPageSize")] + public int SearchResultsPageSize { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.AllowSorting")] public bool AllowSorting { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.SearchResultsPageSize")] - public int SearchResultsPageSize { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.AllowCustomersToVoteOnPosts")] + public bool AllowCustomersToVoteOnPosts { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.AllowGuestsToVoteOnPosts")] + public bool AllowGuestsToVoteOnPosts { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.Forums.ForumEditor")] public EditorType ForumEditor { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml index 85efaac0ef..0d1f79d40b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml @@ -106,6 +106,24 @@ @Html.ValidationMessageFor(model => model.AllowCustomersToManageSubscriptions) +
    + + + + + + + From 0b52c83f529e14f8e1139f519bf2a368c23616c2 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 5 Oct 2018 20:42:36 +0200 Subject: [PATCH 175/932] Added wow script to SmartStore.Web --- .../SmartStore.Web/Content/vendors/wow/wow.js | 511 ++++++++++++++++++ .../SmartStore.Web/SmartStore.Web.csproj | 1 + 2 files changed, 512 insertions(+) create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/wow/wow.js diff --git a/src/Presentation/SmartStore.Web/Content/vendors/wow/wow.js b/src/Presentation/SmartStore.Web/Content/vendors/wow/wow.js new file mode 100644 index 0000000000..eb0cbb090a --- /dev/null +++ b/src/Presentation/SmartStore.Web/Content/vendors/wow/wow.js @@ -0,0 +1,511 @@ +(function() { + var MutationObserver, Util, WeakMap, getComputedStyle, getComputedStyleRX, + bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + Util = (function() { + function Util() {} + + Util.prototype.extend = function(custom, defaults) { + var key, value; + for (key in defaults) { + value = defaults[key]; + if (custom[key] == null) { + custom[key] = value; + } + } + return custom; + }; + + Util.prototype.isMobile = function(agent) { + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(agent); + }; + + Util.prototype.createEvent = function(event, bubble, cancel, detail) { + var customEvent; + if (bubble == null) { + bubble = false; + } + if (cancel == null) { + cancel = false; + } + if (detail == null) { + detail = null; + } + if (document.createEvent != null) { + customEvent = document.createEvent('CustomEvent'); + customEvent.initCustomEvent(event, bubble, cancel, detail); + } else if (document.createEventObject != null) { + customEvent = document.createEventObject(); + customEvent.eventType = event; + } else { + customEvent.eventName = event; + } + return customEvent; + }; + + Util.prototype.emitEvent = function(elem, event) { + if (elem.dispatchEvent != null) { + return elem.dispatchEvent(event); + } else if (event in (elem != null)) { + return elem[event](); + } else if (("on" + event) in (elem != null)) { + return elem["on" + event](); + } + }; + + Util.prototype.addEvent = function(elem, event, fn) { + if (elem.addEventListener != null) { + return elem.addEventListener(event, fn, false); + } else if (elem.attachEvent != null) { + return elem.attachEvent("on" + event, fn); + } else { + return elem[event] = fn; + } + }; + + Util.prototype.removeEvent = function(elem, event, fn) { + if (elem.removeEventListener != null) { + return elem.removeEventListener(event, fn, false); + } else if (elem.detachEvent != null) { + return elem.detachEvent("on" + event, fn); + } else { + return delete elem[event]; + } + }; + + Util.prototype.innerHeight = function() { + if ('innerHeight' in window) { + return window.innerHeight; + } else { + return document.documentElement.clientHeight; + } + }; + + return Util; + + })(); + + WeakMap = this.WeakMap || this.MozWeakMap || (WeakMap = (function() { + function WeakMap() { + this.keys = []; + this.values = []; + } + + WeakMap.prototype.get = function(key) { + var i, item, j, len, ref; + ref = this.keys; + for (i = j = 0, len = ref.length; j < len; i = ++j) { + item = ref[i]; + if (item === key) { + return this.values[i]; + } + } + }; + + WeakMap.prototype.set = function(key, value) { + var i, item, j, len, ref; + ref = this.keys; + for (i = j = 0, len = ref.length; j < len; i = ++j) { + item = ref[i]; + if (item === key) { + this.values[i] = value; + return; + } + } + this.keys.push(key); + return this.values.push(value); + }; + + return WeakMap; + + })()); + + MutationObserver = this.MutationObserver || this.WebkitMutationObserver || this.MozMutationObserver || (MutationObserver = (function() { + function MutationObserver() { + if (typeof console !== "undefined" && console !== null) { + console.warn('MutationObserver is not supported by your browser.'); + } + if (typeof console !== "undefined" && console !== null) { + console.warn('WOW.js cannot detect dom mutations, please call .sync() after loading new content.'); + } + } + + MutationObserver.notSupported = true; + + MutationObserver.prototype.observe = function() {}; + + return MutationObserver; + + })()); + + getComputedStyle = this.getComputedStyle || function(el, pseudo) { + this.getPropertyValue = function(prop) { + var ref; + if (prop === 'float') { + prop = 'styleFloat'; + } + if (getComputedStyleRX.test(prop)) { + prop.replace(getComputedStyleRX, function(_, _char) { + return _char.toUpperCase(); + }); + } + return ((ref = el.currentStyle) != null ? ref[prop] : void 0) || null; + }; + return this; + }; + + getComputedStyleRX = /(\-([a-z]){1})/g; + + this.WOW = (function() { + WOW.prototype.defaults = { + boxClass: 'wow', + animateClass: 'animated', + offset: 0, + mobile: true, + live: true, + callback: null + }; + + function WOW(options) { + if (options == null) { + options = {}; + } + this.scrollCallback = bind(this.scrollCallback, this); + this.scrollHandler = bind(this.scrollHandler, this); + this.resetAnimation = bind(this.resetAnimation, this); + this.start = bind(this.start, this); + this.scrolled = true; + this.config = this.util().extend(options, this.defaults); + this.animationNameCache = new WeakMap(); + this.wowEvent = this.util().createEvent(this.config.boxClass); + } + + WOW.prototype.init = function() { + var ref; + this.element = window.document.documentElement; + if ((ref = document.readyState) === "interactive" || ref === "complete") { + this.start(); + } else { + this.util().addEvent(document, 'DOMContentLoaded', this.start); + } + return this.finished = []; + }; + + WOW.prototype.start = function() { + var box, j, len, ref; + this.stopped = false; + this.boxes = (function() { + var j, len, ref, results; + ref = this.element.querySelectorAll("." + this.config.boxClass); + results = []; + for (j = 0, len = ref.length; j < len; j++) { + box = ref[j]; + results.push(box); + } + return results; + }).call(this); + this.all = (function() { + var j, len, ref, results; + ref = this.boxes; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + box = ref[j]; + results.push(box); + } + return results; + }).call(this); + if (this.boxes.length) { + if (this.disabled()) { + this.resetStyle(); + } else { + ref = this.boxes; + for (j = 0, len = ref.length; j < len; j++) { + box = ref[j]; + this.applyStyle(box, true); + } + } + } + if (!this.disabled()) { + this.util().addEvent(window, 'scroll', this.scrollHandler); + this.util().addEvent(window, 'resize', this.scrollHandler); + this.interval = setInterval(this.scrollCallback, 50); + } + if (this.config.live) { + return new MutationObserver((function(_this) { + return function(records) { + var k, len1, node, record, results; + results = []; + for (k = 0, len1 = records.length; k < len1; k++) { + record = records[k]; + results.push((function() { + var l, len2, ref1, results1; + ref1 = record.addedNodes || []; + results1 = []; + for (l = 0, len2 = ref1.length; l < len2; l++) { + node = ref1[l]; + results1.push(this.doSync(node)); + } + return results1; + }).call(_this)); + } + return results; + }; + })(this)).observe(document.body, { + childList: true, + subtree: true + }); + } + }; + + WOW.prototype.stop = function() { + this.stopped = true; + this.util().removeEvent(window, 'scroll', this.scrollHandler); + this.util().removeEvent(window, 'resize', this.scrollHandler); + if (this.interval != null) { + return clearInterval(this.interval); + } + }; + + WOW.prototype.sync = function(element) { + if (MutationObserver.notSupported) { + return this.doSync(this.element); + } + }; + + WOW.prototype.doSync = function(element) { + var box, j, len, ref, results; + if (element == null) { + element = this.element; + } + if (element.nodeType !== 1) { + return; + } + element = element.parentNode || element; + ref = element.querySelectorAll("." + this.config.boxClass); + results = []; + for (j = 0, len = ref.length; j < len; j++) { + box = ref[j]; + if (indexOf.call(this.all, box) < 0) { + this.boxes.push(box); + this.all.push(box); + if (this.stopped || this.disabled()) { + this.resetStyle(); + } else { + this.applyStyle(box, true); + } + results.push(this.scrolled = true); + } else { + results.push(void 0); + } + } + return results; + }; + + WOW.prototype.show = function(box) { + this.applyStyle(box); + box.className = box.className + " " + this.config.animateClass; + if (this.config.callback != null) { + this.config.callback(box); + } + this.util().emitEvent(box, this.wowEvent); + this.util().addEvent(box, 'animationend', this.resetAnimation); + this.util().addEvent(box, 'oanimationend', this.resetAnimation); + this.util().addEvent(box, 'webkitAnimationEnd', this.resetAnimation); + this.util().addEvent(box, 'MSAnimationEnd', this.resetAnimation); + return box; + }; + + WOW.prototype.applyStyle = function(box, hidden) { + var delay, duration, iteration; + duration = box.getAttribute('data-wow-duration'); + delay = box.getAttribute('data-wow-delay'); + iteration = box.getAttribute('data-wow-iteration'); + return this.animate((function(_this) { + return function() { + return _this.customStyle(box, hidden, duration, delay, iteration); + }; + })(this)); + }; + + WOW.prototype.animate = (function() { + if ('requestAnimationFrame' in window) { + return function(callback) { + return window.requestAnimationFrame(callback); + }; + } else { + return function(callback) { + return callback(); + }; + } + })(); + + WOW.prototype.resetStyle = function() { + var box, j, len, ref, results; + ref = this.boxes; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + box = ref[j]; + results.push(box.style.visibility = 'visible'); + } + return results; + }; + + WOW.prototype.resetAnimation = function(event) { + var target; + if (event.type.toLowerCase().indexOf('animationend') >= 0) { + target = event.target || event.srcElement; + return target.className = target.className.replace(this.config.animateClass, '').trim(); + } + }; + + WOW.prototype.customStyle = function(box, hidden, duration, delay, iteration) { + if (hidden) { + this.cacheAnimationName(box); + } + box.style.visibility = hidden ? 'hidden' : 'visible'; + if (duration) { + this.vendorSet(box.style, { + animationDuration: duration + }); + } + if (delay) { + this.vendorSet(box.style, { + animationDelay: delay + }); + } + if (iteration) { + this.vendorSet(box.style, { + animationIterationCount: iteration + }); + } + this.vendorSet(box.style, { + animationName: hidden ? 'none' : this.cachedAnimationName(box) + }); + return box; + }; + + WOW.prototype.vendors = ["moz", "webkit"]; + + WOW.prototype.vendorSet = function(elem, properties) { + var name, results, value, vendor; + results = []; + for (name in properties) { + value = properties[name]; + elem["" + name] = value; + results.push((function() { + var j, len, ref, results1; + ref = this.vendors; + results1 = []; + for (j = 0, len = ref.length; j < len; j++) { + vendor = ref[j]; + results1.push(elem["" + vendor + (name.charAt(0).toUpperCase()) + (name.substr(1))] = value); + } + return results1; + }).call(this)); + } + return results; + }; + + WOW.prototype.vendorCSS = function(elem, property) { + var j, len, ref, result, style, vendor; + style = getComputedStyle(elem); + result = style.getPropertyCSSValue(property); + ref = this.vendors; + for (j = 0, len = ref.length; j < len; j++) { + vendor = ref[j]; + result = result || style.getPropertyCSSValue("-" + vendor + "-" + property); + } + return result; + }; + + WOW.prototype.animationName = function(box) { + var animationName; + try { + animationName = this.vendorCSS(box, 'animation-name').cssText; + } catch (_error) { + animationName = getComputedStyle(box).getPropertyValue('animation-name'); + } + if (animationName === 'none') { + return ''; + } else { + return animationName; + } + }; + + WOW.prototype.cacheAnimationName = function(box) { + return this.animationNameCache.set(box, this.animationName(box)); + }; + + WOW.prototype.cachedAnimationName = function(box) { + return this.animationNameCache.get(box); + }; + + WOW.prototype.scrollHandler = function() { + return this.scrolled = true; + }; + + WOW.prototype.scrollCallback = function() { + var box; + if (this.scrolled) { + this.scrolled = false; + this.boxes = (function() { + var j, len, ref, results; + ref = this.boxes; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + box = ref[j]; + if (!(box)) { + continue; + } + if (this.isVisible(box)) { + this.show(box); + continue; + } + results.push(box); + } + return results; + }).call(this); + if (!(this.boxes.length || this.config.live)) { + return this.stop(); + } + } + }; + + WOW.prototype.offsetTop = function(element) { + var top; + while (element.offsetTop === void 0) { + element = element.parentNode; + } + top = element.offsetTop; + while (element = element.offsetParent) { + top += element.offsetTop; + } + return top; + }; + + WOW.prototype.isVisible = function(box) { + var bottom, offset, top, viewBottom, viewTop; + offset = box.getAttribute('data-wow-offset') || this.config.offset; + viewTop = window.pageYOffset; + viewBottom = viewTop + Math.min(this.element.clientHeight, this.util().innerHeight()) - offset; + top = this.offsetTop(box); + bottom = top + box.clientHeight; + return top <= viewBottom && bottom >= viewTop; + }; + + WOW.prototype.util = function() { + return this._util != null ? this._util : this._util = new Util(); + }; + + WOW.prototype.disabled = function() { + return !this.config.mobile && this.util().isMobile(navigator.userAgent); + }; + + return WOW; + + })(); + +}).call(this); + +//# sourceMappingURL=wow.js.map \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index 2c5e2a69a4..92d4ab19c7 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -1162,6 +1162,7 @@ + From dfd33f8c779323eece23e1af32daf040e790f78c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 5 Oct 2018 21:32:32 +0200 Subject: [PATCH 176/932] Resolves #1510 Breadcrumb of an associated product should include the grouped product if it has no assigned categories --- changelog.md | 1 + .../Controllers/ProductController.cs | 46 +++++++++---------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/changelog.md b/changelog.md index 58e3aa514c..c24fd6d9c4 100644 --- a/changelog.md +++ b/changelog.md @@ -43,6 +43,7 @@ * Added filter for newsletter subscriber export by working language * Refactored download section * Enhanced EntityPicker to pick from customers, manufacturers & categories +* #1510 Breadcrumb of an associated product should include the grouped product if it has no assigned categories. ### Bugfixes * In a multi-store environment, multiple topics with the same system name cannot be resolved reliably. diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index a69302a416..e2e77bbcde 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -10,12 +10,12 @@ using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Seo; +using SmartStore.Core.Domain.Tax; using SmartStore.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Catalog.Modelling; using SmartStore.Services.Common; using SmartStore.Services.Customers; -using SmartStore.Services.Directory; using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Orders; @@ -29,25 +29,19 @@ using SmartStore.Web.Framework.UI; using SmartStore.Web.Infrastructure.Cache; using SmartStore.Web.Models.Catalog; -using SmartStore.Core.Domain.Tax; namespace SmartStore.Web.Controllers { - public partial class ProductController : PublicControllerBase + public partial class ProductController : PublicControllerBase { private readonly ICommonServices _services; private readonly IManufacturerService _manufacturerService; private readonly IProductService _productService; private readonly IProductAttributeService _productAttributeService; - private readonly IProductAttributeParser _productAttributeParser; private readonly ITaxService _taxService; - private readonly ICurrencyService _currencyService; private readonly IPictureService _pictureService; - private readonly IPriceCalculationService _priceCalculationService; - private readonly IPriceFormatter _priceFormatter; private readonly ICustomerContentService _customerContentService; private readonly ICustomerService _customerService; - private readonly IShoppingCartService _shoppingCartService; private readonly IRecentlyViewedProductsService _recentlyViewedProductsService; private readonly IProductTagService _productTagService; private readonly IOrderReportService _orderReportService; @@ -61,8 +55,6 @@ public partial class ProductController : PublicControllerBase private readonly LocalizationSettings _localizationSettings; private readonly CaptchaSettings _captchaSettings; private readonly CatalogHelper _helper; - private readonly IDownloadService _downloadService; - private readonly ILocalizationService _localizationService; private readonly IBreadcrumb _breadcrumb; private readonly Lazy _privacySettings; private readonly Lazy _taxSettings; @@ -72,15 +64,10 @@ public ProductController( IManufacturerService manufacturerService, IProductService productService, IProductAttributeService productAttributeService, - IProductAttributeParser productAttributeParser, ITaxService taxService, - ICurrencyService currencyService, IPictureService pictureService, - IPriceCalculationService priceCalculationService, - IPriceFormatter priceFormatter, ICustomerContentService customerContentService, ICustomerService customerService, - IShoppingCartService shoppingCartService, IRecentlyViewedProductsService recentlyViewedProductsService, IProductTagService productTagService, IOrderReportService orderReportService, @@ -94,8 +81,6 @@ public ProductController( LocalizationSettings localizationSettings, CaptchaSettings captchaSettings, CatalogHelper helper, - IDownloadService downloadService, - ILocalizationService localizationService, IBreadcrumb breadcrumb, Lazy privacySettings, Lazy taxSettings) @@ -104,15 +89,10 @@ public ProductController( _manufacturerService = manufacturerService; _productService = productService; _productAttributeService = productAttributeService; - _productAttributeParser = productAttributeParser; _taxService = taxService; - _currencyService = currencyService; _pictureService = pictureService; - _priceCalculationService = priceCalculationService; - _priceFormatter = priceFormatter; _customerContentService = customerContentService; _customerService = customerService; - _shoppingCartService = shoppingCartService; _recentlyViewedProductsService = recentlyViewedProductsService; _productTagService = productTagService; _orderReportService = orderReportService; @@ -126,8 +106,6 @@ public ProductController( _localizationSettings = localizationSettings; _captchaSettings = captchaSettings; _helper = helper; - _downloadService = downloadService; - _localizationService = localizationService; _breadcrumb = breadcrumb; _privacySettings = privacySettings; _taxSettings = taxSettings; @@ -193,12 +171,30 @@ public ActionResult ProductDetails(int productId, string attributes, ProductVari if (_catalogSettings.CategoryBreadcrumbEnabled) { _helper.GetCategoryBreadCrumb(0, productId).Select(x => x.Value).Each(x => _breadcrumb.Track(x)); + + // Add parent product if product has no category assigned. + var hasTrail = _breadcrumb.Trail?.Any() ?? false; + if (!hasTrail) + { + var parentGroupedProduct = _productService.GetProductById(product.ParentGroupedProductId); + if (parentGroupedProduct != null) + { + _breadcrumb.Track(new MenuItem + { + Text = parentGroupedProduct.GetLocalized(x => x.Name), + Rtl = model.Name.CurrentLanguage.Rtl, + EntityId = parentGroupedProduct.Id, + Url = Url.RouteUrl("Product", new { SeName = parentGroupedProduct.GetSeName() }) + }); + } + } + _breadcrumb.Track(new MenuItem { Text = model.Name, Rtl = model.Name.CurrentLanguage.Rtl, EntityId = product.Id, - Url = Url.RouteUrl("Product", new { productId = product.Id, SeName = model.SeName }) + Url = Url.RouteUrl("Product", new { model.SeName }) }); } From 14a1c4b4865613a9e30335c131ba5b5399a940be Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 6 Oct 2018 03:50:19 +0200 Subject: [PATCH 177/932] Minor theming stuff --- .../Views/Shared/Partials/Navbar.cshtml | 2 +- src/Presentation/SmartStore.Web/Global.asax | 2 +- .../SmartStore.Web/SmartStore.Web.csproj | 1 + .../Themes/Flex/Content/_layout.scss | 18 +++++++++++++----- .../Views/Shared/Layouts/_Layout.Bare.cshtml | 7 +++++++ src/Presentation/SmartStore.Web/Web.config | 2 +- 6 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.Bare.cshtml diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Navbar.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Navbar.cshtml index 3cfa083c98..b101bfe37e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Navbar.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Navbar.cshtml @@ -7,7 +7,7 @@ var currentCustomer = this.WorkContext.CurrentCustomer; var currentLanguage = this.WorkContext.WorkingLanguage; var userName = (string)ViewBag.UserName; - var stores = (IList)ViewBag.Stores; + var stores = ((IList)ViewBag.Stores) ?? new List(); string currentLanguageCode = currentLanguage.UniqueSeoCode.EmptyNull().ToLower(); string communityUrl = (currentLanguageCode == "de" ? "http://community.smartstore.com" : diff --git a/src/Presentation/SmartStore.Web/Global.asax b/src/Presentation/SmartStore.Web/Global.asax index 612debf2a2..ebaeaa3f40 100644 --- a/src/Presentation/SmartStore.Web/Global.asax +++ b/src/Presentation/SmartStore.Web/Global.asax @@ -1 +1 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="SmartStore.Web.MvcApplication" Language="C#" %> +<%@ Application Codebehind="Global.asax.cs" Inherits="SmartStore.Web.MvcApplication" Language="C#" %> diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index 92d4ab19c7..0a9ee64ef5 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -1767,6 +1767,7 @@ + Web.config diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_layout.scss b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_layout.scss index a04290db25..d459899755 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_layout.scss +++ b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_layout.scss @@ -29,12 +29,20 @@ html { } body { - background: $site-bg; -} + background: $site-bg; -body.popup { - padding: 1.25rem; - background: $body-bg; + &.popup { + padding: 1.25rem; + background: $body-bg; + } + + &.bare { + padding: 0; + margin: 0; + background: $body-bg; + border: 0; + overflow-y: auto; + } } #header { diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.Bare.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.Bare.cshtml new file mode 100644 index 0000000000..71063b0a77 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.Bare.cshtml @@ -0,0 +1,7 @@ +@{ + + Layout = "_Document"; + Html.AddBodyCssClass("bare py-0 m-0"); +} + +@RenderBody() \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index bbc22f262e..ad41f1171d 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -79,7 +79,7 @@ - + From 6adbcae87ac38676c907cb24149676745c2bc6f1 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 7 Oct 2018 10:42:44 +0200 Subject: [PATCH 178/932] Resolves #1518 Allow for blank salutation and country in company settings --- .../Administration/Views/Setting/GeneralCommon.cshtml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/GeneralCommon.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/GeneralCommon.cshtml index 717b94043a..9178e6f490 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/GeneralCommon.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/GeneralCommon.cshtml @@ -551,7 +551,7 @@ @@ -588,7 +588,7 @@ @@ -645,7 +645,7 @@ From fa7606cb963269c87bb05d7deb7662eb067e3da2 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 8 Oct 2018 12:22:55 +0200 Subject: [PATCH 179/932] Resolves #909 Email & Phone number should be optional fields for Address form fields --- .../Controllers/CheckoutController.cs | 120 +++++++++--------- .../Controllers/CustomerController.cs | 1 + 2 files changed, 63 insertions(+), 58 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs index 33a72adf27..bde2fc97f0 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs @@ -14,7 +14,6 @@ using SmartStore.Core.Logging; using SmartStore.Services.Catalog; using SmartStore.Services.Common; -using SmartStore.Services.Configuration; using SmartStore.Services.Customers; using SmartStore.Services.Directory; using SmartStore.Services.Localization; @@ -52,9 +51,7 @@ public partial class CheckoutController : PublicControllerBase private readonly IPaymentService _paymentService; private readonly IOrderTotalCalculationService _orderTotalCalculationService; private readonly IOrderService _orderService; - private readonly IWebHelper _webHelper; private readonly HttpContextBase _httpContext; - private readonly ISettingService _settingService; private readonly OrderSettings _orderSettings; private readonly PaymentSettings _paymentSettings; private readonly AddressSettings _addressSettings; @@ -66,47 +63,54 @@ public partial class CheckoutController : PublicControllerBase #region Constructors - public CheckoutController(IWorkContext workContext, IStoreContext storeContext, - IShoppingCartService shoppingCartService, ILocalizationService localizationService, - ITaxService taxService, ICurrencyService currencyService, - IPriceFormatter priceFormatter, IOrderProcessingService orderProcessingService, - ICustomerService customerService, IGenericAttributeService genericAttributeService, + public CheckoutController( + IWorkContext workContext, + IStoreContext storeContext, + IShoppingCartService shoppingCartService, + ILocalizationService localizationService, + ITaxService taxService, + ICurrencyService currencyService, + IPriceFormatter priceFormatter, + IOrderProcessingService orderProcessingService, + ICustomerService customerService, + IGenericAttributeService genericAttributeService, ICountryService countryService, - IStateProvinceService stateProvinceService, IShippingService shippingService, + IStateProvinceService stateProvinceService, + IShippingService shippingService, IPaymentService paymentService, IOrderTotalCalculationService orderTotalCalculationService, - IOrderService orderService, IWebHelper webHelper, - HttpContextBase httpContext, IMobileDeviceHelper mobileDeviceHelper, + IOrderService orderService, + HttpContextBase httpContext, OrderSettings orderSettings, - PaymentSettings paymentSettings, AddressSettings addressSettings, - ShoppingCartSettings shoppingCartSettings, ShippingSettings shippingSettings, - ISettingService settingService, PluginMediator pluginMediator) + PaymentSettings paymentSettings, + AddressSettings addressSettings, + ShoppingCartSettings shoppingCartSettings, + ShippingSettings shippingSettings, + PluginMediator pluginMediator) { - this._workContext = workContext; - this._storeContext = storeContext; - this._shoppingCartService = shoppingCartService; - this._localizationService = localizationService; - this._taxService = taxService; - this._currencyService = currencyService; - this._priceFormatter = priceFormatter; - this._orderProcessingService = orderProcessingService; - this._customerService = customerService; - this._genericAttributeService = genericAttributeService; - this._countryService = countryService; - this._stateProvinceService = stateProvinceService; - this._shippingService = shippingService; - this._paymentService = paymentService; - this._orderTotalCalculationService = orderTotalCalculationService; - this._orderService = orderService; - this._webHelper = webHelper; - this._httpContext = httpContext; - this._settingService = settingService; - this._orderSettings = orderSettings; - this._paymentSettings = paymentSettings; - this._addressSettings = addressSettings; - this._shippingSettings = shippingSettings; - this._shoppingCartSettings = shoppingCartSettings; - this._pluginMediator = pluginMediator; + _workContext = workContext; + _storeContext = storeContext; + _shoppingCartService = shoppingCartService; + _localizationService = localizationService; + _taxService = taxService; + _currencyService = currencyService; + _priceFormatter = priceFormatter; + _orderProcessingService = orderProcessingService; + _customerService = customerService; + _genericAttributeService = genericAttributeService; + _countryService = countryService; + _stateProvinceService = stateProvinceService; + _shippingService = shippingService; + _paymentService = paymentService; + _orderTotalCalculationService = orderTotalCalculationService; + _orderService = orderService; + _httpContext = httpContext; + _orderSettings = orderSettings; + _paymentSettings = paymentSettings; + _addressSettings = addressSettings; + _shippingSettings = shippingSettings; + _shoppingCartSettings = shoppingCartSettings; + _pluginMediator = pluginMediator; } #endregion @@ -131,19 +135,19 @@ protected bool IsPaymentWorkflowRequired(IList cart, [NonAction] protected CheckoutBillingAddressModel PrepareBillingAddressModel(int? selectedCountryId = null) { + var customer = _workContext.CurrentCustomer; var model = new CheckoutBillingAddressModel(); - //existing addresses - var addresses = _workContext.CurrentCustomer.Addresses.Where(a => a.Country == null || a.Country.AllowsBilling).ToList(); + + // Existing addresses. + var addresses = customer.Addresses.Where(a => a.Country == null || a.Country.AllowsBilling).ToList(); foreach (var address in addresses) { var addressModel = new AddressModel(); - addressModel.PrepareModel(address, - false, - _addressSettings); + addressModel.PrepareModel(address, false, _addressSettings); model.ExistingAddresses.Add(addressModel); } - //new address + // New address. model.NewAddress.CountryId = selectedCountryId; model.NewAddress.PrepareModel(null, false, @@ -151,25 +155,27 @@ protected CheckoutBillingAddressModel PrepareBillingAddressModel(int? selectedCo _localizationService, _stateProvinceService, () => _countryService.GetAllCountriesForBilling()); + model.NewAddress.Email = customer?.Email; + return model; } [NonAction] protected CheckoutShippingAddressModel PrepareShippingAddressModel(int? selectedCountryId = null) { + var customer = _workContext.CurrentCustomer; var model = new CheckoutShippingAddressModel(); - //existing addresses - var addresses = _workContext.CurrentCustomer.Addresses.Where(a => a.Country == null || a.Country.AllowsShipping).ToList(); + + // Existing addresses. + var addresses = customer.Addresses.Where(a => a.Country == null || a.Country.AllowsShipping).ToList(); foreach (var address in addresses) { var addressModel = new AddressModel(); - addressModel.PrepareModel(address, - false, - _addressSettings); + addressModel.PrepareModel(address, false, _addressSettings); model.ExistingAddresses.Add(addressModel); } - //new address + // New address. model.NewAddress.CountryId = selectedCountryId; model.NewAddress.PrepareModel(null, false, @@ -177,6 +183,8 @@ protected CheckoutShippingAddressModel PrepareShippingAddressModel(int? selected _localizationService, _stateProvinceService, () => _countryService.GetAllCountriesForShipping()); + model.NewAddress.Email = customer?.Email; + return model; } @@ -468,16 +476,14 @@ public ActionResult Index() public ActionResult BillingAddress() { - //validation var cart = _workContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, _storeContext.CurrentStore.Id); if (cart.Count == 0) return RedirectToRoute("ShoppingCart"); - if ((_workContext.CurrentCustomer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed)) + if (_workContext.CurrentCustomer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) return new HttpUnauthorizedResult(); - //model var model = PrepareBillingAddressModel(); return View(model); } @@ -529,13 +535,12 @@ public ActionResult NewBillingAddress(CheckoutBillingAddressModel model) public ActionResult ShippingAddress() { - //validation var cart = _workContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, _storeContext.CurrentStore.Id); if (cart.Count == 0) return RedirectToRoute("ShoppingCart"); - if ((_workContext.CurrentCustomer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed)) + if (_workContext.CurrentCustomer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) return new HttpUnauthorizedResult(); if (!cart.RequiresShipping()) @@ -545,7 +550,6 @@ public ActionResult ShippingAddress() return RedirectToAction("ShippingMethod"); } - //model var model = PrepareShippingAddressModel(); return View(model); } @@ -980,7 +984,7 @@ public ActionResult CheckoutProgress(CheckoutProgressStep step) return PartialView(model); } + #endregion - } } diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index 21fdd3e78d..3313cc158e 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -1361,6 +1361,7 @@ public ActionResult AddressAdd() var model = new CustomerAddressEditModel(); model.Address.PrepareModel(null, false, _addressSettings, _localizationService, _stateProvinceService, () => _countryService.GetAllCountries()); + model.Address.Email = customer?.Email; return View(model); } From 3734b2d15404e54cc9934cf680b8515276c1b4c4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 8 Oct 2018 12:50:27 +0200 Subject: [PATCH 180/932] #425 Updates two unclear string resources --- .../SmartStore.Data/Migrations/MigrationsConfiguration.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index c3970d15b8..3ff6e108d7 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -569,6 +569,14 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Common.Voting", "Voting", "Abstimmung"); builder.AddOrUpdate("Common.Answer", "Answer", "Antwort"); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerFormFields.Description", + "Manage form fields that are displayed during registration.", + "Verwalten Sie Formularfelder, die während der Registrierung angezeigt werden."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.AddressFormFields.Description", + "Manage form fields that are displayed during checkout and on \"My account\" page.", + "Verwalten Sie Formularfelder, die während des Checkout-Prozesses und im \"Mein Konto\" Bereich angezeigt werden."); } } } From eb922b0e1bd9e396bbf9b6c7658660f9b35926dc Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 8 Oct 2018 19:56:31 +0200 Subject: [PATCH 181/932] More on "Breadcrumb of an associated product should include the grouped product if it has no assigned categories" --- .../SmartStore.Web/Controllers/ProductController.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index e2e77bbcde..2591bfcc9b 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -125,11 +125,11 @@ public ActionResult ProductDetails(int productId, string attributes, ProductVari if (!product.Published && !_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) return HttpNotFound(); - //ACL (access control list) + // ACL (access control list) if (!_aclService.Authorize(product)) return HttpNotFound(); - //Store mapping + // Store mapping if (!_storeMappingService.Authorize(product)) return HttpNotFound(); @@ -172,13 +172,15 @@ public ActionResult ProductDetails(int productId, string attributes, ProductVari { _helper.GetCategoryBreadCrumb(0, productId).Select(x => x.Value).Each(x => _breadcrumb.Track(x)); - // Add parent product if product has no category assigned. + // Add trail of parent product if product has no category assigned. var hasTrail = _breadcrumb.Trail?.Any() ?? false; if (!hasTrail) { var parentGroupedProduct = _productService.GetProductById(product.ParentGroupedProductId); if (parentGroupedProduct != null) { + _helper.GetCategoryBreadCrumb(0, parentGroupedProduct.Id).Select(x => x.Value).Each(x => _breadcrumb.Track(x)); + _breadcrumb.Track(new MenuItem { Text = parentGroupedProduct.GetLocalized(x => x.Name), From 652cf161d7f4de12bfbb52b706ece8ff55e011fc Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 9 Oct 2018 11:47:52 +0200 Subject: [PATCH 182/932] Removed an unused private method --- .../Messages/MessageContext.cs | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Messages/MessageContext.cs b/src/Libraries/SmartStore.Services/Messages/MessageContext.cs index 9d3014b713..056ab70126 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageContext.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageContext.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Globalization; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Localization; @@ -10,11 +9,11 @@ namespace SmartStore.Services.Messages { - /// - /// A context object which contains all required and optional information - /// for the creation of message templates. - /// - public class MessageContext + /// + /// A context object which contains all required and optional information + /// for the creation of message templates. + /// + public class MessageContext { private IFormatProvider _formatProvider; @@ -88,18 +87,6 @@ public IFormatProvider FormatProvider } } - private IFormatProvider GetFormatProvider(MessageContext messageContext) - { - var culture = messageContext.Language.LanguageCulture; - - if (LocalizationHelper.IsValidCultureCode(culture)) - { - return CultureInfo.GetCultureInfo(culture); - } - - return CultureInfo.CurrentCulture; - } - public static MessageContext Create(string messageTemplateName, int languageId, int? storeId = null, Customer customer = null) { return new MessageContext From e59f7e4e1f6f9e96faa3c45091e49579e102fc7f Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 10 Oct 2018 02:45:31 +0200 Subject: [PATCH 183/932] Storytelling (wip) --- .../SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs index 370a4e1b42..867d97ed5d 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs @@ -8,6 +8,7 @@ public enum StoryViewMode { Public, Preview, + GridEdit, Edit } From ba131a7a7d5de6472f65dd167b8df76405314613 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 11 Oct 2018 20:02:00 +0200 Subject: [PATCH 184/932] Dialog to create an import profile was out of function --- .../Administration/Views/Import/List.cshtml | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml index fcca0e6992..97060764d4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml @@ -113,13 +113,12 @@ else } -
    - @{Html.SmartStore().Window() - .Name("import-profile-dialog") - .Title(T("Common.Profile") + " - " + T("Admin.Common.AddNew")) - .RenderAtPageEnd(false) - .HtmlAttribute("data-profile", T("Common.Profile").Text) - .Content(@ +@{Html.SmartStore().Window() + .Name("import-profile-dialog") + .Title(T("Common.Profile") + " - " + T("Admin.Common.AddNew")) + .HtmlAttribute("data-profile", T("Common.Profile").Text) + .Content(@ +

    @T("Admin.DataExchange.Import.ProfileCreationNote")

    @@ -146,14 +145,14 @@ else   -
    ) - .FooterContent(@ - - - ) - .Render(); - } - + +
    ) + .FooterContent(@ + + + ) + .Render(); +} }
    @if (ViewData["warning.panel.message"] != null) - { + {
    - @Html.Raw(ViewData["warning.panel.message"]) - + @Html.Raw(ViewData["warning.panel.message"]) +
    - } - + } +
    @if (IsSectionDefined("header")) { @@ -56,7 +56,7 @@ @{ Html.RenderWidget("admin_breadcrumb"); }
    - + } @{ Html.RenderZone("beforemaincontent"); } @@ -76,7 +76,7 @@ @{ Html.RenderZone("aftermaincontent"); }
    - @{ Html.RenderPartial("_Notifications"); } - @{ Html.RenderWidget("admin_content_after"); } + @{ Html.RenderWidget("admin_content_after"); } +@{ Html.RenderPartial("_Notifications"); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/shared/_utils.scss b/src/Presentation/SmartStore.Web/Content/shared/_utils.scss index fe8e282617..447457c814 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_utils.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_utils.scss @@ -265,6 +265,30 @@ table th { @extend .text-muted; } +.animated { + animation-duration: 1s; + animation-fill-mode: both; +} + +@keyframes fadeInDown { + from { opacity: 0; transform: translate3d(0, -20px, 0); } + to { opacity: 1; transform: translate3d(0, 0, 0); } +} + +@keyframes fadeOutLeft { + from { opacity: 1; } + to { opacity: 0; transform: translate3d(-20px, 0, 0); } +} + +@keyframes fadeOutRight { + from { opacity: 1; } + to { opacity: 0; transform: translate3d(20px, 0, 0); } +} + +.fadeOutLeft { animation-name: fadeOutLeft; } +.fadeOutRight { animation-name: fadeOutRight; } +.fadeInDown { animation-name: fadeInDown; } + // // Profiler sucks on small screens // ------------------------------------------------------ diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/theme.config b/src/Presentation/SmartStore.Web/Themes/Flex/theme.config index bfd06dc6f5..a2e6050944 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/theme.config +++ b/src/Presentation/SmartStore.Web/Themes/Flex/theme.config @@ -29,12 +29,13 @@ $indigo $gray-600 - $orange + $green $red + $orange $blue - $green lighten($gray-200, 2%) $gray-900 + primary,secondary,success,danger,warning,info,light,gray,dark 0.25rem diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Partials/_Notifications.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Partials/_Notifications.cshtml index 22422d45d8..2b2335e11f 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Partials/_Notifications.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Partials/_Notifications.cshtml @@ -2,7 +2,7 @@ @if (this.HasMessages) { - -
    From e8b80fef6f8308c668242b345b823fe8c4c0857c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 17 Oct 2018 17:10:09 +0200 Subject: [PATCH 205/932] Some parallax script cleanup --- .../Scripts/smartstore.parallax.js | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.parallax.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.parallax.js index 7d7504d57c..0372536303 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.parallax.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.parallax.js @@ -6,14 +6,6 @@ ; (function ($, window, document, undefined) { - //var noParallax = $('body').hasClass('no-parallax'), - // win = $(window), - // winHeight = win.height(); - - //$('.parallax').each(function (i, el) { - - //}); - var initialized = false, stages = [], win, @@ -77,20 +69,4 @@ } }; - //SmartStore.parallax = { - // init: function (ctx, selector) { - // if (noParallax) - // return; - - // selector = selector || '.parallax'; - // } - //}; - - // PARALLAX DATA API - // ====================================================== - - $(function () { - SmartStore.parallax.init(); - }); - })(jQuery, window, document); From b5eb6b58797a4fe2036acec4aa535796faa1fb59 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 17 Oct 2018 18:52:53 +0200 Subject: [PATCH 206/932] Fixed path handling in AppUpdate > DeleteSearchIndex() --- .../SmartStore.Core/Packaging/Updater/AppUpdater.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs b/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs index d269de9195..a645c43d9a 100644 --- a/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs +++ b/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs @@ -210,7 +210,6 @@ private PackageInfo ExecuteUpdate(IPackage package) #endregion - #region Migrations internal void ExecuteMigrations() @@ -419,9 +418,9 @@ private void MigrateInitial() private void DeleteSearchIndex() { - var tenantPath = CommonHelper.MapPath("~/App_Data/Tenants/Default"); + var tenantPath = CommonHelper.MapPath(DataSettings.Current.TenantPath); - try + try { var indexingDir = new DirectoryInfo(Path.Combine(tenantPath, "Indexing")); if (indexingDir.Exists) @@ -444,7 +443,6 @@ private void DeleteSearchIndex() #endregion - protected override void OnDispose(bool disposing) { if (disposing) From 9a34854883aba10c9f8e93be8f9c8f4241c272bb Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 18 Oct 2018 05:04:36 +0200 Subject: [PATCH 207/932] Range editor: remove thousand separator from number --- .../Administration/Scripts/admin.globalinit.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js index 5e3296d96c..559d6801cf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js +++ b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js @@ -127,7 +127,10 @@ el.parent().css('--slider-value', el.val()); } - $(el.data('target')).val(SmartStore.globalization.formatNumber(parseFloat(el.val()))); + var g = SmartStore.globalization; + var nf = g.culture.numberFormat; + + $(el.data('target')).val(g.formatNumber(parseFloat(el.val())).replace(nf[","], "")); }); // Because we restyled the grid, the filter dropdown does not position From b71f62f2a11994ed6c88f7495c1ff9758a075e4d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 18 Oct 2018 13:38:25 +0200 Subject: [PATCH 208/932] Perf: Speed up loading of product bundles in product lists --- .../Catalog/PriceCalculationService.cs | 151 +++++++++--------- .../Catalog/ProductExtensions.cs | 61 +++---- .../Catalog/ProductService.cs | 6 +- .../Controllers/CatalogHelper.cs | 2 +- .../Catalog/PriceCalculationServiceTests.cs | 4 +- .../Orders/OrderProcessingServiceTests.cs | 2 +- .../OrderTotalCalculationServiceTests.cs | 2 +- 7 files changed, 118 insertions(+), 110 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs index 7c0556775b..490a20a7ca 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -4,29 +4,28 @@ using System.Web; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Tax; using SmartStore.Services.Catalog.Extensions; using SmartStore.Services.Catalog.Modelling; using SmartStore.Services.Discounts; using SmartStore.Services.Media; using SmartStore.Services.Tax; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Tax; namespace SmartStore.Services.Catalog { - /// - /// Price calculation service - /// - public partial class PriceCalculationService : IPriceCalculationService + /// + /// Price calculation service + /// + public partial class PriceCalculationService : IPriceCalculationService { private readonly IDiscountService _discountService; private readonly ICategoryService _categoryService; private readonly IManufacturerService _manufacturerService; private readonly IProductAttributeParser _productAttributeParser; private readonly IProductService _productService; - private readonly ShoppingCartSettings _shoppingCartSettings; private readonly CatalogSettings _catalogSettings; private readonly IProductAttributeService _productAttributeService; private readonly IDownloadService _downloadService; @@ -41,7 +40,6 @@ public PriceCalculationService( IManufacturerService manufacturerService, IProductAttributeParser productAttributeParser, IProductService productService, - ShoppingCartSettings shoppingCartSettings, CatalogSettings catalogSettings, IProductAttributeService productAttributeService, IDownloadService downloadService, @@ -55,7 +53,6 @@ public PriceCalculationService( _manufacturerService = manufacturerService; _productAttributeParser = productAttributeParser; _productService = productService; - _shoppingCartSettings = shoppingCartSettings; _catalogSettings = catalogSettings; _productAttributeService = productAttributeService; _downloadService = downloadService; @@ -302,17 +299,17 @@ protected virtual decimal GetPreselectedPrice( var taxRate = decimal.Zero; var attributesTotalPriceBase = decimal.Zero; var preSelectedPriceAdjustmentBase = decimal.Zero; - var isBundle = (product.ProductType == ProductType.BundledProduct); - var isBundleItemPricing = (bundleItem != null && bundleItem.Item.BundleProduct.BundlePerItemPricing); - var isBundlePricing = (bundleItem != null && !bundleItem.Item.BundleProduct.BundlePerItemPricing); - var bundleItemId = (bundleItem == null ? 0 : bundleItem.Item.Id); + var isBundle = product.ProductType == ProductType.BundledProduct; + var isBundleItemPricing = bundleItem != null && bundleItem.Item.BundleProduct.BundlePerItemPricing; + var isBundlePricing = bundleItem != null && !bundleItem.Item.BundleProduct.BundlePerItemPricing; + var bundleItemId = bundleItem == null ? 0 : bundleItem.Item.Id; var query = new ProductVariantQuery(); var selectedAttributeValues = new List(); var attributes = context.Attributes.GetOrLoad(product.Id); // 1. Fill query with initially selected attributes. - foreach (var attribute in attributes.Where(x => x.ProductVariantAttributeValues.Count > 0 && x.ShouldHaveValues())) + foreach (var attribute in attributes.Where(x => x.ProductVariantAttributeValues.Any() && x.ShouldHaveValues())) { int preSelectedValueId = 0; ProductVariantAttributeValue defaultValue = null; @@ -321,9 +318,9 @@ protected virtual decimal GetPreselectedPrice( foreach (var pvaValue in pvaValues) { - ProductBundleItemAttributeFilter attributeFilter = null; + ProductBundleItemAttributeFilter attributeFilter = null; - if (bundleItem.FilterOut(pvaValue, out attributeFilter)) + if (bundleItem?.Item?.FilterOut(pvaValue, out attributeFilter) ?? false) continue; if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected) @@ -374,7 +371,7 @@ protected virtual decimal GetPreselectedPrice( } // 2. Find attribute combination for selected attributes and merge it. - if (!isBundle && query.Variants.Count > 0) + if (!isBundle && query.Variants.Any()) { var attributeXml = query.CreateSelectedAttributesXml(product.Id, bundleItemId, attributes, _productAttributeParser, _services.Localization, _downloadService, _catalogSettings, _httpRequestBase, new List()); @@ -525,24 +522,27 @@ public virtual decimal GetFinalPrice( PriceCalculationContext context = null, bool isTierPrice = false) { - //initial price + // Initial price. decimal result = product.Price; - //special price + // Special price. var specialPrice = GetSpecialPrice(product); if (specialPrice.HasValue) + { result = specialPrice.Value; + } - if (isTierPrice) + if (isTierPrice) + { includeDiscounts = true; + } - //tier prices - if (product.HasTierPrices && !bundleItem.IsValid() && includeDiscounts) + // Tier prices. + if (product.HasTierPrices && includeDiscounts && bundleItem == null) { - decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity, context); - Discount appliedDiscountTest = null; - decimal discountAmountTest = GetDiscountAmount(product, customer, additionalCharge, quantity, out appliedDiscountTest, bundleItem); - decimal discountProductTest = result - discountAmountTest; + var tierPrice = GetMinimumTierPrice(product, customer, quantity, context); + var discountAmountTest = GetDiscountAmount(product, customer, additionalCharge, quantity, out var appliedDiscountTest, bundleItem); + var discountProductTest = result - discountAmountTest; //decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity); if (tierPrice.HasValue && tierPrice < discountProductTest) @@ -552,11 +552,10 @@ public virtual decimal GetFinalPrice( } } - //discount + additional charge + // Discount + additional charge. if (includeDiscounts) { - Discount appliedDiscount = null; - decimal discountAmount = GetDiscountAmount(product, customer, additionalCharge, quantity, out appliedDiscount, bundleItem, context); + var discountAmount = GetDiscountAmount(product, customer, additionalCharge, quantity, out var appliedDiscount, bundleItem, context); result = result + additionalCharge - discountAmount; } else @@ -565,7 +564,9 @@ public virtual decimal GetFinalPrice( } if (result < decimal.Zero) + { result = decimal.Zero; + } return result; } @@ -582,25 +583,24 @@ public virtual decimal GetFinalPrice( { if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing) { - decimal result = decimal.Zero; - + var result = decimal.Zero; var items = bundleItems; + if (items == null) { - //var bundleEntities = context.ProductBundleItems.GetOrLoad(product.Id).Select(x => new ProductBundleItemData(x)); items = context == null ? _productService.GetBundleItems(product.Id) - : context.ProductBundleItems.GetOrLoad(product.Id).Select(x => new ProductBundleItemData(x)); + : context.ProductBundleItems.GetOrLoad(product.Id).Select(x => new ProductBundleItemData(x)).ToList(); } - foreach (var itemData in items.Where(x => x.IsValid())) + foreach (var itemData in items.Where(x => x != null && x.Item != null)) { - decimal itemPrice = GetFinalPrice(itemData.Item.Product, customer, itemData.AdditionalCharge, includeDiscounts, 1, itemData, context); + var itemPrice = GetFinalPrice(itemData.Item.Product, customer, itemData.AdditionalCharge, includeDiscounts, 1, itemData, context); result = result + decimal.Multiply(itemPrice, itemData.Item.Quantity); } - return (result < decimal.Zero ? decimal.Zero : result); + return result < decimal.Zero ? decimal.Zero : result; } return GetFinalPrice(product, customer, additionalCharge, includeDiscounts, quantity, bundleItem, context); @@ -608,22 +608,21 @@ public virtual decimal GetFinalPrice( public virtual decimal GetLowestPrice(Product product, Customer customer, PriceCalculationContext context, out bool displayFromMessage) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); if (product.ProductType == ProductType.GroupedProduct) throw Error.InvalidOperation("Choose the other override for products of type grouped product."); - // note: attribute price adjustments were never regarded here cause of many reasons - - if (context == null) - context = CreatePriceCalculationContext(customer: customer); - - var isBundlePerItemPricing = (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing); + // Note, attribute price adjustments will not be taken into account here. + if (context == null) + { + context = CreatePriceCalculationContext(customer: customer); + } - displayFromMessage = isBundlePerItemPricing; + var isBundlePerItemPricing = product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing; + var lowestPrice = GetFinalPrice(product, null, customer, decimal.Zero, true, int.MaxValue, null, context); - var lowestPrice = GetFinalPrice(product, null, customer, decimal.Zero, true, int.MaxValue, null, context); + displayFromMessage = isBundlePerItemPricing; if (product.LowestAttributeCombinationPrice.HasValue && product.LowestAttributeCombinationPrice.Value < lowestPrice) { @@ -672,8 +671,10 @@ public virtual decimal GetLowestPrice(Product product, Customer customer, PriceC lowestPriceProduct = null; decimal? lowestPrice = null; - if (context == null) - context = CreatePriceCalculationContext(customer: customer); + if (context == null) + { + context = CreatePriceCalculationContext(customer: customer); + } foreach (var associatedProduct in associatedProducts) { @@ -691,35 +692,37 @@ public virtual decimal GetLowestPrice(Product product, Customer customer, PriceC } } - if (lowestPriceProduct == null) - lowestPriceProduct = associatedProducts.FirstOrDefault(); + if (lowestPriceProduct == null) + { + lowestPriceProduct = associatedProducts.FirstOrDefault(); + } return lowestPrice; } public virtual decimal GetPreselectedPrice(Product product, Customer customer, Currency currency, PriceCalculationContext context) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); var result = decimal.Zero; - if (context == null) - context = CreatePriceCalculationContext(customer: customer); + if (context == null) + { + context = CreatePriceCalculationContext(customer: customer); + } if (product.ProductType == ProductType.BundledProduct) { - var bundleItems = _productService.GetBundleItems(product.Id); - - var productIds = bundleItems.Select(x => x.Item.ProductId).ToList(); - productIds.Add(product.Id); - - context.Collect(productIds); - - foreach (var bundleItem in bundleItems.Where(x => x.Item.Product.CanBeBundleItem())) + var bundleItems = context.ProductBundleItems + .GetOrLoad(product.Id) + .Select(x => new ProductBundleItemData(x)) + .ToList(); + var bundleItemContext = CreatePriceCalculationContext(bundleItems.Select(x => x.Item.Product), customer); + + // Fetch bundleItems.AdditionalCharge for all bundle items. + foreach (var bundleItem in bundleItems.Where(x => x.Item.Product.CanBeBundleItem())) { - // fetch bundleItems.AdditionalCharge for all bundle items - var unused = GetPreselectedPrice(bundleItem.Item.Product, customer, currency, context, bundleItem, bundleItems); + var unused = GetPreselectedPrice(bundleItem.Item.Product, customer, currency, bundleItemContext, bundleItem, bundleItems); } result = GetPreselectedPrice(product, customer, currency, context, null, bundleItems); @@ -795,8 +798,7 @@ public virtual decimal GetDiscountAmount(Product product, Customer customer, decimal additionalCharge) { - Discount appliedDiscount = null; - return GetDiscountAmount(product, customer, additionalCharge, out appliedDiscount); + return GetDiscountAmount(product, customer, additionalCharge, out var appliedDiscount); } /// @@ -828,7 +830,7 @@ public virtual decimal GetDiscountAmount( decimal appliedDiscountAmount = decimal.Zero; decimal finalPriceWithoutDiscount = decimal.Zero; - if (bundleItem.IsValid()) + if (bundleItem != null && bundleItem.Item != null) { if (bundleItem.Item.Discount.HasValue && bundleItem.Item.BundleProduct.BundlePerItemPricing) { @@ -908,7 +910,9 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, bundleItem.Item.Product.MergeWithCombination(bundleItem.Item.AttributesXml, _productAttributeParser); } - var bundleItems = shoppingCartItem.ChildItems.Where(x => x.BundleItemData.IsValid()).Select(x => x.BundleItemData).ToList(); + var bundleItems = shoppingCartItem.ChildItems + .Where(x => x.BundleItemData != null && x.BundleItemData.Item != null) + .Select(x => x.BundleItemData).ToList(); finalPrice = GetFinalPrice(product, bundleItems, customer, decimal.Zero, includeDiscounts, shoppingCartItem.Item.Quantity); } @@ -947,8 +951,7 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, /// Discount amount public virtual decimal GetDiscountAmount(OrganizedShoppingCartItem shoppingCartItem) { - Discount appliedDiscount; - return GetDiscountAmount(shoppingCartItem, out appliedDiscount); + return GetDiscountAmount(shoppingCartItem, out var appliedDiscount); } /// @@ -993,16 +996,17 @@ public virtual decimal GetDiscountAmount(OrganizedShoppingCartItem shoppingCartI public virtual decimal GetProductVariantAttributeValuePriceAdjustment(ProductVariantAttributeValue attributeValue, Product product, Customer customer, PriceCalculationContext context, int productQuantity = 1) { - if (attributeValue == null) - throw new ArgumentNullException("attributeValue"); + Guard.NotNull(attributeValue, nameof(attributeValue)); if (attributeValue.ValueType == ProductVariantAttributeValueType.Simple) { if (productQuantity > 1 && attributeValue.PriceAdjustment > 0) { var tierPriceAttributeAdjustment = GetTierPriceAttributeAdjustment(product, customer, productQuantity, context, attributeValue.PriceAdjustment); - if(tierPriceAttributeAdjustment != 0) + if (tierPriceAttributeAdjustment != 0) + { return tierPriceAttributeAdjustment; + } } return attributeValue.PriceAdjustment; @@ -1018,6 +1022,7 @@ public virtual decimal GetProductVariantAttributeValuePriceAdjustment(ProductVar return productPrice; } } + return decimal.Zero; } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs index 1a99142239..f48e90521a 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs @@ -104,12 +104,11 @@ public static IList GetAllCombinationPictureIds(this IEnumerable 0) { - int id; var ids = string.Join(",", data).SplitSafe(",").Distinct(); foreach (string str in ids) { - if (int.TryParse(str, out id) && !pictureIds.Exists(i => i == id)) + if (int.TryParse(str, out var id) && !pictureIds.Exists(i => i == id)) pictureIds.Add(id); } } @@ -145,8 +144,11 @@ public static CrossSellProduct FindCrossSellProduct(this IList int productId1, int productId2) { foreach (CrossSellProduct crossSellProduct in source) + { if (crossSellProduct.ProductId1 == productId1 && crossSellProduct.ProductId2 == productId2) return crossSellProduct; + } + return null; } @@ -156,9 +158,12 @@ public static bool IsAvailableByStock(this Product product) if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock || product.ManageInventoryMethod == ManageInventoryMethod.ManageStockByAttributes) { - if (product.StockQuantity <= 0 && product.BackorderMode == BackorderMode.NoBackorders) - return false; + if (product.StockQuantity <= 0 && product.BackorderMode == BackorderMode.NoBackorders) + { + return false; + } } + return true; } @@ -274,8 +279,7 @@ public static int[] ParseAllowedQuatities(this Product product) .ToList() .ForEach(qtyStr => { - int qty = 0; - if (int.TryParse(qtyStr.Trim(), out qty)) + if (int.TryParse(qtyStr.Trim(), out var qty)) { result.Add(qty); } @@ -298,8 +302,7 @@ public static int[] ParseRequiredProductIds(this Product product) .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim())) { - int id = 0; - if (int.TryParse(idStr, out id)) + if (int.TryParse(idStr, out var id)) ids.Add(id); } @@ -399,33 +402,31 @@ public static string GetProductTypeLabel(this Product product, ILocalizationServ { if (product != null && product.ProductType != ProductType.SimpleProduct) { - string key = "Admin.Catalog.Products.ProductType.{0}.Label".FormatWith(product.ProductType.ToString()); + var key = "Admin.Catalog.Products.ProductType.{0}.Label".FormatInvariant(product.ProductType.ToString()); return localizationService.GetResource(key); } - return ""; + + return string.Empty; } public static bool CanBeBundleItem(this Product product) { - return (product != null && product.ProductType == ProductType.SimpleProduct && !product.IsRecurring && !product.IsDownload); + return product != null && product.ProductType == ProductType.SimpleProduct && !product.IsRecurring && !product.IsDownload; } - public static bool IsValid(this ProductBundleItemData bundleItemData) - { - return (bundleItemData != null && bundleItemData.Item != null); - } - public static bool FilterOut(this ProductBundleItemData bundleItemData, ProductVariantAttributeValue value, out ProductBundleItemAttributeFilter filter) - { - if (bundleItemData.IsValid() && value != null && bundleItemData.Item.FilterAttributes) - { - filter = bundleItemData.Item.AttributeFilters.FirstOrDefault(x => x.AttributeId == value.ProductVariantAttributeId && x.AttributeValueId == value.Id); + public static bool FilterOut(this ProductBundleItem bundleItem, ProductVariantAttributeValue value, out ProductBundleItemAttributeFilter filter) + { + if (bundleItem != null && value != null && bundleItem.FilterAttributes) + { + filter = bundleItem.AttributeFilters.FirstOrDefault(x => x.AttributeId == value.ProductVariantAttributeId && x.AttributeValueId == value.Id); + return filter == null; + } - return (filter == null); - } - filter = null; - return false; - } - public static string GetLocalizedName(this ProductBundleItem bundleItem) + filter = null; + return false; + } + + public static string GetLocalizedName(this ProductBundleItem bundleItem) { if (bundleItem != null) { @@ -447,8 +448,10 @@ public static string GetLocalizedName(this ProductBundleItem bundleItem, int lan public static ProductBundleItemOrderData ToOrderData(this ProductBundleItemData bundleItemData, decimal priceWithDiscount = decimal.Zero, string attributesXml = null, string attributesInfo = null) { - if (!bundleItemData.IsValid()) - return null; + if (bundleItemData == null || bundleItemData.Item == null) + { + return null; + } var item = bundleItemData.Item; string bundleItemName = item.GetLocalized(x => x.Name); @@ -458,7 +461,7 @@ public static ProductBundleItemOrderData ToOrderData(this ProductBundleItemData BundleItemId = item.Id, ProductId = item.ProductId, Sku = item.Product.Sku, - ProductName = (bundleItemName ?? item.Product.GetLocalized(x => x.Name)), + ProductName = bundleItemName ?? item.Product.GetLocalized(x => x.Name), ProductSeName = item.Product.GetSeName(), VisibleIndividually = item.Product.VisibleIndividually, Quantity = item.Quantity, diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index 3f7403adc1..869e725d67 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -1054,8 +1054,10 @@ where productIds.Contains(pbi.BundleProductId) && !p.Deleted && !p.IsSystemProdu orderby pbi.DisplayOrder select pbi; - var map = query.Include(x => x.Product) - .ToList() + var map = query + .Include(x => x.Product) + .Include(x => x.BundleProduct) + .ToList() .ToMultimap(x => x.BundleProductId, x => x); return map; diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index e877e13b3d..05d5eae489 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -734,7 +734,7 @@ public ProductDetailsModel PrepareProductDetailModel( { ProductBundleItemAttributeFilter attributeFilter = null; - if (productBundleItem.FilterOut(pvaValue, out attributeFilter)) + if (productBundleItem?.Item?.FilterOut(pvaValue, out attributeFilter) ?? false) continue; if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected) diff --git a/src/Tests/SmartStore.Services.Tests/Catalog/PriceCalculationServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Catalog/PriceCalculationServiceTests.cs index ba53c46ed8..5cd3a1d86f 100644 --- a/src/Tests/SmartStore.Services.Tests/Catalog/PriceCalculationServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Catalog/PriceCalculationServiceTests.cs @@ -35,7 +35,6 @@ public class PriceCalculationServiceTests : ServiceTest ICommonServices _services; HttpRequestBase _httpRequestBase; ITaxService _taxService; - ShoppingCartSettings _shoppingCartSettings; CatalogSettings _catalogSettings; TaxSettings _taxSettings; Store _store; @@ -69,12 +68,11 @@ public class PriceCalculationServiceTests : ServiceTest _httpRequestBase = MockRepository.GenerateMock(); _taxService = MockRepository.GenerateMock(); - _shoppingCartSettings = new ShoppingCartSettings(); _catalogSettings = new CatalogSettings(); _taxSettings = new TaxSettings(); _priceCalcService = new PriceCalculationService(_discountService, _categoryService, _manufacturerService, _productAttributeParser, _productService, - _shoppingCartSettings, _catalogSettings, _productAttributeService, _downloadService, _services, _httpRequestBase, _taxService, _taxSettings); + _catalogSettings, _productAttributeService, _downloadService, _services, _httpRequestBase, _taxService, _taxSettings); } [Test] diff --git a/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs index 0bebffb2df..eb7d7a871e 100644 --- a/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs @@ -173,7 +173,7 @@ public class OrderProcessingServiceTests : ServiceTest _rewardPointsSettings = new RewardPointsSettings(); _priceCalcService = new PriceCalculationService(_discountService, _categoryService, _manufacturerService, _productAttributeParser, _productService, - _shoppingCartSettings, _catalogSettings, _productAttributeService, _downloadService, _services, _httpRequestBase, _taxService, _taxSettings); + _catalogSettings, _productAttributeService, _downloadService, _services, _httpRequestBase, _taxService, _taxSettings); _orderTotalCalcService = new OrderTotalCalculationService(_workContext, _storeContext, _priceCalcService, _taxService, _shippingService, _providerManager, diff --git a/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs index 61828ffc24..7e9dbc1879 100644 --- a/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs @@ -151,7 +151,7 @@ public class OrderTotalCalculationServiceTests : ServiceTest _rewardPointsSettings = new RewardPointsSettings(); _priceCalcService = new PriceCalculationService(_discountService, _categoryService, _manufacturerService, _productAttributeParser, _productService, - _shoppingCartSettings, _catalogSettings, _productAttributeService, _downloadService, _services, _httpRequestBase, _taxService, _taxSettings); + _catalogSettings, _productAttributeService, _downloadService, _services, _httpRequestBase, _taxService, _taxSettings); _orderTotalCalcService = new OrderTotalCalculationService(_workContext, _storeContext, _priceCalcService, _taxService, _shippingService, _providerManager, From 52e60605b25f5ce194363e7b49a1ed3eeac53188 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 18 Oct 2018 14:31:48 +0200 Subject: [PATCH 209/932] Minor improvement --- .../SmartStore.PayPal/Models/ApiConfigurationModels.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs index 862b281c6b..81f7bc46eb 100644 --- a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs +++ b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs @@ -132,10 +132,12 @@ public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) if (fromSettings) { MiniMapper.Map(settings, this); - } + } else { - MiniMapper.Map(this, settings); + TransactMode = TransactMode.AuthorizeAndCapture; + + MiniMapper.Map(this, settings); settings.ApiAccountName = ApiAccountName.TrimSafe(); settings.ApiAccountPassword = ApiAccountPassword.TrimSafe(); settings.ClientId = ClientId.TrimSafe(); From 000ed272e7870ef6d2a313fbef278cbf239d0363 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 18 Oct 2018 20:22:45 +0200 Subject: [PATCH 210/932] More reliable way of resolving SSL mode --- .../Extensions/HttpExtensions.cs | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs index 8dfa07887e..0b783d4b91 100644 --- a/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs @@ -13,13 +13,21 @@ using System.Web.Mvc; namespace SmartStore -{ - public static class HttpExtensions - { - private const string HTTP_CLUSTER_VAR = "HTTP_CLUSTER_HTTPS"; - private const string HTTP_XFWDPROTO_VAR = "HTTP_X_FORWARDED_PROTO"; +{ + public static class HttpExtensions + { private const string CACHE_REGION_NAME = "SmartStoreNET:"; + private static readonly List> _sslHeaders = new List> + { + new Tuple("HTTP_CLUSTER_HTTPS", "on"), + new Tuple("X-Forwarded-Proto", "https"), + new Tuple("x-arr-ssl", null), + new Tuple("X-Forwarded-Protocol", "https"), + new Tuple("X-Forwarded-Ssl", "on"), + new Tuple("X-Url-Scheme", "https") + }; + /// /// Gets a value which indicates whether the HTTP connection uses secure sockets (HTTPS protocol). /// Works with Cloud's load balancers. @@ -28,10 +36,22 @@ public static class HttpExtensions /// public static bool IsSecureConnection(this HttpRequestBase request) { - return (request.IsSecureConnection - || (request.ServerVariables[HTTP_CLUSTER_VAR] != null || request.ServerVariables[HTTP_CLUSTER_VAR] == "on") - || (request.ServerVariables[HTTP_XFWDPROTO_VAR] != null || request.ServerVariables[HTTP_XFWDPROTO_VAR] == "https")); - } + if (request.IsSecureConnection) + { + return true; + } + + foreach (var tuple in _sslHeaders) + { + var serverVar = request.ServerVariables[tuple.Item1]; + if (serverVar != null) + { + return tuple.Item2 == null || tuple.Item2.Equals(serverVar, StringComparison.OrdinalIgnoreCase); + } + } + + return false; + } /// From 7e49cb188a52b13871fdc394ee1ef4d3fb5e07e7 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 18 Oct 2018 22:52:02 +0200 Subject: [PATCH 211/932] Small fix for last commit --- .../Extensions/HttpExtensions.cs | 55 +++++++++---------- .../SmartStore.Core/SmartException.cs | 3 +- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs index 0b783d4b91..36af5c9eca 100644 --- a/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs @@ -28,32 +28,6 @@ public static class HttpExtensions new Tuple("X-Url-Scheme", "https") }; - /// - /// Gets a value which indicates whether the HTTP connection uses secure sockets (HTTPS protocol). - /// Works with Cloud's load balancers. - /// - /// - /// - public static bool IsSecureConnection(this HttpRequestBase request) - { - if (request.IsSecureConnection) - { - return true; - } - - foreach (var tuple in _sslHeaders) - { - var serverVar = request.ServerVariables[tuple.Item1]; - if (serverVar != null) - { - return tuple.Item2 == null || tuple.Item2.Equals(serverVar, StringComparison.OrdinalIgnoreCase); - } - } - - return false; - } - - /// /// Returns wether the specified url is local to the host or not /// @@ -124,10 +98,35 @@ public static bool IsAppLocalUrl(this HttpRequestBase request, string url) /// public static bool IsSecureConnection(this HttpRequest request) { - return (request.IsSecureConnection || (request.ServerVariables[HTTP_CLUSTER_VAR] != null || request.ServerVariables[HTTP_CLUSTER_VAR] == "on")); + return IsSecureConnection(new HttpRequestWrapper(request)); } - [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + /// + /// Gets a value which indicates whether the HTTP connection uses secure sockets (HTTPS protocol). + /// Works with Cloud's load balancers. + /// + /// + /// + public static bool IsSecureConnection(this HttpRequestBase request) + { + if (request.IsSecureConnection) + { + return true; + } + + foreach (var tuple in _sslHeaders) + { + var serverVar = request.ServerVariables[tuple.Item1]; + if (serverVar != null) + { + return tuple.Item2 == null || tuple.Item2.Equals(serverVar, StringComparison.OrdinalIgnoreCase); + } + } + + return false; + } + + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] public static void SetFormsAuthenticationCookie(this HttpWebRequest webRequest, HttpRequestBase httpRequest) { CopyCookie(webRequest, httpRequest, FormsAuthentication.FormsCookieName); diff --git a/src/Libraries/SmartStore.Core/SmartException.cs b/src/Libraries/SmartStore.Core/SmartException.cs index 67609e40d1..0b620fa609 100644 --- a/src/Libraries/SmartStore.Core/SmartException.cs +++ b/src/Libraries/SmartStore.Core/SmartException.cs @@ -40,8 +40,7 @@ public SmartException(string messageFormat, params object[] args) /// /// The SerializationInfo that holds the serialized object data about the exception being thrown. /// The StreamingContext that contains contextual information about the source or destination. - protected SmartException(SerializationInfo - info, StreamingContext context) + protected SmartException(SerializationInfo info, StreamingContext context) : base(info, context) { } From e01bb85c4ae8cf9098a99c96b47e88c876c9d011 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 19 Oct 2018 04:47:25 +0200 Subject: [PATCH 212/932] (Perf) faster viewport resolution and event handling --- .../SmartStore.Web/Scripts/public.common.js | 13 -- .../Scripts/smartstore.common.js | 13 -- .../Scripts/smartstore.viewport.js | 115 +++++++++++------- .../Views/Shared/Layouts/_Layout.Bare.cshtml | 4 +- 4 files changed, 75 insertions(+), 70 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Scripts/public.common.js b/src/Presentation/SmartStore.Web/Scripts/public.common.js index 410be4fed0..096f7fb4ae 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.common.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.common.js @@ -158,19 +158,6 @@ // on document ready // TODO: reorganize > public.globalinit.js $(function () { - // Notify subscribers about page/content width change - if (window.EventBroker) { - var currentContentWidth = $('#content').width(); - $(window).on('resize', function () { - var contentWidth = $('#content').width(); - if (contentWidth !== currentContentWidth) { - currentContentWidth = contentWidth; - console.debug("Grid tier changed: " + viewport.current()); - EventBroker.publish("page.resized", viewport); - } - }); - } - // Init reveal on scroll with AOS library if (typeof AOS !== 'undefined') { AOS.init({ once: true }); diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js index 8c9fe225fe..46a38183a6 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js @@ -393,19 +393,6 @@ }); } - // Notify subscribers about page/content width change - if (window.EventBroker) { - var currentContentWidth = $('#content').width(); - $(window).on('resize', function () { - var contentWidth = $('#content').width(); - if (contentWidth !== currentContentWidth) { - currentContentWidth = contentWidth; - console.debug("Grid tier changed: " + viewport.current()); - EventBroker.publish("page.resized", viewport); - } - }); - } - // tab strip smart auto selection $('.tabs-autoselect ul.nav a[data-toggle=tab]').on('shown.bs.tab', function (e) { var tab = $(e.target), diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.viewport.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.viewport.js index 52a6320b4c..05d7a672a6 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.viewport.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.viewport.js @@ -11,27 +11,21 @@ // Internal methods var internal = { - /** - * Breakpoint detection divs - */ - detectionDivs: { - 'xs': $('
    '), - 'sm': $('
    '), - 'md': $('
    '), - 'lg': $('
    '), - 'xl': $('
    ') - }, - - /** - * Append visibility divs after DOM laoded - */ - applyDetectionDivs: function () { - $(document).ready(function () { - $.each(self.breakpoints, function (alias) { - self.breakpoints[alias].appendTo('#device-detection'); - }); - }); - }, + breakpoints: { + xs: 0, + sm: 576, + md: 768, + lg: 992, + xl: 1200 + }, + + resolveBreakpoints: function () { + $(document).ready(function () { + $.each(internal.breakpoints, function (alias) { + internal.breakpoints[alias] = parseFloat($('html').css('--breakpoint-' + alias)); + }); + }); + }, /** * Determines whether passed string is a parsable expression @@ -75,14 +69,18 @@ * Returns true if currently active breakpoint matches the expression */ isAnyActive: function (breakpoints) { - var found = false; + var found = false; + var width = self.html.width(); + $.each(breakpoints, function (index, alias) { // Once first breakpoint matches, return true and break out of the loop - if (self.breakpoints[alias].is(':visible')) { + + if (width >= internal.breakpoints[alias]) { found = true; return false; - } - }); + } + }); + return found; }, @@ -94,7 +92,7 @@ var expression = internal.splitExpression(str); // Get names of all breakpoints - var breakpointList = Object.keys(self.breakpoints); + var breakpointList = Object.keys(internal.breakpoints); // Get index of sought breakpoint in the list var pos = breakpointList.indexOf(expression.breakpointName); @@ -134,8 +132,9 @@ var acceptedBreakpoints = breakpointList.slice(start, end); - return internal.isAnyActive(acceptedBreakpoints); + //console.log("BREAKPOINTS", str, acceptedBreakpoints); + return internal.isAnyActive(acceptedBreakpoints); } } @@ -144,32 +143,67 @@ // Public methods and properties var self = { + html: null, + /** * Determines default debouncing interval of 'changed' method */ interval: 300, - /** - * Breakpoint aliases, listed from smallest to biggest - */ - breakpoints: null, - /** * Returns true if current breakpoint matches passed alias */ is: function (str) { if (internal.isAnExpression(str)) { return internal.isMatchingExpression(str); - } - return self.breakpoints[str] && self.breakpoints[str].is(':visible'); + } + + var match = false; + var breakpoints = Object.keys(internal.breakpoints) + $.each(breakpoints, function (index, alias) { + if (alias == str) { + var min = internal.breakpoints[alias]; + var max = internal.breakpoints[breakpoints[index + 1]] || 999999; + var width = self.html.width(); + + if (width >= min && width < max) { + match = true; + return false; + } + } + }); + + return match; }, /** * Initialize breakpoint detection divs */ - init: function () { - self.breakpoints = internal.detectionDivs; - internal.applyDetectionDivs(); + init: function () { + self.html = $('html'); + internal.resolveBreakpoints(); + + // Notify subscribers about page/content width change + $(function () { + if (window.EventBroker) { + var currentContentWidth = $('#content').width(); + var currentTier = self.current(); + $(window).on('resize', function () { + var contentWidth = $('#content').width(); + if (contentWidth !== currentContentWidth) { + currentContentWidth = contentWidth; + EventBroker.publish("page.resized", self); + + var tier = self.current(); + if (tier != currentTier) { + currentTier = tier; + console.debug("Grid tier changed: " + tier); + EventBroker.publish("page.gridtierchanged", tier); + } + } + }); + } + }); }, /** @@ -177,7 +211,7 @@ */ current: function () { var name = 'unrecognized'; - $.each(self.breakpoints, function (alias) { + $.each(internal.breakpoints, function (alias) { if (self.is(alias)) { name = alias; } @@ -200,11 +234,6 @@ }; - // Create a placeholder - $(document).ready(function () { - $('
    ').appendTo('body'); - }); - self.init(); return self; diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.Bare.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.Bare.cshtml index 71063b0a77..f1fdb63536 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.Bare.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.Bare.cshtml @@ -4,4 +4,6 @@ Html.AddBodyCssClass("bare py-0 m-0"); } -@RenderBody() \ No newline at end of file +
    + @RenderBody() +
    From 827ec6486258b2279e8e64ba88f1950e094e47da Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 19 Oct 2018 10:33:18 +0200 Subject: [PATCH 213/932] Updated OData package --- src/Plugins/SmartStore.AmazonPay/web.config | 6 +++--- src/Plugins/SmartStore.Clickatell/web.config | 6 +++--- src/Plugins/SmartStore.DevTools/Web.config | 6 +++--- .../SmartStore.DiscountRules/web.config | 6 +++--- .../SmartStore.FacebookAuth/web.config | 6 +++--- .../SmartStore.GoogleAnalytics/web.config | 6 +++--- .../web.config | 6 +++--- .../SmartStore.OfflinePayment/web.config | 6 +++--- src/Plugins/SmartStore.PayPal/web.config | 6 +++--- src/Plugins/SmartStore.Shipping/web.config | 6 +++--- .../SmartStore.ShippingByWeight/web.config | 6 +++--- src/Plugins/SmartStore.Tax/web.config | 6 +++--- .../SmartStore.Web.Framework.csproj | 20 ++++++++----------- .../SmartStore.Web.Framework/app.config | 6 +++--- .../SmartStore.Web.Framework/packages.config | 8 ++++---- .../SmartStore.Web/Administration/Web.config | 6 +++--- src/Presentation/SmartStore.Web/Web.config | 6 +++--- src/Tests/SmartStore.Web.MVC.Tests/App.config | 6 +++--- 18 files changed, 60 insertions(+), 64 deletions(-) diff --git a/src/Plugins/SmartStore.AmazonPay/web.config b/src/Plugins/SmartStore.AmazonPay/web.config index 7ae89c1d36..ae0230ca02 100644 --- a/src/Plugins/SmartStore.AmazonPay/web.config +++ b/src/Plugins/SmartStore.AmazonPay/web.config @@ -93,15 +93,15 @@ - + - + - + diff --git a/src/Plugins/SmartStore.Clickatell/web.config b/src/Plugins/SmartStore.Clickatell/web.config index bb33bef252..a476d4989f 100644 --- a/src/Plugins/SmartStore.Clickatell/web.config +++ b/src/Plugins/SmartStore.Clickatell/web.config @@ -90,15 +90,15 @@ - + - + - + diff --git a/src/Plugins/SmartStore.DevTools/Web.config b/src/Plugins/SmartStore.DevTools/Web.config index 9c694e9ed6..2f0ab2777e 100644 --- a/src/Plugins/SmartStore.DevTools/Web.config +++ b/src/Plugins/SmartStore.DevTools/Web.config @@ -94,15 +94,15 @@ - + - + - + diff --git a/src/Plugins/SmartStore.DiscountRules/web.config b/src/Plugins/SmartStore.DiscountRules/web.config index bb33bef252..a476d4989f 100644 --- a/src/Plugins/SmartStore.DiscountRules/web.config +++ b/src/Plugins/SmartStore.DiscountRules/web.config @@ -90,15 +90,15 @@ - + - + - + diff --git a/src/Plugins/SmartStore.FacebookAuth/web.config b/src/Plugins/SmartStore.FacebookAuth/web.config index 3c54ff640a..beb5523ac3 100644 --- a/src/Plugins/SmartStore.FacebookAuth/web.config +++ b/src/Plugins/SmartStore.FacebookAuth/web.config @@ -94,15 +94,15 @@ - + - + - + diff --git a/src/Plugins/SmartStore.GoogleAnalytics/web.config b/src/Plugins/SmartStore.GoogleAnalytics/web.config index 5e59e8cb6a..02745f9ebf 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/web.config +++ b/src/Plugins/SmartStore.GoogleAnalytics/web.config @@ -89,15 +89,15 @@ - + - + - + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config index bb33bef252..a476d4989f 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config @@ -90,15 +90,15 @@ - + - + - + diff --git a/src/Plugins/SmartStore.OfflinePayment/web.config b/src/Plugins/SmartStore.OfflinePayment/web.config index bb33bef252..a476d4989f 100644 --- a/src/Plugins/SmartStore.OfflinePayment/web.config +++ b/src/Plugins/SmartStore.OfflinePayment/web.config @@ -90,15 +90,15 @@ - + - + - + diff --git a/src/Plugins/SmartStore.PayPal/web.config b/src/Plugins/SmartStore.PayPal/web.config index 25acec1fc2..7ea242037d 100644 --- a/src/Plugins/SmartStore.PayPal/web.config +++ b/src/Plugins/SmartStore.PayPal/web.config @@ -106,15 +106,15 @@ - + - + - + diff --git a/src/Plugins/SmartStore.Shipping/web.config b/src/Plugins/SmartStore.Shipping/web.config index 5e59e8cb6a..02745f9ebf 100644 --- a/src/Plugins/SmartStore.Shipping/web.config +++ b/src/Plugins/SmartStore.Shipping/web.config @@ -89,15 +89,15 @@ - + - + - + diff --git a/src/Plugins/SmartStore.ShippingByWeight/web.config b/src/Plugins/SmartStore.ShippingByWeight/web.config index 5e59e8cb6a..02745f9ebf 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/web.config +++ b/src/Plugins/SmartStore.ShippingByWeight/web.config @@ -89,15 +89,15 @@ - + - + - + diff --git a/src/Plugins/SmartStore.Tax/web.config b/src/Plugins/SmartStore.Tax/web.config index bb33bef252..a476d4989f 100644 --- a/src/Plugins/SmartStore.Tax/web.config +++ b/src/Plugins/SmartStore.Tax/web.config @@ -90,15 +90,15 @@ - + - + - + diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index 86cc5586ac..28b1c8b4a9 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -118,17 +118,14 @@ ..\..\packages\LibSassHost.1.0.3\lib\net45\LibSassHost.dll True - - ..\..\packages\Microsoft.Data.Edm.5.8.2\lib\net40\Microsoft.Data.Edm.dll - True + + ..\..\packages\Microsoft.Data.Edm.5.8.4\lib\net40\Microsoft.Data.Edm.dll - - ..\..\packages\Microsoft.Data.OData.5.8.2\lib\net40\Microsoft.Data.OData.dll - True + + ..\..\packages\Microsoft.Data.OData.5.8.4\lib\net40\Microsoft.Data.OData.dll - - ..\..\packages\Microsoft.Data.Services.Client.5.8.2\lib\net40\Microsoft.Data.Services.Client.dll - True + + ..\..\packages\Microsoft.Data.Services.Client.5.8.4\lib\net40\Microsoft.Data.Services.Client.dll ..\..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll @@ -155,9 +152,8 @@ - - ..\..\packages\System.Spatial.5.8.2\lib\net40\System.Spatial.dll - True + + ..\..\packages\System.Spatial.5.8.4\lib\net40\System.Spatial.dll ..\..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll diff --git a/src/Presentation/SmartStore.Web.Framework/app.config b/src/Presentation/SmartStore.Web.Framework/app.config index 1300116038..d2ed8f831e 100644 --- a/src/Presentation/SmartStore.Web.Framework/app.config +++ b/src/Presentation/SmartStore.Web.Framework/app.config @@ -28,15 +28,15 @@ - + - + - + diff --git a/src/Presentation/SmartStore.Web.Framework/packages.config b/src/Presentation/SmartStore.Web.Framework/packages.config index 8cda45593f..c24bbb1532 100644 --- a/src/Presentation/SmartStore.Web.Framework/packages.config +++ b/src/Presentation/SmartStore.Web.Framework/packages.config @@ -24,16 +24,16 @@ - - - + + + - + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Web.config b/src/Presentation/SmartStore.Web/Administration/Web.config index 81f4bdf73a..105ffdffab 100644 --- a/src/Presentation/SmartStore.Web/Administration/Web.config +++ b/src/Presentation/SmartStore.Web/Administration/Web.config @@ -78,15 +78,15 @@ - + - + - + diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index 1d04a910d6..aa5a8bee34 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -279,15 +279,15 @@ - + - + - + diff --git a/src/Tests/SmartStore.Web.MVC.Tests/App.config b/src/Tests/SmartStore.Web.MVC.Tests/App.config index 6e69a831e0..f1d53ba4f5 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/App.config +++ b/src/Tests/SmartStore.Web.MVC.Tests/App.config @@ -47,15 +47,15 @@ - + - + - + From a88b5c78b7cd2eb72458770e02f16f3d536c3a2c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 20 Oct 2018 00:43:13 +0200 Subject: [PATCH 214/932] Updated corporate logo --- .../Content/images/smartstore.svg | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/smartstore.svg b/src/Presentation/SmartStore.Web/Administration/Content/images/smartstore.svg index b0d674db68..49fd5bc5e8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/images/smartstore.svg +++ b/src/Presentation/SmartStore.Web/Administration/Content/images/smartstore.svg @@ -1,40 +1,45 @@ - + + viewBox="0 0 250 44.8" style="enable-background:new 0 0 250 44.8;" xml:space="preserve"> - + - - - - - - - - - - - - - + + + + + + + + + + + + + From ee958ce50bc55eabef6ae01af8c63192abd1f486 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 22 Oct 2018 14:45:02 +0200 Subject: [PATCH 215/932] Added export and import of product tags --- changelog.md | 1 + .../Catalog/Importer/ProductImporter.cs | 523 ++++++++++-------- 2 files changed, 297 insertions(+), 227 deletions(-) diff --git a/changelog.md b/changelog.md index 6eb7561cd2..44e17faace 100644 --- a/changelog.md +++ b/changelog.md @@ -35,6 +35,7 @@ * Viveum: Supports payment via "Virtual Account Brands" like PayPal. * Added options for alternating price display (in badges). * #1515 Poll: Add result tab with a list of answers and customers for a poll +* Added export and import of product tags. ### Improvements * (Perf) Significantly increased query performance for products with a lot of category assignments (> 10). diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index b12d3c2d44..ba35980a99 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -23,207 +23,225 @@ namespace SmartStore.Services.Catalog.Importer { - public class ProductImporter : EntityImporterBase - { - private readonly IRepository _productPictureRepository; - private readonly IRepository _productManufacturerRepository; - private readonly IRepository _productCategoryRepository; - private readonly IRepository _productRepository; - private readonly ICommonServices _services; - private readonly ILocalizedEntityService _localizedEntityService; - private readonly IPictureService _pictureService; - private readonly IManufacturerService _manufacturerService; - private readonly ICategoryService _categoryService; - private readonly IProductService _productService; - private readonly IProductTemplateService _productTemplateService; - private readonly FileDownloadManager _fileDownloadManager; - - private static readonly Dictionary>> _localizableProperties = new Dictionary>> - { - { "Name", x => x.Name }, - { "ShortDescription", x => x.ShortDescription }, - { "FullDescription", x => x.FullDescription }, - { "MetaKeywords", x => x.MetaKeywords }, - { "MetaDescription", x => x.MetaDescription }, - { "MetaTitle", x => x.MetaTitle }, - { "BundleTitleText", x => x.BundleTitleText } - }; - - public ProductImporter( - IRepository productPictureRepository, - IRepository productManufacturerRepository, - IRepository productCategoryRepository, - IRepository productRepository, - ICommonServices services, - ILocalizedEntityService localizedEntityService, - IPictureService pictureService, - IManufacturerService manufacturerService, - ICategoryService categoryService, - IProductService productService, - IProductTemplateService productTemplateService, - FileDownloadManager fileDownloadManager) - { - _productPictureRepository = productPictureRepository; - _productManufacturerRepository = productManufacturerRepository; - _productCategoryRepository = productCategoryRepository; - _productRepository = productRepository; - _services = services; - _localizedEntityService = localizedEntityService; - _pictureService = pictureService; - _manufacturerService = manufacturerService; - _categoryService = categoryService; - _productService = productService; - _productTemplateService = productTemplateService; - _fileDownloadManager = fileDownloadManager; - } - - protected override void Import(ImportExecuteContext context) - { - var srcToDestId = new Dictionary(); - var importStartTime = DateTime.UtcNow; - var templateViewPaths = _productTemplateService.GetAllProductTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); - - using (var scope = new DbContextScope(ctx: _productRepository.Context, hooksEnabled: false, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) - { - var segmenter = context.DataSegmenter; - - Initialize(context); - - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) - { - var batch = segmenter.GetCurrentBatch(); - - // Perf: detach entities - _productRepository.Context.DetachEntities(x => - { - return x is Product || x is UrlRecord || x is StoreMapping || x is ProductVariantAttribute || x is LocalizedProperty || - x is ProductBundleItem || x is ProductCategory || x is ProductManufacturer || x is Category || x is Manufacturer || - x is ProductPicture || x is Picture || x is ProductTag || x is TierPrice; - }); - //_productRepository.Context.DetachAll(true); - - context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); - - // =========================================================================== - // 1.) Import products - // =========================================================================== - int savedProducts = 0; - try - { - savedProducts = ProcessProducts(context, batch, templateViewPaths, srcToDestId); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProducts"); - } - - // reduce batch to saved (valid) products. - // No need to perform import operations on errored products. - batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); - - // update result object - context.Result.NewRecords += batch.Count(x => x.IsNew); - context.Result.ModifiedRecords += Math.Max(0, savedProducts - context.Result.NewRecords); - - // =========================================================================== - // 2.) Import SEO Slugs - // IMPORTANT: Unlike with Products AutoCommitEnabled must be TRUE, - // as Slugs are going to be validated against existing ones in DB. - // =========================================================================== - if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) - { - try - { - _productRepository.Context.AutoDetectChangesEnabled = true; - ProcessSlugs(context, batch, typeof(Product).Name); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessSlugs"); - } - finally - { - _productRepository.Context.AutoDetectChangesEnabled = false; - } - } - - // =========================================================================== - // 3.) Import StoreMappings - // =========================================================================== - if (segmenter.HasColumn("StoreIds")) - { - try - { - ProcessStoreMappings(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); - } - } - - // =========================================================================== - // 4.) Import Localizations - // =========================================================================== - try - { - ProcessLocalizations(context, batch, _localizableProperties); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessLocalizations"); - } - - // =========================================================================== - // 5.) Import product category mappings - // =========================================================================== - if (segmenter.HasColumn("CategoryIds")) - { - try - { - ProcessProductCategories(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductCategories"); - } - } - - // =========================================================================== - // 6.) Import product manufacturer mappings - // =========================================================================== - if (segmenter.HasColumn("ManufacturerIds")) - { - try - { - ProcessProductManufacturers(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductManufacturers"); - } - } - - // =========================================================================== - // 7.) Import product picture mappings - // =========================================================================== - if (segmenter.HasColumn("ImageUrls")) - { - try - { - ProcessProductPictures(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductPictures"); - } - } - } - - // =========================================================================== - // 8.) Map parent id of inserted products - // =========================================================================== - if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentGroupedProductId") && !segmenter.IsIgnored("ParentGroupedProductId")) + public class ProductImporter : EntityImporterBase + { + private readonly IRepository _productPictureRepository; + private readonly IRepository _productManufacturerRepository; + private readonly IRepository _productCategoryRepository; + private readonly IRepository _productTagRepository; + private readonly IRepository _productRepository; + private readonly ICommonServices _services; + private readonly ILocalizedEntityService _localizedEntityService; + private readonly IPictureService _pictureService; + private readonly IManufacturerService _manufacturerService; + private readonly ICategoryService _categoryService; + private readonly IProductService _productService; + private readonly IProductTemplateService _productTemplateService; + private readonly FileDownloadManager _fileDownloadManager; + + private static readonly Dictionary>> _localizableProperties = new Dictionary>> + { + { "Name", x => x.Name }, + { "ShortDescription", x => x.ShortDescription }, + { "FullDescription", x => x.FullDescription }, + { "MetaKeywords", x => x.MetaKeywords }, + { "MetaDescription", x => x.MetaDescription }, + { "MetaTitle", x => x.MetaTitle }, + { "BundleTitleText", x => x.BundleTitleText } + }; + + public ProductImporter( + IRepository productPictureRepository, + IRepository productManufacturerRepository, + IRepository productCategoryRepository, + IRepository productTagRepository, + IRepository productRepository, + ICommonServices services, + ILocalizedEntityService localizedEntityService, + IPictureService pictureService, + IManufacturerService manufacturerService, + ICategoryService categoryService, + IProductService productService, + IProductTemplateService productTemplateService, + FileDownloadManager fileDownloadManager) + { + _productPictureRepository = productPictureRepository; + _productManufacturerRepository = productManufacturerRepository; + _productCategoryRepository = productCategoryRepository; + _productTagRepository = productTagRepository; + _productRepository = productRepository; + _services = services; + _localizedEntityService = localizedEntityService; + _pictureService = pictureService; + _manufacturerService = manufacturerService; + _categoryService = categoryService; + _productService = productService; + _productTemplateService = productTemplateService; + _fileDownloadManager = fileDownloadManager; + } + + protected override void Import(ImportExecuteContext context) + { + var srcToDestId = new Dictionary(); + var importStartTime = DateTime.UtcNow; + var templateViewPaths = _productTemplateService.GetAllProductTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); + + using (var scope = new DbContextScope(ctx: _productRepository.Context, hooksEnabled: false, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) + { + var segmenter = context.DataSegmenter; + + Initialize(context); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + { + var batch = segmenter.GetCurrentBatch(); + + // Perf: detach entities + _productRepository.Context.DetachEntities(x => + { + return x is Product || x is UrlRecord || x is StoreMapping || x is ProductVariantAttribute || x is LocalizedProperty || + x is ProductBundleItem || x is ProductCategory || x is ProductManufacturer || x is Category || x is Manufacturer || + x is ProductPicture || x is Picture || x is ProductTag || x is TierPrice; + }); + //_productRepository.Context.DetachAll(true); + + context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + + // =========================================================================== + // 1.) Import products + // =========================================================================== + int savedProducts = 0; + try + { + savedProducts = ProcessProducts(context, batch, templateViewPaths, srcToDestId); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessProducts"); + } + + // reduce batch to saved (valid) products. + // No need to perform import operations on errored products. + batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); + + // update result object + context.Result.NewRecords += batch.Count(x => x.IsNew); + context.Result.ModifiedRecords += Math.Max(0, savedProducts - context.Result.NewRecords); + + // =========================================================================== + // 2.) Import SEO Slugs + // IMPORTANT: Unlike with Products AutoCommitEnabled must be TRUE, + // as Slugs are going to be validated against existing ones in DB. + // =========================================================================== + if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) + { + try + { + _productRepository.Context.AutoDetectChangesEnabled = true; + ProcessSlugs(context, batch, typeof(Product).Name); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessSlugs"); + } + finally + { + _productRepository.Context.AutoDetectChangesEnabled = false; + } + } + + // =========================================================================== + // 3.) Import StoreMappings + // =========================================================================== + if (segmenter.HasColumn("StoreIds")) + { + try + { + ProcessStoreMappings(context, batch); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessStoreMappings"); + } + } + + // =========================================================================== + // 4.) Import Localizations + // =========================================================================== + try + { + ProcessLocalizations(context, batch, _localizableProperties); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessLocalizations"); + } + + // =========================================================================== + // 5.) Import product category mappings + // =========================================================================== + if (segmenter.HasColumn("CategoryIds")) + { + try + { + ProcessProductCategories(context, batch); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessProductCategories"); + } + } + + // =========================================================================== + // 6.) Import product manufacturer mappings + // =========================================================================== + if (segmenter.HasColumn("ManufacturerIds")) + { + try + { + ProcessProductManufacturers(context, batch); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessProductManufacturers"); + } + } + + // =========================================================================== + // 7.) Import product picture mappings + // =========================================================================== + if (segmenter.HasColumn("ImageUrls")) + { + try + { + ProcessProductPictures(context, batch); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessProductPictures"); + } + } + + // =========================================================================== + // 8.) Import product tag names + // =========================================================================== + if (segmenter.HasColumn("TagNames")) + { + try + { + ProcessProductTags(context, batch); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessProductTags"); + } + } + } + + // =========================================================================== + // 9.) Map parent id of inserted products + // =========================================================================== + if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentGroupedProductId") && !segmenter.IsIgnored("ParentGroupedProductId")) { segmenter.Reset(); @@ -237,15 +255,15 @@ x is ProductBundleItem || x is ProductCategory || x is ProductManufacturer || x { ProcessProductMappings(context, batch, srcToDestId); } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessParentMappings"); + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessParentMappings"); } } } // =========================================================================== - // 9.) PostProcess: normalization + // 10.) PostProcess: normalization // =========================================================================== DataMigrator.FixProductMainPictureIds(_productRepository.Context, importStartTime); } @@ -304,7 +322,7 @@ protected virtual int ProcessProducts( continue; } - // a Name is required for new products. + // A name is required for new products. if (!row.HasDataValue("Name")) { ++context.Result.SkippedRecords; @@ -447,16 +465,17 @@ protected virtual int ProcessProducts( } } - // commit whole batch at once + // Commit whole batch at once. var num = _productRepository.Context.SaveChanges(); - // get new product ids + // Get new product ids. foreach (var row in batch) { var id = row.GetDataValue("Id"); - - if (id != 0 && srcToDestId.ContainsKey(id)) - srcToDestId[id].DestinationId = row.Entity.Id; + if (id != 0 && srcToDestId.ContainsKey(id)) + { + srcToDestId[id].DestinationId = row.Entity.Id; + } } return num; @@ -476,7 +495,7 @@ protected virtual int ProcessProductMappings( if (id != 0 && parentGroupedProductId != 0 && srcToDestId.ContainsKey(id) && srcToDestId.ContainsKey(parentGroupedProductId)) { - // only touch relationship if child and parent were inserted + // Only touch relationship if child and parent were inserted. if (srcToDestId[id].Inserted && srcToDestId[parentGroupedProductId].Inserted && srcToDestId[id].DestinationId != 0) { var product = _productRepository.GetById(srcToDestId[id].DestinationId); @@ -490,7 +509,6 @@ protected virtual int ProcessProductMappings( } var num = _productRepository.Context.SaveChanges(); - return num; } @@ -513,7 +531,7 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); var imageFiles = new List(); - // collect required image file infos + // Collect required image file infos. foreach (var urlOrPath in imageUrls) { var image = CreateDownloadImage(urlOrPath, seoName, ++imageNumber); @@ -525,7 +543,7 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu break; } - // download images + // Download images. if (imageFiles.Any(x => x.Url.HasValue())) { // async downloading in batch processing is inefficient cause only the image processing benefits from async, @@ -534,7 +552,7 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, imageFiles.Where(x => x.Url.HasValue() && !x.Success.HasValue))); } - // import images + // Import images. foreach (var image in imageFiles.OrderBy(x => x.DisplayOrder)) { try @@ -589,9 +607,9 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); } } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddWarning(exception.ToAllMessages(), row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + context.Result.AddWarning(ex.ToAllMessages(), row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); } } } @@ -612,7 +630,7 @@ protected virtual int ProcessProductManufacturers(ImportExecuteContext context, { if (_productManufacturerRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.ManufacturerId == id).FirstOrDefault() == null) { - // ensure that manufacturer exists + // Ensure that manufacturer exists. var manufacturer = _manufacturerService.GetManufacturerById(id); if (manufacturer != null) { @@ -628,16 +646,15 @@ protected virtual int ProcessProductManufacturers(ImportExecuteContext context, } } } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "ManufacturerIds"); + context.Result.AddWarning(ex.Message, row.GetRowInfo(), "ManufacturerIds"); } } } - // commit whole batch at once + // Commit whole batch at once. var num = _productManufacturerRepository.Context.SaveChanges(); - return num; } @@ -656,7 +673,7 @@ protected virtual int ProcessProductCategories(ImportExecuteContext context, IEn { if (_productCategoryRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.CategoryId == id).FirstOrDefault() == null) { - // ensure that category exists + // Ensure that category exists. var category = _categoryService.GetCategoryById(id); if (category != null) { @@ -672,19 +689,71 @@ protected virtual int ProcessProductCategories(ImportExecuteContext context, IEn } } } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "CategoryIds"); + context.Result.AddWarning(ex.Message, row.GetRowInfo(), "CategoryIds"); } } } - // commit whole batch at once + // Commit whole batch at once, var num = _productCategoryRepository.Context.SaveChanges(); return num; } - private int? ZeroToNull(object value, CultureInfo culture) + protected virtual int ProcessProductTags(ImportExecuteContext context, IEnumerable> batch) + { + // true, cause product tags must be saved and assigned an id prior adding a mapping. + _productPictureRepository.AutoCommitEnabled = true; + + var productIds = batch.Select(x => x.Entity.Id).ToList(); + var tagsPerBatch = _productRepository.TableUntracked + .Expand(x => x.ProductTags) + .Where(x => productIds.Contains(x.Id)) + .ToDictionary(x => x.Id, x => x.ProductTags); + + foreach (var row in batch) + { + var tagNames = row.GetDataValue>("TagNames"); + if (!tagNames.IsNullOrEmpty()) + { + try + { + var product = row.Entity; + if (!tagsPerBatch.TryGetValue(product.Id, out var existingTags)) + { + existingTags = new List(); + } + + foreach (var tagName in tagNames) + { + if (!existingTags.Any(x => x.Name.IsCaseInsensitiveEqual(tagName))) + { + var productTag = _productTagRepository.Table.FirstOrDefault(x => x.Name == tagName); + if (productTag == null) + { + productTag = new ProductTag { Name = tagName }; + + _productTagRepository.Insert(productTag); + } + + row.Entity.ProductTags.Add(productTag); + } + } + } + catch (Exception ex) + { + context.Result.AddWarning(ex.Message, row.GetRowInfo(), "TagNames"); + } + } + } + + // Commit whole batch at once. + var num = _productRepository.Context.SaveChanges(); + return num; + } + + private int? ZeroToNull(object value, CultureInfo culture) { int result; if (CommonHelper.TryConvert(value, culture, out result) && result > 0) From b6f81c243612ec6f570e911a744236fa8289b739 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 22 Oct 2018 15:36:27 +0200 Subject: [PATCH 216/932] Added more mimetype handling to RoxyFileManager --- .../Controllers/RoxyFileManagerController.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs index 9d839e5ed9..2f20cb39dc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs @@ -115,13 +115,24 @@ private string GetFileContentType(string extension) { extension = extension.EmptyNull().ToLower(); - if (extension == ".swf" || extension == ".flv") - return "flash"; + var mimeType = MimeTypes.MapNameToMimeType(extension).EmptyNull(); - if (MimeTypes.MapNameToMimeType(extension).EmptyNull().StartsWith("image")) + if (extension == ".swf" || extension == ".flv") + return "flash"; + + if (mimeType.StartsWith("image")) return "image"; - return "file"; + if (mimeType.StartsWith("audio")) + return "audio"; + + if (mimeType.StartsWith("video")) + return "video"; + + if (mimeType.EndsWith("/pdf")) + return "pdf"; + + return "file"; } private bool IsAllowedFileType(string extension) From 277d828eb4cfe99b3265d08fe860900038c91a2d Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 22 Oct 2018 21:43:35 +0200 Subject: [PATCH 217/932] Included accordion.scss --- .../SmartStore.Web/SmartStore.Web.csproj | 3 + .../Themes/Flex/Content/_accordion.scss | 85 +++++++++++++++++++ .../Themes/Flex/Content/theme.scss | 1 + 3 files changed, 89 insertions(+) create mode 100644 src/Presentation/SmartStore.Web/Themes/Flex/Content/_accordion.scss diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index 1961bf7d99..2cde8981c8 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -1773,6 +1773,9 @@ + + PreserveNewest + Web.config diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_accordion.scss b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_accordion.scss new file mode 100644 index 0000000000..3388c6538a --- /dev/null +++ b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_accordion.scss @@ -0,0 +1,85 @@ +/// +/// +/// + +// +// Accordion +// -------------------------------------------------- + +.accordion { + padding-bottom: 1px; + + .card+.card { + margin-top: -2px; + } + + .card-title { + margin: 0; + transition: background-color ease-in-out 0.12s; + + &:hover { + background-color: $gray-200; + } + + .fa-chevron { + position: absolute; + left: 12px; + font-size: 14px; + top: 50%; + margin-top: -8px; + opacity: 0.4; + transition: transform 0.35s ease-out; + } + + .card-title-link { + background-color: $danger; + color: #fff !important; + text-decoration: none; + font-weight: 600; + font-size: 1.2rem; + display: block; + position: relative; + vertical-align: middle; + padding: 15px 12px 15px 40px; + + &[aria-expanded=true] .fa-chevron { + opacity: 1; + transform: rotate(-180deg); + } + + &.collapsed { + background-color: $card-cap-bg !important; + color: inherit !important; + } + } + + .hint { + display: inline-block; + font-size: 0.85rem; + opacity: 0.4; + line-height: 1.5; + } + } + + .card-header { + padding: 0; + } + + .card, + .card .card-header { + border-radius: 0; + } + + .card:first-child, + .card:first-child .card-header, + .card:first-child .card-title-link { + border-top-left-radius: $border-radius; + border-top-right-radius: $border-radius; + } + + .card:last-child, + .card:last-child .card-header{ + border-bottom-left-radius: $border-radius; + border-bottom-right-radius: $border-radius; + } +} diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Content/theme.scss b/src/Presentation/SmartStore.Web/Themes/Flex/Content/theme.scss index d28800353d..fd7d6082dc 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Content/theme.scss +++ b/src/Presentation/SmartStore.Web/Themes/Flex/Content/theme.scss @@ -113,6 +113,7 @@ @import "_gallery.scss"; @import "_cart.scss"; @import "_checkout.scss"; +@import "_accordion.scss"; @import "_misc.scss"; @import "_print.scss"; From fdf4f1829c714c51c487dd51ea16d98001dcbfb5 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 23 Oct 2018 03:26:46 +0200 Subject: [PATCH 218/932] Less CSS selectors for AOS --- .../Content/shared/_buttons.scss | 95 +++++++++++++++++++ .../Content/vendors/aos/scss/_animations.scss | 8 +- .../Content/vendors/aos/scss/_core.scss | 39 ++++---- .../Content/vendors/aos/scss/_easing.scss | 16 ++-- 4 files changed, 133 insertions(+), 25 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Content/shared/_buttons.scss b/src/Presentation/SmartStore.Web/Content/shared/_buttons.scss index fc1108016d..575ff64ead 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_buttons.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_buttons.scss @@ -547,6 +547,101 @@ $btn-size-sm: $input-height-sm; } +// 3d Buttons (colorized drop shadows) +// ----------------------------------------------------- + +@mixin button-3d-state($color, $state: "normal") { + $c: theme-color($color); + + $c1: mix($c, #000, 40%); + $c2: mix($c, #000, 10%); + $o1: 0.11; + $o2: 0.09; + + @if $color == "secondary" or $color == "light" { + $c1: #333; + $c2: #000; + $o1: 0.11; + $o2: 0.08; + } + + border-color: $c; + + @if $state == 'normal' { + box-shadow: 0 4px 6px rgba($c1, $o1), 0 1px 3px rgba($c2, $o2); + } + @else if $state == 'pressed' { + @if $color == 'secondary' { + background-color: $gray-100; + border-color: $gray-100; + } + box-shadow: 0 2px 4px rgba($c1, $o1 * 1.36), 0 1px 2px rgba($c2, $o2 * 1.25); + } + @else if $state == 'active' { + @if $color == 'secondary' { + background-color: #fff; + } + //box-shadow: 0 7px 14px rgba($c1, $o1 * 0.9), 0 3px 6px rgba($c2, $o2); + box-shadow: 0 9px 16px rgba($c1, $o1 * 1.4), 0 3px 6px rgba($c2, $o2); + } +} + +.btn-3d { + transition-property: transform, box-shadow, color, background-color, border-color, opacity; + transition-timing-function: cubic-bezier(.2, 1, .22, 1); + transition-timing-function: cubic-bezier(0.25, 1, 0.33, 1); + transition-duration: .35s; + + &:hover, &.hover, + &:focus, &.focus { + transform: translateY(-3px); + } + + &:active, &.active, + &:disabled, &.disabled, + .show > &.dropdown-toggle { + transform: translateY(1px); + } +} + +@each $color, $value in $theme-colors { + .btn-3d.btn-#{$color} { + @include button-3d-state($color, 'normal'); + } + + .btn-3d.btn-#{$color}, + .btn-3d.btn-outline-#{$color} { + &:not(:disabled):not(.disabled):hover, + &:not(:disabled):not(.disabled).hover, + &:not(:disabled):not(.disabled):focus, + &:not(:disabled):not(.disabled).focus { + @include button-3d-state($color, 'active'); + } + + &:disabled, + &.disabled { + @if $color == 'secondary' { + @include button-3d-state($color, 'pressed'); + background-color: #fff; + } + @else { + box-shadow: none; + } + + } + + &:not(:disabled):not(.disabled):active, + &:not(:disabled):not(.disabled):active:focus, + &:not(:disabled):not(.disabled).active, + &:not(:disabled):not(.disabled).active:focus, + .show > &.dropdown-toggle, + .show > &.dropdown-toggle:focus { + @include button-3d-state($color, 'pressed'); + } + } +} + + // Social buttons // -------------------------------------------------- diff --git a/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_animations.scss b/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_animations.scss index 7f13909778..0e8c54e353 100644 --- a/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_animations.scss +++ b/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_animations.scss @@ -1,8 +1,13 @@ +// ---------------------------------- +// Edit info (mc): +// - Removed root html:not(.no-js) +// - body[data-aos-easing="#{$key}"] & >> body[data-aos-easing="#{$key}"] [data-aos] +// ---------------------------------- + // Animations variables $aos-distance: 100px !default; @media screen { - html:not(.no-js) { /** * Fade animations: * fade @@ -176,5 +181,4 @@ $aos-distance: 100px !default; transform: perspective(2500px) rotateX(100deg); &.aos-animate {transform: perspective(2500px) rotateX(0);} } - } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_core.scss b/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_core.scss index 193594d7e4..fc28ff789d 100644 --- a/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_core.scss +++ b/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_core.scss @@ -1,25 +1,32 @@ + // Generate Duration && Delay -[data-aos] { - @for $i from 1 through 60 { - body[data-aos-duration='#{$i * 50}'] &, - &[data-aos][data-aos-duration='#{$i * 50}'] { - transition-duration: #{$i * 50}ms; +// ---------------------------------- +// Edit info (mc): +// - Removed root [data-aos] +// - body[data-aos-duration='#{$i * 50}'] & >> body[data-aos-duration='#{$i * 50}'] [data-aos] +// - 50ms steps > 100ms steps +// - from 1 through 60 > 30 +// ---------------------------------- + +@for $i from 1 through 30 { + body[data-aos-duration='#{$i * 100}'] [data-aos], + [data-aos][data-aos-duration='#{$i * 100}'] { + transition-duration: #{$i * 100}ms; } - body[data-aos-delay='#{$i * 50}'] &, - &[data-aos][data-aos-delay='#{$i * 50}'] { - transition-delay: 0s; + body[data-aos-delay='#{$i * 100}'] [data-aos], + [data-aos][data-aos-delay='#{$i * 100}'] { + transition-delay: 0s; - &.aos-animate { - transition-delay: #{$i * 50}ms; - } + &.aos-animate { + transition-delay: #{$i * 100}ms; + } } - } } [data-aos] { - pointer-events: none; - &.aos-animate { - pointer-events: auto; - } + pointer-events: none; + &.aos-animate { + pointer-events: auto; + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_easing.scss b/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_easing.scss index f7e6ff20df..e0c261f227 100644 --- a/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_easing.scss +++ b/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_easing.scss @@ -29,12 +29,14 @@ $aos-easing: ( // Easings implementations // Default timing function: 'ease' - -[data-aos] { - @each $key, $val in $aos-easing { - body[data-aos-easing="#{$key}"] &, - &[data-aos][data-aos-easing="#{$key}"] { - transition-timing-function: $val; +// ---------------------------------- +// Edit info (mc): +// - Removed root [data-aos] +// - body[data-aos-easing="#{$key}"] & >> body[data-aos-easing="#{$key}"] [data-aos] +// ---------------------------------- +@each $key, $val in $aos-easing { + body[data-aos-easing="#{$key}"] [data-aos], + [data-aos][data-aos-easing="#{$key}"] { + transition-timing-function: $val; } - } } \ No newline at end of file From 728d1e2e53356d1f6da50675638af5b0cc521c07 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 23 Oct 2018 03:26:55 +0200 Subject: [PATCH 219/932] Minor UI stuff --- .../SmartStore.Web.Framework.csproj | 1 + .../UI/Components/BoxSpacing.cs | 26 +++++++++++++++++++ .../Scripts/smartstore.viewport.js | 10 ++----- 3 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 src/Presentation/SmartStore.Web.Framework/UI/Components/BoxSpacing.cs diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index 28b1c8b4a9..66e319ba5e 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -318,6 +318,7 @@ + diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/BoxSpacing.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/BoxSpacing.cs new file mode 100644 index 0000000000..8b72005580 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/BoxSpacing.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace SmartStore.Web.Framework.UI +{ + public class BoxSpacing + { + [Range(0, 6)] + [JsonProperty("t")] + public byte? Top { get; set; } + + [Range(0, 6)] + [JsonProperty("r")] + public byte? Right { get; set; } + + [Range(0, 6)] + [JsonProperty("b")] + public byte? Bottom { get; set; } + + [Range(0, 6)] + [JsonProperty("l")] + public byte? Left { get; set; } + } +} diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.viewport.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.viewport.js index 05d7a672a6..27fa9618b9 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.viewport.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.viewport.js @@ -70,7 +70,7 @@ */ isAnyActive: function (breakpoints) { var found = false; - var width = self.html.width(); + var width = window.innerWidth; $.each(breakpoints, function (index, alias) { // Once first breakpoint matches, return true and break out of the loop @@ -132,8 +132,6 @@ var acceptedBreakpoints = breakpointList.slice(start, end); - //console.log("BREAKPOINTS", str, acceptedBreakpoints); - return internal.isAnyActive(acceptedBreakpoints); } } @@ -142,9 +140,6 @@ // Public methods and properties var self = { - - html: null, - /** * Determines default debouncing interval of 'changed' method */ @@ -164,7 +159,7 @@ if (alias == str) { var min = internal.breakpoints[alias]; var max = internal.breakpoints[breakpoints[index + 1]] || 999999; - var width = self.html.width(); + var width = window.innerWidth; if (width >= min && width < max) { match = true; @@ -180,7 +175,6 @@ * Initialize breakpoint detection divs */ init: function () { - self.html = $('html'); internal.resolveBreakpoints(); // Notify subscribers about page/content width change From 1ba6b9ae2acd070c22cdec6900a80b0824c86deb Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 23 Oct 2018 15:50:28 +0200 Subject: [PATCH 220/932] Getting started with import/export enhancements --- .../Domain/DataExchange/ExportEnums.cs | 5 +- .../Domain/DataExchange/ExportProfile.cs | 16 +- .../Domain/DataExchange/ImportProfile.cs | 13 +- .../Mapping/Tasks/ScheduleTaskHistoryMap.cs | 2 +- ...14068_DataExchangeEnhancements.Designer.cs | 29 ++ ...01810231214068_DataExchangeEnhancements.cs | 50 +++ ...810231214068_DataExchangeEnhancements.resx | 126 ++++++ .../SmartStore.Data/SmartStore.Data.csproj | 7 + .../DataExchange/Export/DataExportResult.cs | 13 +- .../DataExchange/Export/DataExporter.cs | 363 +++++++++++------- .../Export/ExportExecuteContext.cs | 43 ++- .../DataExchange/Export/ExportExtensions.cs | 35 +- .../SmartStore.Services/Tasks/TaskExecutor.cs | 2 +- .../Controllers/ExportController.cs | 52 ++- .../Controllers/ImportController.cs | 18 +- .../Models/DataExchange/ExportProfileModel.cs | 13 +- .../Models/DataExchange/ImportProfileModel.cs | 5 +- .../Administration/Views/Export/Edit.cshtml | 20 +- .../Views/Export/ProfileFileDetails.cshtml | 4 +- .../Views/Import/_Update.cshtml | 12 + 20 files changed, 602 insertions(+), 226 deletions(-) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.resx diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs index c795d66377..036b108512 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs @@ -13,7 +13,10 @@ public enum ExportEntityType Customer, Order, NewsLetterSubscription, - ShoppingCartItem + ShoppingCartItem, + + // Related data + TierPrice = 100 } /// diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs index 3161d1ea94..3d75cf06ef 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; -using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Tasks; namespace SmartStore.Core.Domain { - public class ExportProfile : BaseEntity, ICloneable + public class ExportProfile : BaseEntity, ICloneable { private ICollection _deployments; @@ -52,10 +51,15 @@ public ExportProfile() /// public bool Enabled { get; set; } - /// - /// The scheduling task identifier - /// - public int SchedulingTaskId { get; set; } + /// + /// Whether the export related data + /// + public bool ExportRelatedData { get; set; } + + /// + /// The scheduling task identifier + /// + public int SchedulingTaskId { get; set; } /// /// XML with filtering information diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs index 1139496b4a..4322a88f2f 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs @@ -60,10 +60,15 @@ public ImportEntityType EntityType /// public bool Enabled { get; set; } - /// - /// Number of records to bypass - /// - public int Skip { get; set; } + /// + /// Whether the import related data + /// + public bool ImportRelatedData { get; set; } + + /// + /// Number of records to bypass + /// + public int Skip { get; set; } /// /// Maximum number of records to return diff --git a/src/Libraries/SmartStore.Data/Mapping/Tasks/ScheduleTaskHistoryMap.cs b/src/Libraries/SmartStore.Data/Mapping/Tasks/ScheduleTaskHistoryMap.cs index ff483c81ee..8a715d40a8 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Tasks/ScheduleTaskHistoryMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/Tasks/ScheduleTaskHistoryMap.cs @@ -9,7 +9,7 @@ public ScheduleTaskHistoryMap() { ToTable("ScheduleTaskHistory"); HasKey(x => x.Id); - Property(x => x.MachineName).HasMaxLength(400); + Property(x => x.MachineName).IsRequired().HasMaxLength(400); Property(x => x.Error).HasMaxLength(1000); Property(x => x.ProgressMessage).HasMaxLength(1000); diff --git a/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.Designer.cs new file mode 100644 index 0000000000..9cf3ad8ecd --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class DataExchangeEnhancements : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(DataExchangeEnhancements)); + + string IMigrationMetadata.Id + { + get { return "201810231214068_DataExchangeEnhancements"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.cs b/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.cs new file mode 100644 index 0000000000..1bdcda90d3 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.cs @@ -0,0 +1,50 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class DataExchangeEnhancements : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + DropIndex("dbo.ScheduleTaskHistory", new[] { "MachineName", "IsRunning" }); + AddColumn("dbo.ImportProfile", "ImportRelatedData", c => c.Boolean(nullable: false)); + AddColumn("dbo.ExportProfile", "ExportRelatedData", c => c.Boolean(nullable: false)); + AlterColumn("dbo.ScheduleTaskHistory", "MachineName", c => c.String(nullable: false, maxLength: 400)); + CreateIndex("dbo.ScheduleTaskHistory", new[] { "MachineName", "IsRunning" }); + } + + public override void Down() + { + DropIndex("dbo.ScheduleTaskHistory", new[] { "MachineName", "IsRunning" }); + AlterColumn("dbo.ScheduleTaskHistory", "MachineName", c => c.String(maxLength: 400)); + DropColumn("dbo.ExportProfile", "ExportRelatedData"); + DropColumn("dbo.ImportProfile", "ImportRelatedData"); + CreateIndex("dbo.ScheduleTaskHistory", new[] { "MachineName", "IsRunning" }); + } + + public bool RollbackOnFailure => false; + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + context.SaveChanges(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.DataExchange.Export.ExportRelatedData", + "Export associated data", + "Zugehrige Daten exportieren", + "Specifies whether to also export associated data (e.g. tier prices).", + "Legt fest, ob auch zugehrige Daten (z.B. Staffelpreise) exportiert werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Import.ImportRelatedData", + "Import associated data", + "Zugehrige Daten importieren", + "Specifies whether to also import associated data (e.g. tier prices).", + "Legt fest, ob auch zugehrige Daten (z.B. Staffelpreise) importiert werden sollen."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.resx b/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.resx new file mode 100644 index 0000000000..7fd395473e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index b0d59e0d11..cce37a439c 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -632,6 +632,10 @@ 201810011954195_ForumPostVote.cs + + + 201810231214068_DataExchangeEnhancements.cs + @@ -1135,6 +1139,9 @@ 201810011954195_ForumPostVote.cs + + 201810231214068_DataExchangeEnhancements.cs + diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs index 4be79380ee..4893ef1c6d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; using System.Xml.Serialization; +using SmartStore.Core.Domain.DataExchange; namespace SmartStore.Services.DataExchange.Export { - [Serializable] + [Serializable] public class DataExportResult { public DataExportResult() @@ -55,10 +56,10 @@ public class ExportFileInfo /// public string Label { get; set; } - /// - /// Whether the file contains entity data - /// - public bool IsDataFile { get; set; } - } + /// + /// The entity type. + /// + public ExportEntityType? EntityType { get; set; } + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index fa6461e8cb..e88f2d09e5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -265,7 +265,49 @@ private bool HasPermission(DataExporterContext ctx) return true; } - private void DetachAllEntitiesAndClear(DataExporterContext ctx) + private void StreamToFile(DataExporterContext ctx, Stream stream, string path, Action onDisposed) + { + if (stream != null) + { + try + { + if (ctx.IsFileBasedExport && path.HasValue() && stream.Length > 0) + { + if (!stream.CanSeek) + { + ctx.Log.Warn("Data stream seems to be closed!"); + } + + stream.Seek(0, SeekOrigin.Begin); + + using (_rwLock.GetWriteLock()) + using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + { + ctx.Log.Info($"Creating file {path}."); + stream.CopyTo(fileStream); + } + } + } + catch (Exception ex) + { + ctx.ExecuteContext.Abort = DataExchangeAbortion.Hard; + ctx.Log.ErrorFormat(ex, $"Failed to stream file {path}."); + ctx.Result.LastError = ex.ToString(); + } + finally + { + stream.Dispose(); + onDisposed(stream); + } + } + + if (ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard && ctx.IsFileBasedExport && path.HasValue()) + { + FileSystemHelper.Delete(path); + } + } + + private void DetachAllEntitiesAndClear(DataExporterContext ctx) { try { @@ -454,67 +496,78 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in return ctx.ExecuteContext.DataSegmenter as IExportDataSegmenterProvider; } - private bool CallProvider(DataExporterContext ctx, string streamId, string method, string path) + private void AddUnitsForRelatedData(DataExporterContext ctx) + { + if (ctx.Request.Provider.Value.EntityType != ExportEntityType.Product) + { + return; + } + + var entityTypes = new ExportEntityType[] + { + ExportEntityType.TierPrice + }; + + var context = ctx.ExecuteContext; + var fileExtension = Path.GetExtension(context.FileName); + + foreach (var type in entityTypes) + { + var fileName = ctx.Request.Profile.ResolveFileNamePattern(ctx.Store, context.FileIndex, context.MaxFileNameLength, type.ToString()) + fileExtension; + + context.ExtraDataUnits.Add(new ExportDataUnit + { + IsRelatedData = true, + EntityType = type, + DisplayInFileDialog = true, + FileName = fileName, + DataStream = new MemoryStream() + }); + } + } + + private bool CallProvider(DataExporterContext ctx, string method, string path) { if (method != "Execute" && method != "OnExecuted") { throw new SmartException($"Unknown export method {method.NaIfEmpty()}."); } + var context = ctx.ExecuteContext; + try { - ctx.ExecuteContext.DataStreamId = streamId; + context.DataStream = new MemoryStream(); - using (ctx.ExecuteContext.DataStream = new MemoryStream()) - { - if (method == "Execute") - { - ctx.Request.Provider.Value.Execute(ctx.ExecuteContext); - } - else if (method == "OnExecuted") - { - ctx.Request.Provider.Value.OnExecuted(ctx.ExecuteContext); - } - - if (ctx.IsFileBasedExport && path.HasValue() && ctx.ExecuteContext.DataStream.Length > 0) - { - if (!ctx.ExecuteContext.DataStream.CanSeek) - { - ctx.Log.Warn("Data stream seems to be closed!"); - } - - ctx.ExecuteContext.DataStream.Seek(0, SeekOrigin.Begin); - - using (_rwLock.GetWriteLock()) - using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - { - ctx.Log.Info($"Creating file {path}."); - ctx.ExecuteContext.DataStream.CopyTo(fileStream); - } - } - } - } - catch (Exception exception) + if (method == "Execute") + { + ctx.Request.Provider.Value.Execute(context); + } + else if (method == "OnExecuted") + { + ctx.Request.Provider.Value.OnExecuted(context); + } + } + catch (Exception ex) { - ctx.ExecuteContext.Abort = DataExchangeAbortion.Hard; - ctx.Log.ErrorFormat(exception, $"The provider failed at the {method.NaIfEmpty()} method."); - ctx.Result.LastError = exception.ToString(); + context.Abort = DataExchangeAbortion.Hard; + ctx.Log.ErrorFormat(ex, $"The provider failed at the {method.NaIfEmpty()} method."); + ctx.Result.LastError = ex.ToString(); } finally { - if (ctx.ExecuteContext.DataStream != null) - { - ctx.ExecuteContext.DataStream.Dispose(); - ctx.ExecuteContext.DataStream = null; - } + StreamToFile(ctx, context.DataStream, path, x => context.DataStream = null); - if (ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard && ctx.IsFileBasedExport && path.HasValue()) - { - FileSystemHelper.Delete(path); - } + if (method == "Execute") + { + foreach (var unit in context.ExtraDataUnits.Where(x => x.IsRelatedData)) + { + StreamToFile(ctx, unit.DataStream, Path.Combine(context.Folder, unit.FileName), x => unit.DataStream = null); + } + } } - return (ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard); + return context.Abort != DataExchangeAbortion.Hard; } private bool Deploy(DataExporterContext ctx, string zipPath) @@ -572,16 +625,16 @@ private bool Deploy(DataExporterContext ctx, string zipPath) allSucceeded = false; } } - catch (Exception exception) + catch (Exception ex) { allSucceeded = false; if (context.Result != null) { - context.Result.LastError = exception.ToAllMessages(); + context.Result.LastError = ex.ToAllMessages(); } - ctx.Log.ErrorFormat(exception, "Deployment \"{0}\" of type {1} failed", deployment.Name, deployment.DeploymentType.ToString()); + ctx.Log.ErrorFormat(ex, "Deployment \"{0}\" of type {1} failed", deployment.Name, deployment.DeploymentType.ToString()); } deployment.ResultInfo = XmlHelper.Serialize(context.Result); @@ -1237,37 +1290,41 @@ private List Init(DataExporterContext ctx, int? totalRecords = null) private void ExportCoreInner(DataExporterContext ctx, Store store) { - if (ctx.ExecuteContext.Abort != DataExchangeAbortion.None) - return; - - var fileIndex = 0; - var dataExchangeSettings = _services.Settings.LoadSetting(store.Id); + var context = ctx.ExecuteContext; + var profile = ctx.Request.Profile; + var provider = ctx.Request.Provider; - ctx.Store = store; + if (context.Abort != DataExchangeAbortion.None) + { + return; + } { var logHead = new StringBuilder(); logHead.AppendLine(); logHead.AppendLine(new string('-', 40)); logHead.AppendLine("SmartStore.NET:\t\tv." + SmartStoreVersion.CurrentFullVersion); - logHead.Append("Export profile:\t\t" + ctx.Request.Profile.Name); - logHead.AppendLine(ctx.Request.Profile.Id == 0 ? " (volatile)" : " (Id {0})".FormatInvariant(ctx.Request.Profile.Id)); + logHead.Append("Export profile:\t\t" + profile.Name); + logHead.AppendLine(profile.Id == 0 ? " (volatile)" : $" (Id {profile.Id})"); - if (ctx.Request.Provider.Metadata.FriendlyName.HasValue()) - logHead.AppendLine("Export provider:\t{0} ({1})".FormatInvariant(ctx.Request.Provider.Metadata.FriendlyName, ctx.Request.Profile.ProviderSystemName)); - else - logHead.AppendLine("Export provider:\t{0}".FormatInvariant(ctx.Request.Profile.ProviderSystemName)); + if (provider.Metadata.FriendlyName.HasValue()) + { + logHead.AppendLine($"Export provider:\t{provider.Metadata.FriendlyName} ({profile.ProviderSystemName})"); + } + else + { + logHead.AppendLine("Export provider:\t" + profile.ProviderSystemName); + } - var plugin = ctx.Request.Provider.Metadata.PluginDescriptor; + var plugin = provider.Metadata.PluginDescriptor; logHead.Append("Plugin:\t\t\t"); - logHead.AppendLine(plugin == null ? "".NaIfEmpty() : "{0} ({1}) v.{2}".FormatInvariant(plugin.FriendlyName, plugin.SystemName, plugin.Version.ToString())); - - logHead.AppendLine("Entity:\t\t\t" + ctx.Request.Provider.Value.EntityType.ToString()); + logHead.AppendLine(plugin == null ? "".NaIfEmpty() : $"{plugin.FriendlyName} ({plugin.SystemName}) v.{plugin.Version.ToString()}"); + logHead.AppendLine("Entity:\t\t\t" + provider.Value.EntityType.ToString()); try { var uri = new Uri(store.Url); - logHead.AppendLine("Store:\t\t\t{0} (Id {1})".FormatInvariant(uri.DnsSafeHost.NaIfEmpty(), ctx.Store.Id)); + logHead.AppendLine($"Store:\t\t\t{uri.DnsSafeHost.NaIfEmpty()} (Id {store.Id})"); } catch { } @@ -1277,22 +1334,24 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) ctx.Log.Info(logHead.ToString()); } - ctx.ExecuteContext.Store = ToDynamic(ctx, ctx.Store); - ctx.ExecuteContext.MaxFileNameLength = dataExchangeSettings.MaxFileNameLength; - - var publicDeployment = ctx.Request.Profile.Deployments.FirstOrDefault(x => x.DeploymentType == ExportDeploymentType.PublicFolder); - ctx.ExecuteContext.HasPublicDeployment = (publicDeployment != null); - ctx.ExecuteContext.PublicFolderPath = publicDeployment.GetDeploymentFolder(true); - ctx.ExecuteContext.PublicFolderUrl = publicDeployment.GetPublicFolderUrl(_services, ctx.Store); + var dataExchangeSettings = _services.Settings.LoadSetting(store.Id); + var publicDeployment = profile.Deployments.FirstOrDefault(x => x.DeploymentType == ExportDeploymentType.PublicFolder); - var fileExtension = (ctx.Request.Provider.Value.FileExtension.HasValue() ? ctx.Request.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""); + ctx.Store = store; + context.FileIndex = 0; + context.Store = ToDynamic(ctx, ctx.Store); + context.MaxFileNameLength = dataExchangeSettings.MaxFileNameLength; + context.HasPublicDeployment = publicDeployment != null; + context.PublicFolderPath = publicDeployment.GetDeploymentFolder(true); + context.PublicFolderUrl = publicDeployment.GetPublicFolderUrl(_services, ctx.Store); + var fileExtension = provider.Value.FileExtension.HasValue() ? provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""; using (var segmenter = CreateSegmenter(ctx)) { if (segmenter == null) { - throw new SmartException("Unsupported entity type '{0}'.".FormatInvariant(ctx.Request.Provider.Value.EntityType.ToString())); + throw new SmartException($"Unsupported entity type '{provider.Value.EntityType.ToString()}'."); } if (segmenter.TotalRecords <= 0) @@ -1300,84 +1359,99 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) ctx.Log.Info("There are no records to export."); } - while (ctx.ExecuteContext.Abort == DataExchangeAbortion.None && segmenter.HasData) + while (context.Abort == DataExchangeAbortion.None && segmenter.HasData) { segmenter.RecordPerSegmentCount = 0; - ctx.ExecuteContext.RecordsSucceeded = 0; + context.RecordsSucceeded = 0; string path = null; if (ctx.IsFileBasedExport) { - var resolvedPattern = ctx.Request.Profile.ResolveFileNamePattern(ctx.Store, ++fileIndex, ctx.ExecuteContext.MaxFileNameLength); - - ctx.ExecuteContext.FileName = resolvedPattern + fileExtension; - path = Path.Combine(ctx.ExecuteContext.Folder, ctx.ExecuteContext.FileName); + context.FileIndex = context.FileIndex + 1; + context.FileName = profile.ResolveFileNamePattern(ctx.Store, context.FileIndex, context.MaxFileNameLength) + fileExtension; + path = Path.Combine(context.Folder, context.FileName); + + if (profile.ExportRelatedData) + { + AddUnitsForRelatedData(ctx); + } } - if (CallProvider(ctx, null, "Execute", path)) + if (CallProvider(ctx, "Execute", path)) { - ctx.Log.Info("Provider reports {0} successfully exported record(s).".FormatInvariant(ctx.ExecuteContext.RecordsSucceeded)); + ctx.Log.Info($"Provider reports {context.RecordsSucceeded.ToString("N0")} successfully exported record(s)."); if (ctx.IsFileBasedExport && File.Exists(path)) { ctx.Result.Files.Add(new DataExportResult.ExportFileInfo { StoreId = ctx.Store.Id, - FileName = ctx.ExecuteContext.FileName, - IsDataFile = true - }); - } + FileName = context.FileName, + EntityType = provider.Value.EntityType + }); + } } ctx.EntityIdsPerSegment.Clear(); - if (ctx.ExecuteContext.IsMaxFailures) - ctx.Log.Warn("Export aborted. The maximum number of failures has been reached."); - - if (ctx.CancellationToken.IsCancellationRequested) - ctx.Log.Warn("Export aborted. A cancellation has been requested."); + if (context.IsMaxFailures) + { + ctx.Log.Warn("Export aborted. The maximum number of failures has been reached."); + } + if (ctx.CancellationToken.IsCancellationRequested) + { + ctx.Log.Warn("Export aborted. A cancellation has been requested."); + } DetachAllEntitiesAndClear(ctx); } - if (ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard) + if (context.Abort != DataExchangeAbortion.Hard) { - // always call OnExecuted - if (ctx.ExecuteContext.ExtraDataUnits.Count == 0) - ctx.ExecuteContext.ExtraDataUnits.Add(new ExportDataUnit()); + // Always call OnExecuted. + if (context.ExtraDataUnits.Count == 0) + { + context.ExtraDataUnits.Add(new ExportDataUnit()); + } - ctx.ExecuteContext.ExtraDataUnits.ForEach(x => + context.ExtraDataUnits.ForEach(x => { - var path = (x.FileName.HasValue() ? Path.Combine(ctx.ExecuteContext.Folder, x.FileName) : null); - if (CallProvider(ctx, x.Id, "OnExecuted", path)) - { - if (x.DisplayInFileDialog && ctx.IsFileBasedExport && File.Exists(path)) - { - // save info about extra file - ctx.Result.Files.Add(new DataExportResult.ExportFileInfo - { - StoreId = ctx.Store.Id, - FileName = x.FileName, - Label = x.Label, - IsDataFile = false - }); - } - } - }); - - ctx.ExecuteContext.ExtraDataUnits.Clear(); + context.DataStreamId = x.Id; + + var path = x.FileName.HasValue() ? Path.Combine(context.Folder, x.FileName) : null; + var success = !x.IsRelatedData + ? CallProvider(ctx, "OnExecuted", path) + : true; + + if (success && ctx.IsFileBasedExport && x.DisplayInFileDialog && File.Exists(path)) + { + // Save info about extra file. + ctx.Result.Files.Add(new DataExportResult.ExportFileInfo + { + StoreId = ctx.Store.Id, + FileName = x.FileName, + Label = x.Label, + EntityType = x.EntityType + }); + } + }); } - } + + context.ExtraDataUnits.Clear(); + } } private void ExportCoreOuter(DataExporterContext ctx) { - if (ctx.Request.Profile == null || !ctx.Request.Profile.Enabled) - return; + var profile = ctx.Request.Profile; + if (profile == null || !profile.Enabled) + { + return; + } - var logPath = ctx.Request.Profile.GetExportLogPath(); - var zipPath = ctx.Request.Profile.GetExportZipPath(); + var logPath = profile.GetExportLogPath(); + var zipPath = profile.GetExportZipPath(); FileSystemHelper.Delete(logPath); FileSystemHelper.Delete(zipPath); @@ -1402,12 +1476,12 @@ private void ExportCoreOuter(DataExporterContext ctx) ctx.ExecuteContext.CustomProperties.Add(item.Key, item.Value); } - if (ctx.Request.Profile.ProviderConfigData.HasValue()) + if (profile.ProviderConfigData.HasValue()) { var configInfo = ctx.Request.Provider.Value.ConfigurationInfo; if (configInfo != null) { - ctx.ExecuteContext.ConfigurationData = XmlHelper.Deserialize(ctx.Request.Profile.ProviderConfigData, configInfo.ModelType); + ctx.ExecuteContext.ConfigurationData = XmlHelper.Deserialize(profile.ProviderConfigData, configInfo.ModelType); } } @@ -1442,7 +1516,7 @@ private void ExportCoreOuter(DataExporterContext ctx) ctx.ExecuteContext.Language = ToDynamic(ctx, ctx.ContextLanguage); ctx.ExecuteContext.Customer = ToDynamic(ctx, ctx.ContextCustomer); ctx.ExecuteContext.Currency = ToDynamic(ctx, ctx.ContextCurrency); - ctx.ExecuteContext.Profile = ToDynamic(ctx, ctx.Request.Profile); + ctx.ExecuteContext.Profile = ToDynamic(ctx, profile); stores.ForEach(x => ExportCoreInner(ctx, x)); } @@ -1451,18 +1525,18 @@ private void ExportCoreOuter(DataExporterContext ctx) { if (ctx.IsFileBasedExport) { - if (ctx.Request.Profile.CreateZipArchive) + if (profile.CreateZipArchive) { ZipFile.CreateFromDirectory(ctx.FolderContent, zipPath, CompressionLevel.Fastest, false); } - if (ctx.Request.Profile.Deployments.Any(x => x.Enabled)) + if (profile.Deployments.Any(x => x.Enabled)) { SetProgress(ctx, T("Common.Publishing")); var allDeploymentsSucceeded = Deploy(ctx, zipPath); - if (allDeploymentsSucceeded && ctx.Request.Profile.Cleanup) + if (allDeploymentsSucceeded && profile.Cleanup) { logger.Info("Cleaning up export folder"); @@ -1471,31 +1545,31 @@ private void ExportCoreOuter(DataExporterContext ctx) } } - if (ctx.Request.Profile.EmailAccountId != 0 && !ctx.Supports(ExportFeatures.CanOmitCompletionMail)) + if (profile.EmailAccountId != 0 && !ctx.Supports(ExportFeatures.CanOmitCompletionMail)) { SendCompletionEmail(ctx, zipPath); } } } - catch (Exception exception) + catch (Exception ex) { - logger.ErrorsAll(exception); - ctx.Result.LastError = exception.ToString(); + logger.ErrorsAll(ex); + ctx.Result.LastError = ex.ToString(); } finally { try { - if (!ctx.IsPreview && ctx.Request.Profile.Id != 0) + if (!ctx.IsPreview && profile.Id != 0) { - ctx.Request.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); + profile.ResultInfo = XmlHelper.Serialize(ctx.Result); - _exportProfileService.Value.UpdateExportProfile(ctx.Request.Profile); + _exportProfileService.Value.UpdateExportProfile(profile); } } - catch (Exception exception) + catch (Exception ex) { - logger.ErrorsAll(exception); + logger.ErrorsAll(ex); } DetachAllEntitiesAndClear(ctx); @@ -1517,15 +1591,17 @@ private void ExportCoreOuter(DataExporterContext ctx) ctx.ExecuteContext.Log = null; ctx.Log = null; } - catch (Exception exception) + catch (Exception ex) { - logger.ErrorsAll(exception); + logger.ErrorsAll(ex); } } } - if (ctx.IsPreview || ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard) - return; + if (ctx.IsPreview || ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard) + { + return; + } // Post process order entities. if (ctx.EntityIdsLoaded.Any() && ctx.Request.Provider.Value.EntityType == ExportEntityType.Order && ctx.Projection.OrderStatusChange != ExportOrderStatusChange.None) @@ -1553,12 +1629,12 @@ private void ExportCoreOuter(DataExporterContext ctx) } } - logger.Info("Updated order status for {0} order(s).".FormatInvariant(ctx.EntityIdsLoaded.Count())); + logger.Info($"Updated order status for {ctx.EntityIdsLoaded.Count()} order(s)."); } - catch (Exception exception) + catch (Exception ex) { - logger.ErrorsAll(exception); - ctx.Result.LastError = exception.ToString(); + logger.ErrorsAll(ex); + ctx.Result.LastError = ex.ToString(); } } } @@ -1576,7 +1652,6 @@ public DataExportResult Export(DataExportRequest request, CancellationToken canc var ctx = new DataExporterContext(request, cancellationToken); ExportCoreOuter(ctx); - cancellationToken.ThrowIfCancellationRequested(); return ctx.Result; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs index 27c5e3aa9b..18554416df 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs @@ -9,7 +9,7 @@ namespace SmartStore.Services.DataExchange.Export { - public class ExportExecuteContext + public class ExportExecuteContext { private DataExportResult _result; private CancellationToken _cancellation; @@ -117,10 +117,15 @@ public bool IsMaxFailures ///
    public int MaxFileNameLength { get; internal set; } - /// - /// The name of the current export file - /// - public string FileName { get; internal set; } + /// + /// Index of current export file. + /// + public int FileIndex { get; internal set; } + + /// + /// The name of the current export file + /// + public string FileName { get; internal set; } /// /// The path of the export content folder @@ -216,20 +221,25 @@ public void SetProgress(string message) public class ExportDataUnit { - /// - /// Your Id to identify this stream within a list of streams - /// - public string Id { get; set; } + /// + /// Your Id to identify this stream within a list of streams + /// + public string Id { get; set; } /// /// Stream used to write data to /// public Stream DataStream { get; internal set; } - /// - /// The name of the file to be created - /// - public string FileName { get; set; } + /// + /// The entity type. + /// + public ExportEntityType? EntityType { get; set; } + + /// + /// The name of the file to be created + /// + public string FileName { get; set; } /// /// Short optional text that describes the content of the file @@ -240,5 +250,10 @@ public class ExportDataUnit /// Whether to display the file in the profile file dialog /// public bool DisplayInFileDialog { get; set; } - } + + /// + /// Indicates whether this data unit contains related data (e.g. tier prices) of an primary entity (e.g. product). + /// + public bool IsRelatedData { get; internal set; } + } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs index 7c64bce18e..787ef8501d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs @@ -15,7 +15,7 @@ namespace SmartStore.Services.DataExchange.Export { - public static class ExportExtensions + public static class ExportExtensions { /// /// Returns a value indicating whether the export provider is valid @@ -128,8 +128,9 @@ public static int GetExportFileCount(this ExportProfile profile, ProviderStore /// One based file index /// The maximum length of the file name + /// Optional file name suffix /// Resolved file name pattern - public static string ResolveFileNamePattern(this ExportProfile profile, Store store, int fileIndex, int maxFileNameLength) + public static string ResolveFileNamePattern(this ExportProfile profile, Store store, int fileIndex, int maxFileNameLength, string suffix = null) { var sb = new StringBuilder(profile.FileNamePattern); @@ -138,17 +139,27 @@ public static string ResolveFileNamePattern(this ExportProfile profile, Store st sb.Replace("%Store.Id%", store.Id.ToString()); sb.Replace("%File.Index%", fileIndex.ToString("D4")); - if (profile.FileNamePattern.Contains("%Profile.SeoName%")) - sb.Replace("%Profile.SeoName%", SeoHelper.GetSeName(profile.Name, true, false).Replace("/", "").Replace("-", "")); - - if (profile.FileNamePattern.Contains("%Store.SeoName%")) - sb.Replace("%Store.SeoName%", profile.PerStore ? SeoHelper.GetSeName(store.Name, true, false) : "allstores"); - - if (profile.FileNamePattern.Contains("%Random.Number%")) - sb.Replace("%Random.Number%", CommonHelper.GenerateRandomInteger().ToString()); + if (profile.FileNamePattern.Contains("%Profile.SeoName%")) + { + sb.Replace("%Profile.SeoName%", SeoHelper.GetSeName(profile.Name, true, false).Replace("/", "").Replace("-", "")); + } + if (profile.FileNamePattern.Contains("%Store.SeoName%")) + { + sb.Replace("%Store.SeoName%", profile.PerStore ? SeoHelper.GetSeName(store.Name, true, false) : "allstores"); + } + if (profile.FileNamePattern.Contains("%Random.Number%")) + { + sb.Replace("%Random.Number%", CommonHelper.GenerateRandomInteger().ToString()); + } + if (profile.FileNamePattern.Contains("%Timestamp%")) + { + sb.Replace("%Timestamp%", DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture)); + } - if (profile.FileNamePattern.Contains("%Timestamp%")) - sb.Replace("%Timestamp%", DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture)); + if (suffix.HasValue()) + { + sb.Append("-" + suffix); + } var result = sb.ToString() .ToValidFileName("") diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs index bf14291cd6..6190bf0e94 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs @@ -76,7 +76,7 @@ public void Execute( { ScheduleTaskId = task.Id, IsRunning = true, - MachineName = _env.MachineName, + MachineName = _env.MachineName.EmptyNull(), StartedOnUtc = DateTime.UtcNow }; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 7aad780dc8..6e93da6d1b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -160,18 +160,32 @@ private void AddFileInfo( { if (System.IO.File.Exists(path) && !list.Any(x => x.FilePath == path)) { - var fi = new ExportFileDetailsModel.FileInfo(); - fi.FilePath = path; - fi.FileName = Path.GetFileName(path); - fi.FileExtension = Path.GetExtension(path); - fi.DisplayOrder = (fi.FileExtension.IsCaseInsensitiveEqual(".zip") ? 0 : 1); + var fi = new ExportFileDetailsModel.FileInfo + { + FilePath = path, + FileName = Path.GetFileName(path), + FileExtension = Path.GetExtension(path) + }; - if (fileInfo != null) + fi.DisplayOrder = fi.FileExtension.IsCaseInsensitiveEqual(".zip") ? 0 : 1; + + if (fileInfo != null) { - if (fileInfo.Label.HasValue()) - fi.Label = fileInfo.Label; - else if (fileInfo.IsDataFile) - fi.Label = T("Admin.Common.Data"); + fi.EntityType = fileInfo.EntityType; + + if (fileInfo.Label.HasValue()) + { + fi.Label = fileInfo.Label; + } + else + { + fi.Label = T("Admin.Common.Data"); + + if (fileInfo.EntityType.HasValue) + { + fi.Label = string.Concat(fi.Label, " ", fileInfo.EntityType.Value.GetLocalizedEnum(Services.Localization, Services.WorkContext)); + } + } } if (store != null) @@ -342,12 +356,13 @@ private void PrepareProfileModel( model.FolderName = profile.FolderName; model.FileNamePattern = profile.FileNamePattern; model.Enabled = profile.Enabled; + model.ExportRelatedData = profile.ExportRelatedData; model.ScheduleTaskId = profile.SchedulingTaskId; model.ScheduleTaskName = profile.ScheduleTask.Name.NaIfEmpty(); model.IsTaskRunning = lastHistoryEntry?.IsRunning ?? false; model.IsTaskEnabled = profile.ScheduleTask.Enabled; model.LogFileExists = System.IO.File.Exists(profile.GetExportLogPath()); - model.HasActiveProvider = (provider != null); + model.HasActiveProvider = provider != null; model.FileNamePatternDescriptions = T("Admin.DataExchange.Export.FileNamePatternDescriptions").Text.SplitSafe(";"); model.Provider = new ExportProfileModel.ProviderModel(); @@ -403,7 +418,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile model.AvailableCompletedEmailAddresses = new MultiSelectList(profile.CompletedEmailAddresses.SplitSafe(",")); - // projection + // Projection. model.Projection = new ExportProjectionModel { StoreId = projection.StoreId, @@ -448,7 +463,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) .ToList(); - // filtering + // Filtering. model.Filter = new ExportFilterModel { StoreId = filter.StoreId, @@ -497,7 +512,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile model.Filter.AvailableLanguages.Add(new SelectListItem { Text = lang.Name, Value = lang.Id.ToString() }); } - // deployment + // Deployment. model.Deployments = profile.Deployments .Select(x => { @@ -612,9 +627,9 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile } } } - catch (Exception exc) + catch (Exception ex) { - NotifyError(exc); + NotifyError(ex); } } } @@ -822,6 +837,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) profile.FileNamePattern = model.FileNamePattern; profile.FolderName = model.FolderName; profile.Enabled = model.Enabled; + profile.ExportRelatedData = model.ExportRelatedData; profile.Offset = model.Offset; profile.Limit = model.Limit ?? 0; profile.BatchSize = model.BatchSize ?? 0; @@ -922,9 +938,9 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) profile.ProviderConfigData = XmlHelper.Serialize(model.CustomProperties["ProviderConfigData"], configInfo.ModelType); } } - catch (Exception exc) + catch (Exception ex) { - NotifyError(exc); + NotifyError(ex); } _exportService.UpdateExportProfile(profile); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index aabb5911f7..ea7fdf961c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -124,6 +124,7 @@ private void PrepareProfileModel( model.Name = profile.Name; model.EntityType = profile.EntityType; model.Enabled = profile.Enabled; + model.ImportRelatedData = profile.ImportRelatedData; model.Skip = (profile.Skip == 0 ? (int?)null : profile.Skip); model.Take = (profile.Take == 0 ? (int?)null : profile.Take); model.UpdateOnly = profile.UpdateOnly; @@ -311,9 +312,9 @@ private void PrepareProfileModel( .ToList(); } } - catch (Exception exception) + catch (Exception ex) { - NotifyError(exception, true, false); + NotifyError(ex, true, false); } } @@ -450,7 +451,7 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol if (column.IsEmpty()) { - // tell mapper to explicitly ignore the property + // Tell mapper to explicitly ignore the property. map.AddMapping(property, null, property, "[IGNOREPROPERTY]"); } else if (!column.IsCaseInsensitiveEqual(property) || defaultValue.HasValue()) @@ -463,10 +464,10 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol } } } - catch (Exception exception) + catch (Exception ex) { hasErrors = true; - NotifyError(exception, true, false); + NotifyError(ex, true, false); } if (!ModelState.IsValid || hasErrors) @@ -478,10 +479,11 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol profile.Name = model.Name; profile.EntityType = model.EntityType; profile.Enabled = model.Enabled; + profile.ImportRelatedData = model.ImportRelatedData; profile.Skip = model.Skip ?? 0; profile.Take = model.Take ?? 0; profile.UpdateOnly = model.UpdateOnly; - profile.KeyFieldNames = (model.KeyFieldNames == null ? null : string.Join(",", model.KeyFieldNames)); + profile.KeyFieldNames = model.KeyFieldNames == null ? null : string.Join(",", model.KeyFieldNames); try { @@ -522,10 +524,10 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol profile.ExtraData = XmlHelper.Serialize(extraData); } } - catch (Exception exception) + catch (Exception ex) { hasErrors = true; - NotifyError(exception, true, false); + NotifyError(ex, true, false); } if (!hasErrors) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 1992a00475..85e6c3f214 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -47,7 +47,10 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Common.Enabled")] public bool Enabled { get; set; } - [SmartResourceDisplayName("Common.Execution")] + [SmartResourceDisplayName("Admin.DataExchange.Export.ExportRelatedData")] + public bool ExportRelatedData { get; set; } + + [SmartResourceDisplayName("Common.Execution")] public int ScheduleTaskId { get; set; } public string ScheduleTaskName { get; set; } public bool IsTaskRunning { get; set; } @@ -175,13 +178,15 @@ public class FileInfo public int StoreId { get; set; } public string StoreName { get; set; } public string Label { get; set; } + public ExportEntityType? EntityType { get; set; } - public int DisplayOrder { get; set; } + public int DisplayOrder { get; set; } public string FilePath { get; set; } public string FileUrl { get; set; } + public string FriendlyFileUrl { get; set; } - public string FileName { get; set; } + public string FileName { get; set; } public string FileExtension { get; set; } public string FileRootPath @@ -199,7 +204,7 @@ public string FileRootPath return rootPath; } } - } + } } public partial class ExportProfileValidator : AbstractValidator diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs index df8482fc38..7d4de9684a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs @@ -34,7 +34,10 @@ public ImportProfileModel() [SmartResourceDisplayName("Common.Enabled")] public bool Enabled { get; set; } - [SmartResourceDisplayName("Admin.Common.RecordsSkip")] + [SmartResourceDisplayName("Admin.DataExchange.Import.ImportRelatedData")] + public bool ImportRelatedData { get; set; } + + [SmartResourceDisplayName("Admin.Common.RecordsSkip")] public int? Skip { get; set; } [SmartResourceDisplayName("Admin.Common.RecordsTake")] diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index cabe4063a1..2f6b596375 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -121,6 +121,18 @@ @Html.ValidationMessageFor(x => x.Enabled) + @if (Model.Provider.EntityType == ExportEntityType.Product && Model.Provider.IsFileBasedExport) + { +
    + + + + } @if (Model.ScheduleTaskId > 0) { @@ -337,13 +349,13 @@ try { @Html.Partial(Model.Provider.ConfigPartialViewName, Model.Provider.ConfigData, viewData) - } - catch (Exception exception) - { + } + catch (Exception ex) + {
    @T("Admin.Configuration.Plugins.UnknownError")
    -
    @exception.ToString()
    +
    @ex.ToString()
    } } else diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml index 089d073aea..6e91508d08 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml @@ -15,7 +15,7 @@ foreach (var file in grp) {
    - + @Html.IconForFileExtension(file.FileExtension, false) @(file.Label.HasValue() ? file.Label : file.FileExtension.EmptyNull().ToLower().TrimStart('.')) @@ -41,7 +41,7 @@ {
    - + @Html.IconForFileExtension(file.FileExtension, false) @(file.Label.HasValue() ? file.Label : file.FileExtension.EmptyNull().ToLower().TrimStart('.')) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml index d49f47ca31..76adcc9cf6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml @@ -102,6 +102,18 @@ @Html.ValidationMessageFor(x => x.Enabled)
    + @if (Model.EntityType == ImportEntityType.Product) + { + + + + + }
    + @T("Forum.TopicTitle") diff --git a/src/Presentation/SmartStore.Web/Views/Customer/Avatar.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/Avatar.cshtml index 2ff4a1b87b..7721e561cd 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/Avatar.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/Avatar.cshtml @@ -1,6 +1,6 @@ @using SmartStore.Web.Models.Customer; @using SmartStore.Web.Framework.UI; -@model CustomerAvatarModel +@model CustomerAvatarEditModel @{ Layout = "_MyAccount"; ViewBag.PageClassToken = "avatar"; @@ -15,25 +15,25 @@ }
    - @if (!Model.AvatarUrl.HasValue()) - { -
    @T("Account.Avatar.UploadRules", Model.MaxFileSize)
    - } -
    - Avatar + @{ Html.RenderPartial("Customer.Avatar", Model.Avatar); }
    @(Html.SmartStore().FileUploader() .Name("uploadedFile") .UploadUrl(Url.Action("UploadAvatar", "Customer")) .AcceptedFileTypes("gif|jpe?g|png") - .ShowRemoveButton(Model.AvatarUrl.HasValue()) + .ShowRemoveButton(Model.Avatar.PictureUrl.HasValue()) .ShowRemoveButtonAfterUpload(true) .UploadText(T("Common.FileUploader.Upload")) .OnUploadCompletedHandlerName("onAvatarUploaded") .OnFileRemoveHandlerName("onAvatarRemoved") ) + + @if (!Model.Avatar.PictureUrl.HasValue()) + { +
    @T("Account.Avatar.UploadRules", Model.MaxFileSize)
    + }
    diff --git a/src/Presentation/SmartStore.Web/Views/Profile/Index.cshtml b/src/Presentation/SmartStore.Web/Views/Profile/Index.cshtml index d45b856410..24f2f5b2ab 100644 --- a/src/Presentation/SmartStore.Web/Views/Profile/Index.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Profile/Index.cshtml @@ -1,39 +1,35 @@ -@model ProfileIndexModel -@using SmartStore.Web.Models.Profile; +@using SmartStore.Web.Models.Profile; @using SmartStore.Web.Framework.UI; +@model ProfileIndexModel @{ Layout = "_Layout"; Html.AddTitleParts(T("PageTitle.Profile").Text); - - var postsTabSelected = Model.PagingPosts; }

    @Model.ProfileTitle

    -
    @Html.SmartStore().TabStrip().Name("profile-tabs").HtmlAttributes(new { @class = "profile-tabs" }).SmartTabSelection(false).Style(TabsStyle.Material).Responsive(true, " { x.Add().Text(T("Profile.PersonalInfo").Text) - .Selected(!postsTabSelected) - .Content(Html.Action("info", new { customerProfileId = Model.CustomerProfileId }).ToString()); + .Selected(!Model.PagingPosts) + .Content(Html.Action("info", new { id = Model.Id }).ToString()); if (Model.ForumsEnabled) { x.Add().Text(T("Profile.LatestPosts").Text) - .Selected(postsTabSelected) - .Content(Html.Action("posts", new { customerProfileId = Model.CustomerProfileId, page = Model.PostsPage }).ToString()); + .Selected(Model.PagingPosts) + .Content(Html.Action("posts", new { id = Model.Id, page = Model.PostsPage }).ToString()); } }).TabContentHeaderContent(TabContentHeader()).Fade(false) -
    @helper TabContentHeader() -{{ +{ -}} +} diff --git a/src/Presentation/SmartStore.Web/Views/Profile/Partials/Info.cshtml b/src/Presentation/SmartStore.Web/Views/Profile/Partials/Info.cshtml index 0fd62702f2..3f2fb0e50a 100644 --- a/src/Presentation/SmartStore.Web/Views/Profile/Partials/Info.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Profile/Partials/Info.cshtml @@ -1,16 +1,9 @@ -@model ProfileInfoModel - -@using SmartStore.Web.Models.Profile; +@using SmartStore.Web.Models.Profile; +@model ProfileInfoModel
    -
    - @if (Model.AvatarEnabled && !String.IsNullOrEmpty(Model.AvatarUrl)) - { -
    - Avatar -
    - } + @{ Html.RenderPartial("Customer.Avatar", Model.Avatar); } @if (Model.LocationEnabled) {
    @@ -20,7 +13,7 @@ } @if (Model.PMEnabled) { - + @T("Forum.PrivateMessages.PM") diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Partials/Comment.Item.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Partials/Comment.Item.cshtml index 8a43dc3110..52f40afade 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Partials/Comment.Item.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Partials/Comment.Item.cshtml @@ -1,18 +1,10 @@ -@model CommentModel -@using SmartStore.Web.Models.Common; +@using SmartStore.Web.Models.Common; +@model CommentModel
    - @if (Model.Parent.HasAnyAvatar()) - { -
    - @if (Model.CustomerAvatarUrl.HasValue()) - { -
    - avatar -
    - } -
    - } +
    + @{ Html.RenderPartial("Customer.Avatar", Model.Avatar); } +
    @@ -35,7 +27,7 @@ @Html.Raw(Model.CommentTitle) } -

    +

    @Html.Raw(SmartStore.Core.Html.HtmlUtils.FormatText(Model.CommentText, false, true, false, false, false, false))

    diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Partials/Comment.List.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Partials/Comment.List.cshtml index 4d3305eba0..a92970d22e 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Partials/Comment.List.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Partials/Comment.List.cshtml @@ -1,23 +1,10 @@ -@model CommentListModel -@using SmartStore.Web.Models.Common; -@{ - if (Model.Comments.Count == 0) - { - return; - } +@using SmartStore.Web.Models.Common; +@model CommentListModel +@if (Model.Comments.Count == 0) +{ + return; } - -

    @T("News.Comments") diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Partials/Customer.Avatar.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Partials/Customer.Avatar.cshtml new file mode 100644 index 0000000000..f8c1887b4c --- /dev/null +++ b/src/Presentation/SmartStore.Web/Views/Shared/Partials/Customer.Avatar.cshtml @@ -0,0 +1,34 @@ +@using SmartStore.Web.Models.Customer; +@model CustomerAvatarModel +@{ + if (!Model.AvatarColor.HasValue()) + { + Model.AvatarColor = "light"; + } +} +
    + @if (Model.LinkUrl.HasValue()) + { + @Avatar() + } + else + { + @Avatar() + } +
    + +@helper Avatar() +{ + var textColor = Model.AvatarColor == "light" ? "text-muted" : "text-light"; + + if (Model.PictureUrl.HasValue()) + { + + } + else + { + + @Model.AvatarLetter + + } +} \ No newline at end of file diff --git a/src/Tests/SmartStore.Web.MVC.Tests/Public/Models/News/HomePageNewsItemsModelTests.cs b/src/Tests/SmartStore.Web.MVC.Tests/Public/Models/News/HomePageNewsItemsModelTests.cs index ffb40b5091..7556b2021c 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/Public/Models/News/HomePageNewsItemsModelTests.cs +++ b/src/Tests/SmartStore.Web.MVC.Tests/Public/Models/News/HomePageNewsItemsModelTests.cs @@ -40,7 +40,6 @@ public void Can_clone() Id = 3, CustomerId = 4, CustomerName = "CustomerName 1", - CustomerAvatarUrl = "CustomerAvatarUrl 1", CommentTitle = "CommentTitle 1", CommentText = "CommentText 1", CreatedOn = new DateTime(2010, 01, 02), @@ -67,7 +66,6 @@ public void Can_clone() newsItemModel2.Comments.Comments[0].Id.ShouldEqual(3); newsItemModel2.Comments.Comments[0].CustomerId.ShouldEqual(4); newsItemModel2.Comments.Comments[0].CustomerName.ShouldEqual("CustomerName 1"); - newsItemModel2.Comments.Comments[0].CustomerAvatarUrl.ShouldEqual("CustomerAvatarUrl 1"); newsItemModel2.Comments.Comments[0].CommentTitle.ShouldEqual("CommentTitle 1"); newsItemModel2.Comments.Comments[0].CommentText.ShouldEqual("CommentText 1"); newsItemModel2.Comments.Comments[0].CreatedOn.ShouldEqual(new DateTime(2010, 01, 02)); From 25f054c8ca3ed3db51dad0e3d55a455ed1fc4b0c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 17 Sep 2018 11:09:49 +0200 Subject: [PATCH 109/932] Resolves #1504 Cart item price calculation wrong if attribute combinations with text types are involved. --- changelog.md | 1 + .../Catalog/ProductExtensions.cs | 17 +++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index 81931cb3d6..308a377f18 100644 --- a/changelog.md +++ b/changelog.md @@ -49,6 +49,7 @@ * In a multi-store, message templates may have loaded the wrong disclaimer and conditions-of-use text. * NullReferenceException in manufacturer list when there is no manufacturer. * Wrong order of featured products on category page. +* #1504 Cart item price calculation wrong if attribute combinations with text types are involved. ## SmartStore.NET 3.1.5 diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs index a378fd6021..1a99142239 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs @@ -1,21 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Media; using SmartStore.Core.Infrastructure; using SmartStore.Services.Directory; using SmartStore.Services.Localization; -using SmartStore.Services.Media; using SmartStore.Services.Seo; using SmartStore.Services.Tax; -using SmartStore.Core.Domain.Customers; namespace SmartStore.Services.Catalog { - public static class ProductExtensions + public static class ProductExtensions { public static ProductVariantAttributeCombination MergeWithCombination(this Product product, string selectedAttributes) { @@ -29,12 +26,16 @@ public static ProductVariantAttributeCombination MergeWithCombination(this Produ if (selectedAttributes.IsEmpty()) return null; - // let's find appropriate record + // Let's find appropriate record. var combination = productAttributeParser.FindProductVariantAttributeCombination(product.Id, selectedAttributes); - if (combination != null && combination.IsActive) + if (combination != null && combination.IsActive) + { + product.MergeWithCombination(combination); + } + else if (product.MergedDataValues != null) { - product.MergeWithCombination(combination); + product.MergedDataValues.Clear(); } return combination; From a8bacb1be39a6b8c74a076bdc641e55843fb7b31 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 17 Sep 2018 16:41:20 +0200 Subject: [PATCH 110/932] Minor improvement --- .../Themes/Flex/Content/_forum.scss | 1 - .../SmartStore.Web/Views/Boards/Forum.cshtml | 2 +- .../Boards/Partials/_ForumGroupPartial.cshtml | 15 +++++++-------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_forum.scss b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_forum.scss index c7ca29dea7..fde5be7236 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_forum.scss +++ b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_forum.scss @@ -44,7 +44,6 @@ $forum-unobstrusive-link-color: #646464; .topics-group .views { text-align: center; } -.forum-spacer, .active-discussionstopics .topic-name, .topics-group .topic-name { width: 100%; diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml index 73f3ad053e..5751f09138 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml @@ -77,7 +77,7 @@ @pager

    -
    +
    @if (Model.ForumTopics.Count > 0) { diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumGroupPartial.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumGroupPartial.cshtml index a09a387124..96af463670 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumGroupPartial.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumGroupPartial.cshtml @@ -1,25 +1,24 @@ @model ForumGroupModel @using SmartStore.Web.Models.Boards; -
    +

    @Html.RouteLink(Model.Name.Value, "ForumGroupSlug", new { id = Model.Id, slug = Model.SeName })

    -
    +
    + - - - - + + + + + + + +
    @T("Forum.Forum") - + @T("Forum.Topics") + @T("Forum.Posts") @@ -42,10 +41,10 @@ @forum.Description - + @forum.NumTopics.ToString("N0") + @forum.NumPosts.ToString("N0") From 8f70c2c75bdb5ad8efce8bf81a0de41fef4b25cf Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 17 Sep 2018 19:56:53 +0200 Subject: [PATCH 111/932] Added filter for newsletter subscriber export by working language --- .../Domain/DataExchange/ExportFilter.cs | 17 ++- .../Domain/Messages/NewsLetterSubscription.cs | 5 + ...NewsletterSubscriptionLanguage.Designer.cs | 29 ++++ ...71309522_NewsletterSubscriptionLanguage.cs | 18 +++ ...309522_NewsletterSubscriptionLanguage.resx | 126 ++++++++++++++++++ .../Migrations/MigrationsConfiguration.cs | 7 + .../SmartStore.Data/SmartStore.Data.csproj | 7 + .../DataExchange/Export/DataExporter.cs | 17 ++- .../Messages/NewsLetterSubscriptionService.cs | 3 +- .../Controllers/ExportController.cs | 14 +- .../Models/DataExchange/ExportFilterModel.cs | 10 +- .../Views/Export/_Tab.Filter.cshtml | 9 ++ .../Controllers/CustomerController.cs | 3 +- .../Controllers/NewsletterController.cs | 5 +- 14 files changed, 254 insertions(+), 16 deletions(-) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.resx diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs index 1b00fe2e27..476c1a5b8c 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs @@ -170,14 +170,19 @@ public class ExportFilter /// public bool? IsActiveSubscriber { get; set; } - #endregion + /// + /// Filter by language + /// + public int? WorkingLanguageId { get; set; } - #region Shopping Cart + #endregion - /// - /// Filter by shopping cart type identifier - /// - public int? ShoppingCartTypeId { get; set; } + #region Shopping Cart + + /// + /// Filter by shopping cart type identifier + /// + public int? ShoppingCartTypeId { get; set; } #endregion } diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/NewsLetterSubscription.cs b/src/Libraries/SmartStore.Core/Domain/Messages/NewsLetterSubscription.cs index 6339d91d32..ff24400987 100644 --- a/src/Libraries/SmartStore.Core/Domain/Messages/NewsLetterSubscription.cs +++ b/src/Libraries/SmartStore.Core/Domain/Messages/NewsLetterSubscription.cs @@ -34,5 +34,10 @@ public partial class NewsLetterSubscription : BaseEntity /// [Index("IX_NewsletterSubscription_Email_StoreId", 2)] public int StoreId { get; set; } + + /// + /// Gets or sets the language identifier + /// + public int WorkingLanguageId { get; set; } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.Designer.cs new file mode 100644 index 0000000000..a8abf3ecf1 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class NewsletterSubscriptionLanguage : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(NewsletterSubscriptionLanguage)); + + string IMigrationMetadata.Id + { + get { return "201809171309522_NewsletterSubscriptionLanguage"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.cs b/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.cs new file mode 100644 index 0000000000..f1eccaf9e5 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class NewsletterSubscriptionLanguage : DbMigration + { + public override void Up() + { + AddColumn("dbo.NewsLetterSubscription", "WorkingLanguageId", c => c.Int(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.NewsLetterSubscription", "WorkingLanguageId"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.resx b/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.resx new file mode 100644 index 0000000000..b2b8d1b2db --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 318a741b96..69d6d0c855 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -488,6 +488,13 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayStyle.Default", "Default", "Standard"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayStyle.BadgeAll", "In bagdes", "Markiert"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayStyle.BadgeFreeProductsOnly", "Badge free products only", "Nur kostenlose Produkte markieren"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.WorkingLanguageId", + "Language", + "Sprache", + "Filter by language", + "Nach Sprache filtern"); + } } } diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index e0f3e29138..5f527da1fb 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -619,6 +619,10 @@ 201808051818238_Merge4.cs + + + 201809171309522_NewsletterSubscriptionLanguage.cs + @@ -1113,6 +1117,9 @@ 201808051818238_Merge4.cs + + 201809171309522_NewsletterSubscriptionLanguage.cs + diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 8831a488a0..fa6461e8cb 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -1017,7 +1017,22 @@ private IQueryable GetNewsLetterSubscriptionQuery(DataEx if (ctx.Filter.IsActiveSubscriber.HasValue) query = query.Where(x => x.Active == ctx.Filter.IsActiveSubscriber.Value); - if (ctx.Filter.CreatedFrom.HasValue) + if (ctx.Filter.WorkingLanguageId != null && ctx.Filter.WorkingLanguageId != 0) + { + var defaultLanguage = _languageService.Value.GetAllLanguages().FirstOrDefault(); + var isDefaultLanguage = ctx.Filter.WorkingLanguageId == defaultLanguage.Id; + + if (isDefaultLanguage) + { + query = query.Where(x => x.WorkingLanguageId == 0 || x.WorkingLanguageId == ctx.Filter.WorkingLanguageId); + } + else + { + query = query.Where(x => x.WorkingLanguageId == ctx.Filter.WorkingLanguageId); + } + } + + if (ctx.Filter.CreatedFrom.HasValue) { var createdFrom = _services.DateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _services.DateTimeHelper.CurrentTimeZone); query = query.Where(x => createdFrom <= x.CreatedOnUtc); diff --git a/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs b/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs index 83567209da..5dc78f8647 100644 --- a/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs +++ b/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs @@ -139,7 +139,8 @@ public virtual void DeleteNewsLetterSubscription(NewsLetterSubscription newsLett Email = email, Active = false, CreatedOnUtc = DateTime.UtcNow, - StoreId = storeId + StoreId = storeId, + WorkingLanguageId = _services.WorkContext.WorkingLanguage.Id }; InsertNewsLetterSubscription(newsletter); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 2bd8c4a230..7aad780dc8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -481,6 +481,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile ShippingStatusIds = filter.ShippingStatusIds, CustomerRoleIds = filter.CustomerRoleIds, IsActiveSubscriber = filter.IsActiveSubscriber, + WorkingLanguageId = filter.WorkingLanguageId, ShoppingCartTypeId = filter.ShoppingCartTypeId }; @@ -488,8 +489,16 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) .ToList(); - // deployment - model.Deployments = profile.Deployments + model.Filter.AvailableLanguages = new List(); + model.Filter.AvailableLanguages.Add(new SelectListItem { Text = T("Common.Unspecified"), Value = "" }); + + foreach (var lang in _languageService.GetAllLanguages()) + { + model.Filter.AvailableLanguages.Add(new SelectListItem { Text = lang.Name, Value = lang.Id.ToString() }); + } + + // deployment + model.Deployments = profile.Deployments .Select(x => { var deploymentModel = CreateDeploymentModel(profile, x, null, false); @@ -896,6 +905,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) ShippingStatusIds = model.Filter.ShippingStatusIds, CustomerRoleIds = model.Filter.CustomerRoleIds, IsActiveSubscriber = model.Filter.IsActiveSubscriber, + WorkingLanguageId = model.Filter.WorkingLanguageId, ShoppingCartTypeId = model.Filter.ShoppingCartTypeId }; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs index 4126854105..97b20033c2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs @@ -127,11 +127,15 @@ public class ExportFilterModel [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.IsActiveSubscriber")] public bool? IsActiveSubscriber { get; set; } - #endregion + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.WorkingLanguageId")] + public int? WorkingLanguageId { get; set; } + public List AvailableLanguages { get; set; } + + #endregion - #region Shopping Cart Items + #region Shopping Cart Items - [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.ShoppingCartTypeId")] + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.ShoppingCartTypeId")] public int? ShoppingCartTypeId { get; set; } public List AvailableShoppingCartTypes { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Filter.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Filter.cshtml index 55ba47098a..d96fa564dc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Filter.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Filter.cshtml @@ -312,6 +312,15 @@ @Html.ValidationMessageFor(x => x.Filter.IsActiveSubscriber)
    + @Html.SmartLabelFor(x => x.Filter.WorkingLanguageId) + + @Html.DropDownListFor(model => model.Filter.WorkingLanguageId, Model.Filter.AvailableLanguages) + @Html.ValidationMessageFor(x => x.Filter.WorkingLanguageId) +
    @Html.SmartLabelFor(x => x.Filter.CreatedFrom) diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index 6d4563a46b..81b9d388e4 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -705,7 +705,8 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha Email = model.Email, Active = true, CreatedOnUtc = DateTime.UtcNow, - StoreId = _storeContext.CurrentStore.Id + StoreId = _storeContext.CurrentStore.Id, + WorkingLanguageId = _services.WorkContext.WorkingLanguage.Id }); } } diff --git a/src/Presentation/SmartStore.Web/Controllers/NewsletterController.cs b/src/Presentation/SmartStore.Web/Controllers/NewsletterController.cs index cdd0a94175..5deb512aec 100644 --- a/src/Presentation/SmartStore.Web/Controllers/NewsletterController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/NewsletterController.cs @@ -91,8 +91,9 @@ public ActionResult Subscribe(bool subscribe, string email) Email = email, Active = false, CreatedOnUtc = DateTime.UtcNow, - StoreId = _storeContext.CurrentStore.Id - }; + StoreId = _storeContext.CurrentStore.Id, + WorkingLanguageId = _workContext.WorkingLanguage.Id + }; _newsLetterSubscriptionService.InsertNewsLetterSubscription(subscription); Services.MessageFactory.SendNewsLetterSubscriptionActivationMessage(subscription, _workContext.WorkingLanguage.Id); From b1770bfb954ee2a2314ea84ad8db77b63c476285 Mon Sep 17 00:00:00 2001 From: Raphael Hinterndorfer Date: Mon, 17 Sep 2018 20:35:14 +0200 Subject: [PATCH 112/932] Remove closer; Change tap event on canvas-blocker to click; --- src/Presentation/SmartStore.Web/Scripts/smartstore.offcanvas.js | 2 +- .../SmartStore.Web/Views/Catalog/Partials/OffCanvasMenu.cshtml | 1 - .../Views/ShoppingCart/Partials/OffCanvasCart.cshtml | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.offcanvas.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.offcanvas.js index dc6389a9e5..cb17a036b9 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.offcanvas.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.offcanvas.js @@ -41,7 +41,7 @@ }); if (this.options.autohide) { - $('body, .canvas-blocker').on('tap', $.proxy(this.autohide, this)); + $('body, .canvas-blocker').on('click', $.proxy(this.autohide, this)); } if (this.options.toggle) { diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/Partials/OffCanvasMenu.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/Partials/OffCanvasMenu.cshtml index e5c11c7fb6..0c1cc616b9 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/Partials/OffCanvasMenu.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/Partials/OffCanvasMenu.cshtml @@ -5,7 +5,6 @@
    - ×
    + @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnForumPage) + + @Html.SettingEditorFor(model => model.CaptchaSettings.ShowOnForumPage) + @Html.ValidationMessageFor(model => model.CaptchaSettings.ShowOnForumPage) +
    @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnProductReviewPage) diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 79c5c6814d..18b4210882 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -45,6 +45,7 @@ public partial class BoardsController : PublicControllerBase private readonly ForumSearchSettings _searchSettings; private readonly CustomerSettings _customerSettings; private readonly MediaSettings _mediaSettings; + private readonly CaptchaSettings _captchaSettings; private readonly IDateTimeHelper _dateTimeHelper; private readonly IBreadcrumb _breadcrumb; private readonly Lazy _templateProvider; @@ -60,6 +61,7 @@ public BoardsController( ForumSearchSettings searchSettings, CustomerSettings customerSettings, MediaSettings mediaSettings, + CaptchaSettings captchaSettings, IDateTimeHelper dateTimeHelper, IBreadcrumb breadcrumb, Lazy templateProvider, @@ -74,6 +76,7 @@ public BoardsController( _searchSettings = searchSettings; _customerSettings = customerSettings; _mediaSettings = mediaSettings; + _captchaSettings = captchaSettings; _dateTimeHelper = dateTimeHelper; _breadcrumb = breadcrumb; _templateProvider = templateProvider; @@ -850,6 +853,7 @@ public ActionResult TopicCreate(int id) var model = new EditForumTopicModel(); model.Id = 0; model.IsEdit = false; + model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; model.ForumId = forum.Id; model.ForumName = forum.GetLocalized(x => x.Name); model.ForumSeName = forum.GetSeName(); @@ -866,19 +870,25 @@ public ActionResult TopicCreate(int id) [HttpPost] [ValidateInput(false)] - [GdprConsent] - public ActionResult TopicCreate(EditForumTopicModel model) + [ValidateCaptcha] + [GdprConsent] + public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) { if (!_forumSettings.ForumsEnabled) { return HttpNotFound(); } + if (_captchaSettings.Enabled && _captchaSettings.ShowOnForumPage && !captchaValid) + { + ModelState.AddModelError("", T("Common.WrongCaptcha")); + } + var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(model.ForumId); if (forum == null) { - return RedirectToRoute("Boards"); + return HttpNotFound(); } if (ModelState.IsValid) @@ -966,6 +976,7 @@ public ActionResult TopicCreate(EditForumTopicModel model) // Redisplay form. model.TopicPriorities = ForumTopicTypesList(); model.IsEdit = false; + model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; model.ForumId = forum.Id; model.ForumName = forum.GetLocalized(x => x.Name); model.ForumSeName = forum.GetSeName(); @@ -1006,6 +1017,7 @@ public ActionResult TopicEdit(int id) var model = new EditForumTopicModel(); model.IsEdit = true; + model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; model.TopicPriorities = ForumTopicTypesList(); model.ForumName = forum.GetLocalized(x => x.Name); model.ForumSeName = forum.GetSeName(); @@ -1033,18 +1045,24 @@ public ActionResult TopicEdit(int id) [HttpPost] [ValidateInput(false)] - public ActionResult TopicEdit(EditForumTopicModel model) + [ValidateCaptcha] + public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) { if (!_forumSettings.ForumsEnabled) { return HttpNotFound(); } + if (_captchaSettings.Enabled && _captchaSettings.ShowOnForumPage && !captchaValid) + { + ModelState.AddModelError("", T("Common.WrongCaptcha")); + } + var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(model.Id); if (forumTopic == null) { - return RedirectToRoute("Boards"); + return HttpNotFound(); } var forum = forumTopic.Forum; if (forum == null) @@ -1149,6 +1167,7 @@ public ActionResult TopicEdit(EditForumTopicModel model) // Redisplay form. model.TopicPriorities = ForumTopicTypesList(); model.IsEdit = true; + model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; model.ForumName = forum.GetLocalized(x => x.Name); model.ForumSeName = forum.GetSeName(); model.ForumId = forum.Id; @@ -1223,6 +1242,7 @@ public ActionResult PostCreate(int id, int? quote) Id = 0, ForumTopicId = forumTopic.Id, IsEdit = false, + DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage, ForumEditor = _forumSettings.ForumEditor, ForumName = forum.GetLocalized(x => x.Name), ForumTopicSubject = forumTopic.Subject, @@ -1267,19 +1287,25 @@ public ActionResult PostCreate(int id, int? quote) [HttpPost] [ValidateInput(false)] - [GdprConsent] - public ActionResult PostCreate(EditForumPostModel model) + [ValidateCaptcha] + [GdprConsent] + public ActionResult PostCreate(EditForumPostModel model, bool captchaValid) { if (!_forumSettings.ForumsEnabled) { return HttpNotFound(); } + if (_captchaSettings.Enabled && _captchaSettings.ShowOnForumPage && !captchaValid) + { + ModelState.AddModelError("", T("Common.WrongCaptcha")); + } + var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(model.ForumTopicId); if (forumTopic == null) { - return RedirectToRoute("Boards"); + return HttpNotFound(); } if (ModelState.IsValid) @@ -1368,6 +1394,7 @@ public ActionResult PostCreate(EditForumPostModel model) } model.IsEdit = false; + model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; model.ForumName = forum.GetLocalized(x => x.Name); model.ForumTopicId = forumTopic.Id; model.ForumTopicSubject = forumTopic.Subject; @@ -1415,6 +1442,7 @@ public ActionResult PostEdit(int id) Id = forumPost.Id, ForumTopicId = forumTopic.Id, IsEdit = true, + DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage, ForumEditor = _forumSettings.ForumEditor, ForumName = forum.GetLocalized(x => x.Name), ForumTopicSubject = forumTopic.Subject, @@ -1438,18 +1466,24 @@ public ActionResult PostEdit(int id) [HttpPost] [ValidateInput(false)] - public ActionResult PostEdit(EditForumPostModel model) + [ValidateCaptcha] + public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) { if (!_forumSettings.ForumsEnabled) { return HttpNotFound(); } + if (_captchaSettings.Enabled && _captchaSettings.ShowOnForumPage && !captchaValid) + { + ModelState.AddModelError("", T("Common.WrongCaptcha")); + } + var customer = Services.WorkContext.CurrentCustomer; var forumPost = _forumService.GetPostById(model.Id); if (forumPost == null) { - return RedirectToRoute("Boards"); + return HttpNotFound(); } if (!_forumService.IsCustomerAllowedToEditPost(customer, forumPost)) @@ -1536,6 +1570,7 @@ public ActionResult PostEdit(EditForumPostModel model) // Redisplay form. model.IsEdit = true; + model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; model.ForumName = forum.GetLocalized(x => x.Name); model.ForumTopicId = forumTopic.Id; model.ForumTopicSubject = forumTopic.Subject; diff --git a/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs index 39d72eace5..1842af54ec 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs @@ -12,6 +12,7 @@ public partial class EditForumPostModel : EntityModelBase { public int ForumTopicId { get; set; } public bool IsEdit { get; set; } + public bool DisplayCaptcha { get; set; } [AllowHtml] public string Text { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Boards/EditForumTopicModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/EditForumTopicModel.cs index 712f78816d..d0ba916f22 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/EditForumTopicModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/EditForumTopicModel.cs @@ -17,6 +17,7 @@ public EditForumTopicModel() } public bool IsEdit { get; set; } + public bool DisplayCaptcha { get; set; } public int ForumId { get; set; } public LocalizedValue ForumName { get; set; } diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml index 2ace318cf2..97165cc9b2 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml @@ -1,8 +1,7 @@ -@model EditForumPostModel -@using SmartStore.Core.Domain.Forums; +@using SmartStore.Core.Domain.Forums; @using SmartStore.Web; @using SmartStore.Web.Models.Boards; - +@model EditForumPostModel @using (Html.BeginForm()) {
    @@ -56,6 +55,16 @@
    + @if (Model.DisplayCaptcha) + { +
    +
    +
    + @Html.Raw(Html.GenerateCaptcha()) +
    +
    +
    + }
    diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdateTopic.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdateTopic.cshtml index 4f80ca9c37..f7f69aa490 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdateTopic.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdateTopic.cshtml @@ -69,13 +69,24 @@
    + @if (Model.DisplayCaptcha) + { +
    +
    +
    + @Html.Raw(Html.GenerateCaptcha()) +
    +
    +
    + } +
    - From 0f79325218876dc2ea9dc846f8806eab37b0920a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 18 Sep 2018 21:03:59 +0200 Subject: [PATCH 114/932] Makes BlogPostModel tabbable. Resolves #1507. --- .../SmartStore.Web/Administration/Models/Blogs/BlogPostModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Blogs/BlogPostModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Blogs/BlogPostModel.cs index 2f32af2eda..149fc5d874 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Blogs/BlogPostModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Blogs/BlogPostModel.cs @@ -10,7 +10,7 @@ namespace SmartStore.Admin.Models.Blogs { [Validator(typeof(BlogPostValidator))] - public class BlogPostModel : EntityModelBase, IStoreSelector + public class BlogPostModel : TabbableModel, IStoreSelector { [SmartResourceDisplayName("Admin.ContentManagement.Blog.BlogPosts.Fields.Language")] public int LanguageId { get; set; } From c7359829d4973feff040ca886578357f79d02968 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 19 Sep 2018 02:36:05 +0200 Subject: [PATCH 115/932] Page Blocks infrastructure --- .../SmartStore.Web.Framework.csproj | 2 ++ .../UI/Blocks/IBlock.cs | 31 +++++++++++++++++++ .../UI/Blocks/IBlockContainer.cs | 20 ++++++++++++ .../DefaultFacetTemplateSelector.cs | 4 +-- .../Infrastructure/DefaultWidgetSelector.cs | 2 +- 5 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlock.cs create mode 100644 src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockContainer.cs diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index b001dafaef..0ba30e7725 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -314,6 +314,7 @@ + @@ -356,6 +357,7 @@ + diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlock.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlock.cs new file mode 100644 index 0000000000..5716497d27 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlock.cs @@ -0,0 +1,31 @@ +using System; +using System.Web.Routing; + +namespace SmartStore.Web.Framework.UI.Blocks +{ + public interface IBlock + { + string SystemName { get; } + BlockRouteInfo SelectRoute(BlockUiType uiType, string publicTemplateName); + } + + public enum BlockUiType + { + Public, + Preview, + Edit + } + + public class BlockRouteInfo : RouteInfo + { + public BlockRouteInfo(string action, string controller, string viewPath, object routeValues) + : base(action, controller, routeValues) + { + Guard.NotEmpty(viewPath, nameof(viewPath)); + + ViewPath = viewPath; + } + + public string ViewPath { get; set; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockContainer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockContainer.cs new file mode 100644 index 0000000000..c927e8ff6d --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockContainer.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Web.Framework.UI.Blocks +{ + public interface IBlockContainer where T : IBlock + { + T Block { get; } + + string TagLine { get; } + string Title { get; } + string SubTitle { get; } + string Body { get; } + string MediaBody { get; } + bool IsInversed { get; } + } +} diff --git a/src/Presentation/SmartStore.Web/Infrastructure/DefaultFacetTemplateSelector.cs b/src/Presentation/SmartStore.Web/Infrastructure/DefaultFacetTemplateSelector.cs index e540956369..510bab7841 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/DefaultFacetTemplateSelector.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/DefaultFacetTemplateSelector.cs @@ -19,8 +19,8 @@ public RouteInfo GetTemplateRoute(FacetGroup facetGroup) var routeValues = new RouteValueDictionary(new { area = "", - facetGroup = facetGroup, - templateName = templateName + facetGroup, + templateName }); return new RouteInfo(action, controller, routeValues); diff --git a/src/Presentation/SmartStore.Web/Infrastructure/DefaultWidgetSelector.cs b/src/Presentation/SmartStore.Web/Infrastructure/DefaultWidgetSelector.cs index a27662ebfe..71c7625746 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/DefaultWidgetSelector.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/DefaultWidgetSelector.cs @@ -142,7 +142,7 @@ public virtual IEnumerable GetWidgets(string widgetZone, object { ControllerName = "Topic", ActionName = "TopicWidget", - RouteValues = new RouteValueDictionary() + RouteValues = new RouteValueDictionary { {"Namespaces", "SmartStore.Web.Controllers"}, {"area", null}, From 6a95ebdb030d8fd0064318127fb4529820138406 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 19 Sep 2018 09:43:35 +0200 Subject: [PATCH 116/932] #1507 Publish ModelBoundEvent in blog controller --- .../Administration/Controllers/BlogController.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/BlogController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/BlogController.cs index 4701a9c2ee..13a4f221b4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/BlogController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/BlogController.cs @@ -14,6 +14,7 @@ using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; @@ -147,8 +148,8 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - public ActionResult Create(BlogPostModel model, bool continueEditing) + [HttpPost, ValidateInput(false), ParameterBasedOnFormName("save-continue", "continueEditing")] + public ActionResult Create(BlogPostModel model, bool continueEditing, FormCollection form) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) return AccessDeniedView(); @@ -169,6 +170,8 @@ public ActionResult Create(BlogPostModel model, bool continueEditing) // Stores SaveStoreMappings(blogPost, model); + Services.EventPublisher.Publish(new ModelBoundEvent(model, blogPost, form)); + NotifySuccess(_localizationService.GetResource("Admin.ContentManagement.Blog.BlogPosts.Added")); return continueEditing ? RedirectToAction("Edit", new { id = blogPost.Id }) : RedirectToAction("List"); } @@ -199,8 +202,8 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - public ActionResult Edit(BlogPostModel model, bool continueEditing) + [HttpPost, ValidateInput(false), ParameterBasedOnFormName("save-continue", "continueEditing")] + public ActionResult Edit(BlogPostModel model, bool continueEditing, FormCollection form) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) return AccessDeniedView(); @@ -224,6 +227,8 @@ public ActionResult Edit(BlogPostModel model, bool continueEditing) // Stores SaveStoreMappings(blogPost, model); + Services.EventPublisher.Publish(new ModelBoundEvent(model, blogPost, form)); + NotifySuccess(_localizationService.GetResource("Admin.ContentManagement.Blog.BlogPosts.Updated")); return continueEditing ? RedirectToAction("Edit", new { id = blogPost.Id }) : RedirectToAction("List"); } From 2264aac2b683b7efef15b6f2bb11addd7b95ce49 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 19 Sep 2018 11:27:15 +0200 Subject: [PATCH 117/932] Do not allow to set a product linkage for a product attribute if the related product is bundle item of a product bundle. It can result in an infinite sequence of linked products. --- .../Migrations/MigrationsConfiguration.cs | 4 +++ .../Catalog/IProductService.cs | 9 +++++- .../Catalog/ProductService.cs | 25 +++++++++++++--- .../Controllers/ProductController.cs | 29 ++++++++++++++----- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index f5769b8124..4d00f2b33d 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -501,6 +501,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Auf Forenseiten anzeigen", "Specifies whether to display a CAPTCHA on forum pages when creating or replying to a topic.", "Legt fest, ob ein CAPTCHA auf Forenseiten angezeigt werden soll, wenn ein Thema erstellt oder darauf geantwortet wird."); + + builder.AddOrUpdate("Admin.Catalog.Products.BundleItems.NoProductLinkageForBundleItem", + "The type cannot be changed to \"Product\" because the product \"{0}\" is bundle item of a product bundle.", + "Der Typ kann nicht in \"Produkt\" geändert werden, weil das Produkt \"{0}\" auf der Stückliste eines Produkt-Bundle steht."); } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs index b585dbbc4f..b1f8ddddf1 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs @@ -391,7 +391,14 @@ public partial interface IProductService /// Map of bundle items Multimap GetBundleItemsByProductIds(int[] productIds, bool showHidden = false); - #endregion + /// + /// Checks whether a product is a bundle item. + /// + /// Product identifier. + /// True if the product is a bundle item. + bool IsBundleItem(int productId); + + #endregion } [Flags] diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index ce67b25993..3f7403adc1 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -1025,7 +1025,7 @@ public virtual ProductBundleItem GetBundleItemById(int bundleItemId) return _productBundleItemRepository.GetById(bundleItemId); } - public virtual IList GetBundleItems(int bundleProductId, bool showHidden = false) + public virtual IList GetBundleItems(int bundleProductId, bool showHidden = false) { var query = from pbi in _productBundleItemRepository.Table @@ -1043,7 +1043,7 @@ orderby pbi.DisplayOrder return bundleItemData; } - public virtual Multimap GetBundleItemsByProductIds(int[] productIds, bool showHidden = false) + public virtual Multimap GetBundleItemsByProductIds(int[] productIds, bool showHidden = false) { Guard.NotNull(productIds, nameof(productIds)); @@ -1061,6 +1061,23 @@ orderby pbi.DisplayOrder return map; } - #endregion - } + public virtual bool IsBundleItem(int productId) + { + if (productId == 0) + { + return false; + } + + var query = + from pbi in _productBundleItemRepository.TableUntracked + join bundle in _productRepository.TableUntracked on pbi.BundleProductId equals bundle.Id + where pbi.ProductId == productId && !bundle.Deleted && !bundle.IsSystemProduct + select pbi; + + var result = query.Any(); + return result; + } + + #endregion + } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index fadeb8b7b4..aec7b7df77 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -3477,14 +3477,29 @@ public ActionResult ProductAttributeValueEditPopup(int id) [HttpPost] public ActionResult ProductAttributeValueEditPopup(string btnId, string formId, ProductModel.ProductVariantAttributeValueModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + return AccessDeniedView(); + } var pvav = _productAttributeService.GetProductVariantAttributeValueById(model.Id); - if (pvav == null) - return RedirectToAction("List", "Product"); + if (pvav == null) + { + return RedirectToAction("List", "Product"); + } - if (ModelState.IsValid) + if (model.ValueTypeId == (int)ProductVariantAttributeValueType.ProductLinkage) + { + if (_productService.IsBundleItem(pvav.ProductVariantAttribute.ProductId)) + { + var product = _productService.GetProductById(pvav.ProductVariantAttribute.ProductId); + var productName = product?.Name.NaIfEmpty(); + + ModelState.AddModelError(string.Empty, T("Admin.Catalog.Products.BundleItems.NoProductLinkageForBundleItem", productName)); + } + } + + if (ModelState.IsValid) { pvav.Name = model.Name; pvav.Alias = model.Alias; @@ -3510,9 +3525,9 @@ public ActionResult ProductAttributeValueEditPopup(string btnId, string formId, UpdateLocales(pvav, model); } - catch (Exception exception) + catch (Exception ex) { - ModelState.AddModelError("", exception.Message); + ModelState.AddModelError("", ex.Message); return View(model); } From 2323c5f7865fe6af333026cfaac3c607a3170a10 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 19 Sep 2018 16:04:22 +0200 Subject: [PATCH 118/932] Updated change log --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index c29e6d43f7..c6294afa43 100644 --- a/changelog.md +++ b/changelog.md @@ -18,6 +18,7 @@ * Added option to display a captcha on forum pages when creating or replying to a topic. * MegaSearch: Supports searching for forum posts. * Customer avatar: Letter with colored background if no avatar image was uploaded. +* Viveum: Supports payment via "Virtual Account Brands" like PayPal. ### Improvements * (Perf) Significantly increased query performance for products with a lot of category assignments (> 10). From 21b31ea7c0b7fb4157b501fea5ea9356f7e8bcc1 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 20 Sep 2018 11:50:20 +0200 Subject: [PATCH 119/932] Resolves #1184 Sort Current shopping carts & Current wishlists by ShoppingCartItem.CreatedOn --- changelog.md | 1 + .../Customers/CustomerService.cs | 71 +++++++++------ .../Controllers/ShoppingCartController.cs | 87 ++++++++----------- .../CustomerRegistrationServiceTests.cs | 15 ++-- 4 files changed, 92 insertions(+), 82 deletions(-) diff --git a/changelog.md b/changelog.md index c6294afa43..a03f497548 100644 --- a/changelog.md +++ b/changelog.md @@ -24,6 +24,7 @@ * (Perf) Significantly increased query performance for products with a lot of category assignments (> 10). * Debitoor: Partially update customer instead of full update to avoid all fields being overwritten * #1479 Show in messages the delivery time at the time of purchase +* #1184 Sort Current shopping carts & Current wishlists by ShoppingCartItem.CreatedOn. ### Bugfixes * In a multi-store environment, multiple topics with the same system name cannot be resolved reliably. diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs index 37981c8195..ffeec5273f 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Web; @@ -14,29 +13,28 @@ using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Localization; using SmartStore.Core.Fakes; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; using SmartStore.Data.Caching; using SmartStore.Services.Common; using SmartStore.Services.Localization; -using SmartStore.Core.Logging; -using SmartStore.Services.Messages; namespace SmartStore.Services.Customers { - public partial class CustomerService : ICustomerService + public partial class CustomerService : ICustomerService { private readonly IRepository _customerRepository; private readonly IRepository _customerRoleRepository; private readonly IRepository _gaRepository; private readonly IRepository _rewardPointsHistoryRepository; + private readonly IRepository _shoppingCartItemRepository; private readonly IGenericAttributeService _genericAttributeService; private readonly RewardPointsSettings _rewardPointsSettings; private readonly ICommonServices _services; private readonly HttpContextBase _httpContext; private readonly IUserAgent _userAgent; private readonly CustomerSettings _customerSettings; - private readonly Lazy _messageModelProvider; private readonly Lazy _gdprTool; public CustomerService( @@ -44,26 +42,26 @@ public CustomerService( IRepository customerRoleRepository, IRepository gaRepository, IRepository rewardPointsHistoryRepository, + IRepository shoppingCartItemRepository, IGenericAttributeService genericAttributeService, RewardPointsSettings rewardPointsSettings, ICommonServices services, HttpContextBase httpContext, IUserAgent userAgent, CustomerSettings customerSettings, - Lazy messageModelProvider, Lazy gdprTool) { _customerRepository = customerRepository; _customerRoleRepository = customerRoleRepository; _gaRepository = gaRepository; _rewardPointsHistoryRepository = rewardPointsHistoryRepository; + _shoppingCartItemRepository = shoppingCartItemRepository; _genericAttributeService = genericAttributeService; _rewardPointsSettings = rewardPointsSettings; _services = services; _httpContext = httpContext; _userAgent = userAgent; _customerSettings = customerSettings; - _messageModelProvider = messageModelProvider; _gdprTool = gdprTool; T = NullLocalizer.Instance; @@ -80,7 +78,41 @@ public virtual IPagedList SearchCustomers(CustomerSearchQuery q) { Guard.NotNull(q, nameof(q)); - var query = _customerRepository.Table; + var isOrdered = false; + IQueryable query = null; + + if (q.OnlyWithCart) + { + var cartItemQuery = _shoppingCartItemRepository.TableUntracked.Expand(x => x.Customer); + + if (q.CartType.HasValue) + { + cartItemQuery = cartItemQuery.Where(x => x.ShoppingCartTypeId == (int)q.CartType.Value); + } + + var groupQuery = + from sci in cartItemQuery + group sci by sci.CustomerId into grp + select grp + .OrderByDescending(x => x.CreatedOnUtc) + .Select(x => new + { + x.Customer, + x.CreatedOnUtc + }) + .FirstOrDefault(); + + // We have to sort again because of paging. + query = groupQuery + .OrderByDescending(x => x.CreatedOnUtc) + .Select(x => x.Customer); + + isOrdered = true; + } + else + { + query = _customerRepository.Table; + } if (q.Email.HasValue()) { @@ -184,23 +216,12 @@ public virtual IPagedList SearchCustomers(CustomerSearchQuery q) .Select(z => z.Customer); } - if (q.OnlyWithCart) - { - int? sctId = null; - if (q.CartType.HasValue) - { - sctId = (int)q.CartType.Value; - } - - query = q.CartType.HasValue ? - query.Where(c => c.ShoppingCartItems.Where(x => x.ShoppingCartTypeId == sctId).Count() > 0) : - query.Where(c => c.ShoppingCartItems.Count() > 0); - } - - query = query.OrderByDescending(c => c.CreatedOnUtc); + if (!isOrdered) + { + query = query.OrderByDescending(c => c.CreatedOnUtc); + } var customers = new PagedList(query, q.PageIndex, q.PageSize); - return customers; } @@ -217,7 +238,7 @@ public virtual IPagedList GetAllCustomersByPasswordFormat(PasswordForm return customers; } - public virtual IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, int[] customerRoleIds, int pageIndex, int pageSize) + public virtual IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, int[] customerRoleIds, int pageIndex, int pageSize) { var q = new CustomerSearchQuery { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ShoppingCartController.cs index 1f908ebe5c..e8f7501474 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ShoppingCartController.cs @@ -6,9 +6,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Customers; using SmartStore.Services.Helpers; -using SmartStore.Services.Localization; using SmartStore.Services.Security; -using SmartStore.Services.Stores; using SmartStore.Services.Tax; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Security; @@ -19,45 +17,32 @@ namespace SmartStore.Admin.Controllers [AdminAuthorize] public class ShoppingCartController : AdminControllerBase { - #region Fields - private readonly ICustomerService _customerService; private readonly IDateTimeHelper _dateTimeHelper; private readonly IPriceFormatter _priceFormatter; - private readonly IStoreService _storeService; private readonly ITaxService _taxService; private readonly IPriceCalculationService _priceCalculationService; - private readonly IPermissionService _permissionService; - private readonly ILocalizationService _localizationService; - #endregion - - #region Constructors - public ShoppingCartController(ICustomerService customerService, - IDateTimeHelper dateTimeHelper, IPriceFormatter priceFormatter, - IStoreService storeService, ITaxService taxService, - IPriceCalculationService priceCalculationService, - IPermissionService permissionService, ILocalizationService localizationService) + public ShoppingCartController( + ICustomerService customerService, + IDateTimeHelper dateTimeHelper, + IPriceFormatter priceFormatter, + ITaxService taxService, + IPriceCalculationService priceCalculationService) { - this._customerService = customerService; - this._dateTimeHelper = dateTimeHelper; - this._priceFormatter = priceFormatter; - this._storeService = storeService; - this._taxService = taxService; - this._priceCalculationService = priceCalculationService; - this._permissionService = permissionService; - this._localizationService = localizationService; + _customerService = customerService; + _dateTimeHelper = dateTimeHelper; + _priceFormatter = priceFormatter; + _taxService = taxService; + _priceCalculationService = priceCalculationService; } - - #endregion - #region Methods - - //shopping carts public ActionResult CurrentCarts() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageOrders)) + { return AccessDeniedView(); + } return View(); } @@ -67,7 +52,7 @@ public ActionResult CurrentCarts(GridCommand command) { var gridModel = new GridModel(); - if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageOrders)) { var q = new CustomerSearchQuery { @@ -77,14 +62,15 @@ public ActionResult CurrentCarts(GridCommand command) PageSize = command.PageSize }; - var customers = _customerService.SearchCustomers(q); + var guestStr = T("Admin.Customers.Guest").Text; + var customers = _customerService.SearchCustomers(q); gridModel.Data = customers.Select(x => { return new ShoppingCartModel { CustomerId = x.Id, - CustomerEmail = x.IsGuest() ? T("Admin.Customers.Guest").Text : x.Email, + CustomerEmail = x.IsGuest() ? guestStr : x.Email, TotalItems = x.CountProductsInCart(ShoppingCartType.ShoppingCart) }; }); @@ -109,15 +95,15 @@ public ActionResult GetCartDetails(int customerId) { var gridModel = new GridModel(); - if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageOrders)) { - var customer = _customerService.GetCustomerById(customerId); + decimal taxRate; + var customer = _customerService.GetCustomerById(customerId); var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart); gridModel.Data = cart.Select(sci => { - decimal taxRate; - var store = _storeService.GetStoreById(sci.Item.StoreId); + var store = Services.StoreService.GetStoreById(sci.Item.StoreId); var sciModel = new ShoppingCartItemModel { @@ -126,12 +112,13 @@ public ActionResult GetCartDetails(int customerId) ProductId = sci.Item.ProductId, Quantity = sci.Item.Quantity, ProductName = sci.Item.Product.Name, - ProductTypeName = sci.Item.Product.GetProductTypeLabel(_localizationService), + ProductTypeName = sci.Item.Product.GetProductTypeLabel(Services.Localization), ProductTypeLabelHint = sci.Item.Product.ProductTypeLabelHint, UnitPrice = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate)), Total = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetSubTotal(sci, true), out taxRate)), UpdatedOn = _dateTimeHelper.ConvertToUserTime(sci.Item.UpdatedOnUtc, DateTimeKind.Utc) }; + return sciModel; }); @@ -150,12 +137,12 @@ public ActionResult GetCartDetails(int customerId) }; } - - //wishlists public ActionResult CurrentWishlists() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageOrders)) + { return AccessDeniedView(); + } return View(); } @@ -165,7 +152,7 @@ public ActionResult CurrentWishlists(GridCommand command) { var gridModel = new GridModel(); - if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageOrders)) { var q = new CustomerSearchQuery { @@ -175,14 +162,15 @@ public ActionResult CurrentWishlists(GridCommand command) PageSize = command.PageSize }; - var customers = _customerService.SearchCustomers(q); + var guestStr = T("Admin.Customers.Guest").Text; + var customers = _customerService.SearchCustomers(q); gridModel.Data = customers.Select(x => { return new ShoppingCartModel { CustomerId = x.Id, - CustomerEmail = x.IsGuest() ? T("Admin.Customers.Guest").Text : x.Email, + CustomerEmail = x.IsGuest() ? guestStr : x.Email, TotalItems = x.CountProductsInCart(ShoppingCartType.Wishlist) }; }); @@ -207,15 +195,15 @@ public ActionResult GetWishlistDetails(int customerId) { var gridModel = new GridModel(); - if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageOrders)) { - var customer = _customerService.GetCustomerById(customerId); + decimal taxRate; + var customer = _customerService.GetCustomerById(customerId); var cart = customer.GetCartItems(ShoppingCartType.Wishlist); gridModel.Data = cart.Select(sci => { - decimal taxRate; - var store = _storeService.GetStoreById(sci.Item.StoreId); + var store = Services.StoreService.GetStoreById(sci.Item.StoreId); var sciModel = new ShoppingCartItemModel { @@ -224,12 +212,13 @@ public ActionResult GetWishlistDetails(int customerId) ProductId = sci.Item.ProductId, Quantity = sci.Item.Quantity, ProductName = sci.Item.Product.Name, - ProductTypeName = sci.Item.Product.GetProductTypeLabel(_localizationService), + ProductTypeName = sci.Item.Product.GetProductTypeLabel(Services.Localization), ProductTypeLabelHint = sci.Item.Product.ProductTypeLabelHint, UnitPrice = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate)), Total = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetSubTotal(sci, true), out taxRate)), UpdatedOn = _dateTimeHelper.ConvertToUserTime(sci.Item.UpdatedOnUtc, DateTimeKind.Utc) }; + return sciModel; }); @@ -247,7 +236,5 @@ public ActionResult GetWishlistDetails(int customerId) Data = gridModel }; } - - #endregion } } diff --git a/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs index 3cfc757765..33380138d7 100644 --- a/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Rhino.Mocks; @@ -7,6 +8,7 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Security; using SmartStore.Core.Events; using SmartStore.Core.Fakes; @@ -15,17 +17,17 @@ using SmartStore.Services.Messages; using SmartStore.Services.Security; using SmartStore.Tests; -using System; namespace SmartStore.Services.Tests.Customers { - [TestFixture] + [TestFixture] public class CustomerRegistrationServiceTests : ServiceTest { IRepository _customerRepo; IRepository _customerRoleRepo; IRepository _genericAttributeRepo; IRepository _rewardPointsHistoryRepo; + IRepository _shoppingCartItemRepo; IGenericAttributeService _genericAttributeService; IEncryptionService _encryptionService; ICustomerService _customerService; @@ -38,7 +40,6 @@ public class CustomerRegistrationServiceTests : ServiceTest IStoreContext _storeContext; ICommonServices _services; IUserAgent _userAgent; - Lazy _messageModelProvider; Lazy _gdprTool; [SetUp] @@ -112,11 +113,12 @@ public class CustomerRegistrationServiceTests : ServiceTest _eventPublisher = MockRepository.GenerateMock(); _eventPublisher.Expect(x => x.Publish(Arg.Is.Anything)); - _customerRepo.Expect(x => x.Table).Return(new List() { customer1, customer2, customer3, customer4, customer5 }.AsQueryable()); + _customerRepo.Expect(x => x.Table).Return(new List { customer1, customer2, customer3, customer4, customer5 }.AsQueryable()); _customerRoleRepo = MockRepository.GenerateMock>(); _genericAttributeRepo = MockRepository.GenerateMock>(); _rewardPointsHistoryRepo = MockRepository.GenerateMock>(); + _shoppingCartItemRepo = MockRepository.GenerateMock>(); _userAgent = MockRepository.GenerateMock(); _genericAttributeService = MockRepository.GenerateMock(); @@ -130,7 +132,6 @@ public class CustomerRegistrationServiceTests : ServiceTest _services.Expect(x => x.Cache).Return(NullCache.Instance); _services.Expect(x => x.EventPublisher).Return(_eventPublisher); - _messageModelProvider = MockRepository.GenerateMock>(); _gdprTool = MockRepository.GenerateMock>(); _customerService = new CustomerService( @@ -138,13 +139,13 @@ public class CustomerRegistrationServiceTests : ServiceTest _customerRoleRepo, _genericAttributeRepo, _rewardPointsHistoryRepo, + _shoppingCartItemRepo, _genericAttributeService, _rewardPointsSettings, _services, new FakeHttpContext("~/"), _userAgent, new CustomerSettings(), - _messageModelProvider, _gdprTool); _customerRegistrationService = new CustomerRegistrationService(_customerService, From 7875f39ada0e9f1bd8efb2274e42ce6e79d91961 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 20 Sep 2018 16:52:22 +0200 Subject: [PATCH 120/932] Resolves #1172 Search: Option to display related search terms on search page --- changelog.md | 4 ++- .../Migrations/MigrationsConfiguration.cs | 4 +++ .../Controllers/BoardsController.cs | 2 -- .../Controllers/SearchController.cs | 27 +++++++++---------- .../SmartStore.Web/Views/Boards/Search.cshtml | 8 +++--- .../SmartStore.Web/Views/Search/Search.cshtml | 4 +-- 6 files changed, 25 insertions(+), 24 deletions(-) diff --git a/changelog.md b/changelog.md index a03f497548..54be842951 100644 --- a/changelog.md +++ b/changelog.md @@ -16,7 +16,9 @@ * Added option to add multiple file versions to product download section * Added options for alternating price display (in badges) * Added option to display a captcha on forum pages when creating or replying to a topic. -* MegaSearch: Supports searching for forum posts. +* **MegaSearch**: + * Supports searching for forum posts. + * #1172 Option to display related search terms on search page. * Customer avatar: Letter with colored background if no avatar image was uploaded. * Viveum: Supports payment via "Virtual Account Brands" like PayPal. diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 4d00f2b33d..50190ddea6 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -505,6 +505,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Catalog.Products.BundleItems.NoProductLinkageForBundleItem", "The type cannot be changed to \"Product\" because the product \"{0}\" is bundle item of a product bundle.", "Der Typ kann nicht in \"Produkt\" geändert werden, weil das Produkt \"{0}\" auf der Stückliste eines Produkt-Bundle steht."); + + builder.AddOrUpdate("Search.RelatedSearchTerms", + "Related search terms", + "Verwandte Suchbegriffe"); } } } diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 18b4210882..bdd2715e83 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -1802,8 +1802,6 @@ public ActionResult Search(ForumSearchQuery query) PrepareSearchResult(model, null); - model.AddSpellCheckerSuggestions(model.SearchResult.SpellCheckerSuggestions, T, x => Url.RouteUrl("BoardSearch", new { q = x })); - return View(model); } diff --git a/src/Presentation/SmartStore.Web/Controllers/SearchController.cs b/src/Presentation/SmartStore.Web/Controllers/SearchController.cs index 71fe76ac60..3adf9c34e0 100644 --- a/src/Presentation/SmartStore.Web/Controllers/SearchController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/SearchController.cs @@ -70,9 +70,11 @@ public ActionResult SearchBox() [HttpPost, ValidateInput(false)] public ActionResult InstantSearch(CatalogSearchQuery query) - { - if (string.IsNullOrWhiteSpace(query.Term) || query.Term.Length < _searchSettings.InstantSearchTermMinLength) - return Content(string.Empty); + { + if (string.IsNullOrWhiteSpace(query.Term) || query.Term.Length < _searchSettings.InstantSearchTermMinLength) + { + return Content(string.Empty); + } query .BuildFacetMap(false) @@ -97,12 +99,10 @@ public ActionResult InstantSearch(CatalogSearchQuery query) mappingSettings.MapPictures = _searchSettings.ShowProductImagesInInstantSearch; mappingSettings.ThumbnailSize = _mediaSettings.ProductThumbPictureSizeOnProductDetailsPage; - var summaryModel = _catalogHelper.MapProductSummaryModel(result.Hits, mappingSettings); - - // Add product hits - model.TopProducts = summaryModel; + // Add product hits. + model.TopProducts = _catalogHelper.MapProductSummaryModel(result.Hits, mappingSettings); - // Add spell checker suggestions (if any) + // Add spell checker suggestions (if any). model.AddSpellCheckerSuggestions(result.SpellCheckerSuggestions, T, x => Url.RouteUrl("Search", new { q = x })); return PartialView(model); @@ -131,9 +131,9 @@ public ActionResult Search(CatalogSearchQuery query) { result = _catalogSearchService.Search(query); } - catch (Exception exception) + catch (Exception ex) { - model.Error = exception.ToString(); + model.Error = ex.ToString(); result = new CatalogSearchResult(query); } @@ -166,15 +166,12 @@ public ActionResult Search(CatalogSearchQuery query) var mappingSettings = _catalogHelper.GetBestFitProductSummaryMappingSettings(query.GetViewMode()); var summaryModel = _catalogHelper.MapProductSummaryModel(result.Hits, mappingSettings); - // Prepare paging/sorting/mode stuff + // Prepare paging/sorting/mode stuff. _catalogHelper.MapListActions(summaryModel, null, _catalogSettings.DefaultPageSizeOptions); - // Add product hits + // Add product hits. model.TopProducts = summaryModel; - // Add spell checker suggestions (if any) - model.AddSpellCheckerSuggestions(result.SpellCheckerSuggestions, T, x => Url.RouteUrl("Search", new { q = x })); - return View(model); } diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml index b01c7edc85..7e6cf51acc 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Search.cshtml @@ -36,7 +36,7 @@
    -
    +
    @if (Model.AllowFiltering && Model.SearchResult.Facets.Any()) {
    @@ -44,7 +44,7 @@
    } -
    +
    @{ Html.RenderAction("SearchBox", "Boards"); }
    @@ -68,10 +68,10 @@
    } - @if (suggestions.Any() && (Model.AttemptedTerm.HasValue() || Model.TotalCount == 0)) + @if (suggestions.Any()) {

    - @T("Search.DidYouMean") + @(T(Model.AttemptedTerm.HasValue() || Model.TotalCount == 0 ? "Search.DidYouMean" : "Search.RelatedSearchTerms")): @{ var links = string.Join(", ", suggestions.Select(x => "{0}".FormatCurrentUI(x))); } diff --git a/src/Presentation/SmartStore.Web/Views/Search/Search.cshtml b/src/Presentation/SmartStore.Web/Views/Search/Search.cshtml index d948784176..d9f5c6c604 100644 --- a/src/Presentation/SmartStore.Web/Views/Search/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Search/Search.cshtml @@ -75,10 +75,10 @@

    } - @if (suggestions.Any() && (Model.AttemptedTerm.HasValue() || Model.TotalProductsCount == 0)) + @if (suggestions.Any()) {

    - @T("Search.DidYouMean") + @(T(Model.AttemptedTerm.HasValue() || Model.TotalProductsCount == 0 ? "Search.DidYouMean" : "Search.RelatedSearchTerms")): @{ var links = String.Join(", ", suggestions.Select(x => "{0}".FormatCurrentUI(x))); } From ea92857558690f071c6c7f2e11df127ebe5ae16e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 20 Sep 2018 21:03:40 +0200 Subject: [PATCH 121/932] Resolves #1485 Dropdown list for product sorting does not work with Internet Explorer 11 --- changelog.md | 1 + .../SmartStore.Web/Themes/Flex/Content/_artlist.scss | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 54be842951..815963047d 100644 --- a/changelog.md +++ b/changelog.md @@ -55,6 +55,7 @@ * NullReferenceException in manufacturer list when there is no manufacturer. * Wrong order of featured products on category page. * #1504 Cart item price calculation wrong if attribute combinations with text types are involved. +* #1485 Dropdown list for product sorting does not work with Internet Explorer 11. ## SmartStore.NET 3.1.5 diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_artlist.scss b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_artlist.scss index ffdf3c740f..dc6da7dfab 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_artlist.scss +++ b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_artlist.scss @@ -78,7 +78,6 @@ $art-border-color: #ddd; .artlist-action--filter, .artlist-action--sort { flex-grow: 1; - flex-basis: 0; max-width: 100%; } From 7b4987a733e0add29d516a19cb86f67958f958b2 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 21 Sep 2018 02:47:09 +0200 Subject: [PATCH 122/932] More work on PageBlocks (StoryTelling) --- .../Plugins/ProviderManager.cs | 6 +- .../SmartStore.Web.Framework.csproj | 4 + .../UI/Blocks/BlockHandlerBase.cs | 126 ++++++++++++++++++ .../UI/Blocks/BlockMetadata.cs | 46 +++++++ .../UI/Blocks/IBlock.cs | 22 --- .../UI/Blocks/IBlockContainer.cs | 17 ++- .../UI/Blocks/IBlockEntity.cs | 19 +++ .../UI/Blocks/IBlockHandler.cs | 20 +++ 8 files changed, 226 insertions(+), 34 deletions(-) create mode 100644 src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs create mode 100644 src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockMetadata.cs create mode 100644 src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs create mode 100644 src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs diff --git a/src/Presentation/SmartStore.Web.Framework/Plugins/ProviderManager.cs b/src/Presentation/SmartStore.Web.Framework/Plugins/ProviderManager.cs index f7414b7244..e08ef6561e 100644 --- a/src/Presentation/SmartStore.Web.Framework/Plugins/ProviderManager.cs +++ b/src/Presentation/SmartStore.Web.Framework/Plugins/ProviderManager.cs @@ -15,9 +15,9 @@ public partial class ProviderManager : IProviderManager public ProviderManager(IComponentContext ctx, ICommonServices services, PluginMediator pluginMediator) { - this._ctx = ctx; - this._services = services; - this._pluginMediator = pluginMediator; + _ctx = ctx; + _services = services; + _pluginMediator = pluginMediator; } public Provider GetProvider(string systemName, int storeId = 0) where TProvider : IProvider diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index 0ba30e7725..86cc5586ac 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -314,6 +314,10 @@ + + + + diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs new file mode 100644 index 0000000000..02b7d36ab7 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using System.Web.Mvc.Html; +using Newtonsoft.Json; +using SmartStore.Services; +using SmartStore.Services.Localization; +using SmartStore.Utilities; +using SmartStore.Web.Framework.Localization; + +namespace SmartStore.Web.Framework.UI.Blocks +{ + public abstract class BlockHandlerBase : IBlockHandler where T : IBlock + { + public ICommonServices Services { get; set; } + + public ILocalizedEntityService LocalizedEntityService { get; set; } + + public virtual T Create(IBlockEntity entity) + { + return Activator.CreateInstance(); + } + + public virtual T Load(IBlockEntity entity, bool editMode) + { + Guard.NotNull(entity, nameof(entity)); + + var block = Create(entity); + var json = entity.Model; + + if (json.IsEmpty()) + { + return block; + } + + JsonConvert.PopulateObject(json, block); + + return block; + } + + public virtual void Save(T block, IBlockEntity entity) + { + Guard.NotNull(entity, nameof(entity)); + + if (block == null) + { + return; + } + + var settings = new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.Objects, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + ObjectCreationHandling = ObjectCreationHandling.Replace, + NullValueHandling = NullValueHandling.Ignore + }; + + entity.Model = JsonConvert.SerializeObject(block, Formatting.None, settings); + } + + public void Render(IBlockContainer element, string[] templates, HtmlHelper htmlHelper) + { + RenderCore(element, templates, htmlHelper, htmlHelper.ViewContext.Writer); + } + + public IHtmlString ToHtmlString(IBlockContainer element, string[] templates, HtmlHelper htmlHelper) + { + using (var writer = new StringWriter(CultureInfo.CurrentCulture)) + { + RenderCore(element, templates, htmlHelper, writer); + return MvcHtmlString.Create(writer.ToString()); + } + } + + protected virtual void RenderCore(IBlockContainer element, string[] templates, HtmlHelper htmlHelper, TextWriter textWriter) + { + Guard.NotNull(element, nameof(element)); + Guard.NotNull(templates, nameof(templates)); + Guard.NotNull(htmlHelper, nameof(htmlHelper)); + Guard.NotNull(textWriter, nameof(textWriter)); + + var routeInfo = templates.Select(x => GetRoute(element, x)).FirstOrDefault(); + if (routeInfo == null) + { + throw new InvalidOperationException("The return value of the 'GetRoute()' method cannot be NULL."); + } + + var originalWriter = htmlHelper.ViewContext.Writer; + htmlHelper.ViewContext.Writer = textWriter; + + using (new ActionDisposable(() => htmlHelper.ViewContext.Writer = originalWriter)) + { + htmlHelper.RenderAction(routeInfo.Action, routeInfo.Controller, routeInfo.RouteValues); + } + } + + protected abstract RouteInfo GetRoute(IBlockContainer element, string template); + + ///

    + /// Add locales for localizable entities + /// + /// Localizable model + /// Language service + /// Locales + /// Configure action + protected virtual void AddLocales(IList locales, Action configure) where TLocalizedModelLocal : ILocalizedModelLocal + { + var languageService = Services.Resolve(); + + foreach (var language in languageService.GetAllLanguages(true)) + { + var locale = Activator.CreateInstance(); + locale.LanguageId = language.Id; + if (configure != null) + { + configure.Invoke(locale, locale.LanguageId); + } + locales.Add(locale); + } + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockMetadata.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockMetadata.cs new file mode 100644 index 0000000000..d816798c43 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockMetadata.cs @@ -0,0 +1,46 @@ +using System; + +namespace SmartStore.Web.Framework.UI.Blocks +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class BlockAttribute : Attribute + { + public BlockAttribute(string systemName) + { + Guard.NotNull(systemName, nameof(systemName)); + + SystemName = systemName; + } + + public string SystemName { get; set; } + public string FriendlyName { get; set; } + public string Description { get; set; } + public string Icon { get; set; } + public int DisplayOrder { get; set; } + public bool IsInternal { get; set; } + } + + public class IBlockMetadata + { + string SystemName { get; } + string FriendlyName { get; } + string Description { get; } + string Icon { get; } + int DisplayOrder { get; } + bool IsInternal { get; } + Type BlockClrType { get; } + Type BlockHandlerClrType { get; } + } + + public class BlockMetadata : IBlockMetadata + { + public string SystemName { get; set; } + public string FriendlyName { get; set; } + public string Description { get; set; } + public string Icon { get; set; } + public int DisplayOrder { get; set; } + public bool IsInternal { get; set; } + public Type BlockClrType { get; set; } + public Type BlockHandlerClrType { get; set; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlock.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlock.cs index 5716497d27..fa013959c2 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlock.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlock.cs @@ -5,27 +5,5 @@ namespace SmartStore.Web.Framework.UI.Blocks { public interface IBlock { - string SystemName { get; } - BlockRouteInfo SelectRoute(BlockUiType uiType, string publicTemplateName); - } - - public enum BlockUiType - { - Public, - Preview, - Edit - } - - public class BlockRouteInfo : RouteInfo - { - public BlockRouteInfo(string action, string controller, string viewPath, object routeValues) - : base(action, controller, routeValues) - { - Guard.NotEmpty(viewPath, nameof(viewPath)); - - ViewPath = viewPath; - } - - public string ViewPath { get; set; } } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockContainer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockContainer.cs index c927e8ff6d..71c18aa7b4 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockContainer.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockContainer.cs @@ -1,20 +1,19 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SmartStore.Web.Framework.UI.Blocks { public interface IBlockContainer where T : IBlock { + string BlockType { get; } T Block { get; } - - string TagLine { get; } + IBlockMetadata Metadata { get; } + //IBlockHandler Handler { get; } + string Title { get; } - string SubTitle { get; } - string Body { get; } - string MediaBody { get; } bool IsInversed { get; } + + string HtmlId { get; } + string CssClass { get; } + string CssStyle { get; } } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs new file mode 100644 index 0000000000..aca7d86a3f --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Web.Framework.UI.Blocks +{ + public interface IBlockEntity + { + string BlockType { get; set; } + string Model { get; set; } + string TagLine { get; set; } + string Title { get; set; } + string SubTitle { get; set; } + string Body { get; set; } + string Template { get; set; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs new file mode 100644 index 0000000000..3d982722b5 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs @@ -0,0 +1,20 @@ +using System; +using System.Web; +using System.Web.Mvc; + +namespace SmartStore.Web.Framework.UI.Blocks +{ + public interface IBlockHandler + { + } + + public interface IBlockHandler : IBlockHandler where T : IBlock + { + T Create(IBlockEntity entity); + T Load(IBlockEntity entity, bool editMode); + void Save(T block, IBlockEntity entity); + + void Render(IBlockContainer element, string[] templates, HtmlHelper htmlHeper); + IHtmlString ToHtmlString(IBlockContainer element, string[] templates, HtmlHelper htmlHelper); + } +} From 7c5dce6a9f8931094914c24110f454fe00a7eb8f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 21 Sep 2018 09:54:31 +0200 Subject: [PATCH 123/932] More on "Do not allow to set a product linkage for a product attribute if the related product is bundle item of a product bundle" --- .../Migrations/MigrationsConfiguration.cs | 4 +- .../Controllers/ProductController.cs | 44 ++++++++++++------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 50190ddea6..f4fa247bb7 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -503,8 +503,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Legt fest, ob ein CAPTCHA auf Forenseiten angezeigt werden soll, wenn ein Thema erstellt oder darauf geantwortet wird."); builder.AddOrUpdate("Admin.Catalog.Products.BundleItems.NoProductLinkageForBundleItem", - "The type cannot be changed to \"Product\" because the product \"{0}\" is bundle item of a product bundle.", - "Der Typ kann nicht in \"Produkt\" geändert werden, weil das Produkt \"{0}\" auf der Stückliste eines Produkt-Bundle steht."); + "The product \"{0}\" cannot be assigned an attribute of the type \"product\" because it is bundle item of a product bundle.", + "Dem Produkt \"{0}\" kann kein Attribut vom Typ \"Produkt\" zugeordnet werden, weil es auf der Stückliste eines Produkt-Bundle steht."); builder.AddOrUpdate("Search.RelatedSearchTerms", "Related search terms", diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index aec7b7df77..8466df6cd5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -3368,14 +3368,29 @@ public ActionResult ProductAttributeValueCreatePopup(int productVariantAttribute [HttpPost] public ActionResult ProductAttributeValueCreatePopup(string btnId, string formId, ProductModel.ProductVariantAttributeValueModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + return AccessDeniedView(); + } var pva = _productAttributeService.GetProductVariantAttributeById(model.ProductVariantAttributeId); - if (pva == null) - return RedirectToAction("List", "Product"); + if (pva == null) + { + return RedirectToAction("List", "Product"); + } - if (ModelState.IsValid) + if (model.ValueTypeId == (int)ProductVariantAttributeValueType.ProductLinkage) + { + if (_productService.IsBundleItem(pva.ProductId)) + { + var product = _productService.GetProductById(pva.ProductId); + var productName = product?.Name.NaIfEmpty(); + + ModelState.AddModelError(string.Empty, T("Admin.Catalog.Products.BundleItems.NoProductLinkageForBundleItem", productName)); + } + } + + if (ModelState.IsValid) { var pvav = new ProductVariantAttributeValue { @@ -3389,17 +3404,18 @@ public ActionResult ProductAttributeValueCreatePopup(string btnId, string formId IsPreSelected = model.IsPreSelected, DisplayOrder = model.DisplayOrder, ValueTypeId = model.ValueTypeId, - LinkedProductId = model.LinkedProductId, Quantity = model.Quantity }; - try + pvav.LinkedProductId = pvav.ValueType == ProductVariantAttributeValueType.Simple ? 0 : model.LinkedProductId; + + try { _productAttributeService.InsertProductVariantAttributeValue(pvav); } - catch (Exception exception) + catch (Exception ex) { - ModelState.AddModelError("", exception.Message); + ModelState.AddModelError("", ex.Message); return View(model); } @@ -3420,7 +3436,7 @@ public ActionResult ProductAttributeValueCreatePopup(string btnId, string formId return View(model); } - //If we got this far, something failed, redisplay form + // If we got this far, something failed, redisplay form. return View(model); } @@ -3511,11 +3527,7 @@ public ActionResult ProductAttributeValueEditPopup(string btnId, string formId, pvav.DisplayOrder = model.DisplayOrder; pvav.ValueTypeId = model.ValueTypeId; pvav.Quantity = model.Quantity; - - if (pvav.ValueType == ProductVariantAttributeValueType.Simple) - pvav.LinkedProductId = 0; - else - pvav.LinkedProductId = model.LinkedProductId; + pvav.LinkedProductId = pvav.ValueType == ProductVariantAttributeValueType.Simple ? 0 : model.LinkedProductId; MediaHelper.UpdatePictureTransientStateFor(pvav, m => m.PictureId); @@ -3537,7 +3549,7 @@ public ActionResult ProductAttributeValueEditPopup(string btnId, string formId, return View(model); } - //If we got this far, something failed, redisplay form + // If we got this far, something failed, redisplay form. return View(model); } From 62dca2463beb50cf69dc479e89db173bff704699 Mon Sep 17 00:00:00 2001 From: Raphael Hinterndorfer Date: Fri, 21 Sep 2018 22:27:31 +0200 Subject: [PATCH 124/932] Revert "Use current store theme settings for e-mail styling" This reverts commit 9a13c009129f825fa87f46452455dc2fe8439bc1. --- .../Messages/MessageModelProvider.cs | 50 ++++--------------- 1 file changed, 11 insertions(+), 39 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs index 53e703a922..ad818b0636 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs @@ -23,7 +23,6 @@ using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Domain.Tax; -using SmartStore.Core.Domain.Themes; using SmartStore.Core.Html; using SmartStore.Core.Localization; using SmartStore.Core.Logging; @@ -37,7 +36,6 @@ using SmartStore.Services.Media; using SmartStore.Services.Orders; using SmartStore.Services.Seo; -using SmartStore.Services.Themes; using SmartStore.Templating; using SmartStore.Utilities; @@ -63,7 +61,6 @@ public partial class MessageModelProvider : IMessageModelProvider private readonly ITemplateEngine _templateEngine; private readonly IMessageTemplateService _messageTemplateService; private readonly IEmailAccountService _emailAccountService; - private readonly IThemeVariablesService _themeVariableService; private readonly UrlHelper _urlHelper; public MessageModelProvider( @@ -71,14 +68,12 @@ public MessageModelProvider( ITemplateEngine templateEngine, IMessageTemplateService messageTemplateService, IEmailAccountService emailAccountService, - IThemeVariablesService themeVariableService, UrlHelper urlHelper) { _services = services; _templateEngine = templateEngine; _messageTemplateService = messageTemplateService; _emailAccountService = emailAccountService; - _themeVariableService = themeVariableService; _urlHelper = urlHelper; T = NullLocalizer.InstanceEx; @@ -376,45 +371,22 @@ public string ResolveModelName(object model) protected virtual object CreateThemeModelPart(MessageContext messageContext) { - var themeSettings = _services.Settings.LoadSetting(messageContext.StoreId ?? 0); - var themeVars = _themeVariableService.GetThemeVariables(themeSettings.DefaultTheme, messageContext.StoreId ?? 0) as IDictionary; - var m = new Dictionary { - { "FontFamily", themeVars["font-family-sans-serif"] }, - { "BodyBg", themeVars["site-bg"] }, - { "BodyColor", themeVars["body-color"] }, - { "TitleColor", themeVars["headings-color"] }, - { "ContentBg", themeVars["body-bg"] }, + { "FontFamily", "-apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" }, + { "BodyBg", "#f2f4f6" }, + { "BodyColor", "#555" }, + { "TitleColor", "#2f3133" }, + { "ContentBg", "#fff" }, { "ShadeColor", "#e2e2e2" }, - { "LinkColor", themeVars["link-color"] }, - { "BrandPrimary", themeVars["primary"] }, - { "BrandSuccess", themeVars["success"] }, - { "BrandWarning", themeVars["warning"] }, - { "BrandDanger", themeVars["danger"] }, - { "MutedColor", themeVars["text-muted"] }, + { "LinkColor", "#0066c0" }, + { "BrandPrimary", "#3f51b5" }, + { "BrandSuccess", "#4caf50" }, + { "BrandWarning", "#ff9800" }, + { "BrandDanger", "#f44336" }, + { "MutedColor", "#a5a5a5" }, }; - - // replace Sass variables - bool found; - do - { - found = false; - foreach (var entry in m) - { - if (themeVars.ContainsKey(entry.Value?.ToString().TrimStart('$'))) - { - m[entry.Key] = themeVars[entry.Value?.ToString().TrimStart('$')]; - found = true; - break; - } - } - } while (found); - - - - return m; } From ab134ced5fade523d5aeab53cf5f6b3aa232010c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 22 Sep 2018 04:20:35 +0200 Subject: [PATCH 125/932] More work on PageBlocks (Storytelling) --- src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs | 2 -- .../SmartStore.Web.Framework/UI/Blocks/BlockMetadata.cs | 5 ++--- .../SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs | 1 + 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs b/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs index b4426b1ec8..d9534c06ea 100644 --- a/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs +++ b/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs @@ -9,7 +9,6 @@ namespace SmartStore { public static class IQueryableExtensions { - /// /// Instructs the repository to eager load entities that may be in the type's association path. /// @@ -40,6 +39,5 @@ public static IQueryable Expand(this IQueryable query, Expre return query.Include(path); } - } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockMetadata.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockMetadata.cs index d816798c43..ca5b8a1a7b 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockMetadata.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockMetadata.cs @@ -14,7 +14,6 @@ public BlockAttribute(string systemName) public string SystemName { get; set; } public string FriendlyName { get; set; } - public string Description { get; set; } public string Icon { get; set; } public int DisplayOrder { get; set; } public bool IsInternal { get; set; } @@ -24,10 +23,10 @@ public class IBlockMetadata { string SystemName { get; } string FriendlyName { get; } - string Description { get; } string Icon { get; } int DisplayOrder { get; } bool IsInternal { get; } + bool IsInbuilt { get; } Type BlockClrType { get; } Type BlockHandlerClrType { get; } } @@ -36,10 +35,10 @@ public class BlockMetadata : IBlockMetadata { public string SystemName { get; set; } public string FriendlyName { get; set; } - public string Description { get; set; } public string Icon { get; set; } public int DisplayOrder { get; set; } public bool IsInternal { get; set; } + public bool IsInbuilt { get; set; } public Type BlockClrType { get; set; } public Type BlockHandlerClrType { get; set; } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs index aca7d86a3f..d465a2d0c8 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs @@ -8,6 +8,7 @@ namespace SmartStore.Web.Framework.UI.Blocks { public interface IBlockEntity { + int Id { get; set; } string BlockType { get; set; } string Model { get; set; } string TagLine { get; set; } From 60d231e8f2c6779b1615ff5b6cb448249e49535f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 24 Sep 2018 09:58:36 +0200 Subject: [PATCH 126/932] Added a string resource --- changelog.md | 1 + .../Migrations/MigrationsConfiguration.cs | 4 ++++ .../Controllers/ExternalAuthFacebookController.cs | 3 ++- src/Plugins/SmartStore.FacebookAuth/Description.txt | 2 +- .../FacebookExternalAuthMethod.cs | 13 +++++-------- .../Localization/resources.de-de.xml | 2 +- .../Localization/resources.en-us.xml | 2 +- .../Controllers/PayPalExpressController.cs | 6 +++--- 8 files changed, 18 insertions(+), 15 deletions(-) diff --git a/changelog.md b/changelog.md index 815963047d..6de6f5b983 100644 --- a/changelog.md +++ b/changelog.md @@ -56,6 +56,7 @@ * Wrong order of featured products on category page. * #1504 Cart item price calculation wrong if attribute combinations with text types are involved. * #1485 Dropdown list for product sorting does not work with Internet Explorer 11. +* #1468 Twitter authentication not working anymore. ## SmartStore.NET 3.1.5 diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index f4fa247bb7..3a75d87c08 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -509,6 +509,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Search.RelatedSearchTerms", "Related search terms", "Verwandte Suchbegriffe"); + + builder.AddOrUpdate("Plugins.CannotLoadModule", + "The plugin or provider \"{0}\" cannot be loaded.", + "Das Plugin oder der Provider \"{0}\" kann nicht geladen werden."); } } } diff --git a/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs b/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs index 96ed94260a..d7462cd9e5 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs @@ -103,7 +103,8 @@ private ActionResult LoginInternal(string returnUrl, bool verifyResponse) var processor = _openAuthenticationService.LoadExternalAuthenticationMethodBySystemName(FacebookExternalAuthMethod.SystemName, _services.StoreContext.CurrentStore.Id); if (processor == null || !processor.IsMethodActive(_externalAuthenticationSettings)) { - throw new SmartException("Facebook module cannot be loaded"); + NotifyError(T("Plugins.CannotLoadModule", T("Plugins.FriendlyName.SmartStore.FacebookAuth"))); + return new RedirectResult(Url.LogOn(returnUrl)); } var viewModel = new LoginModel(); diff --git a/src/Plugins/SmartStore.FacebookAuth/Description.txt b/src/Plugins/SmartStore.FacebookAuth/Description.txt index 514333f075..2c84b47230 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Description.txt +++ b/src/Plugins/SmartStore.FacebookAuth/Description.txt @@ -1,4 +1,4 @@ -FriendlyName: Facebook +FriendlyName: Facebook Login SystemName: SmartStore.FacebookAuth Group: Security Version: 3.1.5 diff --git a/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs b/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs index 86ed1ab677..db68ea5d5c 100644 --- a/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs +++ b/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs @@ -1,22 +1,19 @@ using System.Web.Routing; using SmartStore.Core.Plugins; -using SmartStore.FacebookAuth.Core; using SmartStore.Services.Authentication.External; using SmartStore.Services.Localization; namespace SmartStore.FacebookAuth { - /// - /// Facebook externalAuth processor - /// - public class FacebookExternalAuthMethod : BasePlugin, IExternalAuthenticationMethod, IConfigurable + /// + /// Facebook externalAuth processor + /// + public class FacebookExternalAuthMethod : BasePlugin, IExternalAuthenticationMethod, IConfigurable { - private readonly FacebookExternalAuthSettings _facebookExternalAuthSettings; private readonly ILocalizationService _localizationService; - public FacebookExternalAuthMethod(FacebookExternalAuthSettings facebookExternalAuthSettings, ILocalizationService localizationService) + public FacebookExternalAuthMethod(ILocalizationService localizationService) { - _facebookExternalAuthSettings = facebookExternalAuthSettings; _localizationService = localizationService; } diff --git a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml index 6839a4255c..f65160a398 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml @@ -1,6 +1,6 @@  - Facebook + Facebook Login diff --git a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.en-us.xml b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.en-us.xml index fdf0710bfa..0da7b1e2dd 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.en-us.xml @@ -1,6 +1,6 @@  - Facebook + Facebook Login diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs index faed628e5e..1f09fdbfda 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs @@ -166,7 +166,7 @@ public ActionResult SubmitButton() var provider = PaymentService.LoadPaymentMethodBySystemName(PayPalExpressProvider.SystemName, true); var processor = provider != null ? provider.Value as PayPalExpressProvider : null; if (processor == null) - throw new SmartException("PayPal Express Checkout module cannot be loaded"); + throw new SmartException(T("Plugins.CannotLoadModule", T("Plugins.FriendlyName.Payments.PayPalExpress"))); var processPaymentRequest = new PayPalProcessPaymentRequest(); @@ -265,9 +265,9 @@ public ActionResult GetDetails(string token) var provider = PaymentService.LoadPaymentMethodBySystemName("Payments.PayPalExpress", true); var processor = provider != null ? provider.Value as PayPalExpressProvider : null; if (processor == null) - throw new SmartException("PayPal Express module cannot be loaded"); + throw new SmartException(T("Plugins.CannotLoadModule", T("Plugins.FriendlyName.Payments.PayPalExpress"))); - var resp = processor.GetExpressCheckoutDetails(token); + var resp = processor.GetExpressCheckoutDetails(token); if (resp.Ack == AckCodeType.Success) { From 70416279f4f9b3c6151e04012c51d15bac576749 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 24 Sep 2018 15:23:29 +0200 Subject: [PATCH 127/932] Forum: Further limited to store checks necessary --- .../Forums/ForumService.cs | 19 +- .../Controllers/BoardsController.cs | 219 +++++++----------- 2 files changed, 103 insertions(+), 135 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index ae0c3c1172..43a68f4f98 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -204,7 +204,11 @@ public virtual Forum GetForumById(int forumId) return null; } - return _forumRepository.GetById(forumId); + var entity = _forumRepository.Table + .Expand(x => x.ForumGroup) + .FirstOrDefault(x => x.Id == forumId); + + return entity; } public virtual IList GetAllForumsByGroupId(int forumGroupId) @@ -275,7 +279,11 @@ public virtual ForumTopic GetTopicById(int forumTopicId) return null; } - var entity = _forumTopicRepository.Table.FirstOrDefault(x => x.Id == forumTopicId); + var entity = _forumTopicRepository.Table + .Expand(x => x.Forum) + .Expand(x => x.Forum.ForumGroup) + .FirstOrDefault(x => x.Id == forumTopicId); + return entity; } @@ -459,7 +467,12 @@ public virtual ForumPost GetPostById(int forumPostId) return null; } - var forumPost = _forumPostRepository.Table.FirstOrDefault(x => x.Id == forumPostId); + var forumPost = _forumPostRepository.Table + .Expand(x => x.ForumTopic) + .Expand(x => x.ForumTopic.Forum) + .Expand(x => x.ForumTopic.Forum.ForumGroup) + .FirstOrDefault(x => x.Id == forumPostId); + return forumPost; } diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index bdd2715e83..6da936f0e9 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -21,6 +21,7 @@ using SmartStore.Services.Search.Modelling; using SmartStore.Services.Search.Rendering; using SmartStore.Services.Seo; +using SmartStore.Services.Stores; using SmartStore.Utilities; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; @@ -41,6 +42,7 @@ public partial class BoardsController : PublicControllerBase private readonly ICountryService _countryService; private readonly IForumSearchService _forumSearchService; private readonly IGenericAttributeService _genericAttributeService; + private readonly IStoreMappingService _storeMappingService; private readonly ForumSettings _forumSettings; private readonly ForumSearchSettings _searchSettings; private readonly CustomerSettings _customerSettings; @@ -57,6 +59,7 @@ public BoardsController( ICountryService countryService, IForumSearchService forumSearchService, IGenericAttributeService genericAttributeService, + IStoreMappingService storeMappingService, ForumSettings forumSettings, ForumSearchSettings searchSettings, CustomerSettings customerSettings, @@ -72,6 +75,7 @@ public BoardsController( _countryService = countryService; _forumSearchService = forumSearchService; _genericAttributeService = genericAttributeService; + _storeMappingService = storeMappingService; _forumSettings = forumSettings; _searchSettings = searchSettings; _customerSettings = customerSettings; @@ -337,7 +341,7 @@ public ActionResult ForumGroup(int id) } var forumGroup = _forumService.GetForumGroupById(id); - if (forumGroup == null) + if (forumGroup == null || !_storeMappingService.Authorize(forumGroup)) { return HttpNotFound(); } @@ -361,7 +365,7 @@ public ActionResult Forum(int id, int page = 1) var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(id); - if (forum == null) + if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup)) { return HttpNotFound(); } @@ -439,12 +443,12 @@ public ActionResult ForumRss(int id = 0) } var forum = _forumService.GetForumById(id); - if (forum == null) + if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup)) { return new RssActionResult { Feed = feed }; } - feed.Title = new TextSyndicationContent("{0} - {1}".FormatInvariant(store.Name, forum.GetLocalized(x => x.Name, language))); + feed.Title = new TextSyndicationContent("{0} - {1}".FormatInvariant(store.Name, forum.GetLocalized(x => x.Name, language))); var items = new List(); var topics = _forumService.GetAllTopics(id, 0, _forumSettings.ForumFeedCount); @@ -473,12 +477,7 @@ public ActionResult ForumWatch(int id) var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(id); - if (forum == null) - { - return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); - } - - if (!_forumService.IsCustomerAllowedToSubscribe(customer)) + if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup) || !_forumService.IsCustomerAllowedToSubscribe(customer)) { return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } @@ -638,6 +637,11 @@ public ActionResult Topic(int id, int page = 1) if (forumTopic != null) { + if (!_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) + { + return HttpNotFound(); + } + var posts = _forumService.GetAllPosts(forumTopic.Id, 0, true, page - 1, _forumSettings.PostsPageSize); // If no posts area loaded, redirect to the first page. @@ -750,7 +754,7 @@ public ActionResult TopicWatch(int id) return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } - if (!_forumService.IsCustomerAllowedToSubscribe(customer)) + if (!_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) && !_forumService.IsCustomerAllowedToSubscribe(customer)) { return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } @@ -788,8 +792,7 @@ public ActionResult TopicMove(int id) } var forumTopic = _forumService.GetTopicById(id); - - if (forumTopic == null) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) { return HttpNotFound(); } @@ -810,13 +813,13 @@ public ActionResult TopicMove(TopicMoveModel model) { if (!_forumSettings.ForumsEnabled) { - return RedirectToRoute("HomePage"); + return HttpNotFound(); } var forumTopic = _forumService.GetTopicById(model.Id); - if (forumTopic == null) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) { - return RedirectToRoute("Boards"); + return HttpNotFound(); } var newForumId = model.ForumSelected; @@ -840,12 +843,12 @@ public ActionResult TopicCreate(int id) var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(id); - if (forum == null) + if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup)) { return HttpNotFound(); } - if (_forumService.IsCustomerAllowedToCreateTopic(customer, forum) == false) + if (!_forumService.IsCustomerAllowedToCreateTopic(customer, forum)) { return new HttpUnauthorizedResult(); } @@ -886,7 +889,7 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(model.ForumId); - if (forum == null) + if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup)) { return HttpNotFound(); } @@ -997,9 +1000,9 @@ public ActionResult TopicEdit(int id) var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(id); - if (forumTopic == null) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) { - return RedirectToRoute("Boards"); + return HttpNotFound(); } if (!_forumService.IsCustomerAllowedToEditTopic(customer, forumTopic)) @@ -1007,25 +1010,19 @@ public ActionResult TopicEdit(int id) return new HttpUnauthorizedResult(); } - var forum = forumTopic.Forum; - if (forum == null) - { - return RedirectToRoute("Boards"); - } - var firstPost = forumTopic.GetFirstPost(_forumService); var model = new EditForumTopicModel(); model.IsEdit = true; model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; model.TopicPriorities = ForumTopicTypesList(); - model.ForumName = forum.GetLocalized(x => x.Name); - model.ForumSeName = forum.GetSeName(); + model.ForumName = forumTopic.Forum.GetLocalized(x => x.Name); + model.ForumSeName = forumTopic.Forum.GetSeName(); model.Text = firstPost.Text; model.Subject = forumTopic.Subject; model.TopicTypeId = forumTopic.TopicTypeId; model.Id = forumTopic.Id; - model.ForumId = forum.Id; + model.ForumId = forumTopic.Forum.Id; model.ForumEditor = _forumSettings.ForumEditor; model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer); @@ -1038,7 +1035,7 @@ public ActionResult TopicEdit(int id) model.Subscribed = forumSubscription != null; } - CreateForumBreadcrumb(forum: forum, topic: forumTopic); + CreateForumBreadcrumb(forum: forumTopic.Forum, topic: forumTopic); return View(model); } @@ -1060,15 +1057,10 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(model.Id); - if (forumTopic == null) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) { return HttpNotFound(); } - var forum = forumTopic.Forum; - if (forum == null) - { - return RedirectToRoute("Boards"); - } if (ModelState.IsValid) { @@ -1168,9 +1160,9 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) model.TopicPriorities = ForumTopicTypesList(); model.IsEdit = true; model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; - model.ForumName = forum.GetLocalized(x => x.Name); - model.ForumSeName = forum.GetSeName(); - model.ForumId = forum.Id; + model.ForumName = forumTopic.Forum.GetLocalized(x => x.Name); + model.ForumSeName = forumTopic.Forum.GetSeName(); + model.ForumId = forumTopic.Forum.Id; model.ForumEditor = _forumSettings.ForumEditor; model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer); @@ -1187,21 +1179,22 @@ public ActionResult TopicDelete(int id) } var forumTopic = _forumService.GetTopicById(id); - if (forumTopic != null) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) { - if (!_forumService.IsCustomerAllowedToDeleteTopic(Services.WorkContext.CurrentCustomer, forumTopic)) - { - return new HttpUnauthorizedResult(); - } + return HttpNotFound(); + } - var forum = _forumService.GetForumById(forumTopic.ForumId); + if (!_forumService.IsCustomerAllowedToDeleteTopic(Services.WorkContext.CurrentCustomer, forumTopic)) + { + return new HttpUnauthorizedResult(); + } - _forumService.DeleteTopic(forumTopic); + var forum = _forumService.GetForumById(forumTopic.ForumId); + _forumService.DeleteTopic(forumTopic); - if (forum != null) - { - return RedirectToRoute("ForumSlug", new { id = forum.Id, slug = forum.GetSeName() }); - } + if (forum != null) + { + return RedirectToRoute("ForumSlug", new { id = forum.Id, slug = forum.GetSeName() }); } return RedirectToRoute("Boards"); @@ -1221,9 +1214,9 @@ public ActionResult PostCreate(int id, int? quote) var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(id); - if (forumTopic == null) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) { - return RedirectToRoute("Boards"); + return HttpNotFound(); } if (!_forumService.IsCustomerAllowedToCreatePost(customer, forumTopic)) @@ -1231,12 +1224,6 @@ public ActionResult PostCreate(int id, int? quote) return new HttpUnauthorizedResult(); } - var forum = forumTopic.Forum; - if (forum == null) - { - return RedirectToRoute("Boards"); - } - var model = new EditForumPostModel { Id = 0, @@ -1244,7 +1231,7 @@ public ActionResult PostCreate(int id, int? quote) IsEdit = false, DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage, ForumEditor = _forumSettings.ForumEditor, - ForumName = forum.GetLocalized(x => x.Name), + ForumName = forumTopic.Forum.GetLocalized(x => x.Name), ForumTopicSubject = forumTopic.Subject, ForumTopicSeName = forumTopic.GetSeName(), IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), @@ -1270,18 +1257,17 @@ public ActionResult PostCreate(int id, int? quote) switch (_forumSettings.ForumEditor) { case EditorType.SimpleTextBox: - text = String.Format("{0}:\n{1}\n", quotePost.Customer.FormatUserName(), quotePostText); + text = string.Format("{0}:\n{1}\n", quotePost.Customer.FormatUserName(), quotePostText); break; case EditorType.BBCodeEditor: - text = String.Format("[quote={0}]{1}[/quote]", quotePost.Customer.FormatUserName(), BBCodeHelper.RemoveQuotes(quotePostText)); + text = string.Format("[quote={0}]{1}[/quote]", quotePost.Customer.FormatUserName(), BBCodeHelper.RemoveQuotes(quotePostText)); break; } model.Text = text; } } - CreateForumBreadcrumb(forum: forum, topic: forumTopic); - + CreateForumBreadcrumb(forum: forumTopic.Forum, topic: forumTopic); return View(model); } @@ -1303,7 +1289,7 @@ public ActionResult PostCreate(EditForumPostModel model, bool captchaValid) var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(model.ForumTopicId); - if (forumTopic == null) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) { return HttpNotFound(); } @@ -1387,19 +1373,13 @@ public ActionResult PostCreate(EditForumPostModel model, bool captchaValid) } // Redisplay form. - var forum = forumTopic.Forum; - if (forum == null) - { - return RedirectToRoute("Boards"); - } - + model.Id = 0; model.IsEdit = false; model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; - model.ForumName = forum.GetLocalized(x => x.Name); + model.ForumName = forumTopic.Forum.GetLocalized(x => x.Name); model.ForumTopicId = forumTopic.Id; model.ForumTopicSubject = forumTopic.Subject; model.ForumTopicSeName = forumTopic.GetSeName(); - model.Id = 0; model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); model.ForumEditor = _forumSettings.ForumEditor; @@ -1416,37 +1396,25 @@ public ActionResult PostEdit(int id) var customer = Services.WorkContext.CurrentCustomer; var forumPost = _forumService.GetPostById(id); - if (forumPost == null) + if (forumPost == null || !_storeMappingService.Authorize(forumPost.ForumTopic.Forum.ForumGroup)) { - return RedirectToRoute("Boards"); + return HttpNotFound(); } if (!_forumService.IsCustomerAllowedToEditPost(customer, forumPost)) { return new HttpUnauthorizedResult(); } - var forumTopic = forumPost.ForumTopic; - if (forumTopic == null) - { - return RedirectToRoute("Boards"); - } - - var forum = forumTopic.Forum; - if (forum == null) - { - return RedirectToRoute("Boards"); - } - var model = new EditForumPostModel { Id = forumPost.Id, - ForumTopicId = forumTopic.Id, IsEdit = true, + ForumTopicId = forumPost.ForumTopic.Id, DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage, ForumEditor = _forumSettings.ForumEditor, - ForumName = forum.GetLocalized(x => x.Name), - ForumTopicSubject = forumTopic.Subject, - ForumTopicSeName = forumTopic.GetSeName(), + ForumName = forumPost.ForumTopic.Forum.GetLocalized(x => x.Name), + ForumTopicSubject = forumPost.ForumTopic.Subject, + ForumTopicSeName = forumPost.ForumTopic.GetSeName(), IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), Subscribed = false, Text = forumPost.Text, @@ -1455,11 +1423,11 @@ public ActionResult PostEdit(int id) // Subscription. if (model.IsCustomerAllowedToSubscribe) { - var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumPost.ForumTopic.Id, 0, 1).FirstOrDefault(); model.Subscribed = forumSubscription != null; } - CreateForumBreadcrumb(forum: forum, topic: forumTopic); + CreateForumBreadcrumb(forum: forumPost.ForumTopic.Forum, topic: forumPost.ForumTopic); return View(model); } @@ -1481,7 +1449,7 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) var customer = Services.WorkContext.CurrentCustomer; var forumPost = _forumService.GetPostById(model.Id); - if (forumPost == null) + if (forumPost == null || !_storeMappingService.Authorize(forumPost.ForumTopic.Forum.ForumGroup)) { return HttpNotFound(); } @@ -1491,18 +1459,6 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) return new HttpUnauthorizedResult(); } - var forumTopic = forumPost.ForumTopic; - if (forumTopic == null) - { - return RedirectToRoute("Boards"); - } - - var forum = forumTopic.Forum; - if (forum == null) - { - return RedirectToRoute("Boards"); - } - if (ModelState.IsValid) { try @@ -1571,10 +1527,10 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) // Redisplay form. model.IsEdit = true; model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; - model.ForumName = forum.GetLocalized(x => x.Name); - model.ForumTopicId = forumTopic.Id; - model.ForumTopicSubject = forumTopic.Subject; - model.ForumTopicSeName = forumTopic.GetSeName(); + model.ForumName = forumPost.ForumTopic.Forum.GetLocalized(x => x.Name); + model.ForumTopicId = forumPost.ForumTopic.Id; + model.ForumTopicSubject = forumPost.ForumTopic.Subject; + model.ForumTopicSeName = forumPost.ForumTopic.GetSeName(); model.Id = forumPost.Id; model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); model.ForumEditor = _forumSettings.ForumEditor; @@ -1590,33 +1546,32 @@ public ActionResult PostDelete(int id) } var forumPost = _forumService.GetPostById(id); - if (forumPost != null) + if (forumPost == null || !_storeMappingService.Authorize(forumPost.ForumTopic.Forum.ForumGroup)) { - if (!_forumService.IsCustomerAllowedToDeletePost(Services.WorkContext.CurrentCustomer, forumPost)) - { - return new HttpUnauthorizedResult(); - } + return HttpNotFound(); + } - var forumTopic = forumPost.ForumTopic; - var forumId = forumTopic.Forum.Id; - var forumSlug = forumTopic.Forum.GetSeName(); - var url = string.Empty; + if (!_forumService.IsCustomerAllowedToDeletePost(Services.WorkContext.CurrentCustomer, forumPost)) + { + return new HttpUnauthorizedResult(); + } - _forumService.DeletePost(forumPost); + var forumTopic = forumPost.ForumTopic; + var forumId = forumTopic.Forum.Id; + var forumSlug = forumTopic.Forum.GetSeName(); - // Get topic one more time because it can be deleted (first or only post deleted). - forumTopic = _forumService.GetTopicById(forumPost.TopicId); - if (forumTopic == null) - { - return RedirectToRoute("ForumSlug", new { id = forumId, slug = forumSlug }); - } - else - { - return RedirectToRoute("TopicSlug", new { id = forumTopic.Id, slug = forumTopic.GetSeName() }); - } - } + _forumService.DeletePost(forumPost); - return RedirectToRoute("Boards"); + // Get topic one more time because it can be deleted (first or only post deleted). + forumTopic = _forumService.GetTopicById(forumPost.TopicId); + if (forumTopic == null) + { + return RedirectToRoute("ForumSlug", new { id = forumId, slug = forumSlug }); + } + else + { + return RedirectToRoute("TopicSlug", new { id = forumTopic.Id, slug = forumTopic.GetSeName() }); + } } #endregion From a2d09752c0b75f45ae9e15cc457e4343dd0d44bd Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Mon, 24 Sep 2018 21:30:54 +0200 Subject: [PATCH 128/932] Admin theme: css grid layout & colors --- .../Administration/Content/_admin.scss | 86 ++++++++----------- .../Administration/Content/_variables.scss | 8 +- .../Scripts/admin.globalinit.js | 40 +-------- .../Views/Shared/Layouts/_AdminLayout.cshtml | 69 ++++++++------- .../SmartStore.Web/Content/shared/_modal.scss | 9 +- 5 files changed, 87 insertions(+), 125 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss b/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss index 8374987c71..a3fae78f39 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss +++ b/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss @@ -103,44 +103,52 @@ body { padding: 0; } -header { +.page-main { + display: grid; + grid-template-columns: 100%; + grid-template-rows: 72px 72px minmax(calc(100vh - 122px), auto); +} + +#header { @include gradient-bg($indigo); - padding-bottom: 50px; + grid-column: 1 / -1; + grid-row: 1 / span 2; .popup & { display: none; } } -.cph { - padding-top: 55px; -} - #content { position: relative; - height: auto; - margin: 0 15px; - margin-top: -50px; - background-color: #fff; + margin: 0 ($content-padding-x / 2); padding: $content-padding-y ($content-padding-x / 2); padding-bottom: $content-padding-x * 2; - box-shadow: 0 0 6px rgba(0,0,0, 0.2); - border-top-left-radius: $border-radius; - border-top-right-radius: $border-radius; + box-shadow: 0 8px 16px rgba(#32325d, .1), 0 -2px 8px rgba(#000, .09); + border-radius: $border-radius-lg $border-radius-lg 0 0; + background-color: #fff; + grid-column: 1 / -1; + grid-row: 2 / -1; @include media-breakpoint-up(lg) { - margin-left: 30px; - margin-right: 30px; + margin-left: $content-padding-x; + margin-right: $content-padding-x; padding-left: $content-padding-x; padding-right: $content-padding-x; } + + .popup & { + margin: 0; + border: 0; + box-shadow: none; + border-radius: 0; + grid-row: 1 / -1; + } } -.popup #content { - margin: 0; - border: 0; - box-shadow: none; - border-radius: 0; +.cph { + display: flex; + flex-flow: column; } .popup.bare { @@ -155,28 +163,9 @@ header { } } -.section-title { - border-bottom: solid 3px $gray-300; - padding-bottom: 1px; - margin-bottom: 10px; - color: #009FFF; - font-size: 14px; - font-weight: 700; - vertical-align: bottom; -} - -.section-title img { - vertical-align: middle; - padding-bottom: 2px; -} - .section-header { - position: absolute; - left: 0; - top: 0; - right: 0; - padding: 0.75rem ($content-padding-x / 2); - transition: background-color 0.1s ease-in-out, padding 0.1s ease-in-out; + padding-bottom: 0.75rem; + transition: background-color 0.1s ease-in-out; @include make-row(); flex-wrap: nowrap; @@ -255,6 +244,10 @@ header { z-index: 600; left: $content-padding-x / 2; right: $content-padding-x / 2; + top: 0; + padding-left: $content-padding-x / 2; + padding-right: $content-padding-x / 2; + padding-top: $content-padding-y; margin-left: 0 !important; margin-right: 0 !important; background: $gray-100; @@ -265,16 +258,13 @@ header { // add the killed scrollbar width margin-right: 17px !important; } - } - - @include media-breakpoint-up(lg) { - padding-left: $content-padding-x; - padding-right: $content-padding-x; - &.sticky { + @include media-breakpoint-up(lg) { left: $content-padding-x; right: $content-padding-x; - } + padding-left: $content-padding-x; + padding-right: $content-padding-x; + } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss b/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss index 30b574cca2..cd940a8f7d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss +++ b/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss @@ -4,14 +4,14 @@ // SmartStore CI colors // -------------------------------------------------- -$sm-orange1: #ee9B00; +$sm-orange1: #f7a833; $sm-orange2: #e77c00; -$sm-green1: #44b284; +$sm-green1: #009e74; $sm-bluegreen: #2c8d8a; $sm-red1: #ee1111; $sm-red2: #dc3000; $sm-red3: #db004f; -$sm-blue: #37a0e6; +$sm-blue: #307abe; $sm-blue2: #18509f; $sm-violet: #55237d; $sm-gray1: #dadada; @@ -92,7 +92,7 @@ $link-color: desaturate(darken($primary, 12%), 0%); //#0277bb; $theme-color-interval: 8%; // The yiq lightness value that determines when the lightness of color changes from "dark" to "light". Acceptable values are between 0 and 255. -$yiq-contrasted-threshold: 164; +$yiq-contrasted-threshold: 179; // Customize the light and dark text colors for use in our YIQ color contrast function. $yiq-text-dark: $gray-800; diff --git a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js index 95770cb6f9..8707ff513b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js +++ b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js @@ -123,7 +123,7 @@ // sticky section-header var navbar = $("#navbar"); - var navbarHeight = navbar.height() || 1; + var navbarHeight = navbar.height() || 0; var sectionHeader = $('.section-header'); var sectionHeaderHasButtons = undefined; @@ -132,50 +132,14 @@ sectionHeaderHasButtons = sectionHeader.find(".options").children().length > 0; } if (sectionHeaderHasButtons === true) { - var y = $(this).scrollTop(); + var y = $(this).scrollTop(); sectionHeader.toggleClass("sticky", y >= navbarHeight); } }).trigger('resize'); $(window).on('load', function () { - // swap classes onload and domready html.removeClass("loading").addClass("loaded"); - - // make #content fit into viewspace - var fitContentToWindow = function (initial) { - var content = $('#content'); - - if (!content.length) - return; - - var height = initialHeight = content.outerHeight(false), - outerHeight, - winHeight = $(window).height(), - top, - offset = 0; - - if (initial === true) { - top = content.offset().top; - content - .data("initial-height", initialHeight) - .data("initial-offset", offset) - .data("initial-top", top); - } - else { - top = content.data("initial-top"); - offset = content.data("initial-offset"); - initialHeight = content.data("initial-height"); - } - - content.css("min-height", Math.max(initialHeight, winHeight - offset - top) + "px"); - }; - - if (!$('body').is('.popup.bare')) { - fitContentToWindow(true); - $(window).on("resize", fitContentToWindow); - } - }); }); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml index f1cdda6cfa..02d4189516 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml @@ -33,42 +33,45 @@
    } - @if (IsSectionDefined("header")) - { - @RenderSection("header") - } - else - { -
    - @if (IsSectionDefined("navbar")) - { - @RenderSection("navbar") - } - else - { - Html.RenderWidget("admin_navbar_before"); - Html.RenderAction("Navbar", "Common", new { area = "Admin" }); - Html.RenderWidget("admin_navbar_after"); - } +
    + @if (IsSectionDefined("header")) + { + @RenderSection("header") + } + else + { + - } + +
    + } - @{ Html.RenderWidget("admin_content_before"); } -
    -
    - @RenderBody() -
    -
    -
    -
    -
    +
    +
    + @{ Html.RenderWidget("admin_content_before"); } + @RenderBody() +
    +
    +
    +
    +
    +
    -
    +
    + @{ Html.RenderPartial("_Notifications"); } @{ Html.RenderWidget("admin_content_after"); }
    diff --git a/src/Presentation/SmartStore.Web/Content/shared/_modal.scss b/src/Presentation/SmartStore.Web/Content/shared/_modal.scss index 3967dda953..76f57c64e1 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_modal.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_modal.scss @@ -33,8 +33,13 @@ min-height: 300px; } -.modal-dialog-app > .modal-content > .modal-body { - padding: $border-radius 0; +.modal-dialog-app > .modal-content { + border-radius: 0; + + > .modal-body { + //padding: $border-radius 0; + padding: 0; + } } .modal-closer { From 8039e3f02046e8172ed609194e96df9e8b851368 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 25 Sep 2018 03:26:35 +0200 Subject: [PATCH 129/932] Theming: various css enhancements --- .../Extensions/HttpExtensions.cs | 27 ++++++------ .../Administration/Content/_admin.scss | 7 ++- .../Scripts/admin.globalinit.js | 2 +- .../Views/Shared/Layouts/_AdminLayout.cshtml | 4 ++ .../SmartStore.Web/Content/shared/_nav.scss | 36 ++++++++++------ .../Content/shared/_switch.scss | 5 +++ .../Shared/EditorTemplates/DateTime.cshtml | 43 +++++++++++++------ 7 files changed, 78 insertions(+), 46 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HttpExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HttpExtensions.cs index 65b716686a..f3bdab1bfd 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/HttpExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HttpExtensions.cs @@ -25,16 +25,17 @@ public static bool IsAdminArea(this HttpRequestBase request) { try { - if (request != null) + // TODO: not really reliable. Change this. + var area = request?.RequestContext?.RouteData?.GetAreaName(); + if (area != null) { - var area = request.RequestContext.RouteData.GetAreaName(); - if (area != null) - { - return area.IsCaseInsensitiveEqual("admin"); - } + return area.IsCaseInsensitiveEqual("admin"); + } + else + { + var paths = new[] { "~/administration/", "~/admin/" }; + return paths.Any(x => request?.AppRelativeCurrentExecutionFilePath?.StartsWith(x, StringComparison.OrdinalIgnoreCase) == true); } - - return false; } catch { @@ -56,13 +57,9 @@ public static bool IsPublicArea(this HttpRequestBase request) { try { - if (request != null) - { - var area = request.RequestContext.RouteData.GetAreaName(); - return area.IsEmpty(); - } - - return false; + // TODO: not really reliable. Change this. + var area = request?.RequestContext?.RouteData?.GetAreaName(); + return area.IsEmpty(); } catch { diff --git a/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss b/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss index a3fae78f39..6c00126382 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss +++ b/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss @@ -104,6 +104,7 @@ body { } .page-main { + position: relative; display: grid; grid-template-columns: 100%; grid-template-rows: 72px 72px minmax(calc(100vh - 122px), auto); @@ -129,6 +130,7 @@ body { background-color: #fff; grid-column: 1 / -1; grid-row: 2 / -1; + z-index: 9; @include media-breakpoint-up(lg) { margin-left: $content-padding-x; @@ -147,6 +149,7 @@ body { } .cph { + position: relative; display: flex; flex-flow: column; } @@ -268,7 +271,7 @@ body { } } -.popup .section-header.sticky { +.popup .section-header.sticky { left: 0; right: 0; top: 0; @@ -883,7 +886,7 @@ textarea:not([class*="form-control"]) { font-family: $headings-font-family; font-weight: $headings-font-weight; color: inherit; - border-bottom: 1px solid $gray-300; + border-bottom: 1px solid rgba(#000, 0.11); } .sub-title { font-family: $font-family-base; diff --git a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js index 8707ff513b..f5197676da 100644 --- a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js +++ b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js @@ -24,7 +24,7 @@ }, // switch function (ctx) { - ctx.find(".adminData > input[type=checkbox], .multi-store-setting-control > input[type=checkbox]").each(function (i, el) { + ctx.find(".adminData > input[type=checkbox], .multi-store-setting-control > input[type=checkbox], .switcher > input[type=checkbox]").each(function (i, el) { var wrap = $(el) .wrap('') .after(''); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml index 02d4189516..bc76804f20 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml @@ -59,6 +59,8 @@ } + @{ Html.RenderZone("beforemaincontent"); } +
    @{ Html.RenderWidget("admin_content_before"); } @@ -70,6 +72,8 @@
    + + @{ Html.RenderZone("aftermaincontent"); } @{ Html.RenderPartial("_Notifications"); } diff --git a/src/Presentation/SmartStore.Web/Content/shared/_nav.scss b/src/Presentation/SmartStore.Web/Content/shared/_nav.scss index 8a0ee8756d..722feee46c 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_nav.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_nav.scss @@ -72,6 +72,28 @@ } } + &.nav-inverse .nav-link { + color: rgba($yiq-text-light, 0.4); + + &:hover { + color: rgba($yiq-text-light, 0.75); + } + + &:hover:not(.active) { + background-color: rgba(#fff, 0.08); + } + + &:active:not(.active) { + color: $yiq-text-light; + background-color: rgba(#fff, 0.16); + } + + &.active { + background-color: transparent; + color: $yiq-text-light; + } + } + .nav-link:not(.disabled):before { position: absolute; content: ''; @@ -152,20 +174,6 @@ } } -@include media-breakpoint-up(lg) { - /*.nav-aside { - flex: 0 0 auto; - width: auto; - max-width: none; - } - - .nav-content { - flex-basis: 0; - flex-grow: 1; - max-width: 100%; - }*/ -} - // Responsive Tabs // ============================================================== diff --git a/src/Presentation/SmartStore.Web/Content/shared/_switch.scss b/src/Presentation/SmartStore.Web/Content/shared/_switch.scss index 76464ea941..eb8733c11d 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_switch.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_switch.scss @@ -7,6 +7,7 @@ .switch { $h: $font-size-base * $line-height-base; + $h-sm: $font-size-sm * $line-height-sm; position: relative; display: inline-block; vertical-align: bottom; @@ -16,6 +17,10 @@ box-sizing: content-box; height: $h; + .switcher-sm > & { + height: $h-sm; + } + > .switch-toggle { position: relative; display: inline-block; diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/DateTime.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/DateTime.cshtml index 32a351d72d..61a236c6db 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/DateTime.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/DateTime.cshtml @@ -1,17 +1,17 @@ @functions{ - private DateTime? Value - { - get - { - DateTime? value = null; - if (ViewData.Model != null) - { - value = Convert.ToDateTime(ViewData.Model, System.Globalization.CultureInfo.CurrentCulture); - } - return value; - } - } - + private DateTime? Value + { + get + { + DateTime? value = null; + if (ViewData.Model != null) + { + value = Convert.ToDateTime(ViewData.Model, System.Globalization.CultureInfo.CurrentCulture); + } + return value; + } + } + private bool PickTime { get @@ -29,6 +29,21 @@ return false; } } + + private string CssClass + { + get + { + var cls = "input-group date datetimepicker-group"; + + if (ViewData.ContainsKey("size")) + { + cls += " input-group input-group-" + ViewData["size"].Convert(); + } + + return cls; + } + } } @{ @@ -41,7 +56,7 @@ } } -
    +
    @Html.TextBox("", value, new { @class = "form-control datetimepicker-input", data_target = "#" + id + "-parent", data_format = format })
    From 522ec887ca25d374b81774f42d34e95a4841f39c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 25 Sep 2018 11:05:09 +0200 Subject: [PATCH 130/932] Resolves #417 Restrict forum groups to specific customer roles --- changelog.md | 4 +- .../Domain/Forums/ForumGroup.cs | 8 +- .../201809241947314_ForumGroupAcl.Designer.cs | 29 ++++ .../201809241947314_ForumGroupAcl.cs | 18 +++ .../201809241947314_ForumGroupAcl.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 7 + .../Forums/ForumService.cs | 62 +++++++-- .../Forums/IForumService.cs | 6 +- .../Controllers/ForumController.cs | 59 +++++--- .../Models/Forums/ForumGroupModel.cs | 7 +- .../Forum/_CreateOrUpdateForumGroup.cshtml | 12 +- .../Controllers/BoardsController.cs | 116 ++++++++-------- 12 files changed, 362 insertions(+), 92 deletions(-) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.resx diff --git a/changelog.md b/changelog.md index 6de6f5b983..7da2864c7c 100644 --- a/changelog.md +++ b/changelog.md @@ -15,7 +15,9 @@ * Added option to display preview pictures in product lists * Added option to add multiple file versions to product download section * Added options for alternating price display (in badges) -* Added option to display a captcha on forum pages when creating or replying to a topic. +* **Forum**: + * Added option to display a captcha on forum pages when creating or replying to a topic. + * #417 Restrict forum groups to specific customer roles. * **MegaSearch**: * Supports searching for forum posts. * #1172 Option to display related search terms on search page. diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs index d68dceb19c..7974973f45 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Seo; using SmartStore.Core.Domain.Stores; @@ -9,7 +10,7 @@ namespace SmartStore.Core.Domain.Forums /// /// Represents a forum group /// - public partial class ForumGroup : BaseEntity, IAuditable, IStoreMappingSupported, ILocalizedEntity, ISlugSupported + public partial class ForumGroup : BaseEntity, IAuditable, IStoreMappingSupported, IAclSupported, ILocalizedEntity, ISlugSupported { private ICollection _forums; @@ -43,6 +44,11 @@ public partial class ForumGroup : BaseEntity, IAuditable, IStoreMappingSupported /// public bool LimitedToStores { get; set; } + /// + /// Gets or sets a value indicating whether the entity is subject to ACL + /// + public bool SubjectToAcl { get; set; } + /// /// Gets or sets the collection of Forums /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.Designer.cs new file mode 100644 index 0000000000..5d82777a3a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class ForumGroupAcl : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ForumGroupAcl)); + + string IMigrationMetadata.Id + { + get { return "201809241947314_ForumGroupAcl"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.cs b/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.cs new file mode 100644 index 0000000000..25876305cd --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class ForumGroupAcl : DbMigration + { + public override void Up() + { + AddColumn("dbo.Forums_Group", "SubjectToAcl", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.Forums_Group", "SubjectToAcl"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.resx b/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.resx new file mode 100644 index 0000000000..43f5701c1e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index 5f527da1fb..364baeac00 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -623,6 +623,10 @@ 201809171309522_NewsletterSubscriptionLanguage.cs + + + 201809241947314_ForumGroupAcl.cs + @@ -1120,6 +1124,9 @@ 201809171309522_NewsletterSubscriptionLanguage.cs + + 201809241947314_ForumGroupAcl.cs + diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index 43a68f4f98..0ae5e99e30 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -4,6 +4,7 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Stores; using SmartStore.Data.Caching; using SmartStore.Services.Common; @@ -19,6 +20,7 @@ public partial class ForumService : IForumService private readonly IRepository _forumPostRepository; private readonly IRepository _forumPrivateMessageRepository; private readonly IRepository _forumSubscriptionRepository; + private readonly IRepository _aclRepository; private readonly ForumSettings _forumSettings; private readonly IRepository _customerRepository; private readonly IGenericAttributeService _genericAttributeService; @@ -33,6 +35,7 @@ public ForumService( IRepository forumPostRepository, IRepository forumPrivateMessageRepository, IRepository forumSubscriptionRepository, + IRepository aclRepository, ForumSettings forumSettings, IRepository customerRepository, IGenericAttributeService genericAttributeService, @@ -46,6 +49,7 @@ public ForumService( _forumPostRepository = forumPostRepository; _forumPrivateMessageRepository = forumPrivateMessageRepository; _forumSubscriptionRepository = forumSubscriptionRepository; + _aclRepository = aclRepository; _forumSettings = forumSettings; _customerRepository = customerRepository; _genericAttributeService = genericAttributeService; @@ -146,8 +150,9 @@ public virtual ForumGroup GetForumGroupById(int forumGroupId) return _forumGroupRepository.GetById(forumGroupId); } - public virtual IList GetAllForumGroups(int storeId = 0) + public virtual IList GetAllForumGroups(int storeId = 0, bool showHidden = false) { + var joinApplied = false; var query = _forumGroupRepository.Table.Expand(x => x.Forums); if (!QuerySettings.IgnoreMultiStore && storeId > 0) @@ -159,6 +164,25 @@ from sm in fg_sm.DefaultIfEmpty() where !fg.LimitedToStores || storeId == sm.StoreId select fg; + joinApplied = true; + } + + if (!showHidden && !QuerySettings.IgnoreAcl) + { + var allowedCustomerRolesIds = _services.WorkContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); + + query = + from fg in query + join a in _aclRepository.Table on new { a1 = fg.Id, a2 = "ForumGroup" } equals new { a1 = a.EntityId, a2 = a.EntityName } into fg_acl + from a in fg_acl.DefaultIfEmpty() + where !fg.SubjectToAcl || allowedCustomerRolesIds.Contains(a.CustomerRoleId) + select fg; + + joinApplied = true; + } + + if (joinApplied) + { query = from fg in query group fg by fg.Id into fgGroup @@ -317,8 +341,9 @@ public virtual IPagedList GetAllTopics(int forumId, int pageIndex, i return topics; } - public virtual IList GetActiveTopics(int forumId, int count) + public virtual IList GetActiveTopics(int forumId, int count, bool showHidden = false) { + var joinApplied = false; var query = from ft in _forumTopicRepository.Table where (forumId == 0 || ft.ForumId == forumId) && (ft.LastPostTime.HasValue) @@ -337,14 +362,35 @@ from sm in fg_sm.DefaultIfEmpty() where !fg.LimitedToStores || currentStoreId == sm.StoreId select ft; - query = - from ft in query - group ft by ft.Id into ftGroup - orderby ftGroup.Key - select ftGroup.FirstOrDefault(); + joinApplied = true; } - query = query.OrderByDescending(x => x.LastPostTime); + if (!showHidden && !QuerySettings.IgnoreAcl) + { + var allowedCustomerRolesIds = _services.WorkContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); + + query = + from ft in query + join ff in _forumRepository.Table on ft.ForumId equals ff.Id + join fg in _forumGroupRepository.Table on ff.ForumGroupId equals fg.Id + join a in _aclRepository.Table on new { a1 = fg.Id, a2 = "ForumGroup" } equals new { a1 = a.EntityId, a2 = a.EntityName } into fg_acl + from a in fg_acl.DefaultIfEmpty() + where !fg.SubjectToAcl || allowedCustomerRolesIds.Contains(a.CustomerRoleId) + select ft; + + joinApplied = true; + } + + if (joinApplied) + { + query = + from ft in query + group ft by ft.Id into ftGroup + orderby ftGroup.Key + select ftGroup.FirstOrDefault(); + } + + query = query.OrderByDescending(x => x.LastPostTime); var forumTopics = query.Take(count).ToList(); return forumTopics; diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index cf743e3411..ed991c0c8d 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -23,8 +23,9 @@ public partial interface IForumService /// Gets all forum groups /// /// Store identifier + /// Whether to load hidden records /// Forum groups - IList GetAllForumGroups(int storeId = 0); + IList GetAllForumGroups(int storeId = 0, bool showHidden = false); /// /// Deletes a forum group @@ -112,8 +113,9 @@ public partial interface IForumService /// /// The forum identifier /// Count of forum topics to return + /// Whether to load hidden records /// Forum Topics - IList GetActiveTopics(int forumId, int topicCount); + IList GetActiveTopics(int forumId, int topicCount, bool showHidden = false); /// /// Deletes a forum topic diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ForumController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ForumController.cs index d418935457..67e19c99db 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ForumController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ForumController.cs @@ -4,16 +4,17 @@ using SmartStore.Admin.Models.Forums; using SmartStore.Core.Domain.Forums; using SmartStore.Services; +using SmartStore.Services.Customers; using SmartStore.Services.Forums; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Security; using SmartStore.Services.Seo; using SmartStore.Services.Stores; +using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Security; -using SmartStore.Web.Framework; namespace SmartStore.Admin.Controllers { @@ -22,27 +23,34 @@ public class ForumController : AdminControllerBase { private readonly IForumService _forumService; private readonly ICommonServices _services; - private readonly IDateTimeHelper _dateTimeHelper; private readonly IStoreMappingService _storeMappingService; - private readonly ILanguageService _languageService; + private readonly IAclService _aclService; + private readonly ICustomerService _customerService; + private readonly ILanguageService _languageService; private readonly ILocalizedEntityService _localizedEntityService; private readonly IUrlRecordService _urlRecordService; + private readonly IDateTimeHelper _dateTimeHelper; - public ForumController(IForumService forumService, + public ForumController( + IForumService forumService, ICommonServices services, - IDateTimeHelper dateTimeHelper, - IStoreMappingService storeMappingService, + IStoreMappingService storeMappingService, + IAclService aclService, + ICustomerService customerService, ILanguageService languageService, ILocalizedEntityService localizedEntityService, - IUrlRecordService urlRecordService) + IUrlRecordService urlRecordService, + IDateTimeHelper dateTimeHelper) { _forumService = forumService; _services = services; - _dateTimeHelper = dateTimeHelper; _storeMappingService = storeMappingService; + _aclService = aclService; + _customerService = customerService; _languageService = languageService; _localizedEntityService = localizedEntityService; _urlRecordService = urlRecordService; + _dateTimeHelper = dateTimeHelper; } #region Utilities @@ -62,10 +70,13 @@ private void PrepareForumGroupModel(ForumGroupModel model, ForumGroup forumGroup if (!excludeProperties) { model.SelectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(forumGroup); - } + model.SelectedCustomerRoleIds = _aclService.GetCustomerRoleIdsWithAccess(forumGroup); + } model.AvailableStores = allStores.ToSelectListItems(model.SelectedStoreIds); - ViewBag.StoreCount = allStores.Count; + model.AvailableCustomerRoles = _customerService.GetAllCustomerRoles(true).ToSelectListItems(model.SelectedCustomerRoleIds); + + ViewBag.StoreCount = allStores.Count; } [NonAction] @@ -108,7 +119,7 @@ public ActionResult List() if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) return AccessDeniedView(); - var forumGroupsModel = _forumService.GetAllForumGroups(0) + var forumGroupsModel = _forumService.GetAllForumGroups(0, true) .Select(fg => { var forumGroupModel = fg.ToModel(); @@ -151,7 +162,9 @@ public ActionResult CreateForumGroup() public ActionResult CreateForumGroup(ForumGroupModel model, bool continueEditing) { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) + { return AccessDeniedView(); + } if (ModelState.IsValid) { @@ -165,14 +178,14 @@ public ActionResult CreateForumGroup(ForumGroupModel model, bool continueEditing UpdateLocales(model, forumGroup); SaveStoreMappings(forumGroup, model); + SaveAclMappings(forumGroup, model); - NotifySuccess(_services.Localization.GetResource("Admin.ContentManagement.Forums.ForumGroup.Added")); + NotifySuccess(T("Admin.ContentManagement.Forums.ForumGroup.Added")); return continueEditing ? RedirectToAction("EditForumGroup", new { forumGroup.Id }) : RedirectToAction("List"); } - //If we got this far, something failed, redisplay form - + // If we got this far, something failed, redisplay form. PrepareForumGroupModel(model, null, true); return View(model); @@ -187,7 +200,7 @@ public ActionResult CreateForum() AddLocales(_languageService, model.Locales); - foreach (var forumGroup in _forumService.GetAllForumGroups()) + foreach (var forumGroup in _forumService.GetAllForumGroups(0, true)) { var forumGroupModel = forumGroup.ToModel(); model.ForumGroups.Add(forumGroupModel); @@ -218,7 +231,7 @@ public ActionResult CreateForum(ForumModel model, bool continueEditing) } //If we got this far, something failed, redisplay form - foreach (var forumGroup in _forumService.GetAllForumGroups()) + foreach (var forumGroup in _forumService.GetAllForumGroups(0, true)) { var forumGroupModel = forumGroup.ToModel(); model.ForumGroups.Add(forumGroupModel); @@ -257,11 +270,15 @@ public ActionResult EditForumGroup(int id) public ActionResult EditForumGroup(ForumGroupModel model, bool continueEditing) { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) + { return AccessDeniedView(); + } var forumGroup = _forumService.GetForumGroupById(model.Id); if (forumGroup == null) + { return RedirectToAction("List"); + } if (ModelState.IsValid) { @@ -275,14 +292,14 @@ public ActionResult EditForumGroup(ForumGroupModel model, bool continueEditing) UpdateLocales(model, forumGroup); SaveStoreMappings(forumGroup, model); + SaveAclMappings(forumGroup, model); - NotifySuccess(_services.Localization.GetResource("Admin.ContentManagement.Forums.ForumGroup.Updated")); + NotifySuccess(T("Admin.ContentManagement.Forums.ForumGroup.Updated")); return continueEditing ? RedirectToAction("EditForumGroup", forumGroup.Id) : RedirectToAction("List"); } - //If we got this far, something failed, redisplay form - + // If we got this far, something failed, redisplay form. PrepareForumGroupModel(model, forumGroup, true); return View(model); @@ -306,7 +323,7 @@ public ActionResult EditForum(int id) locale.SeName = forum.GetSeName(languageId, false, false); }); - foreach (var forumGroup in _forumService.GetAllForumGroups(0)) + foreach (var forumGroup in _forumService.GetAllForumGroups(0, true)) { var forumGroupModel = forumGroup.ToModel(); model.ForumGroups.Add(forumGroupModel); @@ -341,7 +358,7 @@ public ActionResult EditForum(ForumModel model, bool continueEditing) } //If we got this far, something failed, redisplay form - foreach (var forumGroup in _forumService.GetAllForumGroups()) + foreach (var forumGroup in _forumService.GetAllForumGroups(0, true)) { var forumGroupModel = forumGroup.ToModel(); model.ForumGroups.Add(forumGroupModel); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Forums/ForumGroupModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Forums/ForumGroupModel.cs index 44d1646cd5..361c0d951a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Forums/ForumGroupModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Forums/ForumGroupModel.cs @@ -10,7 +10,7 @@ namespace SmartStore.Admin.Models.Forums { [Validator(typeof(ForumGroupValidator))] - public class ForumGroupModel : EntityModelBase, ILocalizedModel, IStoreSelector + public class ForumGroupModel : EntityModelBase, ILocalizedModel, IStoreSelector, IAclSelector { public ForumGroupModel() { @@ -41,8 +41,11 @@ public ForumGroupModel() public IEnumerable AvailableStores { get; set; } public int[] SelectedStoreIds { get; set; } - public IList ForumModels { get; set; } + public bool SubjectToAcl { get; set; } + public IEnumerable AvailableCustomerRoles { get; set; } + public int[] SelectedCustomerRoleIds { get; set; } + public IList ForumModels { get; set; } public IList Locales { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForumGroup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForumGroup.cshtml index b8776317ee..2c248f2077 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForumGroup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForumGroup.cshtml @@ -1,6 +1,5 @@ -@model ForumGroupModel - -@using Telerik.Web.Mvc.UI; +@using Telerik.Web.Mvc.UI; +@model ForumGroupModel @Html.ValidationSummary(true) @Html.HiddenFor(model => model.Id) @@ -8,9 +7,9 @@ @Html.SmartStore().TabStrip().Name("forumgroup-edit").Style(TabsStyle.Material).Items(x => { x.Add().Text(T("Admin.Common.General").Text).Content(TabInfo()).Selected(true); + x.Add().Text(T("Admin.Catalog.Categories.Acl").Text).Content(TabAcl()); x.Add().Text(T("Admin.Common.Stores").Text).Content(TabStores()); - //generate an event EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "forumgroup-edit", this.Html, this.Model)); }) @@ -98,6 +97,11 @@
    } +@helper TabAcl() +{ + @Html.Partial("AclSelector", Model) +} + @helper TabStores() { @Html.Partial("StoreSelector", Model) diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 6da936f0e9..a6310b4388 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -20,6 +20,7 @@ using SmartStore.Services.Search; using SmartStore.Services.Search.Modelling; using SmartStore.Services.Search.Rendering; +using SmartStore.Services.Security; using SmartStore.Services.Seo; using SmartStore.Services.Stores; using SmartStore.Utilities; @@ -43,6 +44,7 @@ public partial class BoardsController : PublicControllerBase private readonly IForumSearchService _forumSearchService; private readonly IGenericAttributeService _genericAttributeService; private readonly IStoreMappingService _storeMappingService; + private readonly IAclService _aclService; private readonly ForumSettings _forumSettings; private readonly ForumSearchSettings _searchSettings; private readonly CustomerSettings _customerSettings; @@ -60,6 +62,7 @@ public BoardsController( IForumSearchService forumSearchService, IGenericAttributeService genericAttributeService, IStoreMappingService storeMappingService, + IAclService aclService, ForumSettings forumSettings, ForumSearchSettings searchSettings, CustomerSettings customerSettings, @@ -76,6 +79,7 @@ public BoardsController( _forumSearchService = forumSearchService; _genericAttributeService = genericAttributeService; _storeMappingService = storeMappingService; + _aclService = aclService; _forumSettings = forumSettings; _searchSettings = searchSettings; _customerSettings = customerSettings; @@ -341,7 +345,7 @@ public ActionResult ForumGroup(int id) } var forumGroup = _forumService.GetForumGroupById(id); - if (forumGroup == null || !_storeMappingService.Authorize(forumGroup)) + if (forumGroup == null || !_storeMappingService.Authorize(forumGroup) || !_aclService.Authorize(forumGroup)) { return HttpNotFound(); } @@ -365,7 +369,7 @@ public ActionResult Forum(int id, int page = 1) var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(id); - if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup)) + if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup) || !_aclService.Authorize(forum.ForumGroup)) { return HttpNotFound(); } @@ -443,7 +447,7 @@ public ActionResult ForumRss(int id = 0) } var forum = _forumService.GetForumById(id); - if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup)) + if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup) || !_aclService.Authorize(forum.ForumGroup)) { return new RssActionResult { Feed = feed }; } @@ -477,7 +481,10 @@ public ActionResult ForumWatch(int id) var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(id); - if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup) || !_forumService.IsCustomerAllowedToSubscribe(customer)) + if (forum == null || + !_storeMappingService.Authorize(forum.ForumGroup) || + !_aclService.Authorize(forum.ForumGroup) || + !_forumService.IsCustomerAllowedToSubscribe(customer)) { return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } @@ -637,7 +644,7 @@ public ActionResult Topic(int id, int page = 1) if (forumTopic != null) { - if (!_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) + if (!_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) { return HttpNotFound(); } @@ -754,7 +761,9 @@ public ActionResult TopicWatch(int id) return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } - if (!_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) && !_forumService.IsCustomerAllowedToSubscribe(customer)) + if (!_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || + !_aclService.Authorize(forumTopic.Forum.ForumGroup) || + !_forumService.IsCustomerAllowedToSubscribe(customer)) { return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } @@ -792,7 +801,7 @@ public ActionResult TopicMove(int id) } var forumTopic = _forumService.GetTopicById(id); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) { return HttpNotFound(); } @@ -817,7 +826,7 @@ public ActionResult TopicMove(TopicMoveModel model) } var forumTopic = _forumService.GetTopicById(model.Id); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) { return HttpNotFound(); } @@ -843,7 +852,7 @@ public ActionResult TopicCreate(int id) var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(id); - if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup)) + if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup) || !_aclService.Authorize(forum.ForumGroup)) { return HttpNotFound(); } @@ -882,27 +891,27 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) return HttpNotFound(); } - if (_captchaSettings.Enabled && _captchaSettings.ShowOnForumPage && !captchaValid) - { - ModelState.AddModelError("", T("Common.WrongCaptcha")); - } - var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(model.ForumId); - if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup)) + if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup) || !_aclService.Authorize(forum.ForumGroup)) { return HttpNotFound(); } + if (!_forumService.IsCustomerAllowedToCreateTopic(customer, forum)) + { + return new HttpUnauthorizedResult(); + } + + if (_captchaSettings.Enabled && _captchaSettings.ShowOnForumPage && !captchaValid) + { + ModelState.AddModelError("", T("Common.WrongCaptcha")); + } + if (ModelState.IsValid) { try { - if (!_forumService.IsCustomerAllowedToCreateTopic(customer, forum)) - { - return new HttpUnauthorizedResult(); - } - var subject = model.Subject; var maxSubjectLength = _forumSettings.TopicSubjectMaxLength; if (maxSubjectLength > 0 && subject.Length > maxSubjectLength) @@ -1000,7 +1009,7 @@ public ActionResult TopicEdit(int id) var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(id); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) { return HttpNotFound(); } @@ -1050,27 +1059,27 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) return HttpNotFound(); } - if (_captchaSettings.Enabled && _captchaSettings.ShowOnForumPage && !captchaValid) - { - ModelState.AddModelError("", T("Common.WrongCaptcha")); - } - var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(model.Id); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) { return HttpNotFound(); } + if (!_forumService.IsCustomerAllowedToEditTopic(customer, forumTopic)) + { + return new HttpUnauthorizedResult(); + } + + if (_captchaSettings.Enabled && _captchaSettings.ShowOnForumPage && !captchaValid) + { + ModelState.AddModelError("", T("Common.WrongCaptcha")); + } + if (ModelState.IsValid) { try { - if (!_forumService.IsCustomerAllowedToEditTopic(customer, forumTopic)) - { - return new HttpUnauthorizedResult(); - } - var subject = model.Subject; var maxSubjectLength = _forumSettings.TopicSubjectMaxLength; if (maxSubjectLength > 0 && subject.Length > maxSubjectLength) @@ -1179,7 +1188,7 @@ public ActionResult TopicDelete(int id) } var forumTopic = _forumService.GetTopicById(id); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) { return HttpNotFound(); } @@ -1214,7 +1223,7 @@ public ActionResult PostCreate(int id, int? quote) var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(id); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) { return HttpNotFound(); } @@ -1282,27 +1291,27 @@ public ActionResult PostCreate(EditForumPostModel model, bool captchaValid) return HttpNotFound(); } - if (_captchaSettings.Enabled && _captchaSettings.ShowOnForumPage && !captchaValid) - { - ModelState.AddModelError("", T("Common.WrongCaptcha")); - } - var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(model.ForumTopicId); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup)) + if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) { return HttpNotFound(); } + if (!_forumService.IsCustomerAllowedToCreatePost(customer, forumTopic)) + { + return new HttpUnauthorizedResult(); + } + + if (_captchaSettings.Enabled && _captchaSettings.ShowOnForumPage && !captchaValid) + { + ModelState.AddModelError("", T("Common.WrongCaptcha")); + } + if (ModelState.IsValid) { try { - if (!_forumService.IsCustomerAllowedToCreatePost(customer, forumTopic)) - { - return new HttpUnauthorizedResult(); - } - var text = model.Text; var maxPostLength = _forumSettings.PostMaxLength; if (maxPostLength > 0 && text.Length > maxPostLength) @@ -1396,10 +1405,11 @@ public ActionResult PostEdit(int id) var customer = Services.WorkContext.CurrentCustomer; var forumPost = _forumService.GetPostById(id); - if (forumPost == null || !_storeMappingService.Authorize(forumPost.ForumTopic.Forum.ForumGroup)) + if (forumPost == null || !_storeMappingService.Authorize(forumPost.ForumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumPost.ForumTopic.Forum.ForumGroup)) { return HttpNotFound(); } + if (!_forumService.IsCustomerAllowedToEditPost(customer, forumPost)) { return new HttpUnauthorizedResult(); @@ -1442,14 +1452,9 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) return HttpNotFound(); } - if (_captchaSettings.Enabled && _captchaSettings.ShowOnForumPage && !captchaValid) - { - ModelState.AddModelError("", T("Common.WrongCaptcha")); - } - var customer = Services.WorkContext.CurrentCustomer; var forumPost = _forumService.GetPostById(model.Id); - if (forumPost == null || !_storeMappingService.Authorize(forumPost.ForumTopic.Forum.ForumGroup)) + if (forumPost == null || !_storeMappingService.Authorize(forumPost.ForumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumPost.ForumTopic.Forum.ForumGroup)) { return HttpNotFound(); } @@ -1459,6 +1464,11 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) return new HttpUnauthorizedResult(); } + if (_captchaSettings.Enabled && _captchaSettings.ShowOnForumPage && !captchaValid) + { + ModelState.AddModelError("", T("Common.WrongCaptcha")); + } + if (ModelState.IsValid) { try @@ -1546,7 +1556,7 @@ public ActionResult PostDelete(int id) } var forumPost = _forumService.GetPostById(id); - if (forumPost == null || !_storeMappingService.Authorize(forumPost.ForumTopic.Forum.ForumGroup)) + if (forumPost == null || !_storeMappingService.Authorize(forumPost.ForumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumPost.ForumTopic.Forum.ForumGroup)) { return HttpNotFound(); } From db1dfb8fec47acb79ee6be55f753130647678e12 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 25 Sep 2018 13:03:51 +0200 Subject: [PATCH 131/932] Integrate ACL of forum groups in forum search --- .../Forums/ForumService.cs | 6 ++-- .../Search/Forum/ForumSearchQuery.cs | 28 +++++++++++++++++ .../Search/Forum/LinqForumSearchService.cs | 30 +++++++++++++++++++ .../Modelling/ForumSearchQueryFactory.cs | 3 ++ 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index 0ae5e99e30..80f0dea995 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -20,12 +20,12 @@ public partial class ForumService : IForumService private readonly IRepository _forumPostRepository; private readonly IRepository _forumPrivateMessageRepository; private readonly IRepository _forumSubscriptionRepository; + private readonly IRepository _storeMappingRepository; private readonly IRepository _aclRepository; private readonly ForumSettings _forumSettings; private readonly IRepository _customerRepository; private readonly IGenericAttributeService _genericAttributeService; private readonly ICustomerService _customerService; - private readonly IRepository _storeMappingRepository; private readonly ICommonServices _services; public ForumService( @@ -35,12 +35,12 @@ public ForumService( IRepository forumPostRepository, IRepository forumPrivateMessageRepository, IRepository forumSubscriptionRepository, + IRepository storeMappingRepository, IRepository aclRepository, ForumSettings forumSettings, IRepository customerRepository, IGenericAttributeService genericAttributeService, ICustomerService customerService, - IRepository storeMappingRepository, ICommonServices services) { _forumGroupRepository = forumGroupRepository; @@ -49,12 +49,12 @@ public ForumService( _forumPostRepository = forumPostRepository; _forumPrivateMessageRepository = forumPrivateMessageRepository; _forumSubscriptionRepository = forumSubscriptionRepository; + _storeMappingRepository = storeMappingRepository; _aclRepository = aclRepository; _forumSettings = forumSettings; _customerRepository = customerRepository; _genericAttributeService = genericAttributeService; _customerService = customerService; - _storeMappingRepository = storeMappingRepository; _services = services; } diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs index a6b761cec1..1e964bad84 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Search; @@ -65,6 +66,33 @@ public ForumSearchQuery SortBy(ForumTopicSorting sort) } } + public ForumSearchQuery VisibleOnly(Customer customer) + { + if (customer != null) + { + var allowedCustomerRoleIds = customer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToArray(); + + return VisibleOnly(allowedCustomerRoleIds); + } + + return VisibleOnly(new int[0]); + } + + public ForumSearchQuery VisibleOnly(params int[] allowedCustomerRoleIds) + { + if (allowedCustomerRoleIds != null && allowedCustomerRoleIds.Length > 0) + { + var roleIds = allowedCustomerRoleIds.Where(x => x != 0).Distinct().ToList(); + if (roleIds.Any()) + { + roleIds.Insert(0, 0); + WithFilter(SearchFilter.Combined(roleIds.Select(x => SearchFilter.ByField("roleid", x).ExactMatch().NotAnalyzed()).ToArray())); + } + } + + return this; + } + public override ForumSearchQuery HasStoreId(int id) { base.HasStoreId(id); diff --git a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs index 970a5566f8..917221d3b5 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -4,6 +4,7 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Localization; using SmartStore.Core.Search; @@ -18,20 +19,32 @@ namespace SmartStore.Services.Search public partial class LinqForumSearchService : SearchServiceBase, IForumSearchService { private readonly IRepository _forumPostRepository; + private readonly IRepository _forumTopicRepository; + private readonly IRepository _forumRepository; + private readonly IRepository _forumGroupRepository; private readonly IRepository _storeMappingRepository; + private readonly IRepository _aclRepository; private readonly IForumService _forumService; private readonly ICommonServices _services; private readonly CustomerSettings _customerSettings; public LinqForumSearchService( IRepository forumPostRepository, + IRepository forumTopicRepository, + IRepository forumRepository, + IRepository forumGroupRepository, IRepository storeMappingRepository, + IRepository aclRepository, IForumService forumService, ICommonServices services, CustomerSettings customerSettings) { _forumPostRepository = forumPostRepository; + _forumTopicRepository = forumTopicRepository; + _forumRepository = forumRepository; + _forumGroupRepository = forumGroupRepository; _storeMappingRepository = storeMappingRepository; + _aclRepository = aclRepository; _forumService = forumService; _services = services; _customerSettings = customerSettings; @@ -85,6 +98,23 @@ protected virtual IQueryable GetPostQuery(ForumSearchQuery searchQuer // Filters. FlattenFilters(searchQuery.Filters, filters); + if (!QuerySettings.IgnoreAcl) + { + var roleIds = GetIdList(filters, "roleid"); + if (roleIds.Any()) + { + query = + from fp in query + join ft in _forumTopicRepository.TableUntracked on fp.TopicId equals ft.Id + join ff in _forumRepository.Table on ft.ForumId equals ff.Id + join fg in _forumGroupRepository.Table on ff.ForumGroupId equals fg.Id + join a in _aclRepository.Table on new { a1 = fg.Id, a2 = "ForumGroup" } equals new { a1 = a.EntityId, a2 = a.EntityName } into fg_acl + from a in fg_acl.DefaultIfEmpty() + where !fg.SubjectToAcl || roleIds.Contains(a.CustomerRoleId) + select fp; + } + } + foreach (IAttributeSearchFilter filter in filters) { var rangeFilter = filter as IRangeSearchFilter; diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs index 50918174c6..f5b2d7960a 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs @@ -83,6 +83,9 @@ public ForumSearchQuery CreateFromQuery() .WithCurrency(_services.WorkContext.WorkingCurrency) .BuildFacetMap(!isInstantSearch); + // Visibility. + query.VisibleOnly(!QuerySettings.IgnoreAcl ? _services.WorkContext.CurrentCustomer : null); + // Store. if (!QuerySettings.IgnoreMultiStore) { From d3f80fe68cfa33018a590826ba326bb8246890f2 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 25 Sep 2018 15:06:06 +0200 Subject: [PATCH 132/932] Please execute "update-database -TargetMigration NewsletterSubscriptionLanguage" --- .../Domain/Forums/ForumPost.cs | 5 + .../Domain/Forums/ForumTopic.cs | 5 + .../201809241947314_ForumGroupAcl.resx | 126 ------------------ ...201809251233170_ForumGroupAcl.Designer.cs} | 2 +- ...cl.cs => 201809251233170_ForumGroupAcl.cs} | 4 + .../201809251233170_ForumGroupAcl.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 10 +- .../Controllers/BoardsController.cs | 8 +- .../Forums/ForumPostPersistenceTests.cs | 2 + .../ForumSubscriptionPersistenceTests.cs | 2 + .../Forums/ForumTopicPersistenceTests.cs | 1 + 11 files changed, 157 insertions(+), 134 deletions(-) delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.resx rename src/Libraries/SmartStore.Data/Migrations/{201809241947314_ForumGroupAcl.Designer.cs => 201809251233170_ForumGroupAcl.Designer.cs} (92%) rename src/Libraries/SmartStore.Data/Migrations/{201809241947314_ForumGroupAcl.cs => 201809251233170_ForumGroupAcl.cs} (57%) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.resx diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs index 087c6205a2..2cfecbfe97 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs @@ -38,6 +38,11 @@ public partial class ForumPost : BaseEntity, IAuditable /// public DateTime UpdatedOnUtc { get; set; } + /// + /// Gets or sets a value indicating whether the entity is published + /// + public bool Published { get; set; } + /// /// Gets the topic /// diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs index 4b30ac9b02..b77c216592 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs @@ -69,6 +69,11 @@ public partial class ForumTopic : BaseEntity, IAuditable /// public DateTime UpdatedOnUtc { get; set; } + /// + /// Gets or sets a value indicating whether the entity is published + /// + public bool Published { get; set; } + /// /// Gets or sets the forum topic type /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.resx b/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.resx deleted file mode 100644 index 43f5701c1e..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.resx +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 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 - - - dbo - - \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.Designer.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.Designer.cs rename to src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.Designer.cs index 5d82777a3a..9c8853796f 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.Designer.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.Designer.cs @@ -13,7 +13,7 @@ public sealed partial class ForumGroupAcl : IMigrationMetadata string IMigrationMetadata.Id { - get { return "201809241947314_ForumGroupAcl"; } + get { return "201809251233170_ForumGroupAcl"; } } string IMigrationMetadata.Source diff --git a/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.cs b/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.cs similarity index 57% rename from src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.cs rename to src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.cs index 25876305cd..67781b9ef6 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201809241947314_ForumGroupAcl.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.cs @@ -7,12 +7,16 @@ public partial class ForumGroupAcl : DbMigration { public override void Up() { + AddColumn("dbo.Forums_Post", "Published", c => c.Boolean(nullable: false, defaultValue: true)); + AddColumn("dbo.Forums_Topic", "Published", c => c.Boolean(nullable: false, defaultValue: true)); AddColumn("dbo.Forums_Group", "SubjectToAcl", c => c.Boolean(nullable: false)); } public override void Down() { DropColumn("dbo.Forums_Group", "SubjectToAcl"); + DropColumn("dbo.Forums_Topic", "Published"); + DropColumn("dbo.Forums_Post", "Published"); } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.resx b/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.resx new file mode 100644 index 0000000000..572cbd0572 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index 364baeac00..30a53f7b9a 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -623,9 +623,9 @@ 201809171309522_NewsletterSubscriptionLanguage.cs - - - 201809241947314_ForumGroupAcl.cs + + + 201809251233170_ForumGroupAcl.cs @@ -1124,8 +1124,8 @@ 201809171309522_NewsletterSubscriptionLanguage.cs - - 201809241947314_ForumGroupAcl.cs + + 201809251233170_ForumGroupAcl.cs diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index a6310b4388..ed57ca8b54 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -940,7 +940,8 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) ForumId = forum.Id, CustomerId = customer.Id, TopicTypeId = (int) topicType, - Subject = subject + Subject = subject, + Published = true }; _forumService.InsertTopic(forumTopic, true); @@ -950,6 +951,7 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) CustomerId = customer.Id, Text = text, IPAddress = ipAddress, + Published = true }; _forumService.InsertPost(forumPost, false); @@ -1122,6 +1124,7 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) CustomerId = forumTopic.CustomerId, Text = text, IPAddress = ipAddress, + Published = true }; _forumService.InsertPost(firstPost, false); @@ -1327,7 +1330,8 @@ public ActionResult PostCreate(EditForumPostModel model, bool captchaValid) TopicId = forumTopic.Id, CustomerId = customer.Id, Text = text, - IPAddress = ipAddress + IPAddress = ipAddress, + Published = true }; _forumService.InsertPost(forumPost, true); diff --git a/src/Tests/SmartStore.Data.Tests/Forums/ForumPostPersistenceTests.cs b/src/Tests/SmartStore.Data.Tests/Forums/ForumPostPersistenceTests.cs index c9be77bfa9..24b63e38d9 100644 --- a/src/Tests/SmartStore.Data.Tests/Forums/ForumPostPersistenceTests.cs +++ b/src/Tests/SmartStore.Data.Tests/Forums/ForumPostPersistenceTests.cs @@ -64,6 +64,7 @@ public void Can_save_and_load_forumpost() UpdatedOnUtc = DateTime.UtcNow, NumPosts = 100, CustomerId = customerFromDb.Id, + Published = true }; var forumTopicFromDb = SaveAndLoadEntity(forumTopic); @@ -83,6 +84,7 @@ public void Can_save_and_load_forumpost() CreatedOnUtc = DateTime.UtcNow, UpdatedOnUtc = DateTime.UtcNow, CustomerId = customerFromDb.Id, + Published = true }; var forumPostFromDb = SaveAndLoadEntity(forumPost); diff --git a/src/Tests/SmartStore.Data.Tests/Forums/ForumSubscriptionPersistenceTests.cs b/src/Tests/SmartStore.Data.Tests/Forums/ForumSubscriptionPersistenceTests.cs index 2f1ac31fd4..a75aa8ee3d 100644 --- a/src/Tests/SmartStore.Data.Tests/Forums/ForumSubscriptionPersistenceTests.cs +++ b/src/Tests/SmartStore.Data.Tests/Forums/ForumSubscriptionPersistenceTests.cs @@ -65,6 +65,7 @@ public void Can_save_and_load_forum_subscription_forum_subscribed() UpdatedOnUtc = DateTime.UtcNow, NumPosts = 100, CustomerId = customerFromDb.Id, + Published = true }; var forumTopicFromDb = SaveAndLoadEntity(forumTopic); @@ -146,6 +147,7 @@ public void Can_save_and_load_forum_subscription_topic_subscribed() UpdatedOnUtc = DateTime.UtcNow, NumPosts = 100, CustomerId = customerFromDb.Id, + Published = true }; var forumTopicFromDb = SaveAndLoadEntity(forumTopic); diff --git a/src/Tests/SmartStore.Data.Tests/Forums/ForumTopicPersistenceTests.cs b/src/Tests/SmartStore.Data.Tests/Forums/ForumTopicPersistenceTests.cs index fde859aeb4..28d2917caf 100644 --- a/src/Tests/SmartStore.Data.Tests/Forums/ForumTopicPersistenceTests.cs +++ b/src/Tests/SmartStore.Data.Tests/Forums/ForumTopicPersistenceTests.cs @@ -65,6 +65,7 @@ public void Can_save_and_load_forumtopic() UpdatedOnUtc = DateTime.UtcNow, NumPosts = 100, CustomerId = customerFromDb.Id, + Published = true }; var forumTopicFromDb = SaveAndLoadEntity(forumTopic); From 8ba06cf63c3ced046ecf99176abf08b12931c24d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 25 Sep 2018 21:26:42 +0200 Subject: [PATCH 133/932] Stated with published property of forum topic and forum post --- .../Domain/Forums/ForumSettings.cs | 16 +- .../201806231547270_ScheduleTaskHistory.cs | 6 - .../Migrations/MigrationsConfiguration.cs | 6 + .../Forums/ForumService.cs | 75 ++- .../Forums/IForumService.cs | 13 +- .../Controllers/BoardsController.cs | 555 ++++++++++-------- .../Models/Boards/EditForumPostModel.cs | 1 + .../Models/Boards/EditForumTopicModel.cs | 1 + .../Models/Boards/ForumPostModel.cs | 1 + .../Models/Boards/ForumTopicRowModel.cs | 1 + .../Models/Boards/LastPostModel.cs | 3 +- .../Views/Boards/TopicMove.cshtml | 6 +- 12 files changed, 364 insertions(+), 320 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs index 89406566f7..ed484dbb4f 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs @@ -11,22 +11,22 @@ public ForumSettings() TopicSubjectMaxLength = 450; PostMaxLength = 4000; StrippedTopicMaxLength = 45; - TopicsPageSize = 10; - PostsPageSize = 10; - SearchResultsPageSize = 12; + TopicsPageSize = 20; + PostsPageSize = 20; + ForumFeedCount = 20; + SearchResultsPageSize = 20; + LatestCustomerPostsPageSize = 20; AllowSorting = true; - LatestCustomerPostsPageSize = 10; - ShowCustomersPostCount = true; + ShowCustomersPostCount = true; ForumEditor = EditorType.BBCodeEditor; SignaturesEnabled = true; - PrivateMessagesPageSize = 10; - ForumSubscriptionsPageSize = 10; + PrivateMessagesPageSize = 20; + ForumSubscriptionsPageSize = 20; PMSubjectMaxLength = 450; PMTextMaxLength = 4000; HomePageActiveDiscussionsTopicCount = 5; ActiveDiscussionsPageTopicCount = 50; ActiveDiscussionsFeedCount = 25; - ForumFeedCount = 10; } /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.cs b/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.cs index 5d558c26db..f2d93452c9 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.cs @@ -85,12 +85,6 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Common.Succeeded", "Succeeded", "Erfolgreich"); builder.AddOrUpdate("Common.MachineName", "Machine name", "Maschinenname"); - builder.AddOrUpdate("Admin.System.ScheduleTasks.RunPerMachine", - "Run per machine", - "Pro Maschine ausfhren", - "Indicates whether the task is executed decidedly on each machine of a web farm.", - "Gibt an, ob die Aufgabe auf jeder Maschine einer Webfarm dezidiert ausgefhrt wird."); - builder.AddOrUpdate("Admin.System.ScheduleTasks.HistoryCleanupNote", "The history is cleaned up once a day: maximum {0} entries and none older than {1} days.", "Die Historie wird einmal tglich bereinigt: Maximal {0} Eintrge und keine lter als {1} Tage."); diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 3a75d87c08..4e30f67e80 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -513,6 +513,12 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Plugins.CannotLoadModule", "The plugin or provider \"{0}\" cannot be loaded.", "Das Plugin oder der Provider \"{0}\" kann nicht geladen werden."); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.RunPerMachine", + "Run per machine", + "Pro Maschine ausführen", + "Indicates whether the task is executed decidedly on each machine of a web farm.", + "Gibt an, ob die Aufgabe auf jeder Maschine einer Webfarm dezidiert ausgeführt wird."); } } } diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index 80f0dea995..f2ae94acba 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -71,7 +71,7 @@ private void UpdateForumStats(int forumId) var queryLastValues = from ft in _forumTopicRepository.TableUntracked join fp in _forumPostRepository.TableUntracked on ft.Id equals fp.TopicId - where ft.ForumId == forumId + where ft.ForumId == forumId && ft.Published && fp.Published orderby fp.CreatedOnUtc descending, ft.CreatedOnUtc descending select new { @@ -86,11 +86,11 @@ join fp in _forumPostRepository.TableUntracked on ft.Id equals fp.TopicId forum.LastPostId = lastValues?.LastPostId ?? 0; forum.LastPostCustomerId = lastValues?.LastPostCustomerId ?? 0; forum.LastPostTime = lastValues?.LastPostTime; - forum.NumTopics = _forumTopicRepository.Table.Where(x => x.ForumId == forumId).Count(); + forum.NumTopics = _forumTopicRepository.Table.Where(x => x.ForumId == forumId && x.Published).Count(); forum.NumPosts = ( from ft in _forumTopicRepository.Table join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId - where ft.ForumId == forumId + where ft.ForumId == forumId && ft.Published && fp.Published select fp.Id).Count(); UpdateForum(forum); @@ -106,7 +106,7 @@ private void UpdateForumTopicStats(int forumTopicId) var queryLastValues = from fp in _forumPostRepository.TableUntracked - where fp.TopicId == forumTopicId + where fp.TopicId == forumTopicId && fp.Published orderby fp.CreatedOnUtc descending select new { @@ -119,7 +119,7 @@ orderby fp.CreatedOnUtc descending forumTopic.LastPostId = lastValues?.LastPostId ?? 0; forumTopic.LastPostCustomerId = lastValues?.LastPostCustomerId ?? 0; forumTopic.LastPostTime = lastValues?.LastPostTime; - forumTopic.NumPosts = _forumPostRepository.Table.Where(x => x.TopicId == forumTopicId).Count(); + forumTopic.NumPosts = _forumPostRepository.Table.Where(x => x.TopicId == forumTopicId && x.Published).Count(); UpdateTopic(forumTopic); } @@ -131,7 +131,7 @@ private void UpdateCustomerStats(int customerId) var customer = _customerService.GetCustomerById(customerId); if (customer != null) { - var numPosts = _forumPostRepository.Table.Where(x => x.CustomerId == customerId).Count(); + var numPosts = _forumPostRepository.Table.Where(x => x.CustomerId == customerId && x.Published).Count(); _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ForumPostCount, numPosts); } @@ -235,17 +235,6 @@ public virtual Forum GetForumById(int forumId) return entity; } - public virtual IList GetAllForumsByGroupId(int forumGroupId) - { - var query = from f in _forumRepository.Table - orderby f.DisplayOrder - where f.ForumGroupId == forumGroupId - select f; - - var forums = query.ToListCached(); - return forums; - } - public virtual void InsertForum(Forum forum) { Guard.NotNull(forum, nameof(forum)); @@ -324,14 +313,21 @@ public virtual IList GetTopicsByIds(int[] topicIds) return query.OrderBySequence(topicIds).ToList(); } - public virtual IPagedList GetAllTopics(int forumId, int pageIndex, int pageSize) + public virtual IPagedList GetAllTopics(int forumId, int pageIndex, int pageSize, bool showHidden = false) { + var customer = _services.WorkContext.CurrentCustomer; var query = _forumTopicRepository.TableUntracked; + if (forumId != 0) { query = query.Where(x => x.ForumId == forumId); } + if (!showHidden && !customer.IsForumModerator()) + { + query = query.Where(x => x.Published || x.CustomerId == customer.Id); + } + query = query .OrderByDescending(x => x.TopicTypeId) .ThenByDescending(x => x.LastPostTime) @@ -344,11 +340,18 @@ public virtual IPagedList GetAllTopics(int forumId, int pageIndex, i public virtual IList GetActiveTopics(int forumId, int count, bool showHidden = false) { var joinApplied = false; + var customer = _services.WorkContext.CurrentCustomer; + var query = from ft in _forumTopicRepository.Table - where (forumId == 0 || ft.ForumId == forumId) && (ft.LastPostTime.HasValue) + where (forumId == 0 || ft.ForumId == forumId) && ft.LastPostTime.HasValue select ft; + if (!showHidden && !customer.IsForumModerator()) + { + query = query.Where(x => x.Published || x.CustomerId == customer.Id); + } + if (!QuerySettings.IgnoreMultiStore) { var currentStoreId = _services.StoreContext.CurrentStore.Id; @@ -367,7 +370,7 @@ from sm in fg_sm.DefaultIfEmpty() if (!showHidden && !QuerySettings.IgnoreAcl) { - var allowedCustomerRolesIds = _services.WorkContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); + var allowedCustomerRolesIds = customer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); query = from ft in query @@ -535,9 +538,17 @@ public virtual IList GetPostsByIds(int[] postIds) return query.OrderBySequence(postIds).ToList(); } - public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, bool ascSort, int pageIndex, int pageSize) + public virtual IPagedList GetAllPosts( + int forumTopicId, + int customerId, + bool ascSort, + int pageIndex, + int pageSize, + bool showHidden = false) { + var customer = _services.WorkContext.CurrentCustomer; var query = _forumPostRepository.Table; + if (forumTopicId > 0) { query = query.Where(fp => forumTopicId == fp.TopicId); @@ -546,6 +557,11 @@ public virtual IPagedList GetAllPosts(int forumTopicId, int customerI { query = query.Where(fp => customerId == fp.CustomerId); } + if (!showHidden && !customer.IsForumModerator()) + { + query = query.Where(fp => fp.Published || fp.CustomerId == customer.Id); + } + if (ascSort) { query = query.OrderBy(fp => fp.CreatedOnUtc).ThenBy(fp => fp.Id); @@ -578,7 +594,7 @@ public virtual void InsertPost(ForumPost forumPost, bool sendNotifications) var subscriptions = GetAllSubscriptions(0, 0, forumTopic.Id, 0, int.MaxValue); var friendlyTopicPageIndex = CalculateTopicPageIndex( forumPost.TopicId, - _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 10, + _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 20, forumPost.Id) + 1; foreach (var subscription in subscriptions) @@ -835,7 +851,7 @@ public virtual bool IsCustomerAllowedToEditTopic(Customer customer, ForumTopic t return true; } - if (_forumSettings.AllowCustomersToEditPosts) + if (_forumSettings.AllowCustomersToEditPosts && topic.Published) { var ownTopic = customer.Id == topic.CustomerId; return ownTopic; @@ -846,12 +862,7 @@ public virtual bool IsCustomerAllowedToEditTopic(Customer customer, ForumTopic t public virtual bool IsCustomerAllowedToMoveTopic(Customer customer, ForumTopic topic) { - if (topic == null || customer == null) - { - return false; - } - - if (customer.IsGuest()) + if (topic == null || customer == null || customer.IsGuest()) { return false; } @@ -876,7 +887,7 @@ public virtual bool IsCustomerAllowedToDeleteTopic(Customer customer, ForumTopic return true; } - if (_forumSettings.AllowCustomersToDeletePosts) + if (_forumSettings.AllowCustomersToDeletePosts && topic.Published) { var ownTopic = customer.Id == topic.CustomerId; return ownTopic; @@ -912,7 +923,7 @@ public virtual bool IsCustomerAllowedToEditPost(Customer customer, ForumPost pos return true; } - if (_forumSettings.AllowCustomersToEditPosts) + if (_forumSettings.AllowCustomersToEditPosts && post.Published) { var ownPost = customer.Id == post.CustomerId; return ownPost; @@ -933,7 +944,7 @@ public virtual bool IsCustomerAllowedToDeletePost(Customer customer, ForumPost p return true; } - if (_forumSettings.AllowCustomersToDeletePosts) + if (_forumSettings.AllowCustomersToDeletePosts && post.Published) { var ownPost = customer.Id == post.CustomerId; return ownPost; diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index ed991c0c8d..95459269ab 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -56,13 +56,6 @@ public partial interface IForumService /// Forum Forum GetForumById(int forumId); - /// - /// Gets forums by group identifier - /// - /// The forum group identifier - /// Forums - IList GetAllForumsByGroupId(int forumGroupId); - /// /// Deletes a forum /// @@ -105,8 +98,9 @@ public partial interface IForumService /// The forum identifier /// Page index /// Page size + /// Whether to load hidden records /// Forum Topics - IPagedList GetAllTopics(int forumId, int pageIndex, int pageSize); + IPagedList GetAllTopics(int forumId, int pageIndex, int pageSize, bool showHidden = false); /// /// Gets active forum topics @@ -179,8 +173,9 @@ public partial interface IForumService /// Sort order /// Page index /// Page size + /// Whether to load hidden records /// Forum Posts - IPagedList GetAllPosts(int forumTopicId, int customerId, bool ascSort, int pageIndex, int pageSize); + IPagedList GetAllPosts(int forumTopicId, int customerId, bool ascSort, int pageIndex, int pageSize, bool showHidden = false); /// /// Deletes a forum post diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index ed57ca8b54..57e57fac1e 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -103,6 +103,7 @@ private ForumTopicRowModel PrepareForumTopicRowModel( var model = new ForumTopicRowModel { Id = topic.Id, + Published = topic.Published, Subject = topic.Subject, SeName = topic.GetSeName(), FirstPostId = firstPost?.Id ?? topic.FirstPostId, @@ -159,9 +160,7 @@ private ForumGroupModel PrepareForumGroupModel(ForumGroup forumGroup) SeName = forumGroup.GetSeName() }; - var forums = _forumService.GetAllForumsByGroupId(forumGroup.Id); - - var lastPostIds = forums + var lastPostIds = forumGroup.Forums .Where(x => x.LastPostId != 0) .Select(x => x.LastPostId) .Distinct() @@ -169,7 +168,7 @@ private ForumGroupModel PrepareForumGroupModel(ForumGroup forumGroup) var lastPosts = _forumService.GetPostsByIds(lastPostIds).ToDictionary(x => x.Id); - foreach (var forum in forums) + foreach (var forum in forumGroup.Forums) { var forumModel = PrepareForumRowModel(forum, lastPosts); forumModel.LastPost.ShowTopic = true; @@ -192,6 +191,7 @@ private void PrepareLastPostModel(LastPostModel model, ForumPost post) model.AllowViewingProfiles = _customerSettings.AllowViewingProfiles; model.CustomerName = post.Customer.FormatUserName(true); model.IsCustomerGuest = post.Customer.IsGuest(); + model.Published = post.Published; model.PostCreatedOnStr = _forumSettings.RelativeDateTimeFormattingEnabled ? post.CreatedOnUtc.RelativeFormat(true, "f") @@ -236,10 +236,13 @@ private IEnumerable ForumGroupsForumsList() // Add the forum group with Value of 0 so it won't be used as a target forum. forumsList.Add(new SelectListItem { Text = fg.GetLocalized(x => x.Name), Value = "0" }); - var forums = _forumService.GetAllForumsByGroupId(fg.Id); - foreach (var f in forums) + foreach (var f in fg.Forums) { - forumsList.Add(new SelectListItem { Text = string.Format("{0}{1}", separator, f.GetLocalized(x => x.Name)), Value = f.Id.ToString() }); + forumsList.Add(new SelectListItem + { + Text = separator + f.GetLocalized(x => x.Name), + Value = f.Id.ToString() + }); } } @@ -309,6 +312,28 @@ private void SaveLastForumVisit(Customer customer) } } + private bool IsTopicVisible(ForumTopic topic, Customer customer) + { + if (topic == null) + { + return false; + } + if (!topic.Published && topic.CustomerId != customer.Id && !customer.IsForumModerator()) + { + return false; + } + if (!_storeMappingService.Authorize(topic.Forum.ForumGroup)) + { + return false; + } + if (!_aclService.Authorize(topic.Forum.ForumGroup)) + { + return false; + } + + return true; + } + #endregion #region Forum group @@ -364,7 +389,7 @@ public ActionResult Forum(int id, int page = 1) { if (!_forumSettings.ForumsEnabled) { - return HttpNotFound(); + return HttpNotFound(); } var customer = Services.WorkContext.CurrentCustomer; @@ -374,11 +399,22 @@ public ActionResult Forum(int id, int page = 1) return HttpNotFound(); } - var model = new ForumPageModel(); - model.Id = forum.Id; - model.Name = forum.GetLocalized(x => x.Name); - model.SeName = forum.GetSeName(); - model.Description = forum.GetLocalized(x => x.Description); + var pageSize = _forumSettings.TopicsPageSize > 0 ? _forumSettings.TopicsPageSize : 20; + var topics = _forumService.GetAllTopics(forum.Id, page - 1, pageSize); + + var model = new ForumPageModel + { + Id = forum.Id, + Name = forum.GetLocalized(x => x.Name), + SeName = forum.GetSeName(), + Description = forum.GetLocalized(x => x.Description), + TopicPageSize = topics.PageSize, + TopicTotalRecords = topics.TotalCount, + TopicPageIndex = topics.PageIndex, + IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), + ForumFeedsEnabled = _forumSettings.ForumFeedsEnabled, + PostsPageSize = _forumSettings.PostsPageSize + }; // Subscription. if (_forumService.IsCustomerAllowedToSubscribe(customer)) @@ -393,9 +429,6 @@ public ActionResult Forum(int id, int page = 1) } } - var pageSize = _forumSettings.TopicsPageSize > 0 ? _forumSettings.TopicsPageSize : 10; - var topics = _forumService.GetAllTopics(forum.Id, (page - 1), pageSize); - var lastPostIds = topics .Where(x => x.LastPostId != 0) .Select(x => x.LastPostId) @@ -410,13 +443,6 @@ public ActionResult Forum(int id, int page = 1) model.ForumTopics.Add(topicModel); } - model.TopicPageSize = topics.PageSize; - model.TopicTotalRecords = topics.TotalCount; - model.TopicPageIndex = topics.PageIndex; - model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); - model.ForumFeedsEnabled = _forumSettings.ForumFeedsEnabled; - model.PostsPageSize = _forumSettings.PostsPageSize; - CreateForumBreadcrumb(forum: forum); SaveLastForumVisit(customer); @@ -640,112 +666,109 @@ public ActionResult Topic(int id, int page = 1) } var customer = Services.WorkContext.CurrentCustomer; - var forumTopic = _forumService.GetTopicById(id); + var topic = _forumService.GetTopicById(id); - if (forumTopic != null) + if (!IsTopicVisible(topic, customer)) { - if (!_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) - { - return HttpNotFound(); - } + return HttpNotFound(); + } - var posts = _forumService.GetAllPosts(forumTopic.Id, 0, true, page - 1, _forumSettings.PostsPageSize); + var posts = _forumService.GetAllPosts(topic.Id, 0, true, page - 1, _forumSettings.PostsPageSize); - // If no posts area loaded, redirect to the first page. - if (posts.Count == 0 && page > 1) - { - return RedirectToRoute("TopicSlug", new { id = forumTopic.Id, slug = forumTopic.GetSeName() }); - } + // If no posts area loaded, redirect to the first page. + if (posts.Count == 0 && page > 1) + { + return RedirectToRoute("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }); + } - // Update view count. - try - { - if (!customer.Deleted && customer.Active && !customer.IsSystemAccount) - { - forumTopic.Views += 1; - _forumService.UpdateTopic(forumTopic); - } - } - catch (Exception ex) + // Update view count. + try + { + if (!customer.Deleted && customer.Active && !customer.IsSystemAccount) { - Logger.Error(ex); + topic.Views += 1; + _forumService.UpdateTopic(topic); } + } + catch (Exception ex) + { + Logger.Error(ex); + } - var model = new ForumTopicPageModel(); - model.Id = forumTopic.Id; - model.Subject= forumTopic.Subject; - model.SeName = forumTopic.GetSeName(); - model.IsCustomerAllowedToEditTopic = _forumService.IsCustomerAllowedToEditTopic(customer, forumTopic); - model.IsCustomerAllowedToDeleteTopic = _forumService.IsCustomerAllowedToDeleteTopic(customer, forumTopic); - model.IsCustomerAllowedToMoveTopic = _forumService.IsCustomerAllowedToMoveTopic(customer, forumTopic); - model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); + var model = new ForumTopicPageModel + { + Id = topic.Id, + Subject = topic.Subject, + SeName = topic.GetSeName(), + IsCustomerAllowedToEditTopic = _forumService.IsCustomerAllowedToEditTopic(customer, topic), + IsCustomerAllowedToDeleteTopic = _forumService.IsCustomerAllowedToDeleteTopic(customer, topic), + IsCustomerAllowedToMoveTopic = _forumService.IsCustomerAllowedToMoveTopic(customer, topic), + IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), + PostsPageIndex = posts.PageIndex, + PostsPageSize = posts.PageSize, + PostsTotalRecords = posts.TotalCount + }; - if (model.IsCustomerAllowedToSubscribe) - { - model.WatchTopicText = T("Forum.WatchTopic"); + if (model.IsCustomerAllowedToSubscribe) + { + model.WatchTopicText = T("Forum.WatchTopic"); - var forumTopicSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); - if (forumTopicSubscription != null) - { - model.WatchTopicText = T("Forum.UnwatchTopic"); - } + var forumTopicSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, topic.Id, 0, 1).FirstOrDefault(); + if (forumTopicSubscription != null) + { + model.WatchTopicText = T("Forum.UnwatchTopic"); } + } - model.PostsPageIndex = posts.PageIndex; - model.PostsPageSize = posts.PageSize; - model.PostsTotalRecords = posts.TotalCount; - - foreach (var post in posts) + foreach (var post in posts) + { + var forumPostModel = new ForumPostModel { - var forumPostModel = new ForumPostModel - { - Id = post.Id, - ForumTopicId = post.TopicId, - ForumTopicSeName = forumTopic.GetSeName(), - FormattedText = post.FormatPostText(), - IsCurrentCustomerAllowedToEditPost = _forumService.IsCustomerAllowedToEditPost(customer, post), - IsCurrentCustomerAllowedToDeletePost = _forumService.IsCustomerAllowedToDeletePost(customer, post), - CustomerId = post.CustomerId, - AllowViewingProfiles = _customerSettings.AllowViewingProfiles, - CustomerName = post.Customer.FormatUserName(_customerSettings, T, false), - IsCustomerForumModerator = post.Customer.IsForumModerator(), - IsCustomerGuest= post.Customer.IsGuest(), - ShowCustomersPostCount = _forumSettings.ShowCustomersPostCount, - ForumPostCount = post.Customer.GetAttribute(SystemCustomerAttributeNames.ForumPostCount), - ShowCustomersJoinDate = _customerSettings.ShowCustomersJoinDate, - CustomerJoinDate = post.Customer.CreatedOnUtc, - AllowPrivateMessages = _forumSettings.AllowPrivateMessages, - SignaturesEnabled = _forumSettings.SignaturesEnabled, - FormattedSignature = post.Customer.GetAttribute(SystemCustomerAttributeNames.Signature).FormatForumSignatureText(), - }; - - forumPostModel.PostCreatedOnStr = _forumSettings.RelativeDateTimeFormattingEnabled - ? post.CreatedOnUtc.RelativeFormat(true, "f") - : _dateTimeHelper.ConvertToUserTime(post.CreatedOnUtc, DateTimeKind.Utc).ToString("f"); + Id = post.Id, + Published = post.Published, + ForumTopicId = post.TopicId, + ForumTopicSeName = topic.GetSeName(), + FormattedText = post.FormatPostText(), + IsCurrentCustomerAllowedToEditPost = _forumService.IsCustomerAllowedToEditPost(customer, post), + IsCurrentCustomerAllowedToDeletePost = _forumService.IsCustomerAllowedToDeletePost(customer, post), + CustomerId = post.CustomerId, + AllowViewingProfiles = _customerSettings.AllowViewingProfiles, + CustomerName = post.Customer.FormatUserName(_customerSettings, T, false), + IsCustomerForumModerator = post.Customer.IsForumModerator(), + IsCustomerGuest= post.Customer.IsGuest(), + ShowCustomersPostCount = _forumSettings.ShowCustomersPostCount, + ForumPostCount = post.Customer.GetAttribute(SystemCustomerAttributeNames.ForumPostCount), + ShowCustomersJoinDate = _customerSettings.ShowCustomersJoinDate, + CustomerJoinDate = post.Customer.CreatedOnUtc, + AllowPrivateMessages = _forumSettings.AllowPrivateMessages, + SignaturesEnabled = _forumSettings.SignaturesEnabled, + FormattedSignature = post.Customer.GetAttribute(SystemCustomerAttributeNames.Signature).FormatForumSignatureText(), + }; - forumPostModel.Avatar = post.Customer.ToAvatarModel(_genericAttributeService, _pictureService, _customerSettings, _mediaSettings, Url, forumPostModel.CustomerName, true); + forumPostModel.PostCreatedOnStr = _forumSettings.RelativeDateTimeFormattingEnabled + ? post.CreatedOnUtc.RelativeFormat(true, "f") + : _dateTimeHelper.ConvertToUserTime(post.CreatedOnUtc, DateTimeKind.Utc).ToString("f"); - // Location. - forumPostModel.ShowCustomersLocation = _customerSettings.ShowCustomersLocation; - if (_customerSettings.ShowCustomersLocation) - { - var countryId = post.Customer.GetAttribute(SystemCustomerAttributeNames.CountryId); - var country = _countryService.GetCountryById(countryId); - forumPostModel.CustomerLocation = country != null ? country.GetLocalized(x => x.Name) : string.Empty; - } + forumPostModel.Avatar = post.Customer.ToAvatarModel(_genericAttributeService, _pictureService, _customerSettings, _mediaSettings, Url, forumPostModel.CustomerName, true); - // Page number is needed for creating post link in _ForumPost partial view. - forumPostModel.CurrentTopicPage = page; - model.ForumPostModels.Add(forumPostModel); + // Location. + forumPostModel.ShowCustomersLocation = _customerSettings.ShowCustomersLocation; + if (_customerSettings.ShowCustomersLocation) + { + var countryId = post.Customer.GetAttribute(SystemCustomerAttributeNames.CountryId); + var country = _countryService.GetCountryById(countryId); + forumPostModel.CustomerLocation = country != null ? country.GetLocalized(x => x.Name) : string.Empty; } - CreateForumBreadcrumb(topic: forumTopic); - SaveLastForumVisit(customer); - - return View(model); + // Page number is needed for creating post link in _ForumPost partial view. + forumPostModel.CurrentTopicPage = page; + model.ForumPostModels.Add(forumPostModel); } - return RedirectToRoute("Boards"); + CreateForumBreadcrumb(topic: topic); + SaveLastForumVisit(customer); + + return View(model); } [HttpPost] @@ -754,39 +777,31 @@ public ActionResult TopicWatch(int id) var subscribed = false; var returnText = T("Forum.WatchTopic").Text; var customer = Services.WorkContext.CurrentCustomer; - var forumTopic = _forumService.GetTopicById(id); + var topic = _forumService.GetTopicById(id); - if (forumTopic == null) + if (!IsTopicVisible(topic, customer) || !_forumService.IsCustomerAllowedToSubscribe(customer)) { return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } - if (!_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || - !_aclService.Authorize(forumTopic.Forum.ForumGroup) || - !_forumService.IsCustomerAllowedToSubscribe(customer)) + var subscription = _forumService.GetAllSubscriptions(customer.Id, 0, topic.Id, 0, 1).FirstOrDefault(); + if (subscription == null) { - return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); - } - - var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); - - if (forumSubscription == null) - { - forumSubscription = new ForumSubscription + subscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), CustomerId = customer.Id, - TopicId = forumTopic.Id, + TopicId = topic.Id, CreatedOnUtc = DateTime.UtcNow }; - _forumService.InsertSubscription(forumSubscription); + _forumService.InsertSubscription(subscription); subscribed = true; returnText = T("Forum.UnwatchTopic"); } else { - _forumService.DeleteSubscription(forumSubscription); + _forumService.DeleteSubscription(subscription); subscribed = false; } @@ -797,22 +812,30 @@ public ActionResult TopicMove(int id) { if (!_forumSettings.ForumsEnabled) { - return HttpNotFound(); + return HttpNotFound(); } - var forumTopic = _forumService.GetTopicById(id); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) + var customer = Services.WorkContext.CurrentCustomer; + var topic = _forumService.GetTopicById(id); + + if (!IsTopicVisible(topic, customer)) { - return HttpNotFound(); + return HttpNotFound(); + } + if (!_forumService.IsCustomerAllowedToMoveTopic(customer, topic)) + { + return new HttpUnauthorizedResult(); } - var model = new TopicMoveModel(); + var model = new TopicMoveModel + { + Id = topic.Id, + TopicSeName = topic.GetSeName(), + ForumSelected = topic.ForumId, + }; model.ForumList = ForumGroupsForumsList(); - model.Id = forumTopic.Id; - model.TopicSeName = forumTopic.GetSeName(); - model.ForumSelected = forumTopic.ForumId; - CreateForumBreadcrumb(topic: forumTopic); + CreateForumBreadcrumb(topic: topic); return View(model); } @@ -825,55 +848,59 @@ public ActionResult TopicMove(TopicMoveModel model) return HttpNotFound(); } - var forumTopic = _forumService.GetTopicById(model.Id); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) + var customer = Services.WorkContext.CurrentCustomer; + var topic = _forumService.GetTopicById(model.Id); + + if (!IsTopicVisible(topic, customer)) { return HttpNotFound(); } var newForumId = model.ForumSelected; var forum = _forumService.GetForumById(newForumId); - - if (forum != null && forumTopic.ForumId != newForumId) + if (forum != null && topic.ForumId != newForumId) { - _forumService.MoveTopic(forumTopic.Id, newForumId); + _forumService.MoveTopic(topic.Id, newForumId); } - return RedirectToRoute("TopicSlug", new { id = forumTopic.Id, slug = forumTopic.GetSeName() }); + return RedirectToRoute("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }); } - [GdprConsent] - public ActionResult TopicCreate(int id) + [GdprConsent] + public ActionResult TopicCreate(int id) { if (!_forumSettings.ForumsEnabled) { - return HttpNotFound(); + return HttpNotFound(); } var customer = Services.WorkContext.CurrentCustomer; var forum = _forumService.GetForumById(id); + if (forum == null || !_storeMappingService.Authorize(forum.ForumGroup) || !_aclService.Authorize(forum.ForumGroup)) { - return HttpNotFound(); + return HttpNotFound(); } - if (!_forumService.IsCustomerAllowedToCreateTopic(customer, forum)) { return new HttpUnauthorizedResult(); } - var model = new EditForumTopicModel(); - model.Id = 0; - model.IsEdit = false; - model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; - model.ForumId = forum.Id; - model.ForumName = forum.GetLocalized(x => x.Name); - model.ForumSeName = forum.GetSeName(); - model.ForumEditor = _forumSettings.ForumEditor; - model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer); - model.TopicPriorities = ForumTopicTypesList(); - model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); - model.Subscribed = false; + var model = new EditForumTopicModel + { + Id = 0, + IsEdit = false, + Published = true, + DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage, + ForumId = forum.Id, + ForumName = forum.GetLocalized(x => x.Name), + ForumSeName = forum.GetSeName(), + ForumEditor = _forumSettings.ForumEditor, + IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer), + TopicPriorities = ForumTopicTypesList(), + IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), + Subscribed = false, + }; CreateForumBreadcrumb(forum: forum); @@ -897,7 +924,6 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) { return HttpNotFound(); } - if (!_forumService.IsCustomerAllowedToCreateTopic(customer, forum)) { return new HttpUnauthorizedResult(); @@ -935,7 +961,7 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) topicType = (ForumTopicType)Enum.ToObject(typeof (ForumTopicType), model.TopicTypeId); } - var forumTopic = new ForumTopic + var topic = new ForumTopic { ForumId = forum.Id, CustomerId = customer.Id, @@ -943,24 +969,24 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) Subject = subject, Published = true }; - _forumService.InsertTopic(forumTopic, true); + _forumService.InsertTopic(topic, true); - var forumPost = new ForumPost + var post = new ForumPost { - TopicId = forumTopic.Id, + TopicId = topic.Id, CustomerId = customer.Id, Text = text, IPAddress = ipAddress, Published = true }; - _forumService.InsertPost(forumPost, false); + _forumService.InsertPost(post, false); - forumTopic.NumPosts = 1; - forumTopic.LastPostId = forumPost.Id; - forumTopic.LastPostCustomerId = forumPost.CustomerId; - forumTopic.LastPostTime = forumPost.CreatedOnUtc; + topic.NumPosts = 1; + topic.LastPostId = post.Id; + topic.LastPostCustomerId = post.CustomerId; + topic.LastPostTime = post.CreatedOnUtc; - _forumService.UpdateTopic(forumTopic); + _forumService.UpdateTopic(topic); // Subscription. if (_forumService.IsCustomerAllowedToSubscribe(customer)) @@ -971,7 +997,7 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) { SubscriptionGuid = Guid.NewGuid(), CustomerId = customer.Id, - TopicId = forumTopic.Id, + TopicId = topic.Id, CreatedOnUtc = utcNow }; @@ -979,7 +1005,7 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) } } - return RedirectToRoute("TopicSlug", new {id = forumTopic.Id, slug = forumTopic.GetSeName()}); + return RedirectToRoute("TopicSlug", new {id = topic.Id, slug = topic.GetSeName()}); } catch (Exception ex) { @@ -988,13 +1014,13 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) } // Redisplay form. + model.Id = 0; model.TopicPriorities = ForumTopicTypesList(); model.IsEdit = false; model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; model.ForumId = forum.Id; model.ForumName = forum.GetLocalized(x => x.Name); model.ForumSeName = forum.GetSeName(); - model.Id = 0; model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer); model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); model.ForumEditor = _forumSettings.ForumEditor; @@ -1006,47 +1032,48 @@ public ActionResult TopicEdit(int id) { if (!_forumSettings.ForumsEnabled) { - return HttpNotFound(); + return HttpNotFound(); } var customer = Services.WorkContext.CurrentCustomer; - var forumTopic = _forumService.GetTopicById(id); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) + var topic = _forumService.GetTopicById(id); + + if (IsTopicVisible(topic, customer)) { return HttpNotFound(); } - - if (!_forumService.IsCustomerAllowedToEditTopic(customer, forumTopic)) + if (!_forumService.IsCustomerAllowedToEditTopic(customer, topic)) { return new HttpUnauthorizedResult(); } - var firstPost = forumTopic.GetFirstPost(_forumService); - var model = new EditForumTopicModel(); - - model.IsEdit = true; - model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; - model.TopicPriorities = ForumTopicTypesList(); - model.ForumName = forumTopic.Forum.GetLocalized(x => x.Name); - model.ForumSeName = forumTopic.Forum.GetSeName(); - model.Text = firstPost.Text; - model.Subject = forumTopic.Subject; - model.TopicTypeId = forumTopic.TopicTypeId; - model.Id = forumTopic.Id; - model.ForumId = forumTopic.Forum.Id; - model.ForumEditor = _forumSettings.ForumEditor; - - model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer); - model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); + var firstPost = topic.GetFirstPost(_forumService); + var model = new EditForumTopicModel + { + Id = topic.Id, + IsEdit = true, + Published = true, + DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage, + TopicPriorities = ForumTopicTypesList(), + ForumName = topic.Forum.GetLocalized(x => x.Name), + ForumSeName = topic.Forum.GetSeName(), + Text = firstPost?.Text, + Subject = topic.Subject, + TopicTypeId = topic.TopicTypeId, + ForumId = topic.Forum.Id, + ForumEditor = _forumSettings.ForumEditor, + IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer), + IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer) + }; // Subscription. if (model.IsCustomerAllowedToSubscribe) { - var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, topic.Id, 0, 1).FirstOrDefault(); model.Subscribed = forumSubscription != null; } - CreateForumBreadcrumb(forum: forumTopic.Forum, topic: forumTopic); + CreateForumBreadcrumb(forum: topic.Forum, topic: topic); return View(model); } @@ -1063,11 +1090,11 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) var customer = Services.WorkContext.CurrentCustomer; var forumTopic = _forumService.GetTopicById(model.Id); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) + + if (IsTopicVisible(forumTopic, customer)) { return HttpNotFound(); } - if (!_forumService.IsCustomerAllowedToEditTopic(customer, forumTopic)) { return new HttpUnauthorizedResult(); @@ -1171,6 +1198,7 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) // Redisplay form. model.TopicPriorities = ForumTopicTypesList(); model.IsEdit = true; + model.Published = forumTopic.Published; model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; model.ForumName = forumTopic.Forum.GetLocalized(x => x.Name); model.ForumSeName = forumTopic.Forum.GetSeName(); @@ -1190,19 +1218,20 @@ public ActionResult TopicDelete(int id) return HttpNotFound(); } - var forumTopic = _forumService.GetTopicById(id); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) + var customer = Services.WorkContext.CurrentCustomer; + var topic = _forumService.GetTopicById(id); + + if (IsTopicVisible(topic, customer)) { return HttpNotFound(); } - - if (!_forumService.IsCustomerAllowedToDeleteTopic(Services.WorkContext.CurrentCustomer, forumTopic)) + if (!_forumService.IsCustomerAllowedToDeleteTopic(customer, topic)) { return new HttpUnauthorizedResult(); } - var forum = _forumService.GetForumById(forumTopic.ForumId); - _forumService.DeleteTopic(forumTopic); + var forum = _forumService.GetForumById(topic.ForumId); + _forumService.DeleteTopic(topic); if (forum != null) { @@ -1225,13 +1254,13 @@ public ActionResult PostCreate(int id, int? quote) } var customer = Services.WorkContext.CurrentCustomer; - var forumTopic = _forumService.GetTopicById(id); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) + var topic = _forumService.GetTopicById(id); + + if (topic == null || !_storeMappingService.Authorize(topic.Forum.ForumGroup) || !_aclService.Authorize(topic.Forum.ForumGroup)) { return HttpNotFound(); } - - if (!_forumService.IsCustomerAllowedToCreatePost(customer, forumTopic)) + if (!_forumService.IsCustomerAllowedToCreatePost(customer, topic)) { return new HttpUnauthorizedResult(); } @@ -1239,21 +1268,22 @@ public ActionResult PostCreate(int id, int? quote) var model = new EditForumPostModel { Id = 0, - ForumTopicId = forumTopic.Id, + ForumTopicId = topic.Id, IsEdit = false, + Published = true, DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage, ForumEditor = _forumSettings.ForumEditor, - ForumName = forumTopic.Forum.GetLocalized(x => x.Name), - ForumTopicSubject = forumTopic.Subject, - ForumTopicSeName = forumTopic.GetSeName(), + ForumName = topic.Forum.GetLocalized(x => x.Name), + ForumTopicSubject = topic.Subject, + ForumTopicSeName = topic.GetSeName(), IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), - Subscribed = false, + Subscribed = false }; // Subscription. if (model.IsCustomerAllowedToSubscribe) { - var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, topic.Id, 0, 1).FirstOrDefault(); model.Subscribed = forumSubscription != null; } @@ -1262,7 +1292,7 @@ public ActionResult PostCreate(int id, int? quote) if (quote.HasValue) { var quotePost = _forumService.GetPostById(quote.Value); - if (quotePost != null && quotePost.TopicId == forumTopic.Id) + if (quotePost != null && quotePost.TopicId == topic.Id) { var quotePostText = quotePost.Text; @@ -1279,7 +1309,7 @@ public ActionResult PostCreate(int id, int? quote) } } - CreateForumBreadcrumb(forum: forumTopic.Forum, topic: forumTopic); + CreateForumBreadcrumb(forum: topic.Forum, topic: topic); return View(model); } @@ -1295,13 +1325,13 @@ public ActionResult PostCreate(EditForumPostModel model, bool captchaValid) } var customer = Services.WorkContext.CurrentCustomer; - var forumTopic = _forumService.GetTopicById(model.ForumTopicId); - if (forumTopic == null || !_storeMappingService.Authorize(forumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumTopic.Forum.ForumGroup)) + var topic = _forumService.GetTopicById(model.ForumTopicId); + + if (topic == null || !_storeMappingService.Authorize(topic.Forum.ForumGroup) || !_aclService.Authorize(topic.Forum.ForumGroup)) { return HttpNotFound(); } - - if (!_forumService.IsCustomerAllowedToCreatePost(customer, forumTopic)) + if (!_forumService.IsCustomerAllowedToCreatePost(customer, topic)) { return new HttpUnauthorizedResult(); } @@ -1327,7 +1357,7 @@ public ActionResult PostCreate(EditForumPostModel model, bool captchaValid) var forumPost = new ForumPost { - TopicId = forumTopic.Id, + TopicId = topic.Id, CustomerId = customer.Id, Text = text, IPAddress = ipAddress, @@ -1364,8 +1394,8 @@ public ActionResult PostCreate(EditForumPostModel model, bool captchaValid) } } - var pageSize = _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 10; - var pageIndex = (_forumService.CalculateTopicPageIndex(forumPost.TopicId, pageSize, forumPost.Id) + 1); + var pageSize = _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 20; + var pageIndex = _forumService.CalculateTopicPageIndex(forumPost.TopicId, pageSize, forumPost.Id) + 1; var url = string.Empty; if (pageIndex > 1) @@ -1389,10 +1419,10 @@ public ActionResult PostCreate(EditForumPostModel model, bool captchaValid) model.Id = 0; model.IsEdit = false; model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; - model.ForumName = forumTopic.Forum.GetLocalized(x => x.Name); - model.ForumTopicId = forumTopic.Id; - model.ForumTopicSubject = forumTopic.Subject; - model.ForumTopicSeName = forumTopic.GetSeName(); + model.ForumName = topic.Forum.GetLocalized(x => x.Name); + model.ForumTopicId = topic.Id; + model.ForumTopicSubject = topic.Subject; + model.ForumTopicSeName = topic.GetSeName(); model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); model.ForumEditor = _forumSettings.ForumEditor; @@ -1407,41 +1437,41 @@ public ActionResult PostEdit(int id) } var customer = Services.WorkContext.CurrentCustomer; - var forumPost = _forumService.GetPostById(id); + var post = _forumService.GetPostById(id); - if (forumPost == null || !_storeMappingService.Authorize(forumPost.ForumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumPost.ForumTopic.Forum.ForumGroup)) + if (post == null || !_storeMappingService.Authorize(post.ForumTopic.Forum.ForumGroup) || !_aclService.Authorize(post.ForumTopic.Forum.ForumGroup)) { return HttpNotFound(); } - - if (!_forumService.IsCustomerAllowedToEditPost(customer, forumPost)) + if (!_forumService.IsCustomerAllowedToEditPost(customer, post)) { return new HttpUnauthorizedResult(); } var model = new EditForumPostModel { - Id = forumPost.Id, + Id = post.Id, IsEdit = true, - ForumTopicId = forumPost.ForumTopic.Id, + Published = true, + ForumTopicId = post.ForumTopic.Id, DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage, ForumEditor = _forumSettings.ForumEditor, - ForumName = forumPost.ForumTopic.Forum.GetLocalized(x => x.Name), - ForumTopicSubject = forumPost.ForumTopic.Subject, - ForumTopicSeName = forumPost.ForumTopic.GetSeName(), + ForumName = post.ForumTopic.Forum.GetLocalized(x => x.Name), + ForumTopicSubject = post.ForumTopic.Subject, + ForumTopicSeName = post.ForumTopic.GetSeName(), IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), Subscribed = false, - Text = forumPost.Text, + Text = post.Text }; // Subscription. if (model.IsCustomerAllowedToSubscribe) { - var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumPost.ForumTopic.Id, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, post.ForumTopic.Id, 0, 1).FirstOrDefault(); model.Subscribed = forumSubscription != null; } - CreateForumBreadcrumb(forum: forumPost.ForumTopic.Forum, topic: forumPost.ForumTopic); + CreateForumBreadcrumb(forum: post.ForumTopic.Forum, topic: post.ForumTopic); return View(model); } @@ -1457,13 +1487,14 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) } var customer = Services.WorkContext.CurrentCustomer; - var forumPost = _forumService.GetPostById(model.Id); - if (forumPost == null || !_storeMappingService.Authorize(forumPost.ForumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumPost.ForumTopic.Forum.ForumGroup)) + var post = _forumService.GetPostById(model.Id); + + if (post == null || !_storeMappingService.Authorize(post.ForumTopic.Forum.ForumGroup) || !_aclService.Authorize(post.ForumTopic.Forum.ForumGroup)) { return HttpNotFound(); } - if (!_forumService.IsCustomerAllowedToEditPost(customer, forumPost)) + if (!_forumService.IsCustomerAllowedToEditPost(customer, post)) { return new HttpUnauthorizedResult(); } @@ -1485,14 +1516,14 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) text = text.Substring(0, maxPostLength); } - forumPost.Text = text; + post.Text = text; - _forumService.UpdatePost(forumPost); + _forumService.UpdatePost(post); // Subscription. if (_forumService.IsCustomerAllowedToSubscribe(customer)) { - var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumPost.TopicId, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, post.TopicId, 0, 1).FirstOrDefault(); if (model.Subscribed) { if (forumSubscription == null) @@ -1501,7 +1532,7 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) { SubscriptionGuid = Guid.NewGuid(), CustomerId = customer.Id, - TopicId = forumPost.TopicId, + TopicId = post.TopicId, CreatedOnUtc = utcNow }; @@ -1517,20 +1548,20 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) } } - var pageSize = _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 10; - var pageIndex = (_forumService.CalculateTopicPageIndex(forumPost.TopicId, pageSize, forumPost.Id) + 1); + var pageSize = _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 20; + var pageIndex = (_forumService.CalculateTopicPageIndex(post.TopicId, pageSize, post.Id) + 1); var url = string.Empty; if (pageIndex > 1) { - url = Url.RouteUrl("TopicSlug", new { id = forumPost.TopicId, slug = forumPost.ForumTopic.GetSeName(), page = pageIndex }); + url = Url.RouteUrl("TopicSlug", new { id = post.TopicId, slug = post.ForumTopic.GetSeName(), page = pageIndex }); } else { - url = Url.RouteUrl("TopicSlug", new { id = forumPost.TopicId, slug = forumPost.ForumTopic.GetSeName() }); + url = Url.RouteUrl("TopicSlug", new { id = post.TopicId, slug = post.ForumTopic.GetSeName() }); } - return Redirect(string.Format("{0}#{1}", url, forumPost.Id)); + return Redirect(string.Format("{0}#{1}", url, post.Id)); } catch (Exception ex) { @@ -1540,12 +1571,13 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) // Redisplay form. model.IsEdit = true; + model.Published = post.Published; model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; - model.ForumName = forumPost.ForumTopic.Forum.GetLocalized(x => x.Name); - model.ForumTopicId = forumPost.ForumTopic.Id; - model.ForumTopicSubject = forumPost.ForumTopic.Subject; - model.ForumTopicSeName = forumPost.ForumTopic.GetSeName(); - model.Id = forumPost.Id; + model.ForumName = post.ForumTopic.Forum.GetLocalized(x => x.Name); + model.ForumTopicId = post.ForumTopic.Id; + model.ForumTopicSubject = post.ForumTopic.Subject; + model.ForumTopicSeName = post.ForumTopic.GetSeName(); + model.Id = post.Id; model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); model.ForumEditor = _forumSettings.ForumEditor; @@ -1559,25 +1591,26 @@ public ActionResult PostDelete(int id) return HttpNotFound(); } - var forumPost = _forumService.GetPostById(id); - if (forumPost == null || !_storeMappingService.Authorize(forumPost.ForumTopic.Forum.ForumGroup) || !_aclService.Authorize(forumPost.ForumTopic.Forum.ForumGroup)) + var post = _forumService.GetPostById(id); + + if (post == null || !_storeMappingService.Authorize(post.ForumTopic.Forum.ForumGroup) || !_aclService.Authorize(post.ForumTopic.Forum.ForumGroup)) { return HttpNotFound(); } - if (!_forumService.IsCustomerAllowedToDeletePost(Services.WorkContext.CurrentCustomer, forumPost)) + if (!_forumService.IsCustomerAllowedToDeletePost(Services.WorkContext.CurrentCustomer, post)) { return new HttpUnauthorizedResult(); } - var forumTopic = forumPost.ForumTopic; + var forumTopic = post.ForumTopic; var forumId = forumTopic.Forum.Id; var forumSlug = forumTopic.Forum.GetSeName(); - _forumService.DeletePost(forumPost); + _forumService.DeletePost(post); // Get topic one more time because it can be deleted (first or only post deleted). - forumTopic = _forumService.GetTopicById(forumPost.TopicId); + forumTopic = _forumService.GetTopicById(post.TopicId); if (forumTopic == null) { return RedirectToRoute("ForumSlug", new { id = forumId, slug = forumSlug }); diff --git a/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs index 1842af54ec..4e19404cf4 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs @@ -12,6 +12,7 @@ public partial class EditForumPostModel : EntityModelBase { public int ForumTopicId { get; set; } public bool IsEdit { get; set; } + public bool Published { get; set; } public bool DisplayCaptcha { get; set; } [AllowHtml] diff --git a/src/Presentation/SmartStore.Web/Models/Boards/EditForumTopicModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/EditForumTopicModel.cs index d0ba916f22..80b2e7ec7d 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/EditForumTopicModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/EditForumTopicModel.cs @@ -18,6 +18,7 @@ public EditForumTopicModel() public bool IsEdit { get; set; } public bool DisplayCaptcha { get; set; } + public bool Published { get; set; } public int ForumId { get; set; } public LocalizedValue ForumName { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Boards/ForumPostModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/ForumPostModel.cs index 1449c88c4a..b7c7806ab8 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/ForumPostModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/ForumPostModel.cs @@ -21,6 +21,7 @@ public partial class ForumPostModel : EntityModelBase public bool IsCustomerGuest { get; set; } public string PostCreatedOnStr { get; set; } + public bool Published { get; set; } public bool ShowCustomersPostCount { get; set; } public int ForumPostCount { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Boards/ForumTopicRowModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/ForumTopicRowModel.cs index 2464c9c9e7..527fbaf835 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/ForumTopicRowModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/ForumTopicRowModel.cs @@ -15,6 +15,7 @@ public ForumTopicRowModel() public string SeName { get; set; } public int FirstPostId { get; set; } public int LastPostId { get; set; } + public bool Published { get; set; } public ForumTopicType ForumTopicType { get; set; } public int NumPosts { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Boards/LastPostModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/LastPostModel.cs index f68fb56944..ee310c0f7e 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/LastPostModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/LastPostModel.cs @@ -14,7 +14,8 @@ public partial class LastPostModel : EntityModelBase public bool IsCustomerGuest { get; set; } public string PostCreatedOnStr { get; set; } - + public bool Published { get; set; } + public bool ShowTopic { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.cshtml index 477b4b85a7..be35d61a84 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.cshtml @@ -1,5 +1,5 @@ -@model TopicMoveModel -@using SmartStore.Web.Models.Boards; +@using SmartStore.Web.Models.Boards; +@model TopicMoveModel @{ Layout = "_Layout"; Html.AddTitleParts(T("Forum.PageTitle.MoveTopic").Text); @@ -23,7 +23,7 @@ -
    From dd6c3de5b3a209dd4df98b9baffa7a99d26abff0 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 26 Sep 2018 03:22:55 +0200 Subject: [PATCH 134/932] More work on Storytelling feature --- .../Controllers/ContollerExtensions.cs | 3 +-- .../UI/Blocks/BlockHandlerBase.cs | 10 ++++++---- .../UI/Blocks/BlockMetadata.cs | 2 +- .../UI/Blocks/IBlockContainer.cs | 4 ++-- .../UI/Blocks/IBlockHandler.cs | 5 ++--- .../Administration/Controllers/PluginController.cs | 1 + .../Views/CheckoutAttribute/ValueCreatePopup.cshtml | 2 ++ .../Views/CheckoutAttribute/ValueEditPopup.cshtml | 2 ++ src/Presentation/SmartStore.Web/Web.config | 2 +- 9 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/ContollerExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/ContollerExtensions.cs index c6171c46a5..94e9ff679c 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/ContollerExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/ContollerExtensions.cs @@ -75,8 +75,7 @@ public static string RenderPartialViewToString(this ControllerBase controller, s { controller.ViewData.AddRange(CommonHelper.ObjectToDictionary(additionalViewData)); - var vdd = additionalViewData as ViewDataDictionary; - if (vdd != null) + if (additionalViewData is ViewDataDictionary vdd) { controller.ViewData.TemplateInfo.HtmlFieldPrefix = vdd.TemplateInfo.HtmlFieldPrefix; } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs index 02b7d36ab7..93c100e955 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs @@ -62,12 +62,12 @@ public virtual void Save(T block, IBlockEntity entity) entity.Model = JsonConvert.SerializeObject(block, Formatting.None, settings); } - public void Render(IBlockContainer element, string[] templates, HtmlHelper htmlHelper) + public void Render(IBlockContainer element, string[] templates, HtmlHelper htmlHelper) { RenderCore(element, templates, htmlHelper, htmlHelper.ViewContext.Writer); } - public IHtmlString ToHtmlString(IBlockContainer element, string[] templates, HtmlHelper htmlHelper) + public IHtmlString ToHtmlString(IBlockContainer element, string[] templates, HtmlHelper htmlHelper) { using (var writer = new StringWriter(CultureInfo.CurrentCulture)) { @@ -76,7 +76,7 @@ public IHtmlString ToHtmlString(IBlockContainer element, string[] templates, } } - protected virtual void RenderCore(IBlockContainer element, string[] templates, HtmlHelper htmlHelper, TextWriter textWriter) + protected virtual void RenderCore(IBlockContainer element, string[] templates, HtmlHelper htmlHelper, TextWriter textWriter) { Guard.NotNull(element, nameof(element)); Guard.NotNull(templates, nameof(templates)); @@ -89,6 +89,8 @@ protected virtual void RenderCore(IBlockContainer element, string[] templates throw new InvalidOperationException("The return value of the 'GetRoute()' method cannot be NULL."); } + routeInfo.RouteValues["model"] = element.Block; + var originalWriter = htmlHelper.ViewContext.Writer; htmlHelper.ViewContext.Writer = textWriter; @@ -98,7 +100,7 @@ protected virtual void RenderCore(IBlockContainer element, string[] templates } } - protected abstract RouteInfo GetRoute(IBlockContainer element, string template); + protected abstract RouteInfo GetRoute(IBlockContainer element, string template); /// /// Add locales for localizable entities diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockMetadata.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockMetadata.cs index ca5b8a1a7b..952d7690be 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockMetadata.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockMetadata.cs @@ -19,7 +19,7 @@ public BlockAttribute(string systemName) public bool IsInternal { get; set; } } - public class IBlockMetadata + public interface IBlockMetadata { string SystemName { get; } string FriendlyName { get; } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockContainer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockContainer.cs index 71c18aa7b4..1cd523a213 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockContainer.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockContainer.cs @@ -2,10 +2,10 @@ namespace SmartStore.Web.Framework.UI.Blocks { - public interface IBlockContainer where T : IBlock + public interface IBlockContainer { string BlockType { get; } - T Block { get; } + IBlock Block { get; } IBlockMetadata Metadata { get; } //IBlockHandler Handler { get; } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs index 3d982722b5..5032cb037e 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs @@ -6,6 +6,8 @@ namespace SmartStore.Web.Framework.UI.Blocks { public interface IBlockHandler { + void Render(IBlockContainer element, string[] templates, HtmlHelper htmlHeper); + IHtmlString ToHtmlString(IBlockContainer element, string[] templates, HtmlHelper htmlHelper); } public interface IBlockHandler : IBlockHandler where T : IBlock @@ -13,8 +15,5 @@ public interface IBlockHandler : IBlockHandler where T : IBlock T Create(IBlockEntity entity); T Load(IBlockEntity entity, bool editMode); void Save(T block, IBlockEntity entity); - - void Render(IBlockContainer element, string[] templates, HtmlHelper htmlHeper); - IHtmlString ToHtmlString(IBlockContainer element, string[] templates, HtmlHelper htmlHelper); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs index 5627bacdc2..54795e1195 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs @@ -589,6 +589,7 @@ public ActionResult EditProviderPopup(string btnId, ProviderModel model) ViewBag.RefreshPage = true; ViewBag.btnId = btnId; + return View(model); } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/ValueCreatePopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/ValueCreatePopup.cshtml index e8a09b0d6c..12af5a9b10 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/ValueCreatePopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/ValueCreatePopup.cshtml @@ -1,8 +1,10 @@ @model CheckoutAttributeValueModel + @{ Layout = "_AdminPopupLayout"; ViewBag.Title = T("Admin.Catalog.Attributes.CheckoutAttributes.Values.AddNew").Text; } + @using (Html.BeginForm()) {
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/ValueEditPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/ValueEditPopup.cshtml index feed2b2e16..1095ec1afe 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/ValueEditPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/ValueEditPopup.cshtml @@ -1,8 +1,10 @@ @model CheckoutAttributeValueModel + @{ Layout = "_AdminPopupLayout"; ViewBag.Title = T("Admin.Catalog.Attributes.CheckoutAttributes.Values.EditValueDetails").Text; } + @using (Html.BeginForm()) {
    diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index 2a6a78e3f7..bbc22f262e 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -79,7 +79,7 @@ - + From aedfad773e34cc20636bdbc3b0a982875b4c0380 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 26 Sep 2018 12:39:41 +0200 Subject: [PATCH 135/932] Please execute "update-database -TargetMigration NewsletterSubscriptionLanguage" --- .../SmartStore.Core/Domain/Forums/Forum.cs | 3 ++ .../Domain/Forums/ForumGroup.cs | 12 +++-- .../Domain/Forums/ForumPost.cs | 4 +- .../Domain/Forums/ForumTopic.cs | 8 ++++ .../201809251233170_ForumGroupAcl.cs | 22 --------- ...201809261026134_ForumGroupAcl.Designer.cs} | 2 +- .../201809261026134_ForumGroupAcl.cs | 48 +++++++++++++++++++ ...esx => 201809261026134_ForumGroupAcl.resx} | 2 +- .../SmartStore.Data/SmartStore.Data.csproj | 10 ++-- 9 files changed, 77 insertions(+), 34 deletions(-) delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.cs rename src/Libraries/SmartStore.Data/Migrations/{201809251233170_ForumGroupAcl.Designer.cs => 201809261026134_ForumGroupAcl.Designer.cs} (92%) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.cs rename src/Libraries/SmartStore.Data/Migrations/{201809251233170_ForumGroupAcl.resx => 201809261026134_ForumGroupAcl.resx} (72%) diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs b/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs index 0b268be1f1..b4cedb8d01 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Seo; @@ -12,6 +13,7 @@ public partial class Forum : BaseEntity, IAuditable, ILocalizedEntity, ISlugSupp /// /// Gets or sets the forum group identifier /// + [Index("IX_ForumGroupId_DisplayOrder", Order = 0)] public int ForumGroupId { get; set; } /// @@ -57,6 +59,7 @@ public partial class Forum : BaseEntity, IAuditable, ILocalizedEntity, ISlugSupp /// /// Gets or sets the display order /// + [Index("IX_ForumGroupId_DisplayOrder", Order = 1)] public int DisplayOrder { get; set; } /// diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs index 7974973f45..ae83f4f8f9 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Seo; @@ -27,6 +28,7 @@ public partial class ForumGroup : BaseEntity, IAuditable, IStoreMappingSupported /// /// Gets or sets the display order /// + [Index] public int DisplayOrder { get; set; } /// @@ -39,14 +41,16 @@ public partial class ForumGroup : BaseEntity, IAuditable, IStoreMappingSupported /// public DateTime UpdatedOnUtc { get; set; } - /// - /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores - /// - public bool LimitedToStores { get; set; } + /// + /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores + /// + [Index] + public bool LimitedToStores { get; set; } /// /// Gets or sets a value indicating whether the entity is subject to ACL /// + [Index] public bool SubjectToAcl { get; set; } /// diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs index 2cfecbfe97..292615a8d3 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; using SmartStore.Core.Domain.Customers; namespace SmartStore.Core.Domain.Forums @@ -31,6 +32,7 @@ public partial class ForumPost : BaseEntity, IAuditable /// /// Gets or sets the date and time of instance creation /// + [Index] public DateTime CreatedOnUtc { get; set; } /// @@ -41,6 +43,7 @@ public partial class ForumPost : BaseEntity, IAuditable /// /// Gets or sets a value indicating whether the entity is published /// + [Index] public bool Published { get; set; } /// @@ -52,6 +55,5 @@ public partial class ForumPost : BaseEntity, IAuditable /// Gets the customer /// public virtual Customer Customer { get; set; } - } } diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs index b77c216592..c31e029a50 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; using SmartStore.Core.Domain.Customers; namespace SmartStore.Core.Domain.Forums @@ -11,6 +12,7 @@ public partial class ForumTopic : BaseEntity, IAuditable /// /// Gets or sets the forum identifier /// + [Index("IX_ForumId_Published", Order = 0)] public int ForumId { get; set; } /// @@ -21,16 +23,19 @@ public partial class ForumTopic : BaseEntity, IAuditable /// /// Gets or sets the topic type identifier /// + [Index("IX_TopicTypeId_LastPostTime", Order = 0)] public int TopicTypeId { get; set; } /// /// Gets or sets the subject /// + [Index] public string Subject { get; set; } /// /// Gets or sets the number of posts /// + [Index] public int NumPosts { get; set; } /// @@ -57,11 +62,13 @@ public partial class ForumTopic : BaseEntity, IAuditable /// /// Gets or sets the last post date and time /// + [Index("IX_TopicTypeId_LastPostTime", Order = 1)] public DateTime? LastPostTime { get; set; } /// /// Gets or sets the date and time of instance creation /// + [Index] public DateTime CreatedOnUtc { get; set; } /// @@ -72,6 +79,7 @@ public partial class ForumTopic : BaseEntity, IAuditable /// /// Gets or sets a value indicating whether the entity is published /// + [Index("IX_ForumId_Published", Order = 1)] public bool Published { get; set; } /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.cs b/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.cs deleted file mode 100644 index 67781b9ef6..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace SmartStore.Data.Migrations -{ - using System; - using System.Data.Entity.Migrations; - - public partial class ForumGroupAcl : DbMigration - { - public override void Up() - { - AddColumn("dbo.Forums_Post", "Published", c => c.Boolean(nullable: false, defaultValue: true)); - AddColumn("dbo.Forums_Topic", "Published", c => c.Boolean(nullable: false, defaultValue: true)); - AddColumn("dbo.Forums_Group", "SubjectToAcl", c => c.Boolean(nullable: false)); - } - - public override void Down() - { - DropColumn("dbo.Forums_Group", "SubjectToAcl"); - DropColumn("dbo.Forums_Topic", "Published"); - DropColumn("dbo.Forums_Post", "Published"); - } - } -} diff --git a/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.Designer.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.Designer.cs rename to src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.Designer.cs index 9c8853796f..55b7fcd313 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.Designer.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.Designer.cs @@ -13,7 +13,7 @@ public sealed partial class ForumGroupAcl : IMigrationMetadata string IMigrationMetadata.Id { - get { return "201809251233170_ForumGroupAcl"; } + get { return "201809261026134_ForumGroupAcl"; } } string IMigrationMetadata.Source diff --git a/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.cs b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.cs new file mode 100644 index 0000000000..c4d74dbe8a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.cs @@ -0,0 +1,48 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class ForumGroupAcl : DbMigration + { + public override void Up() + { + DropIndex("dbo.Forums_Topic", new[] { "ForumId" }); + DropIndex("dbo.Forums_Forum", new[] { "ForumGroupId" }); + AddColumn("dbo.Forums_Post", "Published", c => c.Boolean(nullable: false)); + AddColumn("dbo.Forums_Topic", "Published", c => c.Boolean(nullable: false)); + AddColumn("dbo.Forums_Group", "SubjectToAcl", c => c.Boolean(nullable: false)); + CreateIndex("dbo.Forums_Post", "CreatedOnUtc"); + CreateIndex("dbo.Forums_Post", "Published"); + CreateIndex("dbo.Forums_Topic", new[] { "ForumId", "Published" }); + CreateIndex("dbo.Forums_Topic", new[] { "TopicTypeId", "LastPostTime" }); + CreateIndex("dbo.Forums_Topic", "Subject"); + CreateIndex("dbo.Forums_Topic", "NumPosts"); + CreateIndex("dbo.Forums_Topic", "CreatedOnUtc"); + CreateIndex("dbo.Forums_Forum", new[] { "ForumGroupId", "DisplayOrder" }); + CreateIndex("dbo.Forums_Group", "DisplayOrder"); + CreateIndex("dbo.Forums_Group", "LimitedToStores"); + CreateIndex("dbo.Forums_Group", "SubjectToAcl"); + } + + public override void Down() + { + DropIndex("dbo.Forums_Group", new[] { "SubjectToAcl" }); + DropIndex("dbo.Forums_Group", new[] { "LimitedToStores" }); + DropIndex("dbo.Forums_Group", new[] { "DisplayOrder" }); + DropIndex("dbo.Forums_Forum", new[] { "ForumGroupId", "DisplayOrder" }); + DropIndex("dbo.Forums_Topic", new[] { "CreatedOnUtc" }); + DropIndex("dbo.Forums_Topic", new[] { "NumPosts" }); + DropIndex("dbo.Forums_Topic", new[] { "Subject" }); + DropIndex("dbo.Forums_Topic", new[] { "TopicTypeId", "LastPostTime" }); + DropIndex("dbo.Forums_Topic", new[] { "ForumId", "Published" }); + DropIndex("dbo.Forums_Post", new[] { "Published" }); + DropIndex("dbo.Forums_Post", new[] { "CreatedOnUtc" }); + DropColumn("dbo.Forums_Group", "SubjectToAcl"); + DropColumn("dbo.Forums_Topic", "Published"); + DropColumn("dbo.Forums_Post", "Published"); + CreateIndex("dbo.Forums_Forum", "ForumGroupId"); + CreateIndex("dbo.Forums_Topic", "ForumId"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.resx b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.resx similarity index 72% rename from src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.resx rename to src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.resx index 572cbd0572..07a094ec2a 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201809251233170_ForumGroupAcl.resx +++ b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.resx @@ -118,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 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 dbo diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index 30a53f7b9a..cc9da1dd58 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -623,9 +623,9 @@ 201809171309522_NewsletterSubscriptionLanguage.cs - - - 201809251233170_ForumGroupAcl.cs + + + 201809261026134_ForumGroupAcl.cs @@ -1124,8 +1124,8 @@ 201809171309522_NewsletterSubscriptionLanguage.cs - - 201809251233170_ForumGroupAcl.cs + + 201809261026134_ForumGroupAcl.cs From a2e806384754615db9c19b7055d5af5a76c5fc35 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 26 Sep 2018 16:04:42 +0200 Subject: [PATCH 136/932] More on published property of forum topic and forum post --- .../201809261026134_ForumGroupAcl.cs | 4 +- .../Forums/ForumService.cs | 112 +++----- .../Forums/IForumService.cs | 7 - .../Controllers/BoardsController.cs | 260 ++++++++---------- .../Models/Boards/EditForumPostModel.cs | 4 + .../Models/Boards/EditForumTopicModel.cs | 6 +- .../Models/Boards/TopicMoveModel.cs | 2 + .../Boards/Partials/_CreateUpdatePost.cshtml | 20 +- .../Boards/Partials/_CreateUpdateTopic.cshtml | 33 ++- 9 files changed, 216 insertions(+), 232 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.cs b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.cs index c4d74dbe8a..4abd203c4a 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.cs @@ -9,8 +9,8 @@ public override void Up() { DropIndex("dbo.Forums_Topic", new[] { "ForumId" }); DropIndex("dbo.Forums_Forum", new[] { "ForumGroupId" }); - AddColumn("dbo.Forums_Post", "Published", c => c.Boolean(nullable: false)); - AddColumn("dbo.Forums_Topic", "Published", c => c.Boolean(nullable: false)); + AddColumn("dbo.Forums_Post", "Published", c => c.Boolean(nullable: false, defaultValue: true)); + AddColumn("dbo.Forums_Topic", "Published", c => c.Boolean(nullable: false, defaultValue: true)); AddColumn("dbo.Forums_Group", "SubjectToAcl", c => c.Boolean(nullable: false)); CreateIndex("dbo.Forums_Post", "CreatedOnUtc"); CreateIndex("dbo.Forums_Post", "Published"); diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index f2ae94acba..2e55625acf 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -841,20 +841,18 @@ public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum foru public virtual bool IsCustomerAllowedToEditTopic(Customer customer, ForumTopic topic) { - if (topic == null || customer == null || customer.IsGuest()) + if (customer != null && topic != null) { - return false; - } - - if (customer.IsForumModerator()) - { - return true; - } + if (customer.IsForumModerator()) + { + return true; + } - if (_forumSettings.AllowCustomersToEditPosts && topic.Published) - { - var ownTopic = customer.Id == topic.CustomerId; - return ownTopic; + if (_forumSettings.AllowCustomersToEditPosts && topic.Published) + { + var ownTopic = customer.Id == topic.CustomerId; + return ownTopic; + } } return false; @@ -862,12 +860,7 @@ public virtual bool IsCustomerAllowedToEditTopic(Customer customer, ForumTopic t public virtual bool IsCustomerAllowedToMoveTopic(Customer customer, ForumTopic topic) { - if (topic == null || customer == null || customer.IsGuest()) - { - return false; - } - - if (customer.IsForumModerator()) + if (customer != null && customer.IsForumModerator()) { return true; } @@ -877,20 +870,18 @@ public virtual bool IsCustomerAllowedToMoveTopic(Customer customer, ForumTopic t public virtual bool IsCustomerAllowedToDeleteTopic(Customer customer, ForumTopic topic) { - if (topic == null || customer == null || customer.IsGuest()) + if (topic != null && customer != null) { - return false; - } - - if (customer.IsForumModerator()) - { - return true; - } + if (customer.IsForumModerator()) + { + return true; + } - if (_forumSettings.AllowCustomersToDeletePosts && topic.Published) - { - var ownTopic = customer.Id == topic.CustomerId; - return ownTopic; + if (_forumSettings.AllowCustomersToDeletePosts && topic.Published) + { + var ownTopic = customer.Id == topic.CustomerId; + return ownTopic; + } } return false; @@ -903,7 +894,7 @@ public virtual bool IsCustomerAllowedToCreatePost(Customer customer, ForumTopic return false; } - if (customer.IsGuest() && !_forumSettings.AllowGuestsToCreatePosts) + if (!_forumSettings.AllowGuestsToCreatePosts && customer.IsGuest()) { return false; } @@ -913,20 +904,18 @@ public virtual bool IsCustomerAllowedToCreatePost(Customer customer, ForumTopic public virtual bool IsCustomerAllowedToEditPost(Customer customer, ForumPost post) { - if (post == null || customer == null || customer.IsGuest()) - { - return false; - } - - if (customer.IsForumModerator()) + if (post != null && customer != null) { - return true; - } + if (customer.IsForumModerator()) + { + return true; + } - if (_forumSettings.AllowCustomersToEditPosts && post.Published) - { - var ownPost = customer.Id == post.CustomerId; - return ownPost; + if (_forumSettings.AllowCustomersToEditPosts && post.Published) + { + var ownPost = customer.Id == post.CustomerId; + return ownPost; + } } return false; @@ -934,37 +923,20 @@ public virtual bool IsCustomerAllowedToEditPost(Customer customer, ForumPost pos public virtual bool IsCustomerAllowedToDeletePost(Customer customer, ForumPost post) { - if (post == null || customer == null || customer.IsGuest()) + if (post != null && customer != null) { - return false; - } - - if (customer.IsForumModerator()) - { - return true; - } - - if (_forumSettings.AllowCustomersToDeletePosts && post.Published) - { - var ownPost = customer.Id == post.CustomerId; - return ownPost; - } - - return false; - } + if (customer.IsForumModerator()) + { + return true; + } - public virtual bool IsCustomerAllowedToSetTopicPriority(Customer customer) - { - if (customer == null || customer.IsGuest()) - { - return false; + if (_forumSettings.AllowCustomersToDeletePosts && post.Published) + { + var ownPost = customer.Id == post.CustomerId; + return ownPost; + } } - if (customer.IsForumModerator()) - { - return true; - } - return false; } diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index 95459269ab..361e1720fb 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -348,13 +348,6 @@ IPagedList GetAllSubscriptions(int customerId, int forumId, /// True if allowed, otherwise false bool IsCustomerAllowedToDeletePost(Customer customer, ForumPost post); - /// - /// Check whether customer is allowed to set topic priority - /// - /// Customer - /// True if allowed, otherwise false - bool IsCustomerAllowedToSetTopicPriority(Customer customer); - /// /// Check whether customer is allowed to watch topics /// diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 57e57fac1e..5f4203551f 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -434,7 +434,6 @@ public ActionResult Forum(int id, int page = 1) .Select(x => x.LastPostId) .Distinct() .ToArray(); - var lastPosts = _forumService.GetPostsByIds(lastPostIds).ToDictionary(x => x.Id); foreach (var topic in topics) @@ -822,17 +821,21 @@ public ActionResult TopicMove(int id) { return HttpNotFound(); } - if (!_forumService.IsCustomerAllowedToMoveTopic(customer, topic)) - { - return new HttpUnauthorizedResult(); - } var model = new TopicMoveModel { Id = topic.Id, TopicSeName = topic.GetSeName(), ForumSelected = topic.ForumId, + CustomerId = topic.CustomerId, + IsCustomerAllowedToEdit = _forumService.IsCustomerAllowedToMoveTopic(customer, topic) }; + + if (!model.IsCustomerAllowedToEdit && customer.Id != topic.CustomerId) + { + return new HttpUnauthorizedResult(); + } + model.ForumList = ForumGroupsForumsList(); CreateForumBreadcrumb(topic: topic); @@ -855,6 +858,10 @@ public ActionResult TopicMove(TopicMoveModel model) { return HttpNotFound(); } + if (!_forumService.IsCustomerAllowedToMoveTopic(customer, topic)) + { + return new HttpUnauthorizedResult(); + } var newForumId = model.ForumSelected; var forum = _forumService.GetForumById(newForumId); @@ -891,12 +898,13 @@ public ActionResult TopicCreate(int id) Id = 0, IsEdit = false, Published = true, + SeName = string.Empty, DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage, ForumId = forum.Id, ForumName = forum.GetLocalized(x => x.Name), ForumSeName = forum.GetSeName(), ForumEditor = _forumSettings.ForumEditor, - IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer), + IsModerator = customer.IsForumModerator(), TopicPriorities = ForumTopicTypesList(), IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), Subscribed = false, @@ -938,47 +946,38 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) { try { - var subject = model.Subject; - var maxSubjectLength = _forumSettings.TopicSubjectMaxLength; - if (maxSubjectLength > 0 && subject.Length > maxSubjectLength) + var topic = new ForumTopic { - subject = subject.Substring(0, maxSubjectLength); - } + ForumId = forum.Id, + CustomerId = customer.Id, + Published = true, + TopicTypeId = (int)ForumTopicType.Normal + }; - var text = model.Text; - var maxPostLength = _forumSettings.PostMaxLength; - if (maxPostLength > 0 && text.Length > maxPostLength) + if (customer.IsForumModerator()) { - text = text.Substring(0, maxPostLength); + model.Published = model.Published; + model.TopicTypeId = model.TopicTypeId; } - var topicType = ForumTopicType.Normal; - var utcNow = DateTime.UtcNow; - var ipAddress = Services.WebHelper.GetCurrentIpAddress(); - - if (_forumService.IsCustomerAllowedToSetTopicPriority(customer)) - { - topicType = (ForumTopicType)Enum.ToObject(typeof (ForumTopicType), model.TopicTypeId); - } + topic.Subject = _forumSettings.TopicSubjectMaxLength > 0 && model.Subject.Length > _forumSettings.TopicSubjectMaxLength + ? model.Subject.Substring(0, _forumSettings.TopicSubjectMaxLength) + : model.Subject; - var topic = new ForumTopic - { - ForumId = forum.Id, - CustomerId = customer.Id, - TopicTypeId = (int) topicType, - Subject = subject, - Published = true - }; _forumService.InsertTopic(topic, true); var post = new ForumPost { TopicId = topic.Id, CustomerId = customer.Id, - Text = text, - IPAddress = ipAddress, + IPAddress = Services.WebHelper.GetCurrentIpAddress(), Published = true }; + + post.Text = _forumSettings.PostMaxLength > 0 && model.Text.Length > _forumSettings.PostMaxLength + ? model.Text.Substring(0, _forumSettings.PostMaxLength) + : model.Text; + _forumService.InsertPost(post, false); topic.NumPosts = 1; @@ -998,7 +997,7 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) SubscriptionGuid = Guid.NewGuid(), CustomerId = customer.Id, TopicId = topic.Id, - CreatedOnUtc = utcNow + CreatedOnUtc = DateTime.UtcNow }; _forumService.InsertSubscription(forumSubscription); @@ -1021,7 +1020,7 @@ public ActionResult TopicCreate(EditForumTopicModel model, bool captchaValid) model.ForumId = forum.Id; model.ForumName = forum.GetLocalized(x => x.Name); model.ForumSeName = forum.GetSeName(); - model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer); + model.IsModerator = customer.IsForumModerator(); model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); model.ForumEditor = _forumSettings.ForumEditor; @@ -1038,21 +1037,18 @@ public ActionResult TopicEdit(int id) var customer = Services.WorkContext.CurrentCustomer; var topic = _forumService.GetTopicById(id); - if (IsTopicVisible(topic, customer)) + if (!IsTopicVisible(topic, customer)) { return HttpNotFound(); } - if (!_forumService.IsCustomerAllowedToEditTopic(customer, topic)) - { - return new HttpUnauthorizedResult(); - } var firstPost = topic.GetFirstPost(_forumService); var model = new EditForumTopicModel { Id = topic.Id, IsEdit = true, - Published = true, + Published = topic.Published, + SeName = topic.GetSeName(), DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage, TopicPriorities = ForumTopicTypesList(), ForumName = topic.Forum.GetLocalized(x => x.Name), @@ -1062,10 +1058,17 @@ public ActionResult TopicEdit(int id) TopicTypeId = topic.TopicTypeId, ForumId = topic.Forum.Id, ForumEditor = _forumSettings.ForumEditor, - IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer), - IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer) + CustomerId = topic.CustomerId, + IsModerator = customer.IsForumModerator(), + IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), + IsCustomerAllowedToEdit = _forumService.IsCustomerAllowedToEditTopic(customer, topic) }; + if (!model.IsCustomerAllowedToEdit && customer.Id != topic.CustomerId) + { + return new HttpUnauthorizedResult(); + } + // Subscription. if (model.IsCustomerAllowedToSubscribe) { @@ -1089,13 +1092,13 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) } var customer = Services.WorkContext.CurrentCustomer; - var forumTopic = _forumService.GetTopicById(model.Id); + var topic = _forumService.GetTopicById(model.Id); - if (IsTopicVisible(forumTopic, customer)) + if (!IsTopicVisible(topic, customer)) { return HttpNotFound(); } - if (!_forumService.IsCustomerAllowedToEditTopic(customer, forumTopic)) + if (!_forumService.IsCustomerAllowedToEditTopic(customer, topic)) { return new HttpUnauthorizedResult(); } @@ -1109,35 +1112,23 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) { try { - var subject = model.Subject; - var maxSubjectLength = _forumSettings.TopicSubjectMaxLength; - if (maxSubjectLength > 0 && subject.Length > maxSubjectLength) + if (customer.IsForumModerator()) { - subject = subject.Substring(0, maxSubjectLength); + topic.Published = model.Published; + topic.TopicTypeId = model.TopicTypeId; } - var text = model.Text; - var maxPostLength = _forumSettings.PostMaxLength; - if (maxPostLength > 0 && text.Length > maxPostLength) - { - text = text.Substring(0, maxPostLength); - } - - var topicType = ForumTopicType.Normal; - var ipAddress = Services.WebHelper.GetCurrentIpAddress(); - var utcNow = DateTime.UtcNow; - - if (_forumService.IsCustomerAllowedToSetTopicPriority(customer)) - { - topicType = (ForumTopicType) Enum.ToObject(typeof (ForumTopicType), model.TopicTypeId); - } + topic.Subject = _forumSettings.TopicSubjectMaxLength > 0 && model.Subject.Length > _forumSettings.TopicSubjectMaxLength + ? model.Subject.Substring(0, _forumSettings.TopicSubjectMaxLength) + : model.Subject; - forumTopic.TopicTypeId = (int) topicType; - forumTopic.Subject = subject; + _forumService.UpdateTopic(topic); - _forumService.UpdateTopic(forumTopic); + var text = _forumSettings.PostMaxLength > 0 && model.Text.Length > _forumSettings.PostMaxLength + ? model.Text.Substring(0, _forumSettings.PostMaxLength) + : model.Text; - var firstPost = forumTopic.GetFirstPost(_forumService); + var firstPost = topic.GetFirstPost(_forumService); if (firstPost != null) { firstPost.Text = text; @@ -1147,10 +1138,10 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) { firstPost = new ForumPost { - TopicId = forumTopic.Id, - CustomerId = forumTopic.CustomerId, + TopicId = topic.Id, + CustomerId = topic.CustomerId, Text = text, - IPAddress = ipAddress, + IPAddress = Services.WebHelper.GetCurrentIpAddress(), Published = true }; @@ -1160,7 +1151,7 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) // Subscription. if (_forumService.IsCustomerAllowedToSubscribe(customer)) { - var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, topic.Id, 0, 1).FirstOrDefault(); if (model.Subscribed) { @@ -1170,8 +1161,8 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) { SubscriptionGuid = Guid.NewGuid(), CustomerId = customer.Id, - TopicId = forumTopic.Id, - CreatedOnUtc = utcNow + TopicId = topic.Id, + CreatedOnUtc = DateTime.UtcNow }; _forumService.InsertSubscription(forumSubscription); @@ -1187,7 +1178,7 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) } // Redirect to the topic page with the topic slug. - return RedirectToRoute("TopicSlug", new {id = forumTopic.Id, slug = forumTopic.GetSeName()}); + return RedirectToRoute("TopicSlug", new {id = topic.Id, slug = topic.GetSeName()}); } catch (Exception ex) { @@ -1198,14 +1189,14 @@ public ActionResult TopicEdit(EditForumTopicModel model, bool captchaValid) // Redisplay form. model.TopicPriorities = ForumTopicTypesList(); model.IsEdit = true; - model.Published = forumTopic.Published; + model.Published = topic.Published; model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage; - model.ForumName = forumTopic.Forum.GetLocalized(x => x.Name); - model.ForumSeName = forumTopic.Forum.GetSeName(); - model.ForumId = forumTopic.Forum.Id; + model.ForumName = topic.Forum.GetLocalized(x => x.Name); + model.ForumSeName = topic.Forum.GetSeName(); + model.ForumId = topic.Forum.Id; model.ForumEditor = _forumSettings.ForumEditor; - - model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(customer); + model.CustomerId = customer.Id; + model.IsModerator = customer.IsForumModerator(); model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); return View(model); @@ -1221,7 +1212,7 @@ public ActionResult TopicDelete(int id) var customer = Services.WorkContext.CurrentCustomer; var topic = _forumService.GetTopicById(id); - if (IsTopicVisible(topic, customer)) + if (!IsTopicVisible(topic, customer)) { return HttpNotFound(); } @@ -1276,6 +1267,7 @@ public ActionResult PostCreate(int id, int? quote) ForumName = topic.Forum.GetLocalized(x => x.Name), ForumTopicSubject = topic.Subject, ForumTopicSeName = topic.GetSeName(), + IsModerator = customer.IsForumModerator(), IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), Subscribed = false }; @@ -1345,31 +1337,29 @@ public ActionResult PostCreate(EditForumPostModel model, bool captchaValid) { try { - var text = model.Text; - var maxPostLength = _forumSettings.PostMaxLength; - if (maxPostLength > 0 && text.Length > maxPostLength) - { - text = text.Substring(0, maxPostLength); - } - - var utcNow = DateTime.UtcNow; - var ipAddress = Services.WebHelper.GetCurrentIpAddress(); - - var forumPost = new ForumPost + var post = new ForumPost { TopicId = topic.Id, CustomerId = customer.Id, - Text = text, - IPAddress = ipAddress, + IPAddress = Services.WebHelper.GetCurrentIpAddress(), Published = true }; - _forumService.InsertPost(forumPost, true); + if (customer.IsForumModerator()) + { + post.Published = model.Published; + } + + post.Text = _forumSettings.PostMaxLength > 0 && model.Text.Length > _forumSettings.PostMaxLength + ? model.Text.Substring(0, _forumSettings.PostMaxLength) + : model.Text; + + _forumService.InsertPost(post, true); // Subscription. if (_forumService.IsCustomerAllowedToSubscribe(customer)) { - var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, forumPost.TopicId, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, 0, post.TopicId, 0, 1).FirstOrDefault(); if (model.Subscribed) { if (forumSubscription == null) @@ -1378,8 +1368,8 @@ public ActionResult PostCreate(EditForumPostModel model, bool captchaValid) { SubscriptionGuid = Guid.NewGuid(), CustomerId = customer.Id, - TopicId = forumPost.TopicId, - CreatedOnUtc = utcNow + TopicId = post.TopicId, + CreatedOnUtc = DateTime.UtcNow }; _forumService.InsertSubscription(forumSubscription); @@ -1395,19 +1385,13 @@ public ActionResult PostCreate(EditForumPostModel model, bool captchaValid) } var pageSize = _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 20; - var pageIndex = _forumService.CalculateTopicPageIndex(forumPost.TopicId, pageSize, forumPost.Id) + 1; - var url = string.Empty; + var pageIndex = _forumService.CalculateTopicPageIndex(post.TopicId, pageSize, post.Id) + 1; - if (pageIndex > 1) - { - url = Url.RouteUrl("TopicSlug", new { id = forumPost.TopicId, slug = forumPost.ForumTopic.GetSeName(), page = pageIndex }); - } - else - { - url = Url.RouteUrl("TopicSlug", new { id = forumPost.TopicId, slug = forumPost.ForumTopic.GetSeName() }); - } + var url = pageIndex > 1 + ? Url.RouteUrl("TopicSlug", new { id = post.TopicId, slug = post.ForumTopic.GetSeName(), page = pageIndex }) + : Url.RouteUrl("TopicSlug", new { id = post.TopicId, slug = post.ForumTopic.GetSeName() }); - return Redirect(string.Format("{0}#{1}", url, forumPost.Id)); + return Redirect(string.Concat(url, "#", post.Id)); } catch (Exception ex) { @@ -1423,6 +1407,7 @@ public ActionResult PostCreate(EditForumPostModel model, bool captchaValid) model.ForumTopicId = topic.Id; model.ForumTopicSubject = topic.Subject; model.ForumTopicSeName = topic.GetSeName(); + model.IsModerator = customer.IsForumModerator(); model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); model.ForumEditor = _forumSettings.ForumEditor; @@ -1443,10 +1428,6 @@ public ActionResult PostEdit(int id) { return HttpNotFound(); } - if (!_forumService.IsCustomerAllowedToEditPost(customer, post)) - { - return new HttpUnauthorizedResult(); - } var model = new EditForumPostModel { @@ -1459,11 +1440,19 @@ public ActionResult PostEdit(int id) ForumName = post.ForumTopic.Forum.GetLocalized(x => x.Name), ForumTopicSubject = post.ForumTopic.Subject, ForumTopicSeName = post.ForumTopic.GetSeName(), - IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), Subscribed = false, - Text = post.Text + Text = post.Text, + CustomerId = customer.Id, + IsModerator = customer.IsForumModerator(), + IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer), + IsCustomerAllowedToEdit = _forumService.IsCustomerAllowedToEditPost(customer, post) }; + if (!model.IsCustomerAllowedToEdit && customer.Id != post.CustomerId) + { + return new HttpUnauthorizedResult(); + } + // Subscription. if (model.IsCustomerAllowedToSubscribe) { @@ -1493,7 +1482,6 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) { return HttpNotFound(); } - if (!_forumService.IsCustomerAllowedToEditPost(customer, post)) { return new HttpUnauthorizedResult(); @@ -1508,15 +1496,14 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) { try { - var utcNow = DateTime.UtcNow; - var text = model.Text; - var maxPostLength = _forumSettings.PostMaxLength; - if (maxPostLength > 0 && text.Length > maxPostLength) + if (customer.IsForumModerator()) { - text = text.Substring(0, maxPostLength); + post.Published = model.Published; } - post.Text = text; + post.Text = _forumSettings.PostMaxLength > 0 && model.Text.Length > _forumSettings.PostMaxLength + ? model.Text.Substring(0, _forumSettings.PostMaxLength) + : model.Text; _forumService.UpdatePost(post); @@ -1533,7 +1520,7 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) SubscriptionGuid = Guid.NewGuid(), CustomerId = customer.Id, TopicId = post.TopicId, - CreatedOnUtc = utcNow + CreatedOnUtc = DateTime.UtcNow }; _forumService.InsertSubscription(forumSubscription); @@ -1549,19 +1536,13 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) } var pageSize = _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 20; - var pageIndex = (_forumService.CalculateTopicPageIndex(post.TopicId, pageSize, post.Id) + 1); - var url = string.Empty; + var pageIndex = _forumService.CalculateTopicPageIndex(post.TopicId, pageSize, post.Id) + 1; - if (pageIndex > 1) - { - url = Url.RouteUrl("TopicSlug", new { id = post.TopicId, slug = post.ForumTopic.GetSeName(), page = pageIndex }); - } - else - { - url = Url.RouteUrl("TopicSlug", new { id = post.TopicId, slug = post.ForumTopic.GetSeName() }); - } + var url = pageIndex > 1 + ? Url.RouteUrl("TopicSlug", new { id = post.TopicId, slug = post.ForumTopic.GetSeName(), page = pageIndex }) + : Url.RouteUrl("TopicSlug", new { id = post.TopicId, slug = post.ForumTopic.GetSeName() }); - return Redirect(string.Format("{0}#{1}", url, post.Id)); + return Redirect(string.Concat(url, "#", post.Id)); } catch (Exception ex) { @@ -1578,9 +1559,11 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) model.ForumTopicSubject = post.ForumTopic.Subject; model.ForumTopicSeName = post.ForumTopic.GetSeName(); model.Id = post.Id; - model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); model.ForumEditor = _forumSettings.ForumEditor; - + model.CustomerId = customer.Id; + model.IsModerator = customer.IsForumModerator(); + model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(customer); + return View(model); } @@ -1597,7 +1580,6 @@ public ActionResult PostDelete(int id) { return HttpNotFound(); } - if (!_forumService.IsCustomerAllowedToDeletePost(Services.WorkContext.CurrentCustomer, post)) { return new HttpUnauthorizedResult(); diff --git a/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs index 4e19404cf4..4f8ac6cb96 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs @@ -23,8 +23,12 @@ public partial class EditForumPostModel : EntityModelBase public string ForumTopicSubject { get; set; } public string ForumTopicSeName { get; set; } + public bool IsModerator { get; set; } public bool IsCustomerAllowedToSubscribe { get; set; } public bool Subscribed { get; set; } + + public bool IsCustomerAllowedToEdit { get; set; } + public int CustomerId { get; set; } } public class EditForumPostValidator : AbstractValidator diff --git a/src/Presentation/SmartStore.Web/Models/Boards/EditForumTopicModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/EditForumTopicModel.cs index 80b2e7ec7d..4f27772b58 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/EditForumTopicModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/EditForumTopicModel.cs @@ -19,6 +19,7 @@ public EditForumTopicModel() public bool IsEdit { get; set; } public bool DisplayCaptcha { get; set; } public bool Published { get; set; } + public string SeName { get; set; } public int ForumId { get; set; } public LocalizedValue ForumName { get; set; } @@ -33,11 +34,14 @@ public EditForumTopicModel() [AllowHtml] public string Text { get; set; } - public bool IsCustomerAllowedToSetTopicPriority { get; set; } + public bool IsModerator { get; set; } public IEnumerable TopicPriorities { get; set; } public bool IsCustomerAllowedToSubscribe { get; set; } public bool Subscribed { get; set; } + + public bool IsCustomerAllowedToEdit { get; set; } + public int CustomerId { get; set; } } public class EditForumTopicValidator : AbstractValidator diff --git a/src/Presentation/SmartStore.Web/Models/Boards/TopicMoveModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/TopicMoveModel.cs index c88dc4c6b4..b784c10cbd 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/TopicMoveModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/TopicMoveModel.cs @@ -13,6 +13,8 @@ public TopicMoveModel() public int ForumSelected { get; set; } public string TopicSeName { get; set; } + public bool IsCustomerAllowedToEdit { get; set; } + public int CustomerId { get; set; } public IEnumerable ForumList { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml index 97165cc9b2..48d3d6a5df 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml @@ -37,12 +37,26 @@
    + @if (Model.IsModerator) + { +
    +
    +
    + @Html.CheckBoxFor(model => model.Published, new { @class = "form-check-input" }) + +
    +
    +
    + } + @if (Model.IsCustomerAllowedToSubscribe) {
    - @Html.CheckBox("Subscribed", @Model.Subscribed, new { @class = "form-check-input" }) + @Html.CheckBoxFor(model => model.Subscribed, new { @class = "form-check-input" })
    @@ -72,10 +86,10 @@ @T("Forum.Submit") - +
    diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdateTopic.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdateTopic.cshtml index f7f69aa490..4bc57726f8 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdateTopic.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdateTopic.cshtml @@ -1,7 +1,12 @@ -@model EditForumTopicModel -@using SmartStore.Core.Domain.Forums; +@using SmartStore.Core.Domain.Forums; @using SmartStore.Web; @using SmartStore.Web.Models.Boards; +@model EditForumTopicModel +@{ + var cancelUrl = Model.Id == 0 ? + Url.RouteUrl("ForumSlug", new { id = Model.ForumId, slug = Model.ForumSeName }) : + Url.RouteUrl("TopicSlug", new { id = Model.Id, slug = Model.SeName }); +} @using (Html.BeginForm()) { @@ -41,7 +46,7 @@
    - @if (Model.IsCustomerAllowedToSetTopicPriority) + @if (Model.IsModerator) {
    @@ -49,14 +54,22 @@ @Html.DropDownList("TopicTypeId", new SelectList(Model.TopicPriorities, "Value", "Text", Model.TopicTypeId), new { @class = "form-control" } )
    - } - - @if (Model.IsCustomerAllowedToSubscribe) - { + +
    +
    +
    + @Html.CheckBoxFor(model => model.Published, new { @class = "form-check-input" }) + +
    +
    +
    +
    - @Html.CheckBox("Subscribed", @Model.Subscribed, new { @class = "form-check-input" }) + @Html.CheckBoxFor(model => model.Subscribed, new { @class = "form-check-input" })
    @@ -86,10 +99,10 @@ @T("Forum.Submit") - +
    From 56ad33c908d4f20713b64ff4bc789d9ca0e462d9 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 26 Sep 2018 20:22:04 +0200 Subject: [PATCH 137/932] Closes #1499 Add hint to forms indicating that fields with an asterisk (*) are mandatory --- .../Migrations/MigrationsConfiguration.cs | 7 +++++++ .../Views/Customer/ChangePassword.cshtml | 6 ++++++ .../SmartStore.Web/Views/Customer/Info.cshtml | 6 ++++++ .../SmartStore.Web/Views/Customer/Register.cshtml | 6 ++++++ .../SmartStore.Web/Views/Home/ContactUs.cshtml | 7 +++++++ .../SmartStore.Web/Views/Product/AskQuestion.cshtml | 6 ++++++ .../SmartStore.Web/Views/Product/EmailAFriend.cshtml | 12 +++++++++--- .../SmartStore.Web/Views/Product/Reviews.cshtml | 10 ++++++++-- .../Shared/Partials/_CreateOrUpdateAddress.cshtml | 2 +- .../Views/ShoppingCart/EmailWishlist.cshtml | 12 +++++++++--- 10 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 4e30f67e80..7deca315ce 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -519,6 +519,13 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Pro Maschine ausführen", "Indicates whether the task is executed decidedly on each machine of a web farm.", "Gibt an, ob die Aufgabe auf jeder Maschine einer Webfarm dezidiert ausgeführt wird."); + + builder.Delete("Address.Fields.Required.Hint"); + + builder.AddOrUpdate("Common.FormFields.Required.Hint", + "* Input elements with asterisk are required and have to be filled out.", + "* Eingabefelder mit Sternchen sind Pflichfelder und müssen ausgefüllt werden."); + } } } diff --git a/src/Presentation/SmartStore.Web/Views/Customer/ChangePassword.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/ChangePassword.cshtml index e4c1c51bb6..fe65dbef52 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/ChangePassword.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/ChangePassword.cshtml @@ -48,6 +48,12 @@ +
    +
    + @T("Common.FormFields.Required.Hint") +
    +
    +
    + +
    +
    + @T("Common.FormFields.Required.Hint") +
    +
    +
    @{ Html.RenderWidget("gdpr_consent"); } diff --git a/src/Presentation/SmartStore.Web/Views/Product/AskQuestion.cshtml b/src/Presentation/SmartStore.Web/Views/Product/AskQuestion.cshtml index 7594c9ae57..5c7cc79ddb 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/AskQuestion.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/AskQuestion.cshtml @@ -60,6 +60,12 @@ @Html.ValidationMessageFor(model => model.Question, null, new { @class = "text-danger" })
    +
    +
    + @T("Common.FormFields.Required.Hint") +
    +
    + @{ Html.RenderWidget("gdpr_consent"); } @if (Model.DisplayCaptcha) diff --git a/src/Presentation/SmartStore.Web/Views/Product/EmailAFriend.cshtml b/src/Presentation/SmartStore.Web/Views/Product/EmailAFriend.cshtml index 5e446d75ee..7c45610a27 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/EmailAFriend.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/EmailAFriend.cshtml @@ -28,13 +28,13 @@

    - @Html.LabelFor(model => model.FriendEmail, new { @for = "FriendEmail" }) + @Html.LabelFor(model => model.FriendEmail, new { @for = "FriendEmail", @class = "col-form-label required" }) @Html.TextBoxFor(model => model.FriendEmail, new { @class = "fullname form-control", placeholder = T("Products.EmailAFriend.FriendEmail.Hint") }) @Html.ValidationMessageFor(model => model.FriendEmail, null, new { @class = "text-danger" })
    - @Html.LabelFor(model => model.YourEmailAddress, new { @for = "YourEmailAddress" }) + @Html.LabelFor(model => model.YourEmailAddress, new { @for = "YourEmailAddress", @class = "col-form-label" }) @if (Model.AllowChangedCustomerEmail) { @Html.TextBoxFor(model => model.YourEmailAddress, new { @class = "fullname form-control", placeholder = T("Products.EmailAFriend.YourEmailAddress.Hint") }) @@ -47,11 +47,17 @@
    - @Html.LabelFor(model => model.PersonalMessage, new { @for = "PersonalMessage" }) + @Html.LabelFor(model => model.PersonalMessage, new { @for = "PersonalMessage", @class = "col-form-label" }) @Html.TextAreaFor(model => model.PersonalMessage, new { @class = "personal-message form-control", placeholder = T("Products.EmailAFriend.PersonalMessage.Hint") }) @Html.ValidationMessageFor(model => model.PersonalMessage, null, new { @class = "text-danger" })
    +
    +
    + @T("Common.FormFields.Required.Hint") +
    +
    + @{ Html.RenderWidget("gdpr_consent"); } @if (Model.DisplayCaptcha) diff --git a/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml b/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml index 4314b61d2f..563fbcab8e 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml @@ -58,19 +58,25 @@
    - @Html.LabelFor(model => model.Title, new { @class = "form-control-label col-form-label col-md-3" }) + @Html.LabelFor(model => model.Title, new { @class = "form-control-label col-form-label col-md-3 required" })
    @Html.TextBoxFor(model => model.Title, Model.CanCurrentCustomerLeaveReview ? (object)(new { @class = "form-control" }) : (object)(new { @class = "form-control", disabled = "disabled" })) @Html.ValidationMessageFor(model => model.Title, null)
    - @Html.LabelFor(model => model.ReviewText, new { @class = "form-control-label col-form-label col-md-3" }) + @Html.LabelFor(model => model.ReviewText, new { @class = "form-control-label col-form-label col-md-3 required" })
    @Html.TextAreaFor(model => model.ReviewText, 4, 1, Model.CanCurrentCustomerLeaveReview ? (object)(new { @class = "form-control" }) : (object)(new { @class = "form-control", disabled = "disabled" })) @Html.ValidationMessageFor(model => model.ReviewText, null)
    + +
    +
    + @T("Common.FormFields.Required.Hint") +
    +
    diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Partials/_CreateOrUpdateAddress.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Partials/_CreateOrUpdateAddress.cshtml index ebae675586..f5ac141f36 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Partials/_CreateOrUpdateAddress.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Partials/_CreateOrUpdateAddress.cshtml @@ -129,7 +129,7 @@
    - @T("Address.Fields.Required.Hint") + @T("Common.FormFields.Required.Hint")
    diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/EmailWishlist.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/EmailWishlist.cshtml index 7ef0ad471b..eaa29675f4 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/EmailWishlist.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/EmailWishlist.cshtml @@ -32,23 +32,29 @@

    - @Html.LabelFor(model => model.FriendEmail, new { @for = "FriendEmail" }) + @Html.LabelFor(model => model.FriendEmail, new { @for = "FriendEmail", @class = "col-form-label required" }) @Html.TextBoxFor(model => model.FriendEmail, new { @class = "friend-email form-control", placeholder = T("Wishlist.EmailAFriend.FriendEmail.Hint") }) @Html.ValidationMessageFor(model => model.FriendEmail)
    - @Html.LabelFor(model => model.YourEmailAddress, new { @for = "YourEmailAddress" }) + @Html.LabelFor(model => model.YourEmailAddress, new { @for = "YourEmailAddress", @class = "col-form-label required" }) @Html.TextBoxFor(model => model.YourEmailAddress, new { @class = "your-email form-control", placeholder = T("Wishlist.EmailAFriend.YourEmailAddress.Hint") }) @Html.ValidationMessageFor(model => model.YourEmailAddress)
    - @Html.LabelFor(model => model.PersonalMessage, new { @for = "PersonalMessage" }) + @Html.LabelFor(model => model.PersonalMessage, new { @for = "PersonalMessage", @class = "col-form-label" }) @Html.TextAreaFor(model => model.PersonalMessage, new { @class = "personal-message form-control", placeholder = T("Wishlist.EmailAFriend.PersonalMessage.Hint") }) @Html.ValidationMessageFor(model => model.PersonalMessage)
    +
    +
    + @T("Common.FormFields.Required.Hint") +
    +
    + @{ Html.RenderWidget("gdpr_consent"); } @if (Model.DisplayCaptcha) From 22dcc7683b73103741ec9cf07204c23f2fde0649 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 26 Sep 2018 21:40:58 +0200 Subject: [PATCH 138/932] More on published property of forum topic and forum post --- .../SmartStore.Web/Content/shared/_card.scss | 16 ++++++++++++---- .../Controllers/BoardsController.cs | 8 ++++---- .../Themes/Flex/Content/_forum.scss | 5 +++++ .../SmartStore.Web/Views/Boards/Forum.cshtml | 4 ++-- .../Views/Boards/Partials/_ActiveTopics.cshtml | 4 ++-- .../Boards/Partials/_CreateUpdatePost.cshtml | 9 ++++++++- .../Views/Boards/Partials/_ForumPost.cshtml | 15 +++++++-------- .../SmartStore.Web/Views/Boards/Topic.cshtml | 10 ++++------ .../SmartStore.Web/Views/Boards/TopicMove.cshtml | 6 ++---- 9 files changed, 46 insertions(+), 31 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Content/shared/_card.scss b/src/Presentation/SmartStore.Web/Content/shared/_card.scss index 6134f0f9b3..676fd52e9b 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_card.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_card.scss @@ -29,44 +29,52 @@ margin-bottom: 0 !important; } +.card-body.disabled { + opacity: .5; +} // "Real" responsive card decks & groups // Remove this, once BS4 natively supports responsive card decks & groups // ====================================================================== - @mixin card-deck-responsive($breakpoint) { $m: $card-deck-margin * 2; + .card-deck { &.card-cols-#{$breakpoint}-1 .card { -ms-flex: 1 0 100% !important; flex: 1 0 calc(100% - #{$m}) !important; max-width: calc(100% - #{$m}); } + &.card-cols-#{$breakpoint}-2 .card { -ms-flex: 0 0 50% !important; flex: 0 0 calc(50% - #{$m}) !important; max-width: calc(50% - #{$m}); } - &.card-cols-#{$breakpoint}-3 .card { - -ms-flex: 0 0 33.333% !important; + + &.card-cols-#{$breakpoint}-3 .card { + -ms-flex: 0 0 33.333% !important; flex: 0 0 calc(33.333% - #{$m}) !important; max-width: calc(33.333% - #{$m}); } + &.card-cols-#{$breakpoint}-4 .card { -ms-flex: 0 0 25% !important; flex: 0 0 calc(25% - #{$m}) !important; max-width: calc(25% - #{$m}); } + &.card-cols-#{$breakpoint}-5 .card { -ms-flex: 0 0 20% !important; flex: 0 0 calc(20% - #{$m}) !important; max-width: calc(20% - #{$m}); } + &.card-cols-#{$breakpoint}-6 .card { -ms-flex: 0 0 16.666% !important; flex: 0 0 calc(16.666% - #{$m}) !important; max-width: calc(16.666% - #{$m}); - } + } } } diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 5f4203551f..96003cf4f5 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -273,7 +273,7 @@ private void CreateForumBreadcrumb(ForumGroup group = null, Forum forum = null, forum = forum ?? topic?.Forum; if (forum != null) { - var forumName = group.GetLocalized(x => x.Name); + var forumName = forum.GetLocalized(x => x.Name); _breadcrumb.Track(new MenuItem { Text = forumName, @@ -1064,7 +1064,7 @@ public ActionResult TopicEdit(int id) IsCustomerAllowedToEdit = _forumService.IsCustomerAllowedToEditTopic(customer, topic) }; - if (!model.IsCustomerAllowedToEdit && customer.Id != topic.CustomerId) + if (!model.IsCustomerAllowedToEdit) { return new HttpUnauthorizedResult(); } @@ -1433,7 +1433,7 @@ public ActionResult PostEdit(int id) { Id = post.Id, IsEdit = true, - Published = true, + Published = post.Published, ForumTopicId = post.ForumTopic.Id, DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage, ForumEditor = _forumSettings.ForumEditor, @@ -1448,7 +1448,7 @@ public ActionResult PostEdit(int id) IsCustomerAllowedToEdit = _forumService.IsCustomerAllowedToEditPost(customer, post) }; - if (!model.IsCustomerAllowedToEdit && customer.Id != post.CustomerId) + if (!model.IsCustomerAllowedToEdit) { return new HttpUnauthorizedResult(); } diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_forum.scss b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_forum.scss index fde5be7236..971984f6e3 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_forum.scss +++ b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_forum.scss @@ -69,6 +69,11 @@ $forum-unobstrusive-link-color: #646464; white-space: nowrap; } +.topics-group .topic.disabled { + opacity: .5; +} + + /* Forum Topic ================================================ */ .forum-topic { diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml index 5751f09138..ac5022ded4 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml @@ -58,7 +58,7 @@
    - + @T("Forum.NewTopic") @@ -101,7 +101,7 @@ } @foreach (var topic in Model.ForumTopics) { -
    @{ Html.RenderPartial("Customer.Avatar", topic.Avatar); } @Html.SettingEditorFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars, - Html.CheckBoxFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars, new { data_toggler_for = "#pnlDefaultAvatarEnabled" })) + Html.CheckBoxFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars)) @Html.ValidationMessageFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars)
    - @Html.SmartLabelFor(model => model.CustomerSettings.DefaultAvatarEnabled) - - @Html.SettingEditorFor(model => model.CustomerSettings.DefaultAvatarEnabled) - @Html.ValidationMessageFor(model => model.CustomerSettings.DefaultAvatarEnabled) -
    @Html.SmartLabelFor(model => model.CustomerSettings.ShowCustomersLocation) diff --git a/src/Presentation/SmartStore.Web/Content/Images/default-avatar.jpg b/src/Presentation/SmartStore.Web/Content/Images/default-avatar.jpg deleted file mode 100644 index 3f8947a5e937b10a5bea323221b6cc155a0fb64c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 901 zcmex=^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<ECr+Na zbot8FYu9hwy!G(W<0ns_J%91?)yGetzkL1n{m0K=Ab&A3Fhjfr_ZgbM1cClyVqsxs zVF&q(k*OSrnFU!`6%E;h90S=C3x$=88aYIqCNA7~kW<+>=!0ld(M2vX6_bamA3RPP`<2nvyg(p%`=eEr_t`Jlg{41=NK_t)>j05&2rE%ZjS{tTs z`20gP&($ghjYVAy+F{DOMC=xATJ3bn>0?6w?xPG5Aj^Q5sr6Ov6!Y(`VQaZ}Y(KiA zG%nk&flFgi1J|Xkz8m-~ZLSqD#5%s5mmQNVQ?h`mO9O&e+>L&#<|40O_%eLOD?+aSQflqfn-neQ?0NYW~nX*SFe#x`tjct0ECUH!nYysyApnEhY rZ96fuJwWqo)F-EnZ{?3Vh - From 4445e932f3f3265b9858c911db9f53efa9e0fad1 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 27 Sep 2018 12:28:35 +0200 Subject: [PATCH 142/932] Integrate published property of forum topics and posts in forum search --- .../Search/Forum/ForumSearchQuery.cs | 41 ++++++++++++------- .../Search/Forum/LinqForumSearchService.cs | 32 ++++++++++++++- .../Modelling/ForumSearchQueryFactory.cs | 2 +- .../Views/Boards/Partials/SearchHits.cshtml | 2 +- 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs index 1e964bad84..9fd25c0d3a 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs @@ -3,6 +3,7 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Search; +using SmartStore.Services.Customers; namespace SmartStore.Services.Search { @@ -66,33 +67,43 @@ public ForumSearchQuery SortBy(ForumTopicSorting sort) } } - public ForumSearchQuery VisibleOnly(Customer customer) + public ForumSearchQuery VisibleOnly(Customer customer, bool includeCustomerRoles) { if (customer != null) { - var allowedCustomerRoleIds = customer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToArray(); - - return VisibleOnly(allowedCustomerRoleIds); - } + if (!customer.IsForumModerator()) + { + // See also LinqForumSearchService. + var publishedCombined = SearchFilter.Combined( + SearchFilter.ByField("published", true).ExactMatch().NotAnalyzed(), + SearchFilter.ByField("customerid", customer.Id).ExactMatch().NotAnalyzed()); - return VisibleOnly(new int[0]); - } + WithFilter(publishedCombined); + } - public ForumSearchQuery VisibleOnly(params int[] allowedCustomerRoleIds) - { - if (allowedCustomerRoleIds != null && allowedCustomerRoleIds.Length > 0) - { - var roleIds = allowedCustomerRoleIds.Where(x => x != 0).Distinct().ToList(); - if (roleIds.Any()) + if (includeCustomerRoles) { - roleIds.Insert(0, 0); - WithFilter(SearchFilter.Combined(roleIds.Select(x => SearchFilter.ByField("roleid", x).ExactMatch().NotAnalyzed()).ToArray())); + var allowedRoleIds = customer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToArray(); + if (allowedRoleIds != null && allowedRoleIds.Length > 0) + { + var roleIds = allowedRoleIds.Where(x => x != 0).Distinct().ToList(); + if (roleIds.Any()) + { + roleIds.Insert(0, 0); + WithFilter(SearchFilter.Combined(roleIds.Select(x => SearchFilter.ByField("roleid", x).ExactMatch().NotAnalyzed()).ToArray())); + } + } } } return this; } + public ForumSearchQuery PublishedOnly(bool value) + { + return WithFilter(SearchFilter.ByField("published", value).Mandatory().ExactMatch().NotAnalyzed()); + } + public override ForumSearchQuery HasStoreId(int id) { base.HasStoreId(id); diff --git a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs index 917221d3b5..c39dec94b2 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -64,6 +64,7 @@ protected virtual IQueryable GetPostQuery(ForumSearchQuery searchQuer var cnf = _customerSettings.CustomerNameFormat; var fields = searchQuery.Fields; var filters = new List(); + var customer = _services.WorkContext.CurrentCustomer; var query = baseQuery ?? _forumPostRepository.TableUntracked.Expand(x => x.ForumTopic); // Apply search term. @@ -95,8 +96,31 @@ protected virtual IQueryable GetPostQuery(ForumSearchQuery searchQuer } } - // Filters. - FlattenFilters(searchQuery.Filters, filters); + // Flatten filters. + foreach (var filter in searchQuery.Filters) + { + var combinedFilter = filter as ICombinedSearchFilter; + if (combinedFilter != null) + { + // Find VisibleOnly combined filter and process it separately. + var cf = combinedFilter.Filters.OfType().ToArray(); + if (cf.Length == 2 && cf[0].FieldName == "published" && true == (bool)cf[0].Term && cf[1].FieldName == "customerid") + { + if (!customer.IsForumModerator()) + { + query = query.Where(x => x.ForumTopic.Published && (x.Published || x.CustomerId == customer.Id)); + } + } + else + { + FlattenFilters(combinedFilter.Filters, filters); + } + } + else + { + filters.Add(filter); + } + } if (!QuerySettings.IgnoreAcl) { @@ -151,6 +175,10 @@ from a in fg_acl.DefaultIfEmpty() { query = query.Where(x => x.CustomerId == (int)filter.Term); } + else if (filter.FieldName == "published") + { + query = query.Where(x => x.Published == (bool)filter.Term); + } else if (filter.FieldName == "createdon") { if (rangeFilter != null) diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs index f5b2d7960a..05f89aa954 100644 --- a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs @@ -84,7 +84,7 @@ public ForumSearchQuery CreateFromQuery() .BuildFacetMap(!isInstantSearch); // Visibility. - query.VisibleOnly(!QuerySettings.IgnoreAcl ? _services.WorkContext.CurrentCustomer : null); + query.VisibleOnly(_services.WorkContext.CurrentCustomer, !QuerySettings.IgnoreAcl); // Store. if (!QuerySettings.IgnoreMultiStore) diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml index 4cc9c4135a..bc6375ca8b 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml @@ -10,7 +10,7 @@ data-cumulativehits="@Model.CumulativeHitCount.ToString("N0")"> @foreach (ForumTopicRowModel topic in Model.PagedList) { -
    @{ Html.RenderPartial("Customer.Avatar", topic.Avatar); } @topic.NumReplies.ToString("N0") From 389a7cef1b3f8ae54ca720c942ee4f348a68e58d Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 28 Sep 2018 03:16:40 +0200 Subject: [PATCH 145/932] Fix for HtmlHelper.BeginZoneContent(): capture inner writers also (Html.BeginForm() etc.) --- .../Extensions/HtmlZoneExtensions.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlZoneExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlZoneExtensions.cs index b74766934a..472a99d9c8 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlZoneExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlZoneExtensions.cs @@ -23,16 +23,23 @@ private class DocumentZone : IDisposable internal const string ItemsKey = "DocumentTail.Snippets"; private const string UniqueKeysKey = "DocumentTail.UniqueKeys"; + private readonly ViewContext _viewContext; + private readonly TextWriter _originalViewContextWriter; private readonly WebViewPage _page; private readonly string _targetZone; private readonly ZoneInjectMode _injectMode; - public DocumentZone(WebViewPage page, string targetZone, ZoneInjectMode injectMode, string key) + public DocumentZone(HtmlHelper html, string targetZone, ZoneInjectMode injectMode, string key) { Guard.NotEmpty(targetZone, nameof(targetZone)); - _page = page; - _page.OutputStack.Push(new StringWriter()); + _viewContext = html.ViewContext; + _originalViewContextWriter = _viewContext.Writer; + _page = (WebViewPage)html.ViewDataContainer; + + var writer = new StringWriter(); + _page.OutputStack.Push(writer); + _viewContext.Writer = writer; _targetZone = targetZone; _injectMode = injectMode; @@ -78,7 +85,10 @@ public void Dispose() if (storage == null) return; - var content = ((StringWriter)_page.OutputStack.Pop()).ToString(); + var writer = ((StringWriter)_page.OutputStack.Pop()); + var content = writer.ToString(); + + _viewContext.Writer = _originalViewContextWriter; if (_injectMode == ZoneInjectMode.Append) { @@ -109,7 +119,7 @@ public static IDisposable BeginZoneContent(this HtmlHelper helper, return ActionDisposable.Empty; } - return new DocumentZone((WebViewPage)helper.ViewDataContainer, targetZone, injectMode, key); + return new DocumentZone(helper, targetZone, injectMode, key); } public static void RenderZone(this HtmlHelper helper, string zone) From 124dd9bd45035c7e14274684a03bcf919c7bca5b Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 28 Sep 2018 03:17:24 +0200 Subject: [PATCH 146/932] CSS option class to skip fixing the top section header (backend) --- .../Scripts/admin.globalinit.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js index 07c3abeb5f..d66b1ead3f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js +++ b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js @@ -127,15 +127,17 @@ var sectionHeader = $('.section-header'); var sectionHeaderHasButtons = undefined; - $(window).on("scroll resize", function (e) { - if (sectionHeaderHasButtons === undefined) { - sectionHeaderHasButtons = sectionHeader.find(".options").children().length > 0; - } - if (sectionHeaderHasButtons === true) { - var y = $(this).scrollTop(); - sectionHeader.toggleClass("sticky", y >= navbarHeight); - } - }).trigger('resize'); + if (!sectionHeader.hasClass('nofix')) { + $(window).on("scroll resize", function (e) { + if (sectionHeaderHasButtons === undefined) { + sectionHeaderHasButtons = sectionHeader.find(".options").children().length > 0; + } + if (sectionHeaderHasButtons === true) { + var y = $(this).scrollTop(); + sectionHeader.toggleClass("sticky", y >= navbarHeight); + } + }).trigger('resize'); + } $(window).on('load', function () { // swap classes onload and domready From f1a399e7b1c48496e1378ae1a5f664c6c9da14da Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 28 Sep 2018 03:17:43 +0200 Subject: [PATCH 147/932] Small variant of numeric textbox --- .../Administration/Content/_telerik.scss | 31 ++++++++++++++++++- .../Views/Shared/EditorTemplates/Byte.cshtml | 17 +++++++++- .../Shared/EditorTemplates/DateTime.cshtml | 2 +- .../Shared/EditorTemplates/Decimal.cshtml | 17 +++++++++- .../Shared/EditorTemplates/Double.cshtml | 17 +++++++++- .../Views/Shared/EditorTemplates/Int32.cshtml | 17 +++++++++- .../Views/Shared/EditorTemplates/Time.cshtml | 15 +++++++++ 7 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/_telerik.scss b/src/Presentation/SmartStore.Web/Administration/Content/_telerik.scss index f1e731882f..e9b91a0fc0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/_telerik.scss +++ b/src/Presentation/SmartStore.Web/Administration/Content/_telerik.scss @@ -359,7 +359,7 @@ a.t-button.t-state-disabled:hover, display: block; position: relative; - .numerictextbox-postfix { + > .numerictextbox-postfix { display: block; position: absolute; z-index: 2; @@ -374,6 +374,35 @@ a.t-button.t-state-disabled:hover, } } +.numerictextbox-group-sm { + > .t-numerictextbox { + &:after { + width: calc(#{$input-height-sm} - 1px); + } + + .t-input { + padding: $input-btn-padding-y-sm $input-btn-padding-x-sm; + padding-right: $input-height-inner-sm + 1rem !important; + } + + .t-formatted-value { + padding: $input-btn-padding-y-sm $input-btn-padding-x-sm; + font-size: $font-size-sm; // Match sm inputs + line-height: $input-btn-line-height-sm; + } + + .t-input ~ .t-icon { + width: calc(#{$input-height-sm}); + } + } + + > .numerictextbox-postfix { + padding: $input-btn-padding-y-sm $input-btn-padding-x-sm; + font-size: $font-size-sm; // Match sm inputs + line-height: $input-btn-line-height-sm; + } +} + /* Grid diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Byte.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Byte.cshtml index 8fe3cbfdb3..e1085d1bf2 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Byte.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Byte.cshtml @@ -21,9 +21,24 @@ return ViewData["postfix"] as string; } } + + private string CssClass + { + get + { + var cls = "numerictextbox-group flex-grow-1"; + + if (ViewData.ContainsKey("size")) + { + cls += " numerictextbox-group-" + ViewData["size"].Convert(); + } + + return cls; + } + } } -
    +
    @(Html.Telerik().NumericTextBox() .Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)) .EmptyMessage(T("Common.EnterValue")) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/DateTime.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/DateTime.cshtml index 61a236c6db..90b7d17811 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/DateTime.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/DateTime.cshtml @@ -38,7 +38,7 @@ if (ViewData.ContainsKey("size")) { - cls += " input-group input-group-" + ViewData["size"].Convert(); + cls += " input-group-" + ViewData["size"].Convert(); } return cls; diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Decimal.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Decimal.cshtml index d2b0131c2c..4c93f01990 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Decimal.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Decimal.cshtml @@ -21,9 +21,24 @@ return ViewData["postfix"] as string; } } + + private string CssClass + { + get + { + var cls = "numerictextbox-group flex-grow-1"; + + if (ViewData.ContainsKey("size")) + { + cls += " numerictextbox-group-" + ViewData["size"].Convert(); + } + + return cls; + } + } } -
    +
    @(Html.Telerik().NumericTextBox() .Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)) .EmptyMessage(T("Common.EnterValue")) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Double.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Double.cshtml index deccb78df2..8946e81ae3 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Double.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Double.cshtml @@ -21,9 +21,24 @@ return ViewData["postfix"] as string; } } + + private string CssClass + { + get + { + var cls = "numerictextbox-group flex-grow-1"; + + if (ViewData.ContainsKey("size")) + { + cls += " numerictextbox-group-" + ViewData["size"].Convert(); + } + + return cls; + } + } } -
    +
    @(Html.Telerik().NumericTextBox() .Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)) .EmptyMessage(T("Common.EnterValue")) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Int32.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Int32.cshtml index 2eb3cf60f0..e70f0e6446 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Int32.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Int32.cshtml @@ -21,9 +21,24 @@ return ViewData["postfix"] as string; } } + + private string CssClass + { + get + { + var cls = "numerictextbox-group flex-grow-1"; + + if (ViewData.ContainsKey("size")) + { + cls += " numerictextbox-group-" + ViewData["size"].Convert(); + } + + return cls; + } + } } -
    +
    @(Html.Telerik().IntegerTextBox() .Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)) .EmptyMessage(T("Common.EnterValue")) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Time.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Time.cshtml index 6e6a2a1492..6bb6dfac23 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Time.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Time.cshtml @@ -11,6 +11,21 @@ return value; } } + + private string CssClass + { + get + { + var cls = "input-group date datetimepicker-group"; + + if (ViewData.ContainsKey("size")) + { + cls += " input-group-" + ViewData["size"].Convert(); + } + + return cls; + } + } } @{ From ee5d99d9372c5f094bd8edf250ca40182bdc9476 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 28 Sep 2018 10:30:33 +0200 Subject: [PATCH 148/932] Do not allow to unpublish first post of a forum topic. Unpublish topic instead. Fixes wrong NumReplies counter problem too. --- .../Forums/ForumExtensions.cs | 109 +++++++++--------- .../Controllers/BoardsController.cs | 12 +- .../Models/Boards/EditForumPostModel.cs | 1 + .../Boards/Partials/_CreateUpdatePost.cshtml | 2 +- 4 files changed, 69 insertions(+), 55 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs b/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs index d6d4fe8b19..41aa0c15bc 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs @@ -1,5 +1,4 @@ -using System; -using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Html; using SmartStore.Core.Infrastructure; @@ -12,14 +11,17 @@ public static class ForumExtensions /// /// Formats the forum post text /// - /// Forum post + /// Forum post /// Formatted text - public static string FormatPostText(this ForumPost forumPost) + public static string FormatPostText(this ForumPost post) { - string text = forumPost.Text; + Guard.NotNull(post, nameof(post)); - if (String.IsNullOrEmpty(text)) + var text = post.Text; + if (text.IsEmpty()) + { return string.Empty; + } switch (EngineContext.Current.Resolve().ForumEditor) { @@ -43,27 +45,26 @@ public static string FormatPostText(this ForumPost forumPost) /// /// Strips the topic subject /// - /// Forum topic + /// Forum topic /// Formatted subject - public static string StripTopicSubject(this ForumTopic forumTopic) + public static string StripTopicSubject(this ForumTopic topic) { - string subject = forumTopic.Subject; - if (String.IsNullOrEmpty(subject)) + Guard.NotNull(topic, nameof(topic)); + + var subject = topic.Subject; + if (subject.IsEmpty()) { return subject; } - int strippedTopicMaxLength = EngineContext.Current.Resolve().StrippedTopicMaxLength; - if (strippedTopicMaxLength > 0) + var strippedTopicMaxLength = EngineContext.Current.Resolve().StrippedTopicMaxLength; + if (strippedTopicMaxLength > 0 && subject.Length > strippedTopicMaxLength) { - if (subject.Length > strippedTopicMaxLength) + var index = subject.IndexOf(" ", strippedTopicMaxLength); + if (index > 0) { - int index = subject.IndexOf(" ", strippedTopicMaxLength); - if (index > 0) - { - subject = subject.Substring(0, index); - subject += "..."; - } + subject = subject.Substring(0, index); + subject += "…"; } } @@ -77,28 +78,30 @@ public static string StripTopicSubject(this ForumTopic forumTopic) /// Formatted text public static string FormatForumSignatureText(this string text) { - if (String.IsNullOrEmpty(text)) + if (text.IsEmpty()) + { return string.Empty; + } - text = SmartStore.Core.Html.HtmlUtils.FormatText(text, false, true, false, false, false, false); - return text; + return HtmlUtils.FormatText(text, false, true, false, false, false, false); } /// /// Formats the private message text /// - /// Private message + /// Private message /// Formatted text - public static string FormatPrivateMessageText(this PrivateMessage pm) + public static string FormatPrivateMessageText(this PrivateMessage message) { - string text = pm.Text; + Guard.NotNull(message, nameof(message)); - if (String.IsNullOrEmpty(text)) + var text = message.Text; + if (text.IsEmpty()) + { return string.Empty; + } - text = SmartStore.Core.Html.HtmlUtils.FormatText(text, false, true, false, true, false, false); - - return text; + return HtmlUtils.FormatText(text, false, true, false, true, false, false); } /// @@ -109,8 +112,8 @@ public static string FormatPrivateMessageText(this PrivateMessage pm) /// Forum topic public static ForumTopic GetLastTopic(this Forum forum, IForumService forumService) { - if (forum == null) - throw new ArgumentNullException("forum"); + Guard.NotNull(forum, nameof(forum)); + Guard.NotNull(forumService, nameof(forumService)); return forumService.GetTopicById(forum.LastTopicId); } @@ -123,8 +126,8 @@ public static ForumTopic GetLastTopic(this Forum forum, IForumService forumServi /// Forum topic public static ForumPost GetLastPost(this Forum forum, IForumService forumService) { - if (forum == null) - throw new ArgumentNullException("forum"); + Guard.NotNull(forum, nameof(forum)); + Guard.NotNull(forumService, nameof(forumService)); return forumService.GetPostById(forum.LastPostId); } @@ -137,8 +140,8 @@ public static ForumPost GetLastPost(this Forum forum, IForumService forumService /// Customer public static Customer GetLastPostCustomer(this Forum forum, ICustomerService customerService) { - if (forum == null) - throw new ArgumentNullException("forum"); + Guard.NotNull(forum, nameof(forum)); + Guard.NotNull(customerService, nameof(customerService)); return customerService.GetCustomerById(forum.LastPostCustomerId); } @@ -146,17 +149,19 @@ public static Customer GetLastPostCustomer(this Forum forum, ICustomerService cu /// /// Get first post /// - /// Forum topic + /// Forum topic /// Forum service /// Forum post - public static ForumPost GetFirstPost(this ForumTopic forumTopic, IForumService forumService) + public static ForumPost GetFirstPost(this ForumTopic topic, IForumService forumService) { - if (forumTopic == null) - throw new ArgumentNullException("forumTopic"); + Guard.NotNull(topic, nameof(topic)); + Guard.NotNull(forumService, nameof(forumService)); - var forumPosts = forumService.GetAllPosts(forumTopic.Id, 0, true, 0, 1); - if (forumPosts.Count > 0) - return forumPosts[0]; + var posts = forumService.GetAllPosts(topic.Id, 0, true, 0, 1); + if (posts.Count > 0) + { + return posts[0]; + } return null; } @@ -164,29 +169,29 @@ public static ForumPost GetFirstPost(this ForumTopic forumTopic, IForumService f /// /// Get last post /// - /// Forum topic + /// Forum topic /// Forum service /// Forum post - public static ForumPost GetLastPost(this ForumTopic forumTopic, IForumService forumService) + public static ForumPost GetLastPost(this ForumTopic topic, IForumService forumService) { - if (forumTopic == null) - throw new ArgumentNullException("forumTopic"); + Guard.NotNull(topic, nameof(topic)); + Guard.NotNull(forumService, nameof(forumService)); - return forumService.GetPostById(forumTopic.LastPostId); + return forumService.GetPostById(topic.LastPostId); } /// /// Get forum last post customer /// - /// Forum topic + /// Forum topic /// Customer service /// Customer - public static Customer GetLastPostCustomer(this ForumTopic forumTopic, ICustomerService customerService) + public static Customer GetLastPostCustomer(this ForumTopic topic, ICustomerService customerService) { - if (forumTopic == null) - throw new ArgumentNullException("forumTopic"); + Guard.NotNull(topic, nameof(topic)); + Guard.NotNull(customerService, nameof(customerService)); - return customerService.GetCustomerById(forumTopic.LastPostCustomerId); + return customerService.GetCustomerById(topic.LastPostCustomerId); } } } diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 14faac192d..789b02dac1 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -1431,11 +1431,14 @@ public ActionResult PostEdit(int id) return HttpNotFound(); } + var firstPost = post.ForumTopic.GetFirstPost(_forumService); + var model = new EditForumPostModel { Id = post.Id, IsEdit = true, Published = post.Published, + IsFirstPost = firstPost?.Id == post.Id, ForumTopicId = post.ForumTopic.Id, DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnForumPage, ForumEditor = _forumSettings.ForumEditor, @@ -1501,8 +1504,13 @@ public ActionResult PostEdit(EditForumPostModel model, bool captchaValid) var updateStatistics = false; if (customer.IsForumModerator()) { - updateStatistics = post.Published != model.Published; - post.Published = model.Published; + // Do not allow to unpublish first post. NumReplies would be wrong. Unpublish topic instead. + var firstPost = post.ForumTopic.GetFirstPost(_forumService); + if (firstPost?.Id != post.Id) + { + updateStatistics = post.Published != model.Published; + post.Published = model.Published; + } } post.Text = _forumSettings.PostMaxLength > 0 && model.Text.Length > _forumSettings.PostMaxLength diff --git a/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs index 4f8ac6cb96..18cc57c8a8 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/EditForumPostModel.cs @@ -14,6 +14,7 @@ public partial class EditForumPostModel : EntityModelBase public bool IsEdit { get; set; } public bool Published { get; set; } public bool DisplayCaptcha { get; set; } + public bool IsFirstPost { get; set; } [AllowHtml] public string Text { get; set; } diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml index e578941f00..27e14b7789 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_CreateUpdatePost.cshtml @@ -44,7 +44,7 @@
    - @if (Model.IsModerator) + @if (Model.IsModerator && !Model.IsFirstPost) {
    From 6adb4dbef60a4d50b4facb1113a905d6cb36a92d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 28 Sep 2018 11:31:16 +0200 Subject: [PATCH 149/932] Resolves forum paging issue --- .../SmartStore.Web/SmartStore.Web.csproj | 2 +- .../SmartStore.Web/Views/Boards/Forum.cshtml | 2 ++ .../Views/Boards/ForumGroup.cshtml | 2 +- .../SmartStore.Web/Views/Boards/Index.cshtml | 2 +- ...mGroupPartial.cshtml => ForumGroup.cshtml} | 0 .../Views/Boards/Partials/SearchHits.cshtml | 1 + .../Boards/Partials/_ActiveTopics.cshtml | 1 + .../Views/Boards/Partials/_ForumPost.cshtml | 19 +++++++++---------- .../SmartStore.Web/Views/Boards/Topic.cshtml | 7 ++++--- 9 files changed, 20 insertions(+), 16 deletions(-) rename src/Presentation/SmartStore.Web/Views/Boards/Partials/{_ForumGroupPartial.cshtml => ForumGroup.cshtml} (100%) diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index 5f87fa935b..2c5e2a69a4 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -1532,7 +1532,7 @@ - + diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml index 6ff88f5941..7027d0692c 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Forum.cshtml @@ -11,6 +11,7 @@ var pager = Html.SmartStore().Pager(Model.TopicPageIndex, Model.TopicPageSize, Model.TopicTotalRecords) .Name("paginator") + .QueryParam("page") .ShowSummary(false) .Route("ForumSlug", new RouteValues { id = Model.Id, slug = Model.SeName }) .Alignment(PagerAlignment.Right); @@ -134,6 +135,7 @@ @if (topic.NumPosts > Model.PostsPageSize) { @(Html.SmartStore().Pager(topic.TotalPostPages) + .QueryParam("page") .HtmlAttributes(new { @class = "topics-pager" }) .WithRenderer() .ShowPrevious(false) diff --git a/src/Presentation/SmartStore.Web/Views/Boards/ForumGroup.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/ForumGroup.cshtml index c96972078f..77316961a7 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/ForumGroup.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/ForumGroup.cshtml @@ -10,6 +10,6 @@ @{ Html.RenderAction("SearchBox", "Boards"); }
    - @{ Html.RenderPartial("_ForumGroupPartial", Model); } + @{ Html.RenderPartial("ForumGroup", Model); }
    diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Index.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Index.cshtml index d156ae5b45..465d4414f3 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Index.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Index.cshtml @@ -19,7 +19,7 @@ @foreach (var forumGroup in Model.ForumGroups) { - Html.RenderPartial("_ForumGroupPartial", forumGroup); + Html.RenderPartial("ForumGroup", forumGroup); } @{ Html.RenderAction("ActiveDiscussionsSmall"); } diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumGroupPartial.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/ForumGroup.cshtml similarity index 100% rename from src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumGroupPartial.cshtml rename to src/Presentation/SmartStore.Web/Views/Boards/Partials/ForumGroup.cshtml diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml index bc6375ca8b..c702fa4442 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml @@ -29,6 +29,7 @@ @if (topic.NumPosts > Model.PostsPageSize) { @(Html.SmartStore().Pager(topic.TotalPostPages) + .QueryParam("page") .HtmlAttributes(new { @class = "topics-pager" }) .WithRenderer() .ShowPrevious(false) diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml index f59ca9deb5..cf279dfce3 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ActiveTopics.cshtml @@ -91,6 +91,7 @@ @if (@topic.NumPosts > Model.PostsPageSize) { @(Html.SmartStore().Pager(topic.TotalPostPages) + .QueryParam("page") .HtmlAttributes(new { @class = "topics-pager" }) .WithRenderer() .ShowPrevious(false) diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml index e257858c2c..b271913bc1 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml @@ -20,23 +20,22 @@ @if (Model.IsCurrentCustomerAllowedToEditPost) { - @Html.ActionLink(@T("Forum.EditPost").Text, "PostEdit", new { id = Model.Id }, new { @class = "edit-post-link-button" }) + @Html.ActionLink(T("Forum.EditPost").Text, "PostEdit", new { id = Model.Id }, new { @class = "edit-post-link-button" }) } @if (Model.IsCurrentCustomerAllowedToDeletePost) { - @Html.ActionLink(@T("Forum.DeletePost").Text, "PostDelete", new { id = Model.Id }, new { onClick = String.Format("return confirm({0});", T("Common.AreYouSure").JsText), @class = "delete-post-link-button" }) + @Html.ActionLink(T("Forum.DeletePost").Text, "PostDelete", new { id = Model.Id }, new { onClick = "return confirm(" + T("Common.AreYouSure").JsText + ");", @class = "delete-post-link-button" }) } - - @if (@Model.CurrentTopicPage > 1) - { - @Html.RouteLink(String.Format("#{0}", Model.Id.ToString()), "TopicSlug", "http", string.Empty, Model.Id.ToString(), new { id = Model.ForumTopicId, slug = Model.ForumTopicSeName, page = Model.CurrentTopicPage }, new { @class = "post-link", title = T("Forum.PostLinkTitle") }) - } - else - { - @Html.RouteLink(String.Format("#{0}", Model.Id.ToString()), "TopicSlug", "http", string.Empty, Model.Id.ToString(), new { id = Model.ForumTopicId, slug = Model.ForumTopicSeName }, new { @class = "post-link pr-0", title = T("Forum.PostLinkTitle") }) + @{ + var postLinkRoutValues = Model.CurrentTopicPage == 1 + ? (object)(new { id = Model.ForumTopicId, slug = Model.ForumTopicSeName }) + : (object)(new { id = Model.ForumTopicId, slug = Model.ForumTopicSeName, page = Model.CurrentTopicPage }); } + + #@(Model.Id) +
    diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml index cd633b0952..ab9b4acda1 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml @@ -27,21 +27,21 @@
    @if (Model.IsCustomerAllowedToEditTopic) { - + @T("Forum.EditTopic") } @if (Model.IsCustomerAllowedToDeleteTopic) { - + @T("Forum.DeleteTopic") } @if (Model.IsCustomerAllowedToMoveTopic) { - + @T("Forum.MoveTopic") @@ -65,6 +65,7 @@ { var pager = Html.SmartStore().Pager(Model.PostsPageIndex, Model.PostsPageSize, Model.PostsTotalRecords) .Name("paginator") + .QueryParam("page") .Size(PagerSize.Small) .Alignment(PagerAlignment.Centered) .Route("TopicSlug", new RouteValues { id = Model.Id, slug = Model.SeName }); From 260d02ec65219eb2d315af75e2fdceeece110fb3 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 28 Sep 2018 13:56:12 +0200 Subject: [PATCH 150/932] Improved forum selection box when moving a topic --- changelog.md | 2 + .../Controllers/BoardsController.cs | 94 +++++++++---------- .../Models/Boards/TopicMoveModel.cs | 9 +- .../Views/Boards/TopicMove.cshtml | 9 +- 4 files changed, 51 insertions(+), 63 deletions(-) diff --git a/changelog.md b/changelog.md index 7da2864c7c..759bbdbfc1 100644 --- a/changelog.md +++ b/changelog.md @@ -18,6 +18,8 @@ * **Forum**: * Added option to display a captcha on forum pages when creating or replying to a topic. * #417 Restrict forum groups to specific customer roles. + * Added published property to forum topic and post. + * Several performance improvements. * **MegaSearch**: * Supports searching for forum posts. * #1172 Option to display related search terms on search page. diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 789b02dac1..51bb2e5537 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -150,17 +150,17 @@ private ForumRowModel PrepareForumRowModel(Forum forum, Dictionary x.Name), - Description = forumGroup.GetLocalized(x => x.Description), - SeName = forumGroup.GetSeName() + Id = group.Id, + Name = group.GetLocalized(x => x.Name), + Description = group.GetLocalized(x => x.Description), + SeName = group.GetSeName() }; - var lastPostIds = forumGroup.Forums + var lastPostIds = group.Forums .Where(x => x.LastPostId != 0) .Select(x => x.LastPostId) .Distinct() @@ -168,7 +168,7 @@ private ForumGroupModel PrepareForumGroupModel(ForumGroup forumGroup) var lastPosts = _forumService.GetPostsByIds(lastPostIds).ToDictionary(x => x.Id); - foreach (var forum in forumGroup.Forums) + foreach (var forum in group.Forums.OrderBy(x => x.DisplayOrder)) { var forumModel = PrepareForumRowModel(forum, lastPosts); forumModel.LastPost.ShowTopic = true; @@ -224,31 +224,6 @@ private IEnumerable ForumTopicTypesList() return list; } - private IEnumerable ForumGroupsForumsList() - { - var forumsList = new List(); - var separator = "--"; - var store = Services.StoreContext.CurrentStore; - var forumGroups = _forumService.GetAllForumGroups(store.Id); - - foreach (var fg in forumGroups) - { - // Add the forum group with Value of 0 so it won't be used as a target forum. - forumsList.Add(new SelectListItem { Text = fg.GetLocalized(x => x.Name), Value = "0" }); - - foreach (var f in fg.Forums) - { - forumsList.Add(new SelectListItem - { - Text = separator + f.GetLocalized(x => x.Name), - Value = f.Id.ToString() - }); - } - } - - return forumsList; - } - private void CreateForumBreadcrumb(ForumGroup group = null, Forum forum = null, ForumTopic topic = null) { _breadcrumb.Track(new MenuItem @@ -346,17 +321,17 @@ public ActionResult Index() } var store = Services.StoreContext.CurrentStore; - var forumGroups = _forumService.GetAllForumGroups(store.Id); + var groups = _forumService.GetAllForumGroups(store.Id); var model = new BoardsIndexModel { CurrentTime = _dateTimeHelper.ConvertToUserTime(DateTime.UtcNow) }; - foreach (var forumGroup in forumGroups) + foreach (var group in groups) { - var forumGroupModel = PrepareForumGroupModel(forumGroup); - model.ForumGroups.Add(forumGroupModel); + var groupModel = PrepareForumGroupModel(group); + model.ForumGroups.Add(groupModel); } return View(model); @@ -369,14 +344,14 @@ public ActionResult ForumGroup(int id) return HttpNotFound(); } - var forumGroup = _forumService.GetForumGroupById(id); - if (forumGroup == null || !_storeMappingService.Authorize(forumGroup) || !_aclService.Authorize(forumGroup)) + var group = _forumService.GetForumGroupById(id); + if (group == null || !_storeMappingService.Authorize(group) || !_aclService.Authorize(group)) { return HttpNotFound(); } - var model = PrepareForumGroupModel(forumGroup); - CreateForumBreadcrumb(group: forumGroup); + var model = PrepareForumGroupModel(group); + CreateForumBreadcrumb(group: group); return View(model); } @@ -514,10 +489,10 @@ public ActionResult ForumWatch(int id) return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } - var forumSubscription = _forumService.GetAllSubscriptions(customer.Id, forum.Id, 0, 0, 1).FirstOrDefault(); - if (forumSubscription == null) + var subscription = _forumService.GetAllSubscriptions(customer.Id, forum.Id, 0, 0, 1).FirstOrDefault(); + if (subscription == null) { - forumSubscription = new ForumSubscription + subscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), CustomerId = customer.Id, @@ -525,13 +500,13 @@ public ActionResult ForumWatch(int id) CreatedOnUtc = DateTime.UtcNow }; - _forumService.InsertSubscription(forumSubscription); + _forumService.InsertSubscription(subscription); subscribed = true; returnText = T("Forum.UnwatchForum"); } else { - _forumService.DeleteSubscription(forumSubscription); + _forumService.DeleteSubscription(subscription); subscribed = false; } @@ -836,7 +811,22 @@ public ActionResult TopicMove(int id) return new HttpUnauthorizedResult(); } - model.ForumList = ForumGroupsForumsList(); + // Forums select box. + model.Forums = new List(); + var groups = _forumService.GetAllForumGroups(Services.StoreContext.CurrentStore.Id); + foreach (var group in groups) + { + var optGroup = new SelectListGroup { Name = group.GetLocalized(x => x.Name) }; + foreach (var forum in group.Forums.OrderBy(x => x.DisplayOrder)) + { + model.Forums.Add(new SelectListItem + { + Text = forum.GetLocalized(x => x.Name), + Value = forum.Id.ToString(), + Group = optGroup + }); + } + } CreateForumBreadcrumb(topic: topic); @@ -1597,21 +1587,21 @@ public ActionResult PostDelete(int id) return new HttpUnauthorizedResult(); } - var forumTopic = post.ForumTopic; - var forumId = forumTopic.Forum.Id; - var forumSlug = forumTopic.Forum.GetSeName(); + var topic = post.ForumTopic; + var forumId = topic.Forum.Id; + var forumSlug = topic.Forum.GetSeName(); _forumService.DeletePost(post); // Get topic one more time because it can be deleted (first or only post deleted). - forumTopic = _forumService.GetTopicById(post.TopicId); - if (forumTopic == null) + topic = _forumService.GetTopicById(post.TopicId); + if (topic == null) { return RedirectToRoute("ForumSlug", new { id = forumId, slug = forumSlug }); } else { - return RedirectToRoute("TopicSlug", new { id = forumTopic.Id, slug = forumTopic.GetSeName() }); + return RedirectToRoute("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }); } } diff --git a/src/Presentation/SmartStore.Web/Models/Boards/TopicMoveModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/TopicMoveModel.cs index b784c10cbd..6a5dccb4cc 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/TopicMoveModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/TopicMoveModel.cs @@ -6,16 +6,11 @@ namespace SmartStore.Web.Models.Boards { public partial class TopicMoveModel : EntityModelBase { - public TopicMoveModel() - { - ForumList = new List(); - } - - public int ForumSelected { get; set; } public string TopicSeName { get; set; } public bool IsCustomerAllowedToEdit { get; set; } public int CustomerId { get; set; } - public IEnumerable ForumList { get; set; } + public int ForumSelected { get; set; } + public IList Forums { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.cshtml index 15e314e63c..0562526ec4 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.cshtml @@ -12,16 +12,17 @@

    @T("Forum.MoveTopic")

    -

    @T("Forum.SelectTheForumToMoveTopic")

    -
    - @Html.DropDownList("ForumSelected", new SelectList(Model.ForumList, "Value", "Text"), new { @class = "form-control" }) +
    + + @Html.DropDownListFor(x => x.ForumSelected, Model.Forums, new { @class = "form-control" })
    -
    + @T("Forum.Cancel")
    From 904f60cbf145032c0a3f341a5299c874f1253269 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 28 Sep 2018 16:09:02 +0200 Subject: [PATCH 151/932] Improved asynchronous loading of forum search hits --- .../Views/Boards/Partials/SearchHits.cshtml | 17 +++-- .../SmartStore.Web/Views/Boards/Search.cshtml | 67 +++++++++++++------ 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml index c702fa4442..5a5ec93ad6 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/SearchHits.cshtml @@ -76,13 +76,16 @@ @if (Model.PagedList.TotalCount == 0 || !Model.PagedList.HasNextPage) { -
    - @T("Search.NoMoreHitsFound") -
    + @T("Search.NoMoreHitsFound") +
    + @Html.SmartLabelFor(model => model.AllowCustomersToVoteOnPosts) + + @Html.SettingEditorFor(model => model.AllowCustomersToVoteOnPosts, Html.CheckBoxFor(x => x.AllowCustomersToVoteOnPosts, new { data_toggler_for = "#pnlForumsEnabled .post-voting" })) + @Html.ValidationMessageFor(model => model.AllowCustomersToVoteOnPosts) +
    + @Html.SmartLabelFor(model => model.AllowGuestsToVoteOnPosts) + + @Html.SettingEditorFor(model => model.AllowGuestsToVoteOnPosts) + @Html.ValidationMessageFor(model => model.AllowGuestsToVoteOnPosts) +
    @Html.SmartLabelFor(model => model.ForumEditor) diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 7eac114bea..35707cde1d 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -725,7 +725,15 @@ public ActionResult Topic(int id, int page = 1) if (postModel.AllowVoting) { - postModel.Vote = post.ForumPostVotes.FirstOrDefault(x => x.CustomerId == customer.Id)?.Vote ?? false; + if (!_forumSettings.AllowGuestsToVoteOnPosts && customer.IsGuest()) + { + postModel.AllowVoting = false; + } + else + { + postModel.Vote = post.ForumPostVotes.FirstOrDefault(x => x.CustomerId == customer.Id)?.Vote ?? false; + postModel.VoteCount = post.ForumPostVotes.Count; + } } postModel.PostCreatedOnStr = _forumSettings.RelativeDateTimeFormattingEnabled @@ -1642,6 +1650,8 @@ public ActionResult PostVote(int id, bool vote) } var voteEntity = post.ForumPostVotes.FirstOrDefault(x => x.CustomerId == customer.Id); + var voteCount = post.ForumPostVotes.Count; + if (vote) { if (voteEntity == null) @@ -1654,6 +1664,7 @@ public ActionResult PostVote(int id, bool vote) IpAddress = Services.WebHelper.GetCurrentIpAddress() }; _customerContentService.InsertCustomerContent(voteEntity); + ++voteCount; } else { @@ -1666,10 +1677,17 @@ public ActionResult PostVote(int id, bool vote) if (voteEntity != null) { _customerContentService.DeleteCustomerContent(voteEntity); + --voteCount; } } - return Json(new { success = true, message = T("Forum.Post.Vote.SuccessfullyVoted").Text }); + return Json(new + { + success = true, + message = T("Forum.Post.Vote.SuccessfullyVoted").Text, + voteCount, + voteCountString = voteCount.ToString("N0") + }); } #endregion diff --git a/src/Presentation/SmartStore.Web/Models/Boards/ForumPostModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/ForumPostModel.cs index 57ba6d5db5..5271f12c12 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/ForumPostModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/ForumPostModel.cs @@ -41,6 +41,7 @@ public partial class ForumPostModel : EntityModelBase public bool AllowVoting { get; set; } public bool Vote { get; set; } + public int VoteCount { get; set; } public CustomerAvatarModel Avatar { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_forum.scss b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_forum.scss index 971984f6e3..348b5848eb 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_forum.scss +++ b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_forum.scss @@ -92,13 +92,6 @@ $forum-unobstrusive-link-color: #646464; } } .post-content { - /* - .functions { - right: 10px; - bottom: 10px; - .fa-quote-left { font-size: 10px;} - } - */ .signature { position: absolute; bottom: 10px; @@ -110,7 +103,7 @@ $forum-unobstrusive-link-color: #646464; } } .post-info .user-info .user-stats, -.post-content .posttime { +.post-content .post-stats { font-size: $font-size-xs; } .posttext .quotefrom { @@ -122,9 +115,6 @@ $forum-unobstrusive-link-color: #646464; .posttext blockquote { margin-bottom: 0; } -.post-content .posttime { - margin-bottom: 5px; -} /* Forum Search ================================================ */ diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml index 411d957ad8..c1c5cf6cb9 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml @@ -85,9 +85,16 @@
    -
    - @T("Forum.Posted"): - @Model.PostCreatedOnStr +
    + + @T("Forum.Posted"): + @Model.PostCreatedOnStr + + + · + + @Model.VoteCount.ToString("N0") +
    diff --git a/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml index a670fe2f87..6596777acb 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/Topic.cshtml @@ -100,17 +100,25 @@ // Post voting. $('button.post-vote-button').on('click', function () { var self = $(this), - postId = self.closest('.forum-post').data('id'); + post = self.closest('.forum-post'); $.ajax({ type: 'POST', url: '@Url.Action("PostVote", "Boards")', - data: { "id": postId, "vote": self.attr('value') }, + data: { "id": post.data('id'), "vote": self.attr('value') }, cache: false, success: function (resp) { if (resp.success) { self.closest('div').find(self.hasClass('post-vote-up') ? '.post-vote-down' : '.post-vote-up').show(); self.hide(); + + // Update counter. + if (resp.voteCount > 0) { + post.find('.votes').show().find('.vote-count').text(resp.voteCountString); + } + else { + post.find('.votes').text(resp.voteCountString).hide(); + } } displayNotification(resp.message, resp.success ? 'success' : 'error'); From 73d14c2224238b741873b78286d06d7be1f38223 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 2 Oct 2018 12:49:42 +0200 Subject: [PATCH 164/932] Fixed typos --- .../Migrations/MigrationsConfiguration.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 980c0bb4fa..4449fab2e9 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -554,6 +554,19 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Gäste können Beiträge bewerten", "Specifies whether guests can vote on posts.", "Legt fest, ob Gäste Beiträge bewerten können."); + + // Typos. + builder.AddOrUpdate("Admin.Promotions.Discounts.Requirements") + .Value("de", "Voraussetzungen"); + builder.AddOrUpdate("Admin.Promotions.Discounts.Requirements.DiscountRequirementType") + .Value("de", "Typ der Voraussetzung"); + builder.AddOrUpdate("Admin.Promotions.Discounts.Requirements.DiscountRequirementType.Hint") + .Value("de", "Voraussetzungen für den Rabatt"); + builder.AddOrUpdate("Admin.Promotions.Discounts.Requirements.Remove") + .Value("de", "Voraussetzung für den Rabatt entfernen"); + builder.AddOrUpdate("Admin.Promotions.Discounts.Requirements.SaveBeforeEdit") + .Value("de", "Sie müssen den Rabatt zunächst speichern, bevor Sie Voraussetzungen für seine Anwendung festlegen können"); + } } } From 9f26066ca0217779ee6c40a0a8108ad0d41c8c79 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 2 Oct 2018 14:29:51 +0200 Subject: [PATCH 165/932] Fixed non-localized strings --- .../Administration/Controllers/ProductController.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 8466df6cd5..41dacdba20 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -3946,13 +3946,15 @@ public ActionResult CombinationExistenceNote(int productId, ProductVariantQuery public ActionResult DeleteDownloadVersion(int downloadId, int productId) { - if (downloadId == 0) - NotifySuccess("Der Download wurde nicht gefunden."); - var download = _downloadService.GetDownloadById(downloadId); + if (download == null) + { + return HttpNotFound(); + } + _downloadService.DeleteDownload(download); - NotifySuccess("Der Download wurde erfolgreich gelöscht."); + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); return RedirectToAction("Edit", new { id = productId }); } From d487e6f9483e3e1ae7c4fda2396612430d1e2a01 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 2 Oct 2018 15:06:36 +0200 Subject: [PATCH 166/932] Votes of forum posts are customer content thus subject to GDPR --- .../SmartStore.Services/Customers/GdprTool.cs | 13 ++++++++-- .../Messages/MessageModelProvider.cs | 25 ++++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Customers/GdprTool.cs b/src/Libraries/SmartStore.Services/Customers/GdprTool.cs index b8a52c9666..1c2cc5d174 100644 --- a/src/Libraries/SmartStore.Services/Customers/GdprTool.cs +++ b/src/Libraries/SmartStore.Services/Customers/GdprTool.cs @@ -25,6 +25,7 @@ using System.Net; using System.Net.Sockets; using SmartStore.Core.Logging; +using SmartStore.Core.Domain.Forums; namespace SmartStore.Services.Customers { @@ -151,8 +152,16 @@ public virtual IDictionary ExportCustomer(Customer customer) model["ForumPosts"] = forumPosts.Select(x => _messageModelProvider.CreateModelPart(x, true)).ToList(); } - // Product reviews - var productReviews = customer.CustomerContent.OfType(); + // Forum post votes + var forumPostVotes = customer.CustomerContent.OfType(); + if (forumPostVotes.Any()) + { + ignoreMemberNames = new string[] { "CustomerId", "UpdatedOn" }; + model["ForumPostVotes"] = forumPostVotes.Select(x => _messageModelProvider.CreateModelPart(x, true, ignoreMemberNames)).ToList(); + } + + // Product reviews + var productReviews = customer.CustomerContent.OfType(); if (productReviews.Any()) { model["ProductReviews"] = productReviews.Select(x => _messageModelProvider.CreateModelPart(x, true)).ToList(); diff --git a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs index ad818b0636..3485c46262 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs @@ -258,6 +258,9 @@ public virtual void AddModelPart(object part, MessageContext messageContext, str case ForumPost x: modelPart = CreateModelPart(x, messageContext); break; + case ForumPostVote x: + modelPart = CreateModelPart(x, messageContext); + break; case Forum x: modelPart = CreateModelPart(x, messageContext); break; @@ -887,7 +890,27 @@ protected virtual object CreateModelPart(ForumPost part, MessageContext messageC return m; } - protected virtual object CreateModelPart(Forum part, MessageContext messageContext) + protected virtual object CreateModelPart(ForumPostVote part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "ForumPostId", part.ForumPostId }, + { "Vote", part.Vote }, + { "TopicId", part.ForumPost.TopicId }, + { "TopicSubject", part.ForumPost.ForumTopic.Subject.NullEmpty() }, + }; + + ApplyCustomerContentPart(m, part, messageContext); + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(Forum part, MessageContext messageContext) { Guard.NotNull(messageContext, nameof(messageContext)); Guard.NotNull(part, nameof(part)); From 233fbcffaa535f66721a255193650ae9dce93803 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 2 Oct 2018 20:38:21 +0200 Subject: [PATCH 167/932] Resolves #1517 Creating a manufacturer throws an exception --- changelog.md | 1 + .../Administration/Models/Catalog/ManufacturerModel.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index d79f588e5b..4a7e2926b1 100644 --- a/changelog.md +++ b/changelog.md @@ -23,6 +23,7 @@ * Added option to display a captcha on forum pages when creating or replying to a topic. * #417 Restrict forum groups to specific customer roles. * Added published property to forum topic and post. + * Added voting for forum posts. * Several performance improvements. * **MegaSearch**: * Supports searching for forum posts. diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerModel.cs index 9642a98edb..727ac92048 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerModel.cs @@ -53,7 +53,7 @@ public ManufacturerModel() [UIHint("Picture")] [SmartResourceDisplayName("Admin.Catalog.Manufacturers.Fields.Picture")] - public int PictureId { get; set; } + public int? PictureId { get; set; } [SmartResourceDisplayName("Admin.Catalog.Manufacturers.Fields.PageSize")] public int? PageSize { get; set; } From 94cf628a31bb9bd2399a99a37e5d3458eac42bf6 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 2 Oct 2018 19:37:51 +0200 Subject: [PATCH 168/932] Code convention --- .../SmartStore.Core/Domain/Catalog/Product.cs | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs index a4bc4c5251..41a35ad433 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs @@ -22,32 +22,21 @@ public partial class Product : BaseEntity, IAuditable, ISoftDeletable, ILocalize { #region static - private static readonly HashSet _visibilityAffectingProductProps = new HashSet(); - - static Product() - { - AddPropsToSet(_visibilityAffectingProductProps, - x => x.AvailableEndDateTimeUtc, - x => x.AvailableStartDateTimeUtc, - x => x.Deleted, - x => x.LowStockActivityId, - x => x.LimitedToStores, - x => x.ManageInventoryMethodId, - x => x.MinStockQuantity, - x => x.Published, - x => x.SubjectToAcl, - x => x.VisibleIndividually); - } - - static void AddPropsToSet(HashSet props, params Expression>[] lambdas) + private static readonly HashSet _visibilityAffectingProductProps = new HashSet { - foreach (var lambda in lambdas) - { - props.Add(lambda.ExtractPropertyInfo().Name); - } - } - - public static HashSet GetVisibilityAffectingPropertyNames() + nameof(Product.AvailableEndDateTimeUtc), + nameof(Product.AvailableStartDateTimeUtc), + nameof(Product.Deleted), + nameof(Product.LowStockActivityId), + nameof(Product.LimitedToStores), + nameof(Product.ManageInventoryMethodId), + nameof(Product.MinStockQuantity), + nameof(Product.Published), + nameof(Product.SubjectToAcl), + nameof(Product.VisibleIndividually) + }; + + public static IReadOnlyCollection GetVisibilityAffectingPropertyNames() { return _visibilityAffectingProductProps; } From 4c278344f65451f2bb662396031535cbb0bd0ec9 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 3 Oct 2018 03:31:58 +0200 Subject: [PATCH 169/932] Minor refactoring --- .../Domain/Catalog/SpecificationAttributeOption.cs | 2 +- .../UI/Blocks/BlockHandlerBase.cs | 2 +- .../SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs | 9 ++++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttributeOption.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttributeOption.cs index 64827d8ce4..47b5a55cef 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttributeOption.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttributeOption.cs @@ -50,7 +50,7 @@ public partial class SpecificationAttributeOption : BaseEntity, ILocalizedEntity public virtual SpecificationAttribute SpecificationAttribute { get; set; } /// - /// Gets or sets the product specification attribute + /// Gets or sets the product specification attributes /// [DataMember] public virtual ICollection ProductSpecificationAttributes diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs index 0d395920de..b94ab59af3 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs @@ -25,7 +25,7 @@ public virtual T Create(IBlockEntity entity) return Activator.CreateInstance(); } - public virtual T Load(IBlockEntity entity, bool editMode) + public virtual T Load(IBlockEntity entity, StoryViewMode viewMode) { Guard.NotNull(entity, nameof(entity)); diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs index 5032cb037e..370a4e1b42 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs @@ -4,6 +4,13 @@ namespace SmartStore.Web.Framework.UI.Blocks { + public enum StoryViewMode + { + Public, + Preview, + Edit + } + public interface IBlockHandler { void Render(IBlockContainer element, string[] templates, HtmlHelper htmlHeper); @@ -13,7 +20,7 @@ public interface IBlockHandler public interface IBlockHandler : IBlockHandler where T : IBlock { T Create(IBlockEntity entity); - T Load(IBlockEntity entity, bool editMode); + T Load(IBlockEntity entity, StoryViewMode viewMode); void Save(T block, IBlockEntity entity); } } From 512fd4c993bbab113dff3b525882c78a97813b79 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 3 Oct 2018 11:16:17 +0200 Subject: [PATCH 170/932] SQL CE: Fixes SqlCeException in CatalogSearchQueryAliasMapper. Resolves #1516. --- .../Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs index 67c3ad8337..6fbc08bc10 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs @@ -70,7 +70,7 @@ protected string CreateOptionKey(string prefix, int languageId, int optionId) protected void CachedLocalizedAlias(string localeKeyGroup, Action caching) { _localizedPropertyRepository.TableUntracked - .Where(x => x.LocaleKeyGroup == localeKeyGroup && x.LocaleKey == "Alias" && x.LocaleValue != null && x.LocaleValue != string.Empty) + .Where(x => x.LocaleKeyGroup == localeKeyGroup && x.LocaleKey == "Alias" && x.LocaleValue != null) .ToList() .ForEach(caching); } From daf9a9cf9ae71fee77a34a0ff56f0f67a6dbb0f7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 3 Oct 2018 11:20:51 +0200 Subject: [PATCH 171/932] SQL CE: DropIndex fails during installation --- .../SmartStore.Data/Migrations/201609201852449_log4net.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs index 640f6fca9e..5eda541bc0 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs @@ -22,7 +22,8 @@ public override void Up() else { Sql(@"SET LOCK_TIMEOUT 20000;"); - DropIndex("Log", "IX_Log_ContentHash"); + // SQL CE: DropIndex fails during installation, IF EXISTS not supported. + //DropIndex("Log", "IX_Log_ContentHash"); Sql(@"DELETE FROM Log;"); } } From b72df747c3870c85c0b486da4b1074a616185a90 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 4 Oct 2018 09:30:15 +0200 Subject: [PATCH 172/932] More on SqlCeException in CatalogSearchQueryAliasMapper fix --- .../Modelling/CatalogSearchQueryAliasMapper.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs index 6fbc08bc10..be969584e7 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs @@ -69,10 +69,15 @@ protected string CreateOptionKey(string prefix, int languageId, int optionId) protected void CachedLocalizedAlias(string localeKeyGroup, Action caching) { - _localizedPropertyRepository.TableUntracked - .Where(x => x.LocaleKeyGroup == localeKeyGroup && x.LocaleKey == "Alias" && x.LocaleValue != null) - .ToList() - .ForEach(caching); + var properties = _localizedPropertyRepository.TableUntracked + .Where(x => x.LocaleKeyGroup == localeKeyGroup && x.LocaleKey == "Alias") + .ToList(); + + // SQL CE: leads to an error when checked in the query. + properties + .Where(x => !string.IsNullOrWhiteSpace(x.LocaleValue)) + .ToList() + .ForEach(caching); } #region Specification Attributes From 80d528eeae0bc1d0c24038d8809148308f4b387f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 4 Oct 2018 12:15:11 +0200 Subject: [PATCH 173/932] Resolves #1515 Poll: Add result tab with a list of answers and customers for a poll --- changelog.md | 3 +- .../Migrations/MigrationsConfiguration.cs | 2 + .../SmartStore.Services/Polls/IPollService.cs | 9 ++ .../SmartStore.Services/Polls/PollService.cs | 23 ++++ .../Controllers/PollController.cs | 106 ++++++++++++------ .../Administration/Models/Polls/PollModel.cs | 3 + .../Models/Polls/PollVotingRecordModel.cs | 28 +++++ .../Administration/SmartStore.Admin.csproj | 1 + .../Views/Poll/_CreateOrUpdate.cshtml | 54 ++++++--- 9 files changed, 183 insertions(+), 46 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Administration/Models/Polls/PollVotingRecordModel.cs diff --git a/changelog.md b/changelog.md index 4a7e2926b1..58e3aa514c 100644 --- a/changelog.md +++ b/changelog.md @@ -30,7 +30,8 @@ * #1172 Option to display related search terms on search page. * Customer avatar: Letter with colored background if no avatar image was uploaded. * Viveum: Supports payment via "Virtual Account Brands" like PayPal. -* Added options for alternating price display (in badges) +* Added options for alternating price display (in badges). +* #1515 Poll: Add result tab with a list of answers and customers for a poll ### Improvements * (Perf) Significantly increased query performance for products with a lot of category assignments (> 10). diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 4449fab2e9..c3970d15b8 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -567,6 +567,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Promotions.Discounts.Requirements.SaveBeforeEdit") .Value("de", "Sie müssen den Rabatt zunächst speichern, bevor Sie Voraussetzungen für seine Anwendung festlegen können"); + builder.AddOrUpdate("Common.Voting", "Voting", "Abstimmung"); + builder.AddOrUpdate("Common.Answer", "Answer", "Antwort"); } } } diff --git a/src/Libraries/SmartStore.Services/Polls/IPollService.cs b/src/Libraries/SmartStore.Services/Polls/IPollService.cs index fb5929cf51..bdf19bab73 100644 --- a/src/Libraries/SmartStore.Services/Polls/IPollService.cs +++ b/src/Libraries/SmartStore.Services/Polls/IPollService.cs @@ -72,5 +72,14 @@ public partial interface IPollService /// Customer identifier /// Result bool AlreadyVoted(int pollId, int customerId); + + /// + /// Get voting records for a poll. + /// + /// Poll identifier. + /// Page index. + /// Page size. + /// List of voting records. + IPagedList GetVotingRecords(int pollId, int pageIndex, int pageSize); } } diff --git a/src/Libraries/SmartStore.Services/Polls/PollService.cs b/src/Libraries/SmartStore.Services/Polls/PollService.cs index 327d3f3075..e12cb2f6af 100644 --- a/src/Libraries/SmartStore.Services/Polls/PollService.cs +++ b/src/Libraries/SmartStore.Services/Polls/PollService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using SmartStore.Core; using SmartStore.Core.Caching; @@ -164,5 +165,27 @@ join pvr in _pollVotingRecords.Table on pa.Id equals pvr.PollAnswerId select pvr).Count() > 0; return result; } + + public virtual IPagedList GetVotingRecords(int pollId, int pageIndex, int pageSize) + { + if (pollId == 0) + { + return new PagedList(new List(), pageIndex, pageSize); + } + + var query = + from pa in _pollAnswerRepository.TableUntracked + join pvr in _pollVotingRecords.TableUntracked on pa.Id equals pvr.PollAnswerId + where pa.PollId == pollId + orderby pa.DisplayOrder, pvr.CreatedOnUtc descending + select pvr; + + query = query + .Expand(x => x.Customer) + .Expand(x => x.PollAnswer); + + var votings = new PagedList(query, pageIndex, pageSize); + return votings; + } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs index a01316b5fd..3825a02d75 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs @@ -3,56 +3,54 @@ using System.Web.Mvc; using SmartStore.Admin.Models.Polls; using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Polls; +using SmartStore.Services.Customers; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Polls; using SmartStore.Services.Security; using SmartStore.Services.Stores; +using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; -using SmartStore.Web.Framework; namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public class PollController : AdminControllerBase { - #region Fields - private readonly IPollService _pollService; private readonly ILanguageService _languageService; private readonly IDateTimeHelper _dateTimeHelper; - private readonly ILocalizationService _localizationService; private readonly IPermissionService _permissionService; private readonly AdminAreaSettings _adminAreaSettings; private readonly IStoreService _storeService; private readonly IStoreMappingService _storeMappingService; - - #endregion - - #region Constructors - - public PollController(IPollService pollService, ILanguageService languageService, - IDateTimeHelper dateTimeHelper, ILocalizationService localizationService, - IPermissionService permissionService, AdminAreaSettings adminAreaSettings, + private readonly CustomerSettings _customerSettings; + + public PollController( + IPollService pollService, + ILanguageService languageService, + IDateTimeHelper dateTimeHelper, + IPermissionService permissionService, + AdminAreaSettings adminAreaSettings, IStoreService storeService, - IStoreMappingService storeMappingService) + IStoreMappingService storeMappingService, + CustomerSettings customerSettings) { - this._pollService = pollService; - this._languageService = languageService; - this._dateTimeHelper = dateTimeHelper; - this._localizationService = localizationService; - this._permissionService = permissionService; - this._adminAreaSettings = adminAreaSettings; - this._storeService = storeService; - this._storeMappingService = storeMappingService; + _pollService = pollService; + _languageService = languageService; + _dateTimeHelper = dateTimeHelper; + _permissionService = permissionService; + _adminAreaSettings = adminAreaSettings; + _storeService = storeService; + _storeMappingService = storeMappingService; + _customerSettings = customerSettings; } - #endregion  - #region Utilities private void PreparePollModel(PollModel model, Poll poll, bool excludeProperties) @@ -65,6 +63,8 @@ private void PreparePollModel(PollModel model, Poll poll, bool excludeProperties } model.AvailableStores = _storeService.GetAllStores().ToSelectListItems(model.SelectedStoreIds); + model.UsernamesEnabled = _customerSettings.UsernamesEnabled; + model.GridPageSize = _adminAreaSettings.GridPageSize; } #endregion Utilities @@ -170,11 +170,11 @@ public ActionResult Create(PollModel model, bool continueEditing) SaveStoreMappings(poll, model); - NotifySuccess(_localizationService.GetResource("Admin.ContentManagement.Polls.Added")); + NotifySuccess(T("Admin.ContentManagement.Polls.Added")); return continueEditing ? RedirectToAction("Edit", new { id = poll.Id }) : RedirectToAction("List"); } - //If we got this far, something failed, redisplay form + // If we got this far, something failed, redisplay form. ViewBag.AllLanguages = _languageService.GetAllLanguages(true); PreparePollModel(model, null, true); @@ -221,11 +221,11 @@ public ActionResult Edit(PollModel model, bool continueEditing) SaveStoreMappings(poll, model); - NotifySuccess(_localizationService.GetResource("Admin.ContentManagement.Polls.Updated")); + NotifySuccess(T("Admin.ContentManagement.Polls.Updated")); return continueEditing ? RedirectToAction("Edit", new { id = poll.Id }) : RedirectToAction("List"); } - //If we got this far, something failed, redisplay form + // If we got this far, something failed, redisplay form. ViewBag.AllLanguages = _languageService.GetAllLanguages(true); PreparePollModel(model, poll, true); @@ -245,7 +245,7 @@ public ActionResult DeleteConfirmed(int id) _pollService.DeletePoll(poll); - NotifySuccess(_localizationService.GetResource("Admin.ContentManagement.Polls.Deleted")); + NotifySuccess(T("Admin.ContentManagement.Polls.Deleted")); return RedirectToAction("List"); } @@ -291,7 +291,6 @@ public ActionResult PollAnswers(int pollId, GridCommand command) }; } - [GridAction(EnableCustomBinding = true)] public ActionResult PollAnswerUpdate(PollAnswerModel model, GridCommand command) { @@ -301,7 +300,7 @@ public ActionResult PollAnswerUpdate(PollAnswerModel model, GridCommand command) { if (!ModelState.IsValid) { - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + var modelStateErrors = ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); return Content(modelStateErrors.FirstOrDefault()); } @@ -339,7 +338,6 @@ public ActionResult PollAnswerAdd(int pollId, PollAnswerModel model, GridCommand return PollAnswers(pollId, command); } - [GridAction(EnableCustomBinding = true)] public ActionResult PollAnswerDelete(int id, GridCommand command) { @@ -355,5 +353,49 @@ public ActionResult PollAnswerDelete(int id, GridCommand command) } #endregion + + #region Voting records + + [HttpPost, GridAction(EnableCustomBinding = true)] + public ActionResult VotingRecords(int pollId, GridCommand command) + { + var model = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) + { + var guestString = T("Admin.Customers.Guest").Text; + var votings = _pollService.GetVotingRecords(pollId, command.Page - 1, command.PageSize); + + model.Data = votings.Select(x => + { + var votingModel = new PollVotingRecordModel + { + Id = x.Id, + CustomerId = x.CustomerId, + IsGuest = x.Customer.IsGuest(), + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), + AnswerName = x.PollAnswer.Name, + Username = x.Customer.Username, + FullName = x.Customer.GetFullName() + }; + + votingModel.Email = x.Customer.Email.HasValue() ? x.Customer.Email : (votingModel.IsGuest ? guestString : "".NaIfEmpty()); + + return votingModel; + }); + + model.Total = votings.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + + return new JsonResult { Data = model }; + } + + #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollModel.cs index ca5a995cde..7638748736 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollModel.cs @@ -12,6 +12,9 @@ namespace SmartStore.Admin.Models.Polls [Validator(typeof(PollValidator))] public class PollModel : EntityModelBase, IStoreSelector { + public bool UsernamesEnabled { get; set; } + public int GridPageSize { get; set; } + [SmartResourceDisplayName("Admin.ContentManagement.Polls.Fields.Language")] public int LanguageId { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollVotingRecordModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollVotingRecordModel.cs new file mode 100644 index 0000000000..b504fbe55a --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollVotingRecordModel.cs @@ -0,0 +1,28 @@ +using System; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.Polls +{ + public class PollVotingRecordModel : EntityModelBase + { + public int CustomerId { get; set; } + public bool IsGuest { get; set; } + + [SmartResourceDisplayName("Common.CreatedOn")] + public DateTime CreatedOn { get; set; } + + [SmartResourceDisplayName("Common.Answer")] + public string AnswerName { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.Email")] + public string Email { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.Username")] + public string Username { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.FullName")] + public string FullName { get; set; } + + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index d12a93a5f4..1b53de7ab0 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -296,6 +296,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Poll/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Poll/_CreateOrUpdate.cshtml index 3dd7be83a7..b0688dcb15 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Poll/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Poll/_CreateOrUpdate.cshtml @@ -1,14 +1,15 @@ -@model PollModel -@using Telerik.Web.Mvc.UI; +@using Telerik.Web.Mvc.UI; +@model PollModel + @Html.ValidationSummary(true) @Html.HiddenFor(model => model.Id) @Html.SmartStore().TabStrip().Name("poll-edit").Style(TabsStyle.Material).Items(x => { x.Add().Text(T("Admin.ContentManagement.Polls.Info").Text).Content(TabInfo()).Selected(true); x.Add().Text(T("Admin.ContentManagement.Polls.Answers").Text).Content(TabAnswers()); + x.Add().Text(T("Common.Voting").Text).Content(TabVoting()); x.Add().Text(T("Admin.Common.Stores").Text).Content(TabStores()); - //generate an event EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "poll-edit", this.Html, this.Model)); }) @@ -116,9 +117,11 @@ .Width("60%"); columns.Bound(x => x.NumberOfVotes) .ReadOnly() - .Width("10%"); + .Width("10%") + .Centered(); columns.Bound(x => x.DisplayOrder1) - .Width("10%"); + .Width("10%") + .Centered(); columns.Command(commands => { commands.Edit().Localize(T); @@ -139,15 +142,7 @@ .Delete("PollAnswerDelete", "Poll") .Insert("PollAnswerAdd", "Poll", new { pollId = Model.Id }); }) - .ClientEvents(x => x.OnError("grid_onError")) .EnableCustomBinding(true)) - -
    } else @@ -156,6 +151,39 @@ } } +@helper TabVoting() +{ + if (Model.Id > 0) + { +
    + @(Html.Telerik().Grid() + .Name("voting-records-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("Id"); + }) + .Columns(columns => + { + columns.Bound(x => x.Email) + .Template(x => Html.ActionLink(x.Email, "Edit", "Customer", new { id = x.CustomerId }, new { })) + .ClientTemplate("\"><#= Email #>"); + columns.Bound(x => x.Username) + .Visible(Model.UsernamesEnabled); + columns.Bound(x => x.FullName); + columns.Bound(x => x.AnswerName); + columns.Bound(x => x.CreatedOn); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("VotingRecords", "Poll", new { pollId = Model.Id })) + .EnableCustomBinding(true)) +
    + } + else + { + @T("Admin.ContentManagement.Polls.Answers.SaveBeforeEdit") + } +} + @helper TabStores() { @Html.Partial("StoreSelector", Model) From 32e1e2836d8012b23bb4d335f6216dcce768f85e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 4 Oct 2018 16:09:10 +0200 Subject: [PATCH 174/932] Minor improvements --- .../Setup/SeedData/InvariantSeedData.cs | 34 +++++++------------ .../Controllers/PollController.cs | 25 ++++++-------- .../Infrastructure/AutoMapperAdminProfile.cs | 4 ++- .../Administration/Models/Polls/PollModel.cs | 1 + .../Views/Poll/_CreateOrUpdate.cshtml | 2 +- 5 files changed, 28 insertions(+), 38 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs index e61e2c513b..a7947fd324 100644 --- a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs +++ b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs @@ -13306,83 +13306,73 @@ public IList PollAnswers() public IList Polls() { var defaultLanguage = _ctx.Set().FirstOrDefault(); - var poll1 = new Poll() + var poll1 = new Poll { Language = defaultLanguage, Name = "How do you like the shop?", - SystemKeyword = "RightColumnPoll", + SystemKeyword = "Blog", Published = true, DisplayOrder = 1, }; - poll1.PollAnswers.Add(new PollAnswer() + poll1.PollAnswers.Add(new PollAnswer { Name = "Excellent", DisplayOrder = 1, }); - poll1.PollAnswers.Add(new PollAnswer() + poll1.PollAnswers.Add(new PollAnswer { Name = "Good", DisplayOrder = 2, }); - poll1.PollAnswers.Add(new PollAnswer() + poll1.PollAnswers.Add(new PollAnswer { Name = "Poor", DisplayOrder = 3, }); - poll1.PollAnswers.Add(new PollAnswer() + poll1.PollAnswers.Add(new PollAnswer { Name = "Very bad", DisplayOrder = 4, }); - //_pollAnswerRepository.Table.Where(x => x.DisplayOrder < 5).Each(y => - // { - // poll1.PollAnswers.Add(y); - // }); - - var poll2 = new Poll() + var poll2 = new Poll { Language = defaultLanguage, Name = "How often do you buy online?", - SystemKeyword = "RightColumnPoll", + SystemKeyword = "Blog", Published = true, DisplayOrder = 2, }; - poll2.PollAnswers.Add(new PollAnswer() + poll2.PollAnswers.Add(new PollAnswer { Name = "Daily", DisplayOrder = 1, }); - poll2.PollAnswers.Add(new PollAnswer() + poll2.PollAnswers.Add(new PollAnswer { Name = "Once a week", DisplayOrder = 2, }); - poll2.PollAnswers.Add(new PollAnswer() + poll2.PollAnswers.Add(new PollAnswer { Name = "Every two weeks", DisplayOrder = 3, }); - poll2.PollAnswers.Add(new PollAnswer() + poll2.PollAnswers.Add(new PollAnswer { Name = "Once a month", DisplayOrder = 4, }); - //_pollAnswerRepository.Table.Where(x => x.DisplayOrder > 4).Each(y => - //{ - // poll2.PollAnswers.Add(y); - //}); - var entities = new List { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs index 3825a02d75..53b4748fb5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs @@ -65,7 +65,11 @@ private void PreparePollModel(PollModel model, Poll poll, bool excludeProperties model.AvailableStores = _storeService.GetAllStores().ToSelectListItems(model.SelectedStoreIds); model.UsernamesEnabled = _customerSettings.UsernamesEnabled; model.GridPageSize = _adminAreaSettings.GridPageSize; - } + + model.AvailableLanguages = _languageService.GetAllLanguages(true) + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + } #endregion Utilities @@ -143,11 +147,11 @@ public ActionResult Create() if (!_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) return AccessDeniedView(); - ViewBag.AllLanguages = _languageService.GetAllLanguages(true); - - var model = new PollModel(); - model.Published = true; - model.ShowOnHomePage = true; + var model = new PollModel + { + Published = true, + ShowOnHomePage = true + }; PreparePollModel(model, null, false); @@ -174,11 +178,8 @@ public ActionResult Create(PollModel model, bool continueEditing) return continueEditing ? RedirectToAction("Edit", new { id = poll.Id }) : RedirectToAction("List"); } - // If we got this far, something failed, redisplay form. - ViewBag.AllLanguages = _languageService.GetAllLanguages(true); - + // If we got this far, something failed, redisplay form. PreparePollModel(model, null, true); - return View(model); } @@ -191,7 +192,6 @@ public ActionResult Edit(int id) if (poll == null) return RedirectToAction("List"); - ViewBag.AllLanguages = _languageService.GetAllLanguages(true); var model = poll.ToModel(); model.StartDate = poll.StartDateUtc; model.EndDate = poll.EndDateUtc; @@ -226,10 +226,7 @@ public ActionResult Edit(PollModel model, bool continueEditing) } // If we got this far, something failed, redisplay form. - ViewBag.AllLanguages = _languageService.GetAllLanguages(true); - PreparePollModel(model, poll, true); - return View(model); } diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs index 7906ddcbea..068081f3dd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs @@ -498,7 +498,9 @@ public AutoMapperAdminProfile() .ForMember(dest => dest.StartDate, mo => mo.Ignore()) .ForMember(dest => dest.EndDate, mo => mo.Ignore()) .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) - .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()); + .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) + .ForMember(dest => dest.UsernamesEnabled, mo => mo.Ignore()) + .ForMember(dest => dest.GridPageSize, mo => mo.Ignore()); CreateMap() .ForMember(dest => dest.PollAnswers, mo => mo.Ignore()) .ForMember(dest => dest.Language, mo => mo.Ignore()) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollModel.cs index 7638748736..30fef1abf8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollModel.cs @@ -17,6 +17,7 @@ public class PollModel : EntityModelBase, IStoreSelector [SmartResourceDisplayName("Admin.ContentManagement.Polls.Fields.Language")] public int LanguageId { get; set; } + public List AvailableLanguages { get; set; } [SmartResourceDisplayName("Admin.ContentManagement.Polls.Fields.Language")] [AllowHtml] diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Poll/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Poll/_CreateOrUpdate.cshtml index b0688dcb15..b6889cc50f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Poll/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Poll/_CreateOrUpdate.cshtml @@ -21,7 +21,7 @@ @Html.SmartLabelFor(model => model.LanguageId)
    - @Html.DropDownListFor(model => model.LanguageId, new SelectList(ViewBag.AllLanguages, "Id", "Name")) + @Html.DropDownListFor(model => model.LanguageId, Model.AvailableLanguages)
    @Html.SettingEditorFor(model => model.CompanyInformationSettings.Salutation, - Html.DropDownListFor(model => model.CompanyInformationSettings.Salutation, Model.CompanyInformationSettings.Salutations)) + Html.DropDownListFor(model => model.CompanyInformationSettings.Salutation, Model.CompanyInformationSettings.Salutations, T("Common.Unspecified"))) @Html.ValidationMessageFor(model => model.CompanyInformationSettings.Salutation)
    @Html.SettingEditorFor(model => model.CompanyInformationSettings.CompanyManagementDescription, - Html.DropDownListFor(model => model.CompanyInformationSettings.CompanyManagementDescription, Model.CompanyInformationSettings.ManagementDescriptions)) + Html.DropDownListFor(model => model.CompanyInformationSettings.CompanyManagementDescription, Model.CompanyInformationSettings.ManagementDescriptions, T("Common.Unspecified"))) @Html.ValidationMessageFor(model => model.CompanyInformationSettings.CompanyManagementDescription)
    @Html.SettingEditorFor(model => model.CompanyInformationSettings.CountryId, - Html.DropDownListFor(model => model.CompanyInformationSettings.CountryId, Model.CompanyInformationSettings.AvailableCountries)) + Html.DropDownListFor(model => model.CompanyInformationSettings.CountryId, Model.CompanyInformationSettings.AvailableCountries, T("Common.Unspecified"))) @Html.ValidationMessageFor(model => model.CompanyInformationSettings.CountryId)
    - @Html.DropDownListFor(model => model.TaxCategoryId, Model.AvailableTaxCategories, new { placeholder = T("Common.PleaseSelect").Text }) + @Html.DropDownListFor(model => model.TaxCategoryId, Model.AvailableTaxCategories) @Html.ValidationMessageFor(model => model.TaxCategoryId)
    + @Html.SmartLabelFor(x => x.ExportRelatedData) + + @Html.EditorFor(x => x.ExportRelatedData) + @Html.ValidationMessageFor(x => x.ExportRelatedData) +
    + @Html.SmartLabelFor(x => x.ImportRelatedData) + + @Html.EditorFor(x => x.ImportRelatedData) + @Html.ValidationMessageFor(x => x.ImportRelatedData) +
    @Html.SmartLabelFor(x => x.Name) From 210d680dad571de7e94f4a88f542ac47f0d1d290 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 23 Oct 2018 21:04:58 +0200 Subject: [PATCH 221/932] Minor code improvement --- .../Controllers/RoxyFileManagerController.cs | 32 ++++++++++++------- .../Views/Country/_CreateOrUpdate.cshtml | 2 +- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs index 2f20cb39dc..cf667f9bab 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs @@ -118,21 +118,29 @@ private string GetFileContentType(string extension) var mimeType = MimeTypes.MapNameToMimeType(extension).EmptyNull(); if (extension == ".swf" || extension == ".flv") - return "flash"; - - if (mimeType.StartsWith("image")) - return "image"; - - if (mimeType.StartsWith("audio")) + { + return "flash"; + } + else if (mimeType.StartsWith("image")) + { + return "image"; + } + else if (mimeType.StartsWith("audio")) + { return "audio"; - - if (mimeType.StartsWith("video")) + } + else if (mimeType.StartsWith("video")) + { return "video"; - - if (mimeType.EndsWith("/pdf")) + } + else if (mimeType.EndsWith("/pdf")) + { return "pdf"; - - return "file"; + } + else + { + return "file"; + } } private bool IsAllowedFileType(string extension) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Country/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Country/_CreateOrUpdate.cshtml index 1cbfdc8882..e4b935a697 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Country/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Country/_CreateOrUpdate.cshtml @@ -169,7 +169,7 @@ -} \ No newline at end of file + + From 35a5eeb6962258fb1887c406a836def5a48b7ba3 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 21 Nov 2018 13:17:55 +0100 Subject: [PATCH 313/932] Export: Testing and correcting the updated data exporter --- .../Extensions/EnumerableExtensions.cs | 30 ++++++++++++ .../DataExchange/Export/DataExporter.cs | 47 ++++++++++++------- .../Deployment/FileSystemFilePublisher.cs | 2 +- .../Deployment/PublicFolderPublisher.cs | 4 +- 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs index f345fa1b19..a711c68aa7 100644 --- a/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs @@ -394,5 +394,35 @@ public static string BuildQueryString(this NameValueCollection nvc, Encoding enc } #endregion + + #region List + + /// + /// Safe way to remove selected entries from a list. + /// + /// To be used for materialized lists only, not IEnumerable or similar. + /// Object type. + /// List. + /// Selector for the entries to be removed. + /// Number of removed entries. + public static int Remove(this IList list, Func selector) + { + Guard.NotNull(list, nameof(list)); + Guard.NotNull(selector, nameof(selector)); + + var count = 0; + for (var i = list.Count - 1; i >= 0; i--) + { + if (selector(list[i])) + { + list.RemoveAt(i); + ++count; + } + } + + return count; + } + + #endregion } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index ab29740212..811dfa2abe 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -314,7 +314,6 @@ private void StreamToFile(DataExporterContext ctx, Stream stream, string path, A using (_rwLock.GetWriteLock()) using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) { - ctx.Log.Info($"Creating file {path}."); stream.CopyTo(fileStream); } } @@ -323,7 +322,7 @@ private void StreamToFile(DataExporterContext ctx, Stream stream, string path, A { ctx.ExecuteContext.Abort = DataExchangeAbortion.Hard; ctx.Log.ErrorFormat(ex, $"Failed to stream file {path}."); - ctx.Result.LastError = ex.ToString(); + ctx.Result.LastError = ex.ToAllMessages(true); } finally { @@ -640,6 +639,7 @@ private bool CallProvider(DataExporterContext ctx, string method, string path) } var context = ctx.ExecuteContext; + var provider = ctx.Request.Provider.Value; try { @@ -647,18 +647,18 @@ private bool CallProvider(DataExporterContext ctx, string method, string path) if (method == "Execute") { - ctx.Request.Provider.Value.Execute(context); + provider.Execute(context); } else if (method == "OnExecuted") { - ctx.Request.Provider.Value.OnExecuted(context); + provider.OnExecuted(context); } } catch (Exception ex) { context.Abort = DataExchangeAbortion.Hard; ctx.Log.ErrorFormat(ex, $"The provider failed at the {method.NaIfEmpty()} method."); - ctx.Result.LastError = ex.ToString(); + ctx.Result.LastError = ex.ToAllMessages(true); } finally { @@ -666,14 +666,20 @@ private bool CallProvider(DataExporterContext ctx, string method, string path) if (method == "Execute") { - ctx.Log.Info($"Provider reports {context.RecordsSucceeded.ToString("N0")} successfully exported record(s) of type {ctx.Request.Provider.Value.EntityType.ToString()}."); + var sb = new StringBuilder(); + sb.Append($"Provider reports {context.RecordsSucceeded.ToString("N0")} successfully exported record(s) of type {provider.EntityType.ToString()} to {path.NaIfEmpty()}."); - foreach (var unit in context.ExtraDataUnits.Where(x => x.RelatedType.HasValue)) + foreach (var unit in context.ExtraDataUnits.Where(x => x.RelatedType.HasValue && x.DataStream != null)) { - StreamToFile(ctx, unit.DataStream, Path.Combine(context.Folder, unit.FileName), x => unit.DataStream = null); + // Set unit.DataStream to null so that providers know that this unit should no longer be written to. + // We need these units later for ExportProfile.ResultInfo. + var unitPath = Path.Combine(context.Folder, unit.FileName); + StreamToFile(ctx, unit.DataStream, unitPath, x => unit.DataStream = null); - ctx.Log.Info($"Provider reports {unit.RecordsSucceeded.ToString("N0")} successfully exported record(s) of type {unit.RelatedType.Value.ToString()}."); + sb.Append($"\r\nProvider reports {unit.RecordsSucceeded.ToString("N0")} successfully exported record(s) of type {unit.RelatedType.Value.ToString()} to {unitPath.NaIfEmpty()}."); } + + ctx.Log.Info(sb.ToString()); } } @@ -741,10 +747,10 @@ private bool Deploy(DataExporterContext ctx, string zipPath) if (context.Result != null) { - context.Result.LastError = ex.ToAllMessages(); + context.Result.LastError = ex.ToAllMessages(true); } - ctx.Log.ErrorFormat(ex, "Deployment \"{0}\" of type {1} failed", deployment.Name, deployment.DeploymentType.ToString()); + ctx.Log.Error(ex, $"Deployment \"{deployment.Name}\" of type {deployment.DeploymentType.ToString()} failed."); } deployment.ResultInfo = XmlHelper.Serialize(context.Result); @@ -1671,7 +1677,7 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) context.PublicFolderPath = publicDeployment.GetDeploymentFolder(true); context.PublicFolderUrl = publicDeployment.GetPublicFolderUrl(_services, ctx.Store); - var fileExtension = provider.Value.FileExtension.HasValue() ? provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""; + var fileExtension = provider.Value.FileExtension.HasValue() ? provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""; using (var segmenter = CreateSegmenter(ctx)) { @@ -1880,7 +1886,7 @@ private void ExportCoreOuter(DataExporterContext ctx) catch (Exception ex) { logger.ErrorsAll(ex); - ctx.Result.LastError = ex.ToString(); + ctx.Result.LastError = ex.ToAllMessages(true); } finally { @@ -1888,6 +1894,7 @@ private void ExportCoreOuter(DataExporterContext ctx) { if (!ctx.IsPreview && profile.Id != 0) { + ctx.Result.Files = ctx.Result.Files.OrderBy(x => x.RelatedType).ToList(); profile.ResultInfo = XmlHelper.Serialize(ctx.Result); _exportProfileService.Value.UpdateExportProfile(profile); @@ -1940,10 +1947,14 @@ private void ExportCoreOuter(DataExporterContext ctx) { int? orderStatusId = null; - if (ctx.Projection.OrderStatusChange == ExportOrderStatusChange.Processing) - orderStatusId = (int)OrderStatus.Processing; - else if (ctx.Projection.OrderStatusChange == ExportOrderStatusChange.Complete) - orderStatusId = (int)OrderStatus.Complete; + if (ctx.Projection.OrderStatusChange == ExportOrderStatusChange.Processing) + { + orderStatusId = (int)OrderStatus.Processing; + } + else if (ctx.Projection.OrderStatusChange == ExportOrderStatusChange.Complete) + { + orderStatusId = (int)OrderStatus.Complete; + } using (var scope = new DbContextScope(_dbContext, false, null, false, false, false, false)) { @@ -1962,7 +1973,7 @@ private void ExportCoreOuter(DataExporterContext ctx) catch (Exception ex) { logger.ErrorsAll(ex); - ctx.Result.LastError = ex.ToString(); + ctx.Result.LastError = ex.ToAllMessages(true); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs index 8fdfe9ba43..346cf02176 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs @@ -19,7 +19,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de context.Result.LastError = context.T("Admin.DataExchange.Export.Deployment.CopyFileFailed"); } - context.Log.Info("Copied export data files to " + targetFolder); + context.Log.Info($"Copied export data files to {targetFolder}."); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs index a008e69b0c..7e9d67beb3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs @@ -27,7 +27,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de File.Copy(context.ZipPath, destinationFile, true); - context.Log.Info("Copied zipped export data to " + destinationFile); + context.Log.Info($"Copied zipped export data to {destinationFile}."); } } else @@ -37,7 +37,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de context.Result.LastError = context.T("Admin.DataExchange.Export.Deployment.CopyFileFailed"); } - context.Log.Info("Copied export data files to " + destinationFolder); + context.Log.Info($"Copied export data files to {destinationFolder}."); } } } From ef76318ba584017da894e9b63ce9e3060205c9e7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 21 Nov 2018 18:28:38 +0100 Subject: [PATCH 314/932] Updated change log --- changelog.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 828a9a7d03..0d87ccaebc 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -# Release Notes +# Release Notes ## SmartStore.NET 3.2 @@ -51,7 +51,9 @@ * At least 10x faster * Generated files are saved on the hard disk now: a rebuild after an app restart is no longer necessary. * No exclusive locks during rebuilds anymore: if an (outdated) file already exists, it is returned instantly. -* Debitoor: Partially update customer instead of full update to avoid all fields being overwritten +* **Debitoor**: + * Partially update customer instead of full update to avoid all fields being overwritten. + * #1540 Place company name in front of customer name (according to address format of the particular country). * #1479 Show in messages the delivery time at the time of purchase * #1184 Sort Current shopping carts & Current wishlists by ShoppingCartItem.CreatedOn. * #1106 BMECat: import & export support for product keywords From a8a594383fcff414eb755e5f996eb4c7a95d8acf Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 21 Nov 2018 22:33:26 +0100 Subject: [PATCH 315/932] Locale editor: hide tab labels from 8th child onward to prevent line wrapping --- .../Extensions/HtmlExtensions.cs | 1 + .../UI/Components/TabStrip/TabStripRenderer.cs | 13 ++++++------- .../Administration/Content/_admin.scss | 5 +++++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs index 3e3d884333..4fc585afd1 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs @@ -82,6 +82,7 @@ public static HelperResult LocalizedEditor(this HtmlHel var language = languageService.GetLanguageById(locale.LanguageId); x.Add().Text(language.Name) + .LinkHtmlAttributes(new { title = language.Name }) .ContentHtmlAttributes(new { @class = "locale-editor-content", data_lang = language.LanguageCulture, data_rtl = language.Rtl.ToString().ToLower() }) .Content(localizedTemplate(i)) .ImageUrl("~/Content/images/flags/" + language.FlagImageFileName) diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs index c55f9d03a0..3dadb4b8db 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs @@ -367,13 +367,16 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind writer.RenderBeginTag("img"); writer.RenderEndTag(); // img } - //writer.WriteEncodedText(item.Text); + + // caption + writer.AddAttribute("class", "tab-caption"); + writer.RenderBeginTag("span"); + writer.WriteEncodedText(item.Text); + writer.RenderEndTag(); // Badge if (item.BadgeText.HasValue()) { - //writer.Write(" "); - // caption writer.AddAttribute("class", "tab-caption"); writer.RenderBeginTag("span"); @@ -392,10 +395,6 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind writer.WriteEncodedText(item.BadgeText); writer.RenderEndTag(); // span > badge } - else - { - writer.WriteEncodedText(item.Text); - } // nav link short summary for collapsed state if (this.Component.IsResponsive && item.Summary.HasValue()) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss b/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss index 7fd8438551..fbf1736a30 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss +++ b/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss @@ -789,6 +789,11 @@ legend { } } + .nav-item:nth-child(n+8) > .nav-link { + > img, > i { margin-left: 0 !important; margin-right: 0 !important; } + > span { display: none; } + } + .nav-link:hover, .nav-link.active { color: inherit; From 8a42feecc16c98ee08ba76df84103cb4e8624276 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 23 Nov 2018 23:58:09 +0100 Subject: [PATCH 316/932] (Perf) FindEqualPicture with streams, not with byte arrays --- .../SmartStore.Core/Extensions/StreamExtensions.cs | 7 ++++--- .../SmartStore.Services/Media/PictureService.cs | 12 +++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs index e67a92a8a1..6b286561a0 100644 --- a/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs @@ -68,7 +68,8 @@ public static bool ContentsEqual(this Stream src, Stream other) if (src.Length != other.Length) return false; - const int bufferSize = 2048; + const int intSize = sizeof(Int64); + const int bufferSize = 2048; var buffer1 = new byte[bufferSize]; var buffer2 = new byte[bufferSize]; @@ -77,7 +78,7 @@ public static bool ContentsEqual(this Stream src, Stream other) int len1 = src.Read(buffer1, 0, bufferSize); int len2 = other.Read(buffer2, 0, bufferSize); - if (len1 != len2) + if (len1 != len2) return false; if (len1 == 0) @@ -86,7 +87,7 @@ public static bool ContentsEqual(this Stream src, Stream other) int iterations = (int)Math.Ceiling((double)len1 / sizeof(Int64)); for (int i = 0; i < iterations; i++) { - if (BitConverter.ToInt64(buffer1, i * sizeof(Int64)) != BitConverter.ToInt64(buffer2, i * sizeof(Int64))) + if (BitConverter.ToInt64(buffer1, i * intSize) != BitConverter.ToInt64(buffer2, i * intSize)) { return false; } diff --git a/src/Libraries/SmartStore.Services/Media/PictureService.cs b/src/Libraries/SmartStore.Services/Media/PictureService.cs index 438499e8f1..850ea22bdb 100644 --- a/src/Libraries/SmartStore.Services/Media/PictureService.cs +++ b/src/Libraries/SmartStore.Services/Media/PictureService.cs @@ -208,14 +208,16 @@ public virtual byte[] ValidatePicture(byte[] pictureBinary, string mimeType, out public virtual byte[] FindEqualPicture(byte[] pictureBinary, IEnumerable pictures, out int equalPictureId) { equalPictureId = 0; + + var myStream = new MemoryStream(pictureBinary); + try { foreach (var picture in pictures) { - var otherPictureBinary = LoadPictureBinary(picture); + myStream.Seek(0, SeekOrigin.Begin); - using (var myStream = new MemoryStream(pictureBinary)) - using (var otherStream = new MemoryStream(otherPictureBinary)) + using (var otherStream = OpenPictureStream(picture)) { if (myStream.ContentsEqual(otherStream)) { @@ -231,6 +233,10 @@ public virtual byte[] FindEqualPicture(byte[] pictureBinary, IEnumerable Date: Mon, 26 Nov 2018 21:00:08 +0100 Subject: [PATCH 317/932] Theming: minor CSS improvement --- src/Presentation/SmartStore.Web/Content/shared/_buttons.scss | 2 +- src/Presentation/SmartStore.Web/Scripts/public.common.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Content/shared/_buttons.scss b/src/Presentation/SmartStore.Web/Content/shared/_buttons.scss index 575ff64ead..49cf3b0235 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_buttons.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_buttons.scss @@ -594,7 +594,7 @@ $btn-size-sm: $input-height-sm; &:hover, &.hover, &:focus, &.focus { - transform: translateY(-3px); + transform: translateY(-2px); } &:active, &.active, diff --git a/src/Presentation/SmartStore.Web/Scripts/public.common.js b/src/Presentation/SmartStore.Web/Scripts/public.common.js index aca5b636c7..90a925b795 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.common.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.common.js @@ -160,7 +160,7 @@ $(function () { // Init reveal on scroll with AOS library if (typeof AOS !== 'undefined') { - AOS.init({ once: true }); + AOS.init({ once: true, duration: 1000 }); } if (SmartStore.parallax !== undefined && !$('body').hasClass('no-parallax')) { From 412efcfb277156bd3fbe811407f48f79901fc57e Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 28 Nov 2018 03:43:03 +0100 Subject: [PATCH 318/932] Fixed minor CSS issue --- .../SmartStore.Web/Content/vendors/aos/scss/_easing.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_easing.scss b/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_easing.scss index e0c261f227..b419e81343 100644 --- a/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_easing.scss +++ b/src/Presentation/SmartStore.Web/Content/vendors/aos/scss/_easing.scss @@ -33,10 +33,11 @@ $aos-easing: ( // Edit info (mc): // - Removed root [data-aos] // - body[data-aos-easing="#{$key}"] & >> body[data-aos-easing="#{$key}"] [data-aos] +// - prepended 'body' to second selector because of correct specifity // ---------------------------------- @each $key, $val in $aos-easing { body[data-aos-easing="#{$key}"] [data-aos], - [data-aos][data-aos-easing="#{$key}"] { + body [data-aos][data-aos-easing="#{$key}"] { transition-timing-function: $val; } } \ No newline at end of file From 43d20d58dd2fbb38bc290d8df51d91097e20e3d2 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 28 Nov 2018 23:31:52 +0100 Subject: [PATCH 319/932] Minor refactoring --- .../SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs index d465a2d0c8..81e8a86cdb 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs @@ -9,6 +9,7 @@ namespace SmartStore.Web.Framework.UI.Blocks public interface IBlockEntity { int Id { get; set; } + int StoryId { get; } string BlockType { get; set; } string Model { get; set; } string TagLine { get; set; } From ecaaba49167ba53b0d7251255497577ad6361b0f Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 29 Nov 2018 03:34:15 +0100 Subject: [PATCH 320/932] Minor localization issue --- .../SmartStore.Data/Migrations/MigrationsConfiguration.cs | 3 +++ .../SmartStore.Services/Catalog/CopyProductService.cs | 3 +-- .../Administration/Controllers/ProductController.cs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 801c1adbe2..9fb1c0698b 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -627,6 +627,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Top-Produkte auch in Unterseiten anzeigen", "Subpage: List index greater than 1 or any active filter.", "Unterseite: Listenindex größer 1 oder mind. ein aktiver Filter."); + + + builder.AddOrUpdate("Admin.Common.CopyOf", "Copy of {0}", "Kopie von {0}"); } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs index 52bb1b48e5..15088a1c1f 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs @@ -493,7 +493,6 @@ private void ProcessLocalization(Product product, Product clone, IEnumerable Date: Thu, 29 Nov 2018 23:12:07 +0100 Subject: [PATCH 321/932] Summernote: turned off 'prettifyHtml' option. It produces messy html code. --- .../SmartStore.Web/Content/editors/summernote/globalinit.js | 2 +- src/Presentation/SmartStore.Web/Themes/Web.config | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Content/editors/summernote/globalinit.js b/src/Presentation/SmartStore.Web/Content/editors/summernote/globalinit.js index cf990eb64a..65ad968dc6 100644 --- a/src/Presentation/SmartStore.Web/Content/editors/summernote/globalinit.js +++ b/src/Presentation/SmartStore.Web/Content/editors/summernote/globalinit.js @@ -29,7 +29,7 @@ var summernote_image_upload_url; dialogsInBody: true, dialogsFade: true, height: 300, - prettifyHtml: true, + prettifyHtml: false, onCreateLink: function (url) { // Prevents that summernote prepends "http://" to our links (WTF!!!) var c = url[0]; diff --git a/src/Presentation/SmartStore.Web/Themes/Web.config b/src/Presentation/SmartStore.Web/Themes/Web.config index 46e041887b..b59004c448 100644 --- a/src/Presentation/SmartStore.Web/Themes/Web.config +++ b/src/Presentation/SmartStore.Web/Themes/Web.config @@ -10,9 +10,10 @@ + - + \ No newline at end of file From 55fb8c9333e61966453b43f4f8390ffc4d1d3b4b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 30 Nov 2018 11:23:49 +0100 Subject: [PATCH 322/932] Fixes tab caption rendered twice if tab item has a badge text --- .../UI/Components/TabStrip/TabStripRenderer.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs index 3dadb4b8db..cb070f79c0 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs @@ -368,7 +368,7 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind writer.RenderEndTag(); // img } - // caption + // Caption writer.AddAttribute("class", "tab-caption"); writer.RenderBeginTag("span"); writer.WriteEncodedText(item.Text); @@ -377,13 +377,7 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind // Badge if (item.BadgeText.HasValue()) { - // caption - writer.AddAttribute("class", "tab-caption"); - writer.RenderBeginTag("span"); - writer.WriteEncodedText(item.Text); - writer.RenderEndTag(); // span > badge - - // label/badge + // Label/badge temp = "ml-2 badge"; temp += " badge-" + item.BadgeStyle.ToString().ToLower(); if (base.Component.Position == TabsPosition.Left) @@ -396,7 +390,7 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind writer.RenderEndTag(); // span > badge } - // nav link short summary for collapsed state + // Nav link short summary for collapsed state if (this.Component.IsResponsive && item.Summary.HasValue()) { writer.AddAttribute("class", "nav-link-summary"); From e33958b09d20c1deb38511db744ae327bf3711f9 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 4 Dec 2018 00:29:15 +0100 Subject: [PATCH 323/932] scrim-gradient Sass mixin --- .../Content/shared/_mixins.scss | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Presentation/SmartStore.Web/Content/shared/_mixins.scss b/src/Presentation/SmartStore.Web/Content/shared/_mixins.scss index e38caf13d7..20f549476e 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_mixins.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_mixins.scss @@ -4,6 +4,36 @@ // Mixins // ======================================================================== +@mixin scrim-gradient($start-color: #000, $direction: 'to bottom') { + $coords: ( + 0: 1, + 19: 0.738, + 34: 0.541, + 47: 0.382, + 56.5: 0.278, + 65: 0.194, + 73: 0.126, + 80.2: 0.075, + 86.1: 0.042, + 91: 0.021, + 95.2: 0.008, + 98.2: 0.002, + 100: 0 + ); + + $hue: hue($start-color); + $saturation: saturation($start-color); + $lightness: lightness($start-color); + $stops: (); + + @each $colorstop, $alpha in $coords { + $stop: hsla($hue, $saturation, $lightness, $alpha) percentage($colorstop/100); + $stops: append($stops, $stop, comma); + } + + background: linear-gradient(unquote($direction), $stops); +} + @mixin center($xy: xy) { @if $xy == xy { left: 50%; From f6cb634d56ade9d013350ed7897e9044e719ab9f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 4 Dec 2018 12:46:48 +0100 Subject: [PATCH 324/932] Fixes linq search returns products multiple times. Reverts "Avoid group-by in linq search if possible because it's very slow if there are millions of products". --- .../Catalog/LinqCatalogSearchService.cs | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs index e026c8c4da..fcd96b6675 100644 --- a/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs @@ -131,7 +131,6 @@ protected virtual IQueryable GetProductQuery(CatalogSearchQuery searchQ { var ordered = false; var utcNow = DateTime.UtcNow; - var isGroupingRequired = false; var categoryId = 0; var manufacturerId = 0; var query = baseQuery ?? _productRepository.Table; @@ -161,7 +160,6 @@ protected virtual IQueryable GetProductQuery(CatalogSearchQuery searchQ } else { - isGroupingRequired = true; query = QueryCategories(query, categoryIds, null); } } @@ -170,13 +168,11 @@ protected virtual IQueryable GetProductQuery(CatalogSearchQuery searchQ var notFeaturedCategoryIds = GetIdList(filters, "notfeaturedcategoryid"); if (featuredCategoryIds.Any()) { - isGroupingRequired = true; categoryId = categoryId == 0 ? featuredCategoryIds.First() : categoryId; query = QueryCategories(query, featuredCategoryIds, true); } if (notFeaturedCategoryIds.Any()) { - isGroupingRequired = true; categoryId = categoryId == 0 ? notFeaturedCategoryIds.First() : categoryId; query = QueryCategories(query, notFeaturedCategoryIds, false); } @@ -192,7 +188,6 @@ protected virtual IQueryable GetProductQuery(CatalogSearchQuery searchQ } else { - isGroupingRequired = true; query = QueryManufacturers(query, manufacturerIds, null); } } @@ -201,13 +196,11 @@ protected virtual IQueryable GetProductQuery(CatalogSearchQuery searchQ var notFeaturedManuIds = GetIdList(filters, "notfeaturedmanufacturerid"); if (featuredManuIds.Any()) { - isGroupingRequired = true; manufacturerId = manufacturerId == 0 ? featuredManuIds.First() : manufacturerId; query = QueryManufacturers(query, featuredManuIds, true); } if (notFeaturedManuIds.Any()) { - isGroupingRequired = true; manufacturerId = manufacturerId == 0 ? notFeaturedManuIds.First() : manufacturerId; query = QueryManufacturers(query, notFeaturedManuIds, false); } @@ -215,7 +208,6 @@ protected virtual IQueryable GetProductQuery(CatalogSearchQuery searchQ var tagIds = GetIdList(filters, "tagid"); if (tagIds.Any()) { - isGroupingRequired = true; query = from p in query from pt in p.ProductTags.Where(pt => tagIds.Contains(pt.Id)) @@ -227,7 +219,6 @@ from pt in p.ProductTags.Where(pt => tagIds.Contains(pt.Id)) var roleIds = GetIdList(filters, "roleid"); if (roleIds.Any()) { - isGroupingRequired = true; query = from p in query join acl in _aclRepository.Table on new { pid = p.Id, pname = "Product" } equals new { pid = acl.EntityId, pname = acl.EntityName } into pacl @@ -468,7 +459,6 @@ from acl in pacl.DefaultIfEmpty() var storeId = (int)filter.Term; if (storeId != 0) { - isGroupingRequired = true; query = from p in query join sm in _storeMappingRepository.Table on new { pid = p.Id, pname = "Product" } equals new { pid = sm.EntityId, pname = sm.EntityName } into psm @@ -482,15 +472,12 @@ from sm in psm.DefaultIfEmpty() #endregion - if (isGroupingRequired) - { - // Grouping is very slow if there are many products. - query = - from p in query - group p by p.Id into grp - orderby grp.Key - select grp.FirstOrDefault(); - } + // Grouping is very slow if there are many products. + query = + from p in query + group p by p.Id into grp + orderby grp.Key + select grp.FirstOrDefault(); #region Sorting From 096ed3272607572220af736ac5c25eb6f95d74ce Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 5 Dec 2018 03:30:36 +0100 Subject: [PATCH 325/932] Removed urlCompression node from web.config --- src/Presentation/SmartStore.Web/Web.config | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index e8a9693af2..ab1f27c441 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -190,7 +190,6 @@ - From 54415415f94a4310aaa6c9abb0d6eb506d99ce18 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 6 Dec 2018 04:41:12 +0100 Subject: [PATCH 326/932] Minor issues --- README.md | 7 +++-- .../SmartStore.Web/Content/shared/_typo.scss | 4 ++- .../Scripts/smartstore.parallax.js | 29 ++++++++++++++++--- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index aa25d27b32..95b669b8f9 100644 --- a/README.md +++ b/README.md @@ -56,14 +56,17 @@ The state-of-the-art architecture of SmartStore.NET - with `ASP.NET 4.5` + `MVC * and many more... ## Project Status -SmartStore.NET V3.1.0 has been released on April 20, 2018. The highlights are: +SmartStore.NET V3.1.5 has been released on 25 May, 2018. The highlights are: * **Wallet \***: Enables full or partial order payment via credit account. Includes REST-Api. * **[Liquid](https://github.com/Shopify/liquid/wiki/Liquid-for-Designers) template engine**: very flexible templating for e-mails and campaigns with autocompletion and syntax highlighting. * **Cash Rounding**: define money rounding rules on currency and payment method level. * **Modern, responsive backend**: migrated backend to Bootstrap 4, overhauled and improved the user interface. -* **Enhanced MegaMenu \***: virtual dropdowns for surplus top-level categories and brands. * **RTL**: comprehensive RTL (Right-to-left) and bidi(rectional) support. +* Search engine friendly topic URLs +* Compliance with **EU-GDPR** requirements +* **Enhanced MegaMenu \***: virtual dropdowns for surplus top-level categories and brands. +* **Honeypot** bot detection for registration and contact forms. * **Amazon Pay**: * Supports merchants registered in the USA and Japan * External authentication via *Login with Amazon* button in shop frontend diff --git a/src/Presentation/SmartStore.Web/Content/shared/_typo.scss b/src/Presentation/SmartStore.Web/Content/shared/_typo.scss index a736889cf2..3575656949 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_typo.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_typo.scss @@ -143,7 +143,9 @@ a.pretty-link { } .text-body { - font-size: $lead-font-size; + @if $font-size-base < 1rem { + font-size: $lead-font-size; + } } code { diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.parallax.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.parallax.js index 3fdcc2bedd..73c5d4afb8 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.parallax.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.parallax.js @@ -8,6 +8,8 @@ var initialized = false, stages = [], + // to adjust speed on smaller devices + speedRatios = { xs: 0.5, sm: 0.6, md: 0.7, lg: 0.9, xl: 1 }, viewport = ResponsiveBootstrapToolkit; function update() { @@ -24,18 +26,34 @@ if (!visible) return; - if (item.filter && !viewport.is(item.filter)) + if (item.filter && !viewport.is(item.filter)) { + if (item.initialized) { + // Restore original styling + el.css('background-position', item.originalPosition); + el.css('background-attachment', item.originalAttachment); + item.initialized = false; + } + return; + } + + speed = item.ratio * speedRatios[viewport.current()]; if (item.type === 'bg') { + if (!item.initialized) { + // for smoother scrolling + el.css('background-attachment', 'fixed'); + item.initialized = true; + } + // set bg parallax offset - var ypos = Math.round((top - scrollTop) * item.ratio) + item.offset; + var ypos = Math.round((top - scrollTop) * speed) + item.offset; el.css('background-position-y', ypos + "px"); } else if (item.type === 'content') { var bottom = top + height, rate = 100 / (bottom + winHeight - top) * ((scrollTop + winHeight) - top), - ytransform = (rate - 50) * (item.ratio * -3); + ytransform = (rate - 50) * (speed * -3); el.css(window.Prefixer.css('transform'), 'translate3d(0, ' + ytransform + 'px, 0)'); } @@ -55,7 +73,10 @@ type: el.data('parallax-type') || 'bg', filter: el.data('parallax-filter'), offset: (el.data('parallax-offset') || 0) * -1, - ratio: el.data('parallax-speed') || 0.5 + ratio: el.data('parallax-speed') || 0.5, + originalPosition: el.css('background-position'), + originalAttachment: el.css('background-attachment'), + initialized: false }; }); From 2e7e6f6c1eb1dff4065af29f6e5e99c7cda2d094 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 6 Dec 2018 11:50:45 +0100 Subject: [PATCH 327/932] Fixes "Column name 'IsSystemProduct' does not exist in the target table or view." Fixes "SqlCeCommand.CommandTimeout does not support non-zero values" Fixes "Direct index renaming is not supported by SQL Server Compact. To rename an index in SQL Server Compact, you will need to recreate it." What the hell does SQL Server Compact support anyway? Resolves #1544. --- .../201811061745204_IsSystemProductIndex.cs | 13 ++++++++++--- .../Migrations/MigrationsConfiguration.cs | 18 +++++++++++------- .../SmartStore.Data/ObjectContextBase.cs | 2 +- .../Setup/MigrateDatabaseInitializer.cs | 3 ++- .../SmartStore.Data/Sql/Indexes.SqlServer.sql | 13 ------------- 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201811061745204_IsSystemProductIndex.cs b/src/Libraries/SmartStore.Data/Migrations/201811061745204_IsSystemProductIndex.cs index 0bdb446c38..a51d10dc8f 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201811061745204_IsSystemProductIndex.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201811061745204_IsSystemProductIndex.cs @@ -2,20 +2,27 @@ namespace SmartStore.Data.Migrations { using System; using System.Data.Entity.Migrations; - + using SmartStore.Core.Data; + public partial class IsSystemProductIndex : DbMigration { public override void Up() { DropIndex("dbo.Product", "IX_Product_Deleted_and_Published"); - RenameIndex(table: "dbo.Product", name: "Product_SystemName_IsSystemProduct", newName: "IX_Product_SystemName_IsSystemProduct"); + if (DataSettings.Current.IsSqlServer) + { + RenameIndex(table: "dbo.Product", name: "Product_SystemName_IsSystemProduct", newName: "IX_Product_SystemName_IsSystemProduct"); + } CreateIndex("dbo.Product", new[] { "Published", "Deleted", "IsSystemProduct" }, name: "IX_Product_Published_Deleted_IsSystemProduct"); } public override void Down() { DropIndex("dbo.Product", "IX_Product_Published_Deleted_IsSystemProduct"); - RenameIndex(table: "dbo.Product", name: "IX_Product_SystemName_IsSystemProduct", newName: "Product_SystemName_IsSystemProduct"); + if (DataSettings.Current.IsSqlServer) + { + RenameIndex(table: "dbo.Product", name: "IX_Product_SystemName_IsSystemProduct", newName: "Product_SystemName_IsSystemProduct"); + } CreateIndex("dbo.Product", new[] { "Published", "Deleted" }, name: "IX_Product_Deleted_and_Published"); } } diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 9fb1c0698b..1ae92861b0 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -3,7 +3,8 @@ using System; using System.Data.Entity.Migrations; using Setup; - using SmartStore.Core.Domain.Catalog; + using SmartStore.Core.Data; + using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; using SmartStore.Utilities; @@ -15,13 +16,16 @@ public MigrationsConfiguration() AutomaticMigrationDataLossAllowed = true; ContextKey = "SmartStore.Core"; - var commandTimeout = CommonHelper.GetAppSetting("sm:EfMigrationsCommandTimeout"); - if (commandTimeout.HasValue) - { - CommandTimeout = commandTimeout.Value; - } + if (DataSettings.Current.IsSqlServer) + { + var commandTimeout = CommonHelper.GetAppSetting("sm:EfMigrationsCommandTimeout"); + if (commandTimeout.HasValue) + { + CommandTimeout = commandTimeout.Value; + } - CommandTimeout = 9999999; + CommandTimeout = 9999999; + } } public void SeedDatabase(SmartObjectContext context) diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.cs index a00e80662f..0e7c9c73bf 100644 --- a/src/Libraries/SmartStore.Data/ObjectContextBase.cs +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.cs @@ -42,7 +42,7 @@ protected ObjectContextBase(string nameOrConnectionString, string alias = null) this.Alias = null; this.DbHookHandler = NullDbHookHandler.Instance; - if (_commandTimeoutInSeconds >= 0) + if (_commandTimeoutInSeconds >= 0 && DataSettings.Current.IsSqlServer) { Database.CommandTimeout = _commandTimeoutInSeconds; } diff --git a/src/Libraries/SmartStore.Data/Setup/MigrateDatabaseInitializer.cs b/src/Libraries/SmartStore.Data/Setup/MigrateDatabaseInitializer.cs index c11b71aedc..c92790df60 100644 --- a/src/Libraries/SmartStore.Data/Setup/MigrateDatabaseInitializer.cs +++ b/src/Libraries/SmartStore.Data/Setup/MigrateDatabaseInitializer.cs @@ -4,6 +4,7 @@ using System.Data.Entity.Infrastructure; using System.Data.Entity.Migrations; using System.Linq; +using SmartStore.Core.Data; using SmartStore.Data.Migrations; using SmartStore.Utilities; @@ -134,7 +135,7 @@ protected virtual TConfig CreateConfiguration() config.TargetDatabase = new DbConnectionInfo(this.ConnectionString, dbContextInfo.ConnectionProviderName); } - if (config.CommandTimeout == null) + if (config.CommandTimeout == null && DataSettings.Current.IsSqlServer) { var commandTimeout = CommonHelper.GetAppSetting("sm:EfMigrationsCommandTimeout"); if (commandTimeout.HasValue) diff --git a/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.sql b/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.sql index fc515b3c99..c871635093 100644 --- a/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.sql +++ b/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.sql @@ -9,16 +9,3 @@ GO CREATE NONCLUSTERED INDEX [IX_LocalizedProperty_Key] ON [LocalizedProperty] ([Id]) INCLUDE ([EntityId], [LocaleKeyGroup], [LocaleKey]) GO - -CREATE NONCLUSTERED INDEX [IX_SeekExport1] ON [dbo].[Product] -( - [Published] ASC, - [Id] ASC, - [VisibleIndividually] ASC, - [Deleted] ASC, - [IsSystemProduct] ASC, - [AvailableStartDateTimeUtc] ASC, - [AvailableEndDateTimeUtc] ASC -) -INCLUDE ([UpdatedOnUtc]) WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [PRIMARY] -GO \ No newline at end of file From 9faada8bad9c59ffc432dfc6f3ee810adc99291d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 6 Dec 2018 20:45:02 +0100 Subject: [PATCH 328/932] Removed drop index IX_SeekExport1 from Indexes.SqlServer.Inverse.sql (not required) --- .../SmartStore.Data/Sql/Indexes.SqlServer.Inverse.sql | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.Inverse.sql b/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.Inverse.sql index 82855517b1..709dabb0ec 100644 --- a/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.Inverse.sql +++ b/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.Inverse.sql @@ -9,6 +9,3 @@ GO DROP INDEX [IX_LocalizedProperty_Key] ON [LocalizedProperty] GO - -DROP INDEX [IX_SeekExport1] ON [Product] -GO \ No newline at end of file From 94aff1d5f20a214b477184c86abc9d0188e3c35d Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 6 Dec 2018 21:08:30 +0100 Subject: [PATCH 329/932] Disabled parallax effect for Firefox & Edge --- src/Presentation/SmartStore.Web/Scripts/public.common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Scripts/public.common.js b/src/Presentation/SmartStore.Web/Scripts/public.common.js index 90a925b795..f43b0140f8 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.common.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.common.js @@ -163,7 +163,7 @@ AOS.init({ once: true, duration: 1000 }); } - if (SmartStore.parallax !== undefined && !$('body').hasClass('no-parallax')) { + if (SmartStore.parallax !== undefined && !$('body').hasClass('no-parallax') && !$('html').hasClass('moz') && !$('html').hasClass('edge')) { SmartStore.parallax.init({ context: document.body, selector: '.parallax' From 02dcfb7c94ee3d22fecc499e4aa68d85b9e59624 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 8 Dec 2018 03:36:14 +0100 Subject: [PATCH 330/932] Fixed parallax FF & Edge issues --- .../Orders/OrderService.cs | 189 +----------------- .../Content/shared/_offcanvas.scss | 1 - .../SmartStore.Web/Content/shared/_utils.scss | 9 + .../SmartStore.Web/Scripts/public.common.js | 2 +- .../Scripts/smartstore.offcanvas.js | 2 +- .../Scripts/smartstore.parallax.js | 23 ++- 6 files changed, 28 insertions(+), 198 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Orders/OrderService.cs b/src/Libraries/SmartStore.Services/Orders/OrderService.cs index 62714bfebe..586f3656e1 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderService.cs @@ -13,13 +13,8 @@ namespace SmartStore.Services.Orders { - /// - /// Order service - /// public partial class OrderService : IOrderService { - #region Fields - private readonly IRepository _orderRepository; private readonly IRepository _orderItemRepository; private readonly IRepository _orderNoteRepository; @@ -29,21 +24,6 @@ public partial class OrderService : IOrderService private readonly IRepository _returnRequestRepository; private readonly IEventPublisher _eventPublisher; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Order repository - /// Order item repository - /// Order note repository - /// Product repository - /// Recurring payment repository - /// Customer repository - /// Return request repository - /// Event published public OrderService(IRepository orderRepository, IRepository orderItemRepository, IRepository orderNoteRepository, @@ -63,17 +43,6 @@ public OrderService(IRepository orderRepository, _eventPublisher = eventPublisher; } - #endregion - - #region Methods - - #region Orders - - /// - /// Gets an order - /// - /// The order identifier - /// Order public virtual Order GetOrderById(int orderId) { if (orderId == 0) @@ -82,11 +51,6 @@ public virtual Order GetOrderById(int orderId) return _orderRepository.GetById(orderId); } - /// - /// Get orders by identifiers - /// - /// Order identifiers - /// Order public virtual IList GetOrdersByIds(int[] orderIds) { if (orderIds == null || orderIds.Length == 0) @@ -122,11 +86,6 @@ public virtual Order GetOrderByNumber(string orderNumber) return order; } - /// - /// Gets an order - /// - /// The order identifier - /// Order public virtual Order GetOrderByGuid(Guid orderGuid) { if (orderGuid == Guid.Empty) @@ -139,12 +98,6 @@ public virtual Order GetOrderByGuid(Guid orderGuid) return order; } - /// - /// Get order by payment authorization data - /// - /// System name of the payment method - /// Authorization transaction Id - /// Order entity public virtual Order GetOrderByPaymentAuthorization(string paymentMethodSystemName, string authorizationTransactionId) { if (paymentMethodSystemName.IsEmpty() || authorizationTransactionId.IsEmpty()) @@ -158,12 +111,6 @@ from o in _orderRepository.Table return query.FirstOrDefault(); } - /// - /// Get order by payment capture data - /// - /// System name of the payment method - /// Capture transaction Id - /// Order entity public virtual Order GetOrderByPaymentCapture(string paymentMethodSystemName, string captureTransactionId) { if (paymentMethodSystemName.IsEmpty() || captureTransactionId.IsEmpty()) @@ -231,23 +178,6 @@ public virtual IQueryable GetOrders( return query; } - /// - /// Search orders - /// - /// Store identifier; 0 to load all orders - /// Customer identifier; 0 to load all orders - /// Order start time; null to load all orders - /// Order end time; null to load all orders - /// Filter by order status - /// Filter by payment status - /// Filter by shipping status - /// Billing email. Leave empty to load all records. - /// Search by order GUID (Global unique identifier) or part of GUID. Leave empty to load all orders. - /// Filter by order number - /// Page index - /// Page size - /// Billing name. Leave empty to load all records. - /// Order collection public virtual IPagedList SearchOrders(int storeId, int customerId, DateTime? startTime, DateTime? endTime, int[] orderStatusIds, int[] paymentStatusIds, int[] shippingStatusIds, string billingEmail, string orderGuid, string orderNumber, int pageIndex, int pageSize, string billingName = null) @@ -259,7 +189,7 @@ public virtual IPagedList SearchOrders(int storeId, int customerId, DateT if (orderGuid.HasValue()) { - //filter by GUID. Filter in BLL because EF doesn't support casting of GUID to string + // Filter by GUID. Filter in BLL because EF doesn't support casting of GUID to string var orders = query.ToList(); orders = orders.FindAll(x => x.OrderGuid.ToString().ToLowerInvariant().Contains(orderGuid.ToLowerInvariant())); @@ -267,18 +197,10 @@ public virtual IPagedList SearchOrders(int storeId, int customerId, DateT } else { - //database layer paging return new PagedList(query, pageIndex, pageSize); } } - /// - /// Gets all orders by affiliate identifier - /// - /// Affiliate identifier - /// Page index - /// Page size - /// Orders public virtual IPagedList GetAllOrders(int affiliateId, int pageIndex, int pageSize) { var query = _orderRepository.Table; @@ -290,20 +212,11 @@ public virtual IPagedList GetAllOrders(int affiliateId, int pageIndex, in return orders; } - /// - /// Load all orders - /// - /// Order collection public virtual IList LoadAllOrders() { return SearchOrders(0, 0, null, null, null, null, null, null, null, null, 0, int.MaxValue); } - /// - /// Gets all orders by affiliate identifier - /// - /// Affiliate identifier - /// Order collection public virtual IList GetOrdersByAffiliateId(int affiliateId) { var query = from o in _orderRepository.Table @@ -314,10 +227,6 @@ orderby o.CreatedOnUtc descending return orders; } - /// - /// Inserts an order - /// - /// Order public virtual void InsertOrder(Order order) { if (order == null) @@ -326,10 +235,6 @@ public virtual void InsertOrder(Order order) _orderRepository.Insert(order); } - /// - /// Updates the order - /// - /// The order public virtual void UpdateOrder(Order order) { if (order == null) @@ -340,10 +245,6 @@ public virtual void UpdateOrder(Order order) _eventPublisher.PublishOrderUpdated(order); } - /// - /// Deletes an order - /// - /// The order public virtual void DeleteOrder(Order order) { if (order == null) @@ -353,10 +254,6 @@ public virtual void DeleteOrder(Order order) UpdateOrder(order); } - /// - /// Deletes an order note - /// - /// The order note public virtual void DeleteOrderNote(OrderNote orderNote) { if (orderNote == null) @@ -370,12 +267,6 @@ public virtual void DeleteOrderNote(OrderNote orderNote) _eventPublisher.PublishOrderUpdated(order); } - /// - /// Get an order by authorization transaction ID and payment method system name - /// - /// Authorization transaction ID - /// Payment method system name - /// Order public virtual Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string authorizationTransactionId, string paymentMethodSystemName) { var query = _orderRepository.Table; @@ -405,15 +296,8 @@ public virtual void AddOrderNote(Order order, string note, bool displayToCustome } } - #endregion - #region Order items - /// - /// Gets an Order item - /// - /// Order item identifier - /// Order item public virtual OrderItem GetOrderItemById(int orderItemId) { if (orderItemId == 0) @@ -422,11 +306,6 @@ public virtual OrderItem GetOrderItemById(int orderItemId) return _orderItemRepository.GetById(orderItemId); } - /// - /// Gets an Order item - /// - /// Order item identifier - /// Order item public virtual OrderItem GetOrderItemByGuid(Guid orderItemGuid) { if (orderItemGuid == Guid.Empty) @@ -438,19 +317,7 @@ public virtual OrderItem GetOrderItemByGuid(Guid orderItemGuid) var item = query.FirstOrDefault(); return item; } - - /// - /// Gets all Order items - /// - /// Order identifier; null to load all records - /// Customer identifier; null to load all records - /// Order start time; null to load all records - /// Order end time; null to load all records - /// Order status; null to load all records - /// Order payment status; null to load all records - /// Order shippment status; null to load all records - /// Value indicating whether to load downloadable products only - /// Order collection + public virtual IList GetAllOrderItems(int? orderId, int? customerId, DateTime? startTime, DateTime? endTime, OrderStatus? os, PaymentStatus? ps, ShippingStatus? ss, @@ -505,10 +372,6 @@ where orderIds.Contains(x.OrderId) return map; } - /// - /// Delete an Order item - /// - /// The Order item public virtual void DeleteOrderItem(OrderItem orderItem) { if (orderItem == null) @@ -526,10 +389,6 @@ public virtual void DeleteOrderItem(OrderItem orderItem) #region Recurring payments - /// - /// Deletes a recurring payment - /// - /// Recurring payment public virtual void DeleteRecurringPayment(RecurringPayment recurringPayment) { if (recurringPayment == null) @@ -539,11 +398,6 @@ public virtual void DeleteRecurringPayment(RecurringPayment recurringPayment) UpdateRecurringPayment(recurringPayment); } - /// - /// Gets a recurring payment - /// - /// The recurring payment identifier - /// Recurring payment public virtual RecurringPayment GetRecurringPaymentById(int recurringPaymentId) { if (recurringPaymentId == 0) @@ -552,10 +406,6 @@ public virtual RecurringPayment GetRecurringPaymentById(int recurringPaymentId) return _recurringPaymentRepository.GetById(recurringPaymentId); } - /// - /// Inserts a recurring payment - /// - /// Recurring payment public virtual void InsertRecurringPayment(RecurringPayment recurringPayment) { if (recurringPayment == null) @@ -566,10 +416,6 @@ public virtual void InsertRecurringPayment(RecurringPayment recurringPayment) _eventPublisher.PublishOrderUpdated(recurringPayment.InitialOrder); } - /// - /// Updates the recurring payment - /// - /// Recurring payment public virtual void UpdateRecurringPayment(RecurringPayment recurringPayment) { if (recurringPayment == null) @@ -580,15 +426,6 @@ public virtual void UpdateRecurringPayment(RecurringPayment recurringPayment) _eventPublisher.PublishOrderUpdated(recurringPayment.InitialOrder); } - /// - /// Search recurring payments - /// - /// The store identifier; 0 to load all records - /// The customer identifier; 0 to load all records - /// The initial order identifier; 0 to load all records - /// Initial order status identifier; null to load all records - /// A value indicating whether to show hidden records - /// Recurring payment collection public virtual IList SearchRecurringPayments(int storeId, int customerId, int initialOrderId, OrderStatus? initialOrderStatus, bool showHidden = false) @@ -623,10 +460,6 @@ where query1.Contains(rp.Id) #region Return requests - /// - /// Deletes a return request - /// - /// Return request public virtual void DeleteReturnRequest(ReturnRequest returnRequest) { if (returnRequest == null) @@ -640,11 +473,6 @@ public virtual void DeleteReturnRequest(ReturnRequest returnRequest) _eventPublisher.PublishOrderUpdated(orderItem.Order); } - /// - /// Gets a return request - /// - /// Return request identifier - /// Return request public virtual ReturnRequest GetReturnRequestById(int returnRequestId) { if (returnRequestId == 0) @@ -653,17 +481,6 @@ public virtual ReturnRequest GetReturnRequestById(int returnRequestId) return _returnRequestRepository.GetById(returnRequestId); } - /// - /// Search return requests - /// - /// Store identifier; 0 to load all entries - /// Customer identifier; null to load all entries - /// Order item identifier; null to load all entries - /// Return request status; null to load all entries - /// Page index - /// Page size - /// Return request Id - /// Return requests public virtual IPagedList SearchReturnRequests(int storeId, int customerId, int orderItemId, ReturnRequestStatus? rs, int pageIndex, int pageSize, int id = 0) { var query = _returnRequestRepository.Table; @@ -693,7 +510,5 @@ public virtual IPagedList SearchReturnRequests(int storeId, int c } #endregion - - #endregion } } diff --git a/src/Presentation/SmartStore.Web/Content/shared/_offcanvas.scss b/src/Presentation/SmartStore.Web/Content/shared/_offcanvas.scss index e0128c2b39..8a6a888075 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_offcanvas.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_offcanvas.scss @@ -17,7 +17,6 @@ $offcanvas-z-index: 99999; .canvas-slidable { z-index: 0; - transform: translate3d(0, 0, 0); transition: opacity $offcanvas-duration $offcanvas-easing, transform $offcanvas-duration $offcanvas-easing; diff --git a/src/Presentation/SmartStore.Web/Content/shared/_utils.scss b/src/Presentation/SmartStore.Web/Content/shared/_utils.scss index 58ea82127d..7a6440db88 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_utils.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_utils.scss @@ -93,6 +93,7 @@ // Data binding 3d rotate // ------------------------------------------------------ + @keyframes data-binding { 0% { transform: rotateY(0deg); @@ -350,6 +351,14 @@ table th { } +// Parallax stuff +// ------------------------------------------------------ + +.parallax[data-parallax-type=content] { + will-change: transform; +} + + // Misc utils // ------------------------- diff --git a/src/Presentation/SmartStore.Web/Scripts/public.common.js b/src/Presentation/SmartStore.Web/Scripts/public.common.js index f43b0140f8..90a925b795 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.common.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.common.js @@ -163,7 +163,7 @@ AOS.init({ once: true, duration: 1000 }); } - if (SmartStore.parallax !== undefined && !$('body').hasClass('no-parallax') && !$('html').hasClass('moz') && !$('html').hasClass('edge')) { + if (SmartStore.parallax !== undefined && !$('body').hasClass('no-parallax')) { SmartStore.parallax.init({ context: document.body, selector: '.parallax' diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.offcanvas.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.offcanvas.js index cb17a036b9..cfc222c8e8 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.offcanvas.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.offcanvas.js @@ -200,7 +200,7 @@ e.preventDefault(); self.hide(); }); - + body.addClass('canvas-sliding'); body.addClass('canvas-sliding-' + (this.options.placement == 'right' ? 'left' : 'right') diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.parallax.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.parallax.js index 73c5d4afb8..cf535aacbc 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.parallax.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.parallax.js @@ -7,13 +7,20 @@ ; (function ($, window, document, undefined) { var initialized = false, + isTouch = Modernizr.touchevents, stages = [], - // to adjust speed on smaller devices - speedRatios = { xs: 0.5, sm: 0.6, md: 0.7, lg: 0.9, xl: 1 }, + //// to adjust speed on smaller devices + //speedRatios = { xs: 0.5, sm: 0.6, md: 0.7, lg: 0.9, xl: 1 }, viewport = ResponsiveBootstrapToolkit; function update() { _.each(stages, function (item, i) { + if (item.type == 'bg' && isTouch) { + // Found no proper way to make bg parallax + // run reliably on touch devices. + return; + } + var el = $(item.el); var winHeight = window.innerHeight; var scrollTop = window.pageYOffset; @@ -37,7 +44,7 @@ return; } - speed = item.ratio * speedRatios[viewport.current()]; + speed = item.speed; // * speedRatios[viewport.current()]; if (item.type === 'bg') { if (!item.initialized) { @@ -47,13 +54,13 @@ } // set bg parallax offset - var ypos = Math.round((top - scrollTop) * speed) + item.offset; - el.css('background-position-y', ypos + "px"); + var ypos = Math.round((top - scrollTop) * speed) + (item.offset * -1); + el.css('background-position', 'center ' + ypos + "px"); } else if (item.type === 'content') { var bottom = top + height, rate = 100 / (bottom + winHeight - top) * ((scrollTop + winHeight) - top), - ytransform = (rate - 50) * (speed * -3); + ytransform = (rate - 50) * (speed * -6) + item.offset; el.css(window.Prefixer.css('transform'), 'translate3d(0, ' + ytransform + 'px, 0)'); } @@ -71,9 +78,9 @@ return { el: val, type: el.data('parallax-type') || 'bg', + speed: toFloat(el.data('parallax-speed'), 0.5), + offset: (el.data('parallax-offset') || 0), filter: el.data('parallax-filter'), - offset: (el.data('parallax-offset') || 0) * -1, - ratio: el.data('parallax-speed') || 0.5, originalPosition: el.css('background-position'), originalAttachment: el.css('background-attachment'), initialized: false From f26d2482524030f7e1b86f4d33f674dd7c51c732 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 9 Dec 2018 19:08:53 +0100 Subject: [PATCH 331/932] Resolves #1551 IShippingMethodFilter: IsExcluded repeat every step checkout --- .../SmartStore.Web/Controllers/CheckoutController.cs | 11 +++-------- .../Models/Checkout/CheckoutProgressModel.cs | 2 -- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs index bde2fc97f0..eb45eca2ff 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs @@ -972,15 +972,10 @@ public ActionResult Completed() [ChildActionOnly] public ActionResult CheckoutProgress(CheckoutProgressStep step) { - var model = new CheckoutProgressModel() {CheckoutProgressStep = step}; - - var cart = _workContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, _storeContext.CurrentStore.Id); - var shippingOptions = _shippingService.GetShippingOptions(cart, _workContext.CurrentCustomer.ShippingAddress, "", _storeContext.CurrentStore.Id).ShippingOptions; - - if (shippingOptions.Count <= 1 && _shippingSettings.SkipShippingIfSingleOption) + var model = new CheckoutProgressModel { - model.DisplayShippingOptions = false; - } + CheckoutProgressStep = step + }; return PartialView(model); } diff --git a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutProgressModel.cs b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutProgressModel.cs index 0093a6091f..5ef7e38f32 100644 --- a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutProgressModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutProgressModel.cs @@ -5,8 +5,6 @@ namespace SmartStore.Web.Models.Checkout public partial class CheckoutProgressModel : ModelBase { public CheckoutProgressStep CheckoutProgressStep { get; set; } - - public bool DisplayShippingOptions { get; set; } } public enum CheckoutProgressStep From be2581253da39e78c653969a9e9fa2b320ffede9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 10 Dec 2018 11:20:01 +0100 Subject: [PATCH 332/932] Added default language note to installed languages list --- .../Migrations/MigrationsConfiguration.cs | 11 +++++++++-- .../Controllers/LanguageController.cs | 7 +++++++ .../Administration/Views/Language/List.cshtml | 15 ++++++++++----- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 1ae92861b0..c24d77ffd2 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -632,8 +632,15 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Subpage: List index greater than 1 or any active filter.", "Unterseite: Listenindex größer 1 oder mind. ein aktiver Filter."); - builder.AddOrUpdate("Admin.Common.CopyOf", "Copy of {0}", "Kopie von {0}"); - } + + builder.AddOrUpdate("Admin.Configuration.Languages.DefaultLanguage.Note", + "The default language of the shop is {0}. The default is always the first published language.", + "Die Standardsprache des Shops ist {0}. Standard ist stets die erste veröffentlichte Sprache."); + + builder.AddOrUpdate("Admin.Configuration.Languages.AvailableLanguages.Note", + "Click Download to install a new language including all localized resources. On translate.smartstore.com you will find more details about available resources.", + "Klicken Sie auf Download, um eine neue Sprache mit allen lokalisierten Ressourcen zu installieren. Auf translate.smartstore.com finden Sie weitere Details zu verfügbaren Ressourcen."); + } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs index 75f375c200..f5388be5d7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs @@ -335,6 +335,8 @@ public ActionResult List() var lastImportInfos = GetLastResourcesImportInfos(); var languages = _languageService.GetAllLanguages(true); + var defaultLanguageId = _languageService.GetDefaultLanguageId(); + var model = languages.Select(x => { var langModel = x.ToModel(); @@ -346,6 +348,11 @@ public ActionResult List() langModel.LastResourcesImportOnString = langModel.LastResourcesImportOn.Value.RelativeFormat(false, "f"); } + if (x.Id == defaultLanguageId) + { + ViewBag.DefaultLanguageNote = T("Admin.Configuration.Languages.DefaultLanguage.Note", langModel.Name).Text; + } + return langModel; }) .ToList(); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Language/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Language/List.cshtml index af4be8644d..9f7a2db4d1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Language/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Language/List.cshtml @@ -2,6 +2,8 @@ @model List @{ ViewBag.Title = T("Admin.Configuration.Languages").Text; + + var defaultLanguageNote = ViewBag.DefaultLanguageNote as string; }
    @@ -18,6 +20,12 @@
    @T("Admin.Configuration.Languages.InstalledLanguages")
    + @if (defaultLanguageNote.HasValue()) + { +
    + @Html.Raw(defaultLanguageNote) +
    + }
    @@ -92,10 +100,7 @@ +
    @using (Ajax.BeginForm("Authenticate", new AjaxOptions { @@ -74,33 +77,36 @@
    } -
    - @if (Model.Title.HasValue()) - { -
    -
    - @Html.Raw("<" + Model.TitleTag + Html.LanguageAttributes(Model.Title) + " class=\"block-title\">") - @Html.Raw(Model.Title) - @Html.Raw("") +@if (Model.Title.HasValue() || Model.Intro.HasValue() || Model.Body.HasValue()) +{ +
    + @if (Model.Title.HasValue()) + { +
    +
    + @Html.Raw("<" + Model.TitleTag + Html.LanguageAttributes(Model.Title) + " class=\"block-title\">") + @Html.Raw(Model.Title) + @Html.Raw("") +
    -
    - } + } - @if (Model.Intro.HasValue()) - { -

    - @Html.Raw(Model.Intro) -

    - } + @if (Model.Intro.HasValue()) + { +

    + @Html.Raw(Model.Intro) +

    + } - @{ Html.RenderWidget("topic_body_before"); } + @{ Html.RenderWidget("topic_body_before"); } - @if (Model.Body.HasValue()) - { -
    - @Html.Raw(Model.Body) -
    - } + @if (Model.Body.HasValue()) + { +
    + @Html.Raw(Model.Body) +
    + } - @{ Html.RenderWidget("topic_body_after"); } -
    + @{ Html.RenderWidget("topic_body_after"); } +
    +} From 78cb78270e01dafd5a1aaff7c5f9c5f7b546597f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 13 Dec 2018 09:22:39 +0100 Subject: [PATCH 338/932] Amazon Pay: Fixes missing scope parameter in OffAmazonPayments.Widgets.Wallet --- .../Views/AmazonPayCheckout/PaymentMethod.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/SmartStore.AmazonPay/Views/AmazonPayCheckout/PaymentMethod.cshtml b/src/Plugins/SmartStore.AmazonPay/Views/AmazonPayCheckout/PaymentMethod.cshtml index a736fed4e8..233d0251c7 100644 --- a/src/Plugins/SmartStore.AmazonPay/Views/AmazonPayCheckout/PaymentMethod.cshtml +++ b/src/Plugins/SmartStore.AmazonPay/Views/AmazonPayCheckout/PaymentMethod.cshtml @@ -57,7 +57,7 @@ try { new OffAmazonPayments.Widgets.Wallet({ sellerId: '@Model.SellerId', - scope: '', + scope: 'profile payments:widget payments:shipping_address payments:billing_address', amazonOrderReferenceId: '@Model.OrderReferenceId', design: { designMode: 'responsive' From 9fb73b334c6762e8f7278cac9349edf3e428b3dd Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 13 Dec 2018 18:44:46 +0100 Subject: [PATCH 339/932] Theming: enhanced #scroll-top button --- .../SmartStore.Web/Themes/Flex/Content/_layout.scss | 11 +++++++---- .../Views/Shared/Layouts/_Layout.cshtml | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_layout.scss b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_layout.scss index 22107df483..cf7cd56db7 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_layout.scss +++ b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_layout.scss @@ -109,15 +109,18 @@ body { right: 0; width: 36px; height: 36px; - border-radius: 3px 0 0 3px; - border: 1px solid rgba(0,0,0, 0.15); + border-radius: $border-radius 0 0 $border-radius; + border: 1px solid rgba(#000, 0.1); border-right-width: 0; - background: rgba(255,255,255, 0.9); + background: rgba(#fff, 0.9); color: $body-color !important; text-decoration: none; transform: translate3d(100%, 0, 0); - transition: transform .2s ease-in-out; + transition: all .5s ease; + opacity: 0; + &.in { transform: translate3d(0, 0, 0) !important; + opacity: 1; } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.cshtml index 6670f8fa52..17e37593b0 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.cshtml @@ -132,7 +132,7 @@
    - + From 5bad2a1a21a4f49ac78983f90aafd57c076d6b8b Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 13 Dec 2018 18:45:52 +0100 Subject: [PATCH 340/932] Theming: granularity > separated assets registration from _Document layout --- .../SmartStore.Web/SmartStore.Web.csproj | 1 + .../Views/Shared/Layouts/_Document.cshtml | 109 ++++-------------- .../Views/Shared/Partials/_Assets.cshtml | 69 +++++++++++ 3 files changed, 90 insertions(+), 89 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Views/Shared/Partials/_Assets.cshtml diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index e012358e35..9e347df693 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -1780,6 +1780,7 @@ PreserveNewest + Web.config diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Document.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Document.cshtml index 4a16d4be4d..d65c873084 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Document.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Document.cshtml @@ -1,88 +1,19 @@ @using SmartStore.Core; -@{ - var jsRoot = "~/Scripts/"; - var vendorsRoot = "~/Content/vendors/"; - var contentRoot = "~/Content/"; - - // add css assets - Html.AddCssFileParts( - contentRoot + "fontastic/fontastic.css", - vendorsRoot + "font-awesome/font-awesome.css", - vendorsRoot + "pnotify/css/pnotify.css", - vendorsRoot + "pnotify/css/pnotify.mobile.css", - vendorsRoot + "pnotify/css/pnotify.buttons.css"); - - Html.AppendScriptParts(ResourceLocation.Head, - vendorsRoot + "modernizr/modernizr.js", - vendorsRoot + "jquery/jquery-3.2.1.js"/*, - vendorsRoot + "jquery/jquery-migrate-3.0.0.js"*/); - - Html.AppendScriptParts(ResourceLocation.Foot, - // Vendors - vendorsRoot + "underscore/underscore.js", - vendorsRoot + "underscore/underscore.string.js", - vendorsRoot + "jquery/jquery.addeasing.js", - vendorsRoot + "jquery-ui/effect.js", - vendorsRoot + "jquery-ui/effect-shake.js", - vendorsRoot + "jquery/jquery.unobtrusive-ajax.js", - vendorsRoot + "jquery/jquery.validate.js", - vendorsRoot + "jquery/jquery.validate.unobtrusive.js", - vendorsRoot + "jquery/jquery.ba-outside-events.js", - vendorsRoot + "jquery/jquery.scrollTo.js", - vendorsRoot + "moment/moment.js", - vendorsRoot + "datetimepicker/js/tempusdominus-bootstrap-4.js", - vendorsRoot + "select2/js/select2.js", - vendorsRoot + "pnotify/js/pnotify.js", - vendorsRoot + "pnotify/js/pnotify.mobile.js", - vendorsRoot + "pnotify/js/pnotify.buttons.js", - vendorsRoot + "pnotify/js/pnotify.animate.js", - vendorsRoot + "slick/slick.js", - vendorsRoot + "touchspin/jquery.bootstrap-touchspin.js", - vendorsRoot + "aos/js/aos.js", - contentRoot + "bs4/js/bootstrap.bundle.js", - // Common - jsRoot + "underscore.mixins.js", - jsRoot + "smartstore.system.js", - jsRoot + "smartstore.touchevents.js", - jsRoot + "smartstore.jquery.utils.js", - jsRoot + "smartstore.globalization.js", - jsRoot + "jquery.validate.unobtrusive.custom.js", - jsRoot + "smartstore.viewport.js", - jsRoot + "smartstore.doajax.js", - jsRoot + "smartstore.eventbroker.js", - jsRoot + "smartstore.hacks.js", - jsRoot + "smartstore.common.js", - jsRoot + "smartstore.selectwrapper.js", - jsRoot + "smartstore.throbber.js", - jsRoot + "smartstore.thumbzoomer.js", - jsRoot + "smartstore.responsiveNav.js", - jsRoot + "smartstore.keynav.js", - jsRoot + "smartstore.articlelist.js", - jsRoot + "smartstore.megamenu.js", - jsRoot + "smartstore.offcanvas.js", - jsRoot + "smartstore.parallax.js", - // Shop - jsRoot + "public.common.js", - jsRoot + "public.search.js", - jsRoot + "public.offcanvas-cart.js", - jsRoot + "public.offcanvas-menu.js", - jsRoot + "public.product.js"); - - //Html.AddBodyCssClass(this.WorkContext.WorkingLanguage.Rtl ? "rtl" : ""); -} - @Html.SmartTitle(true) + @Html.SmartTitle(true) - - - + + + - @*This is used so that themes can inject content into the header*@ + @{ Html.RenderPartial("_Assets"); } + + @*This is used so that themes can inject content into the header*@ @{ Html.RenderPartial("Head"); } @Html.SmartMetaRobots() @@ -90,34 +21,34 @@ @{ Html.RenderPartial("_ClientRes"); } @{ Html.RenderPartial("_GoogleFonts"); } - @Html.SmartCssFiles(this.Url, ResourceLocation.Head) - @Html.SmartScripts(this.Url, ResourceLocation.Head) + @Html.SmartCssFiles(this.Url, ResourceLocation.Head) + @Html.SmartScripts(this.Url, ResourceLocation.Head) - @{ Html.RenderWidget("head_html_tag"); } + @{ Html.RenderWidget("head_html_tag"); } - @Html.CanonicalUrls() + @Html.CanonicalUrls() @Html.LinkRels() - @{ + @{ Html.RenderAction("RssHeaderLink", "News", new { area = "" }); Html.RenderAction("RssHeaderLink", "Blog", new { area = "" }); } - @*Favicon - upload favicon.ico or favicon-[StoreId].ico file either to the root web or your theme directory*@ + @*Favicon - upload favicon.ico or favicon-[StoreId].ico file either to the root web or your theme directory*@ @{ Html.RenderAction("Favicon", "Common", new { area = "" }); } - + @Html.CustomHead() @{ Html.RenderZone("head"); } - + - + @{ Html.RenderZone("start"); } - @RenderBody() + @RenderBody() @{ Html.RenderZone("aftercontent"); } - - @Html.SmartCssFiles(this.Url, ResourceLocation.Foot) - @Html.SmartScripts(this.Url, ResourceLocation.Foot) + + @Html.SmartCssFiles(this.Url, ResourceLocation.Foot) + @Html.SmartScripts(this.Url, ResourceLocation.Foot) @Html.LocalizationScript(WorkContext.WorkingLanguage.UniqueSeoCode, vendorsRoot + "select2/js/i18n", "*.js", null) @Html.LocalizationScript(WorkContext.WorkingLanguage.UniqueSeoCode, vendorsRoot + "moment/locale", "*.js", null) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Partials/_Assets.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Partials/_Assets.cshtml new file mode 100644 index 0000000000..e48f35e1c4 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Views/Shared/Partials/_Assets.cshtml @@ -0,0 +1,69 @@ +@{ + var jsRoot = "~/Scripts/"; + var vendorsRoot = "~/Content/vendors/"; + var contentRoot = "~/Content/"; + + // add css assets + Html.AddCssFileParts( + contentRoot + "fontastic/fontastic.css", + vendorsRoot + "font-awesome/font-awesome.css", + vendorsRoot + "pnotify/css/pnotify.css", + vendorsRoot + "pnotify/css/pnotify.mobile.css", + vendorsRoot + "pnotify/css/pnotify.buttons.css"); + + Html.AppendScriptParts(ResourceLocation.Head, + vendorsRoot + "modernizr/modernizr.js", + vendorsRoot + "jquery/jquery-3.2.1.js"/*, + vendorsRoot + "jquery/jquery-migrate-3.0.0.js"*/); + + Html.AppendScriptParts(ResourceLocation.Foot, + // Vendors + vendorsRoot + "underscore/underscore.js", + vendorsRoot + "underscore/underscore.string.js", + vendorsRoot + "jquery/jquery.addeasing.js", + vendorsRoot + "jquery-ui/effect.js", + vendorsRoot + "jquery-ui/effect-shake.js", + vendorsRoot + "jquery/jquery.unobtrusive-ajax.js", + vendorsRoot + "jquery/jquery.validate.js", + vendorsRoot + "jquery/jquery.validate.unobtrusive.js", + vendorsRoot + "jquery/jquery.ba-outside-events.js", + vendorsRoot + "jquery/jquery.scrollTo.js", + vendorsRoot + "moment/moment.js", + vendorsRoot + "datetimepicker/js/tempusdominus-bootstrap-4.js", + vendorsRoot + "select2/js/select2.js", + vendorsRoot + "pnotify/js/pnotify.js", + vendorsRoot + "pnotify/js/pnotify.mobile.js", + vendorsRoot + "pnotify/js/pnotify.buttons.js", + vendorsRoot + "pnotify/js/pnotify.animate.js", + vendorsRoot + "slick/slick.js", + vendorsRoot + "touchspin/jquery.bootstrap-touchspin.js", + vendorsRoot + "aos/js/aos.js", + contentRoot + "bs4/js/bootstrap.bundle.js", + // Common + jsRoot + "underscore.mixins.js", + jsRoot + "smartstore.system.js", + jsRoot + "smartstore.touchevents.js", + jsRoot + "smartstore.jquery.utils.js", + jsRoot + "smartstore.globalization.js", + jsRoot + "jquery.validate.unobtrusive.custom.js", + jsRoot + "smartstore.viewport.js", + jsRoot + "smartstore.doajax.js", + jsRoot + "smartstore.eventbroker.js", + jsRoot + "smartstore.hacks.js", + jsRoot + "smartstore.common.js", + jsRoot + "smartstore.selectwrapper.js", + jsRoot + "smartstore.throbber.js", + jsRoot + "smartstore.thumbzoomer.js", + jsRoot + "smartstore.responsiveNav.js", + jsRoot + "smartstore.keynav.js", + jsRoot + "smartstore.articlelist.js", + jsRoot + "smartstore.megamenu.js", + jsRoot + "smartstore.offcanvas.js", + jsRoot + "smartstore.parallax.js", + // Shop + jsRoot + "public.common.js", + jsRoot + "public.search.js", + jsRoot + "public.offcanvas-cart.js", + jsRoot + "public.offcanvas-menu.js", + jsRoot + "public.product.js"); +} \ No newline at end of file From b9daa1fafc0bdd7a04b84718ed9f97c4f5e1d9f5 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 13 Dec 2018 23:21:14 +0100 Subject: [PATCH 341/932] Theming: FontAwesome icons should be 14px fixed, as they are based on a 14px grid. --- .../Content/vendors/font-awesome/font-awesome.css | 2 +- .../Content/vendors/font-awesome/font-awesome.min.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Content/vendors/font-awesome/font-awesome.css b/src/Presentation/SmartStore.Web/Content/vendors/font-awesome/font-awesome.css index eab1cbb5b7..3afa96bacf 100644 --- a/src/Presentation/SmartStore.Web/Content/vendors/font-awesome/font-awesome.css +++ b/src/Presentation/SmartStore.Web/Content/vendors/font-awesome/font-awesome.css @@ -14,7 +14,7 @@ .fa { display: inline-block; font: normal normal normal 14px/1 FontAwesome; - font-size: inherit; + font-size: 14px; /* MC: changed from 'inherit' to '14px' */ text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; diff --git a/src/Presentation/SmartStore.Web/Content/vendors/font-awesome/font-awesome.min.css b/src/Presentation/SmartStore.Web/Content/vendors/font-awesome/font-awesome.min.css index f7b70903ee..a9a5f6d60e 100644 --- a/src/Presentation/SmartStore.Web/Content/vendors/font-awesome/font-awesome.min.css +++ b/src/Presentation/SmartStore.Web/Content/vendors/font-awesome/font-awesome.min.css @@ -1,4 +1,4 @@ /*! * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('fontawesome-webfont.eot?v=4.7.0');src:url('fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('fontawesome-webfont.woff?v=4.7.0') format('woff'),url('fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} + */@font-face{font-family:'FontAwesome';src:url('fontawesome-webfont.eot?v=4.7.0');src:url('fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('fontawesome-webfont.woff?v=4.7.0') format('woff'),url('fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:14px;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} From b4ab5934acd57c66422763c6edd93fcb871b1943 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 13 Dec 2018 23:22:48 +0100 Subject: [PATCH 342/932] Theming: enhanced modal dialog fade in/out animations --- .../Administration/Content/_variables.scss | 6 +++ .../SmartStore.Web/Content/shared/_modal.scss | 39 +++++++++++++++---- .../Scripts/smartstore.common.js | 22 +++++++---- .../Views/Shared/Layouts/_Document.cshtml | 4 +- 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss b/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss index 7e0de9e97f..321501a1e6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss +++ b/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss @@ -175,6 +175,12 @@ $component-active-bg: lighten($primary, 36%); $component-active-color: inherit; +$modal-content-border-width: 0; +$modal-backdrop-opacity: .55; +$modal-content-box-shadow-xs: 0 12px 26px rgba(#32325d, .12), 0 3px 10px rgba(#000, .08); +$modal-content-box-shadow-sm-up: 0 50px 100px rgba(#32325d, .12), 0 15px 35px rgba(#32325d, .27), 0 5px 15px rgba(#000, .25); + + /*$custom-control-indicator-bg: $gray-200; $custom-control-indicator-disabled-bg: $gray-100; $custom-control-indicator-checked-color: #fff; diff --git a/src/Presentation/SmartStore.Web/Content/shared/_modal.scss b/src/Presentation/SmartStore.Web/Content/shared/_modal.scss index 76f57c64e1..dc62ad85a1 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_modal.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_modal.scss @@ -2,21 +2,46 @@ /// /// +//$modal-transition-timing: cubic-bezier(0.25, 0.46, 0.45, 0.94); // easeOutQuad +$modal-transition-timing: cubic-bezier(0.165, 0.84, 0.44, 1); // easeOutQuart +//$modal-transition-timing: cubic-bezier(0.19, 1, 0.22, 1); // easeOutExpo + // FLEX MODAL // ------------------------------------ -.modal-backdrop.fade, + +.modal-backdrop.fade { + // fade out + transition: opacity 0.4s $modal-transition-timing; + // fade in + &.show { + transition: opacity 0.15s ease-out; + } +} + .modal.fade { - transition: opacity 0.2s linear, transform 0.2s ease-out; + .modal-dialog { + transition: opacity 0.4s $modal-transition-timing, transform 0.4s $modal-transition-timing; + transform: scale(1.1, 1.1); + will-change: transform, opacity; + } + + &.show .modal-dialog { + transform: scale(1, 1); + opacity: 1; + } } -.modal.fade .modal-dialog { - transition: opacity 0.2s linear, transform 0.2s ease-out; - transform: translate(0, -75px) scale(0.9, 0.9); +.modal-open #page { + // fade in + filter: blur(2px); + transition: filter 0.4s $modal-transition-timing 0.15s; } -.modal.show .modal-dialog { - transform: translate(0, 0); +.modal-hiding #page { + // fade out + filter: blur(0); + transition: filter 0.4s $modal-transition-timing; } .modal-content { diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js index 46a38183a6..d64551d8ab 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js @@ -52,7 +52,7 @@ '' ].join(""); - modal = $(html).appendTo('body').on('hidden.bs.modal', function (e) { + modal = $(html).appendTo('body').on('hidden.bs.modal', function (e) { modal.remove(); }); @@ -311,7 +311,9 @@ // on document ready $(function () { - var rtl = SmartStore.globalization.culture.isRTL; + var rtl = SmartStore.globalization.culture.isRTL, + win = $(window), + body = $(document.body); function getFunction(code, argNames) { var fn = window, parts = (code || "").split("."); @@ -515,7 +517,7 @@ elLabel.text(sel); }); - $('body').on('mouseenter mouseleave mousedown change', '.mf-dropdown > select', function (e) { + body.on('mouseenter mouseleave mousedown change', '.mf-dropdown > select', function (e) { var btn = $(this).parent().find('> .btn'); if (e.type == "mouseenter") { btn.addClass('hover'); @@ -523,7 +525,7 @@ else if (e.type == "mousedown") { btn.addClass('active focus').removeClass('hover'); _.delay(function () { - $('body').one('mousedown touch', function (e) { btn.removeClass('active focus'); }); + body.one('mousedown touch', function (e) { btn.removeClass('active focus'); }); }, 50); } else if (e.type == "mouseleave") { @@ -632,14 +634,14 @@ (function () { $('#scroll-top').on('click', function (e) { e.preventDefault(); - $(window).scrollTo(0, 600); + win.scrollTo(0, 600); return false; }); var prevY; var throttledScroll = _.throttle(function (e) { - var y = $(window).scrollTop(); + var y = win.scrollTop(); if (_.isNumber(prevY)) { // Show scroll button only when scrolled up if (y < prevY && y > 500) { @@ -653,8 +655,12 @@ prevY = y; }, 100); - $(window).on("scroll", throttledScroll); - })(); + win.on("scroll", throttledScroll); + })(); + + // Modal stuff + $(document).on('hide.bs.modal', '.modal', function (e) { body.addClass('modal-hiding'); }) + $(document).on('hidden.bs.modal', '.modal', function (e) { body.removeClass('modal-hiding'); }) }); })( jQuery, this, document ); diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Document.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Document.cshtml index d65c873084..af9251b06f 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Document.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Document.cshtml @@ -49,8 +49,8 @@ @Html.SmartCssFiles(this.Url, ResourceLocation.Foot) @Html.SmartScripts(this.Url, ResourceLocation.Foot) - @Html.LocalizationScript(WorkContext.WorkingLanguage.UniqueSeoCode, vendorsRoot + "select2/js/i18n", "*.js", null) - @Html.LocalizationScript(WorkContext.WorkingLanguage.UniqueSeoCode, vendorsRoot + "moment/locale", "*.js", null) + @Html.LocalizationScript(WorkContext.WorkingLanguage.UniqueSeoCode, "~/Content/vendors/select2/js/i18n", "*.js", null) + @Html.LocalizationScript(WorkContext.WorkingLanguage.UniqueSeoCode, "~/Content/vendors/moment/locale", "*.js", null) @{ Html.RenderZone("end"); } From bd6534ac7d5127c7612739fa31baf63ab24b2755 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 14 Dec 2018 02:17:45 +0100 Subject: [PATCH 343/932] Theming: minor fix --- .../SmartStore.Web/Views/Shared/Layouts/_Layout.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.cshtml index 17e37593b0..f0d14c3065 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Layouts/_Layout.cshtml @@ -131,7 +131,7 @@ @{ Html.RenderZone("page-end"); } - + From dad0336921ef26b16244dd3e85e3492a4cd1633e Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 15 Dec 2018 03:24:58 +0100 Subject: [PATCH 344/932] Theming: updated to FontAwesome 5 Free (Pro usage also possible with switch 'fa-use-pro', but only with a valid personal license) --- .../DataExchange/Export/ExportExtensions.cs | 12 +- .../Views/AmazonPay/Configure.cshtml | 4 +- .../SmartStore.Clickatell/AdminMenu.cs | 2 +- src/Plugins/SmartStore.DevTools/AdminMenu.cs | 4 +- .../Views/DevTools/BackendExtension.cshtml | 2 +- .../ExternalAuthFacebook/PublicInfo.cshtml | 2 +- .../AdminMenu.cs | 2 +- .../SmartStore.GoogleMerchantCenter/Events.cs | 2 +- .../Views/PayPalPlus/Configure.cshtml | 6 +- .../Extensions/HtmlExtensions.cs | 28 +- .../Administration/Content/_variables.scss | 15 +- .../Content/filemanager/js/file.js | 4 +- .../Content/filemanager/js/utils.js | 30 +- .../Administration/Content/theme.scss | 15 +- .../Models/Orders/OrderModel.cs | 4 +- .../Views/ActivityLog/ListLogs.cshtml | 4 +- .../Views/Affiliate/Edit.cshtml | 2 +- .../Administration/Views/Blog/Edit.cshtml | 2 +- .../Administration/Views/Blog/List.cshtml | 2 +- .../Administration/Views/Campaign/Edit.cshtml | 4 +- .../Administration/Views/Category/Edit.cshtml | 4 +- .../Views/Category/_CreateOrUpdate.cshtml | 6 +- .../Views/CheckoutAttribute/Edit.cshtml | 2 +- .../Views/Common/CheckUpdate.cshtml | 4 +- .../Views/Common/Maintenance.cshtml | 6 +- .../Administration/Views/Country/Edit.cshtml | 2 +- .../Administration/Views/Currency/Edit.cshtml | 2 +- .../Administration/Views/Currency/List.cshtml | 4 +- .../Views/Currency/_CreateOrUpdate.cshtml | 4 +- .../Administration/Views/Customer/Edit.cshtml | 2 +- .../Views/Customer/Reports.cshtml | 2 +- .../Views/Customer/_CreateOrUpdate.cshtml | 4 +- .../Views/CustomerRole/Edit.cshtml | 2 +- .../Views/DeliveryTime/Edit.cshtml | 2 +- .../Views/DeliveryTime/List.cshtml | 2 +- .../Administration/Views/Discount/Edit.cshtml | 2 +- .../Views/Discount/_CreateOrUpdate.cshtml | 2 +- .../Views/EmailAccount/Edit.cshtml | 2 +- .../Administration/Views/Export/Edit.cshtml | 8 +- .../Views/Export/EditDeployment.cshtml | 2 +- .../Views/Export/Preview.cshtml | 6 +- .../Views/Export/ProfileFileDetails.cshtml | 2 +- .../Export/_CreateOrUpdate.Deployment.cshtml | 2 +- .../Views/Export/_ProfileList.cshtml | 2 +- .../Views/Export/_Tab.Deployment.cshtml | 4 +- .../Views/Forum/EditForum.cshtml | 2 +- .../Views/Forum/EditForumGroup.cshtml | 2 +- .../Administration/Views/GiftCard/Edit.cshtml | 2 +- .../Views/GiftCard/_CreateOrUpdate.cshtml | 2 +- .../Administration/Views/Home/Index.cshtml | 2 +- .../Administration/Views/Home/UaTester.cshtml | 2 +- .../Administration/Views/Import/Edit.cshtml | 6 +- .../Administration/Views/Import/List.cshtml | 2 +- .../Views/Import/_ColumnMappings.cshtml | 6 +- .../Views/Import/_Update.cshtml | 8 +- .../Administration/Views/Language/Edit.cshtml | 2 +- .../Partials/AvailableLanguages.cshtml | 2 +- .../Administration/Views/Log/List.cshtml | 4 +- .../Administration/Views/Log/View.cshtml | 2 +- .../Views/Manufacturer/Edit.cshtml | 4 +- .../Views/Manufacturer/List.cshtml | 2 +- .../Views/MessageTemplate/Edit.cshtml | 8 +- .../Views/MessageTemplate/List.cshtml | 2 +- .../Administration/Views/News/Edit.cshtml | 2 +- .../Administration/Views/News/List.cshtml | 2 +- .../Views/NewsLetterSubscription/List.cshtml | 2 +- .../Views/OnlineCustomer/List.cshtml | 2 +- .../Administration/Views/Order/Edit.cshtml | 4 +- .../Administration/Views/Order/List.cshtml | 6 +- .../Views/Order/ShipmentDetails.cshtml | 4 +- .../Order/_Edit.BillingAndShipment.cshtml | 6 +- .../Views/Order/_Edit.Info.cshtml | 6 +- .../Views/Payment/Providers.cshtml | 2 +- .../Views/Plugin/ConfigurePlugin.cshtml | 2 +- .../Views/Plugin/ConfigureProvider.cshtml | 2 +- .../Administration/Views/Plugin/List.cshtml | 8 +- .../Administration/Views/Poll/Edit.cshtml | 2 +- .../Administration/Views/Product/Edit.cshtml | 8 +- .../Administration/Views/Product/List.cshtml | 2 +- .../Views/Product/LowStockReport.cshtml | 2 +- .../Product/_CreateOrUpdate.Attributes.cshtml | 4 +- .../Product/_CreateOrUpdate.Downloads.cshtml | 2 +- .../Views/Product/_CreateOrUpdate.cshtml | 10 +- .../Views/ProductAttribute/Edit.cshtml | 2 +- .../Views/ProductReview/Edit.cshtml | 2 +- .../Views/ProductReview/List.cshtml | 2 +- .../Views/QuantityUnit/Edit.cshtml | 2 +- .../Views/QueuedEmail/Edit.cshtml | 4 +- .../Views/QueuedEmail/List.cshtml | 6 +- .../Views/RecurringPayment/Edit.cshtml | 2 +- .../Views/RecurringPayment/List.cshtml | 2 +- .../Views/ReturnRequest/Edit.cshtml | 2 +- .../Views/RoxyFileManager/Index.cshtml | 24 +- .../Views/ScheduleTask/Edit.cshtml | 2 +- .../Views/ScheduleTask/List.cshtml | 2 +- .../ScheduleTask/_MinimalTaskWidget.cshtml | 4 +- .../Administration/Views/Setting/Blog.cshtml | 2 +- .../Views/Setting/DataExchange.cshtml | 2 +- .../Views/Setting/GeneralCommon.cshtml | 2 +- .../Administration/Views/Setting/Media.cshtml | 4 +- .../Administration/Views/Setting/Order.cshtml | 2 +- .../Views/Setting/Payment.cshtml | 2 +- .../Shared/EditorTemplates/Download.cshtml | 4 +- .../Views/Shared/Layouts/_AdminRoot.cshtml | 4 +- .../Views/Shared/Partials/Menu.cshtml | 7 +- .../Views/Shared/Partials/Navbar.cshtml | 14 +- .../Views/Shared/Partials/_Providers.cshtml | 4 +- .../Views/Shipping/EditMethod.cshtml | 2 +- .../ShoppingCart/CurrentWishlists.cshtml | 2 +- .../Views/SpecificationAttribute/Edit.cshtml | 2 +- .../Views/SpecificationAttribute/List.cshtml | 2 +- .../Administration/Views/Store/Edit.cshtml | 2 +- .../Views/Theme/Configure.cshtml | 6 +- .../Administration/Views/Theme/List.cshtml | 8 +- .../Views/Theme/PreviewTool.cshtml | 4 +- .../Administration/Views/Topic/Edit.cshtml | 2 +- .../Administration/Views/Topic/List.cshtml | 2 +- .../Views/UrlRecord/Edit.cshtml | 4 +- .../Views/UrlRecord/List.cshtml | 2 +- .../Administration/sitemap.config | 133 +- .../Content/editors/summernote/globalinit.js | 12 +- .../summernote/plugins/smartstore.cssclass.js | 4 +- .../summernote/plugins/smartstore.image.js | 4 +- .../SmartStore.Web/Content/shared/_alert.scss | 2 +- .../Content/shared/_choice.scss | 3 +- .../Content/shared/_dropdown.scss | 10 +- .../SmartStore.Web/Content/shared/_fa.scss | 57 + .../Content/shared/_mixins.scss | 37 +- .../SmartStore.Web/Content/shared/_nav.scss | 6 +- .../Content/shared/_star-rating.scss | 6 +- .../Content/shared/_variables-shared.scss | 178 + .../Content/skinning/_photoswipe.scss | 2 +- .../Content/vendors/fa5/css/all.css | 4223 +++++++++++++++++ .../Content/vendors/fa5/css/all.min.css | 5 + .../Content/vendors/fa5/css/brands.css | 13 + .../Content/vendors/fa5/css/brands.min.css | 5 + .../Content/vendors/fa5/css/fontawesome.css | 4193 ++++++++++++++++ .../vendors/fa5/css/fontawesome.min.css | 5 + .../Content/vendors/fa5/css/regular.css | 14 + .../Content/vendors/fa5/css/regular.min.css | 5 + .../Content/vendors/fa5/css/solid.css | 15 + .../Content/vendors/fa5/css/solid.min.css | 5 + .../Content/vendors/fa5/css/svg-with-js.css | 345 ++ .../vendors/fa5/css/svg-with-js.min.css | 5 + .../Content/vendors/fa5/css/v4-shims.css | 2166 +++++++++ .../Content/vendors/fa5/css/v4-shims.min.css | 5 + .../Content/vendors/fa5/scss/_animated.scss | 20 + .../vendors/fa5/scss/_bordered-pulled.scss | 20 + .../Content/vendors/fa5/scss/_core.scss | 20 + .../vendors/fa5/scss/_fixed-width.scss | 6 + .../Content/vendors/fa5/scss/_icons.scss | 1332 ++++++ .../Content/vendors/fa5/scss/_larger.scss | 23 + .../Content/vendors/fa5/scss/_list.scss | 18 + .../Content/vendors/fa5/scss/_mixins.scss | 57 + .../vendors/fa5/scss/_rotated-flipped.scss | 23 + .../vendors/fa5/scss/_screen-reader.scss | 5 + .../Content/vendors/fa5/scss/_shims.scss | 2062 ++++++++ .../Content/vendors/fa5/scss/_stacked.scss | 31 + .../Content/vendors/fa5/scss/_variables.scss | 1346 ++++++ .../Content/vendors/fa5/scss/brands.scss | 21 + .../Content/vendors/fa5/scss/fontawesome.scss | 16 + .../Content/vendors/fa5/scss/regular.scss | 22 + .../Content/vendors/fa5/scss/solid.scss | 23 + .../Content/vendors/fa5/scss/v4-shims.scss | 6 + .../vendors/fa5/webfonts/fa-brands-400.eot | Bin 0 -> 134000 bytes .../vendors/fa5/webfonts/fa-brands-400.svg | 1260 +++++ .../vendors/fa5/webfonts/fa-brands-400.ttf | Bin 0 -> 133764 bytes .../vendors/fa5/webfonts/fa-brands-400.woff | Bin 0 -> 86736 bytes .../vendors/fa5/webfonts/fa-brands-400.woff2 | Bin 0 -> 73936 bytes .../vendors/fa5/webfonts/fa-regular-400.eot | Bin 0 -> 40308 bytes .../vendors/fa5/webfonts/fa-regular-400.svg | 471 ++ .../vendors/fa5/webfonts/fa-regular-400.ttf | Bin 0 -> 40080 bytes .../vendors/fa5/webfonts/fa-regular-400.woff | Bin 0 -> 18164 bytes .../vendors/fa5/webfonts/fa-regular-400.woff2 | Bin 0 -> 14868 bytes .../vendors/fa5/webfonts/fa-solid-900.eot | Bin 0 -> 208828 bytes .../vendors/fa5/webfonts/fa-solid-900.svg | 2760 +++++++++++ .../vendors/fa5/webfonts/fa-solid-900.ttf | Bin 0 -> 208608 bytes .../vendors/fa5/webfonts/fa-solid-900.woff | Bin 0 -> 102120 bytes .../vendors/fa5/webfonts/fa-solid-900.woff2 | Bin 0 -> 79072 bytes .../Controllers/CustomerController.cs | 22 +- src/Presentation/SmartStore.Web/Global.asax | 2 +- .../Scripts/smartstore.common.js | 4 +- .../SmartStore.Web/SmartStore.Web.csproj | 48 + .../Themes/Flex/Content/_megamenu.scss | 6 +- .../Themes/Flex/Content/_product.scss | 3 +- .../Themes/Flex/Content/_search.scss | 4 +- .../Themes/Flex/Content/_shopbar.scss | 5 +- .../Themes/Flex/Content/_variables.scss | 15 +- .../Themes/Flex/Content/theme.scss | 3 +- .../SmartStore.Web/Themes/Flex/theme.config | 1 + .../Views/Boards/Partials/_ForumPost.cshtml | 2 +- .../SmartStore.Web/Views/Boards/Topic.cshtml | 6 +- .../Views/Catalog/CompareProducts.cshtml | 4 +- .../Catalog/Partials/OffCanvasCompare.cshtml | 4 +- .../Views/Checkout/Completed.cshtml | 2 +- .../Common/Partials/AccountDropdown.cshtml | 12 +- .../Views/Common/Partials/Breadcrumb.cshtml | 2 +- .../Views/Common/Partials/Footer.cshtml | 14 +- .../Views/Common/Partials/Menu.cshtml | 10 +- .../Views/Customer/Orders.cshtml | 6 +- .../Customer/Partials/MyAccountMenu.cshtml | 2 +- .../Views/Entity/Partials/Picker.List.cshtml | 2 +- .../SmartStore.Web/Views/Order/Details.cshtml | 2 +- .../PrivateMessages/Partials/Inbox.cshtml | 2 +- .../PrivateMessages/Partials/SentItems.cshtml | 2 +- .../Views/PrivateMessages/View.cshtml | 2 +- .../Views/Product/Product.cshtml | 2 +- .../Shared/EditorTemplates/DateTime.cshtml | 2 +- .../Views/Shared/EditorTemplates/Html.cshtml | 2 +- .../Shared/EditorTemplates/QtyInput.cshtml | 2 +- .../Views/Shared/EditorTemplates/Time.cshtml | 2 +- .../Partials/Product.List.FilterSort.cshtml | 4 +- .../Partials/Product.List.Item.Buttons.cshtml | 2 +- .../Shared/Partials/Product.List.Pager.cshtml | 4 +- .../Views/Shared/Partials/_Assets.cshtml | 17 +- .../ShoppingCart/Partials/CartItems.cshtml | 2 +- .../Partials/OffCanvasShoppingCart.cshtml | 4 +- .../Partials/OffCanvasWishlist.cshtml | 6 +- .../Views/ShoppingCart/Wishlist.cshtml | 2 +- 219 files changed, 21311 insertions(+), 432 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Content/shared/_fa.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/all.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/all.min.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/brands.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/brands.min.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/fontawesome.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/fontawesome.min.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/regular.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/regular.min.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/solid.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/solid.min.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/svg-with-js.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/svg-with-js.min.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/v4-shims.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/css/v4-shims.min.css create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/_animated.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/_bordered-pulled.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/_core.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/_fixed-width.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/_icons.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/_larger.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/_list.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/_mixins.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/_rotated-flipped.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/_screen-reader.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/_shims.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/_stacked.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/_variables.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/brands.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/fontawesome.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/regular.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/solid.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/scss/v4-shims.scss create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-brands-400.eot create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-brands-400.svg create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-brands-400.ttf create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-brands-400.woff create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-brands-400.woff2 create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-regular-400.eot create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-regular-400.svg create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-regular-400.ttf create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-regular-400.woff create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-regular-400.woff2 create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-solid-900.eot create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-solid-900.svg create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-solid-900.ttf create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-solid-900.woff create mode 100644 src/Presentation/SmartStore.Web/Content/vendors/fa5/webfonts/fa-solid-900.woff2 diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs index c05c807ac7..83a3fb4f96 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs @@ -250,17 +250,17 @@ public static string GetIconClass(this ExportDeploymentType type) switch (type) { case ExportDeploymentType.FileSystem: - return "fa-folder-open-o"; + return "far fa-folder-open"; case ExportDeploymentType.Email: - return "fa-envelope-o"; + return "far fa-envelope"; case ExportDeploymentType.Http: - return "fa-globe"; + return "fa fa-globe"; case ExportDeploymentType.Ftp: - return "fa-files-o"; + return "far fa-copy"; case ExportDeploymentType.PublicFolder: - return "fa-unlock"; + return "fa fa-unlock"; default: - return "fa-question"; + return "fa fa-question"; } } } diff --git a/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml b/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml index 213e0c1cf5..1a342b477f 100644 --- a/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml +++ b/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml @@ -47,11 +47,11 @@ diff --git a/src/Plugins/SmartStore.Clickatell/AdminMenu.cs b/src/Plugins/SmartStore.Clickatell/AdminMenu.cs index 0795b68f34..f336662023 100644 --- a/src/Plugins/SmartStore.Clickatell/AdminMenu.cs +++ b/src/Plugins/SmartStore.Clickatell/AdminMenu.cs @@ -10,7 +10,7 @@ protected override void BuildMenuCore(TreeNode pluginsNode) var menuItem = new MenuItem().ToBuilder() .Text("Clickatell SMS Provider") .ResKey("Plugins.FriendlyName.SmartStore.Clickatell") - .Icon("paper-plane-o") + .Icon("far fa-paper-plane") .Action("ConfigurePlugin", "Plugin", new { systemName = "SmartStore.Clickatell", area = "Admin" }) .ToItem(); diff --git a/src/Plugins/SmartStore.DevTools/AdminMenu.cs b/src/Plugins/SmartStore.DevTools/AdminMenu.cs index 8808b9731c..8110443480 100644 --- a/src/Plugins/SmartStore.DevTools/AdminMenu.cs +++ b/src/Plugins/SmartStore.DevTools/AdminMenu.cs @@ -9,7 +9,7 @@ protected override void BuildMenuCore(TreeNode pluginsNode) { var menuItem = new MenuItem().ToBuilder() .Text("Developer Tools") - .Icon("terminal") + .Icon("far fa-terminal") .Action("ConfigurePlugin", "Plugin", new { systemName = "SmartStore.DevTools", area = "Admin" }) .ToItem(); @@ -18,7 +18,7 @@ protected override void BuildMenuCore(TreeNode pluginsNode) // uncomment to add to admin menu (see plugin sub-menu) //var backendExtensionItem = new MenuItem().ToBuilder() // .Text("Backend extension") - // .Icon("area-chart") + // .Icon("chart-area") // .Action("BackendExtension", "DevTools", new { area = "SmartStore.DevTools" }) // .ToItem(); //pluginsNode.Append(backendExtensionItem); diff --git a/src/Plugins/SmartStore.DevTools/Views/DevTools/BackendExtension.cshtml b/src/Plugins/SmartStore.DevTools/Views/DevTools/BackendExtension.cshtml index 26a5c5311a..0af50e879f 100644 --- a/src/Plugins/SmartStore.DevTools/Views/DevTools/BackendExtension.cshtml +++ b/src/Plugins/SmartStore.DevTools/Views/DevTools/BackendExtension.cshtml @@ -8,7 +8,7 @@
    - + My SmartStore.NET backend extension page
    diff --git a/src/Plugins/SmartStore.FacebookAuth/Views/ExternalAuthFacebook/PublicInfo.cshtml b/src/Plugins/SmartStore.FacebookAuth/Views/ExternalAuthFacebook/PublicInfo.cshtml index 167d36243f..2d894ad278 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Views/ExternalAuthFacebook/PublicInfo.cshtml +++ b/src/Plugins/SmartStore.FacebookAuth/Views/ExternalAuthFacebook/PublicInfo.cshtml @@ -6,6 +6,6 @@ - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs index 9c8413dd67..6a62863d9a 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs @@ -9,7 +9,7 @@ protected override void BuildMenuCore(TreeNode pluginsNode) { var menuItem = new MenuItem().ToBuilder() .Text("Google Merchant Center") - .Icon("google") + .Icon("fab fa-google") .ResKey("Plugins.FriendlyName.SmartStore.GoogleMerchantCenter") .Action("ConfigurePlugin", "Plugin", new { systemName = GoogleMerchantCenterFeedPlugin.SystemName, area = "Admin" }) .ToItem(); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs index 62ec60a9a6..f129d4c009 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs @@ -27,7 +27,7 @@ public void HandleEvent(TabStripCreated eventMessage) eventMessage.ItemFactory.Add().Text("GMC") .Name("tab-gmc") - .Icon("fa fa-google fa-lg fa-fw") + .Icon("fab fa-google fa-lg fa-fw") .LinkHtmlAttributes(new { data_tab_name = "GMC" }) .Route("SmartStore.GoogleMerchantCenter", new { action = "ProductEditTab", productId = productId }) .Ajax(); diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml index 9458875371..efce2e5bbc 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml @@ -95,7 +95,7 @@ - + @T(Model.ExperienceProfileId.HasValue() ? "Common.Refresh" : "Common.AddNew") @@ -103,7 +103,7 @@ { - + @T("Admin.Common.Delete") } @@ -125,7 +125,7 @@ { - + @T("Admin.Common.Delete") } diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs index 4fc585afd1..40e05538a9 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs @@ -817,7 +817,7 @@ public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string Guard.NotNull(helper, nameof(helper)); Guard.NotEmpty(fileExtension, nameof(fileExtension)); - var icon = "file-o"; + var icon = "far fa-file"; var ext = fileExtension; if (ext != null && ext.StartsWith(".")) @@ -830,7 +830,7 @@ public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string switch (ext.ToLowerInvariant()) { case "pdf": - icon = "file-pdf-o"; + icon = "far fa-file-pdf"; break; case "doc": case "docx": @@ -839,18 +839,18 @@ public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string case "dot": case "dotx": case "dotm": - icon = "file-word-o"; + icon = "far fa-file-word"; break; case "xls": case "xlsx": case "xlsm": case "xlsb": case "ods": - icon = "file-excel-o"; + icon = "far fa-file-excel"; break; case "csv": case "tab": - icon = "table"; + icon = "fa fa-file-csv"; break; case "ppt": case "pptx": @@ -862,25 +862,25 @@ public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string case "potm": case "pps": case "ppsm": - icon = "file-powerpoint-o"; + icon = "far fa-file-powerpoint"; break; case "zip": case "rar": case "7z": - icon = "file-archive-o"; + icon = "far fa-file-archive"; break; case "png": case "jpg": case "jpeg": case "bmp": case "psd": - icon = "file-image-o"; + icon = "far fa-file-image"; break; case "mp3": case "wav": case "ogg": case "wma": - icon = "file-audio-o"; + icon = "far fa-file-audio"; break; case "mp4": case "mkv": @@ -889,25 +889,25 @@ public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string case "asf": case "mpg": case "mpeg": - icon = "file-video-o"; + icon = "far fa-file-video"; break; case "txt": - icon = "file-text-o"; + icon = "far fa-file-alt"; break; case "exe": - icon = "gear"; + icon = "fa fa-cog"; break; case "xml": case "html": case "htm": - icon = "file-code-o"; + icon = "far fa-file-code"; break; } } var label = ext.NaIfEmpty().ToUpper(); - var result = "".FormatInvariant( + var result = "".FormatInvariant( icon, extraCssClasses.HasValue() ? " " + extraCssClasses : "", label); diff --git a/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss b/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss index 321501a1e6..d14b8cd8b4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss +++ b/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss @@ -110,9 +110,18 @@ $h5-font-size: 1.25rem; $h6-font-size: 1rem; $small-font-size: 90%; - $headings-font-weight: 400; + +// Shadows + +$box-shadow-sm: 0 0 .625rem 0 rgba(#000, .05); +$box-shadow: 0 1rem 2.25rem rgba(#32325d, .03), 0 0.3125rem 1rem rgba(#000, .12); +$box-shadow-lg: 0 1rem 3rem rgba(#000, .125); + + +// Tables + $table-cell-padding: .75rem; $table-cell-padding-sm: .3rem; $table-border-color: $gray-300; @@ -150,8 +159,8 @@ $btn-border-radius-lg: $input-border-radius-lg; //.1875rem; $btn-border-radius-sm: $input-border-radius-sm; //.125rem; $btn-disabled-opacity: 0.5; -$dropdown-box-shadow: 0 3px 12px rgba(27,31,35, 0.15); //0 2px 6px rgba(#000, .15); -$dropdown-border-color: rgba(#000, 0.08); +$dropdown-box-shadow: $box-shadow; // 0 3px 12px rgba(27,31,35, 0.15); //0 2px 6px rgba(#000, .15); +$dropdown-border-color: rgba(#000, 0.05); $dropdown-link-color: $body-color; //$gray-800; $dropdown-link-hover-color: darken($dropdown-link-color, 5%); $dropdown-link-hover-bg: lighten($gray-200, 3%); diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js index 9d27380ad6..fa252adf39 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js @@ -101,7 +101,7 @@ function File(filePath, fileSize, modTime, w, h, mime) { ]; var html = [ '
  • ', - '
    ', + '
    ', '' + RoxyUtils.FormatDate(new Date(this.time * 1000)) + '', '' + this.name + '', '' + RoxyUtils.FormatFileSize(this.size) + '', @@ -176,7 +176,7 @@ function File(filePath, fileSize, modTime, w, h, mime) { var icon = RoxyIconHints[fileType]; var li = $('li[data-path="' + item.fullPath + '"]'); li.toggleClass('file-image', fileType == 'image'); - $('.file-icon', li).attr('class', "").addClass('file-icon fa fa-fw fa-' + icon.name).css('color', icon.color); + $('.file-icon', li).attr('class', "").addClass('file-icon fa-fw ' + icon.name).css('color', icon.color); $('.name', li).text(newName); $('li[data-path="' + newPath + '"]').attr('data-path', newPath); ret = true; diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/utils.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/utils.js index d056f763ce..bd7c547e9e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/utils.js +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/utils.js @@ -107,21 +107,21 @@ RoxyLang = { } var RoxyIconHints = { - "pdf": { name: "file-pdf-o", color: "#F44336" }, - "document": { name: "file-word-o", color: "#2B579A" }, - "spreadsheet": { name: "file-excel-o", color: "#217346" }, - "database": { name: "database", color: "#3ba074" }, - "presentation": { name: "file-powerpoint-o", color: "#D24726" }, - "archive": { name: "file-archive-o", color: "#3F51B5" }, - "audio": { name: "file-audio-o", color: "#009688" }, - "markup": { name: "file-code-o", color: "#4CAF50" }, - "code": { name: "bolt", color: "#4CAF50" }, - "exe": { name: "gear", color: "#58595B" }, - "image": { name: "file-image-o", color: "#e77c00" }, - "text": { name: "file-text-o", color: "#607D8B" }, - "video": { name: "file-video-o", color: "#FF5722" }, - "font": { name: "font", color: "#797985" }, - "misc": { name: "file-o", color: "#ccc" } + "pdf": { name: "far fa-file-pdf", color: "#F44336" }, + "document": { name: "far fa-file-word", color: "#2B579A" }, + "spreadsheet": { name: "far fa-file-excel", color: "#217346" }, + "database": { name: "fa fa-database", color: "#3ba074" }, + "presentation": { name: "far fa-file-powerpoint", color: "#D24726" }, + "archive": { name: "far fa-file-archive", color: "#3F51B5" }, + "audio": { name: "far fa-file-audio", color: "#009688" }, + "markup": { name: "far fa-file-code", color: "#4CAF50" }, + "code": { name: "fa fa-bolt", color: "#4CAF50" }, + "exe": { name: "fa fa-cog", color: "#58595B" }, + "image": { name: "far fa-file-image", color: "#e77c00" }, + "text": { name: "far fa-file-alt", color: "#607D8B" }, + "video": { name: "far fa-file-video", color: "#FF5722" }, + "font": { name: "fa fa-font", color: "#797985" }, + "misc": { name: "far fa-file", color: "#ccc" } } function RoxyUtils() { } diff --git a/src/Presentation/SmartStore.Web/Administration/Content/theme.scss b/src/Presentation/SmartStore.Web/Administration/Content/theme.scss index 923ebaec36..dabb611b58 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/theme.scss +++ b/src/Presentation/SmartStore.Web/Administration/Content/theme.scss @@ -41,20 +41,20 @@ } } -.btn-secondary { +/*.btn-secondary { @include button-variant($secondary, $secondary, $secondary, rgba(#000, 0.15), darken($secondary, 10%), rgba(#000, 0.2)); -} +}*/ -.btn-light { +/*.btn-light { @include button-variant($light, $light, $light, rgba(#000, 0.15), darken($light, 10%), rgba(#000, 0.2)); -} +}*/ -.btn-secondary, +/*.btn-secondary, .btn-light { &:focus, &.focus { border-color: rgba(#000, 0.2); } -} +}*/ // Light button icons lighter (looks nice) // ----------------------------------------------------- @@ -68,7 +68,7 @@ &:not(:hover):not(.hover):not(:active):not(.active):not(:focus):not(.focus):not([disabled]):not(.disabled) { > i { //opacity: 0.6; - color: lighten($body-color, 20%); + color: lighten($body-color, 12%); } } } @@ -79,6 +79,7 @@ @import '../../Content/shared/_variables-shared.scss'; @import '../../Content/shared/_mixins.scss'; +@import '../../Content/shared/_fa.scss'; @import '../../Content/shared/_typo.scss'; @import '../../Content/shared/_alert.scss'; @import '../../Content/shared/_buttons.scss'; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderModel.cs index fd2e79a904..7d595a21f8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderModel.cs @@ -183,9 +183,9 @@ public string PaymentStatusLabelClass case Core.Domain.Payments.PaymentStatus.Paid: return "fa fa-fw fa-check text-success"; case Core.Domain.Payments.PaymentStatus.PartiallyRefunded: - return "fa fa-fw fa-exchange text-warning"; + return "fa fa-fw fa-exchange-alt text-warning"; case Core.Domain.Payments.PaymentStatus.Refunded: - return "fa fa-fw fa-exchange text-success"; + return "fa fa-fw fa-exchange-alt text-success"; case Core.Domain.Payments.PaymentStatus.Voided: return "fa fa-fw fa-ban muted"; default: diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ActivityLog/ListLogs.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ActivityLog/ListLogs.cshtml index d66c8247a9..104618e045 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ActivityLog/ListLogs.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ActivityLog/ListLogs.cshtml @@ -13,11 +13,11 @@
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Affiliate/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Affiliate/Edit.cshtml index d3a0e807e8..c6c90d06ee 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Affiliate/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Affiliate/Edit.cshtml @@ -18,7 +18,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Blog/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Blog/Edit.cshtml index a7001577b3..95c5235662 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Blog/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Blog/Edit.cshtml @@ -21,7 +21,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Blog/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Blog/List.cshtml index eb2bc78b07..1301a3b84e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Blog/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Blog/List.cshtml @@ -8,7 +8,7 @@ }
    - + @T("Admin.ContentManagement.Blog.BlogPosts")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Campaign/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Campaign/Edit.cshtml index b0b8562938..8a1f55e5f5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Campaign/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Campaign/Edit.cshtml @@ -19,7 +19,7 @@ - + @T("Admin.Common.Preview") } @@ -31,7 +31,7 @@ @T("Admin.Common.SaveContinue")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/Edit.cshtml index 24a9d2773f..16788e029a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Category/Edit.cshtml @@ -13,7 +13,7 @@ @{ Html.RenderWidget("admin_button_toolbar_before"); } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml index f40907d2e0..7a0d9bfd6b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml @@ -7,7 +7,7 @@ @Html.SmartStore().TabStrip().Name("category-edit").OnAjaxSuccess("categoryEditTab_onAjaxSuccess").Style(TabsStyle.Material).Position(TabsPosition.Left).Items(x => { - x.Add().Text(T("Admin.Catalog.Categories.Info").Text).Icon("fa fa-pencil fa-lg fa-fw").Content(TabInfo()).Selected(true); + x.Add().Text(T("Admin.Catalog.Categories.Info").Text).Icon("fa fa-pencil-alt fa-lg fa-fw").Content(TabInfo()).Selected(true); x.Add().Text(T("Admin.Common.SEO").Text).Icon("fa fa-search fa-lg fa-fw").Content(TabSeo()); x.Add().Text(T("Admin.Catalog.Categories.Products").Text).Icon("fa fa-cube fa-lg fa-fw").Content(TabProducts()); x.Add().Text(T("Admin.Catalog.Categories.Discounts").Text).Icon("fa fa-percent fa-lg fa-fw").Content(TabDiscounts()); @@ -81,7 +81,7 @@
  • @@ -138,7 +138,7 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/Edit.cshtml index a669015600..0eafa758be 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/Edit.cshtml @@ -20,7 +20,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Common/CheckUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Common/CheckUpdate.cshtml index 6d753c9fc9..55a33211b3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Common/CheckUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Common/CheckUpdate.cshtml @@ -6,7 +6,7 @@
    - + @ViewBag.Title
    @@ -41,7 +41,7 @@ @Html.Raw(T("Admin.CheckUpdate.AutoUpdatePossibleInfo"))

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

    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Common/Maintenance.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Common/Maintenance.cshtml index 1a0526f6dd..6d9f727847 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Common/Maintenance.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Common/Maintenance.cshtml @@ -42,7 +42,7 @@ @@ -93,7 +93,7 @@ @@ -143,7 +143,7 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Country/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Country/Edit.cshtml index ca742f081f..841d226c66 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Country/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Country/Edit.cshtml @@ -18,7 +18,7 @@ @T("Admin.Common.SaveContinue") - + @T("Admin.Common.Delete") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Currency/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Currency/Edit.cshtml index b3221627a1..e09d8871f5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Currency/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Currency/Edit.cshtml @@ -18,7 +18,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Currency/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Currency/List.cshtml index 2bad1d1227..7ad1e8c55d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Currency/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Currency/List.cshtml @@ -9,7 +9,7 @@
    - + @T("Admin.Configuration.Currencies")
    @@ -22,7 +22,7 @@ @T("Admin.Common.Save") - + @T("Admin.Configuration.Currencies.GetLiveRates")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Currency/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Currency/_CreateOrUpdate.cshtml index 3be6d4f87a..045995d078 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Currency/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Currency/_CreateOrUpdate.cshtml @@ -20,7 +20,7 @@ @Html.SmartStore().TabStrip().Name("currency-edit").Style(TabsStyle.Material).Position(TabsPosition.Top).Items(x => { x.Add().Text(T("Admin.Common.Info").Text) - //.Icon("fa fa-pencil fa-lg fa-fw") + //.Icon("fa fa-pencil-alt fa-lg fa-fw") .Content(TabInfo()) .Selected(true); @@ -28,7 +28,7 @@ //.Icon("fa fa-globe fa-lg fa-fw") .Content(TabStores()); - //generate an event + //generate an event EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "currency-edit", this.Html, this.Model)); }) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/Edit.cshtml index 7e1f08386e..30f350f1b4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/Edit.cshtml @@ -40,7 +40,7 @@ @if (!Model.Deleted) { } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/Reports.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/Reports.cshtml index c6243d97b1..9bee9eea4f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/Reports.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/Reports.cshtml @@ -5,7 +5,7 @@ }
    - + @T("Admin.Customers.Reports")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml index 4533ced3fc..0eafdd7c64 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml @@ -97,7 +97,7 @@ { @@ -719,7 +719,7 @@

    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/CustomerRole/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/CustomerRole/Edit.cshtml index a9d5fe0350..77861faa53 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/CustomerRole/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/CustomerRole/Edit.cshtml @@ -18,7 +18,7 @@ @T("Admin.Common.SaveContinue")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/DeliveryTime/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/DeliveryTime/Edit.cshtml index 466a0c5d8b..79ff89dac5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/DeliveryTime/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/DeliveryTime/Edit.cshtml @@ -17,7 +17,7 @@ @T("Admin.Common.SaveContinue")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/DeliveryTime/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/DeliveryTime/List.cshtml index fcc0bdc0c4..796ded1627 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/DeliveryTime/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/DeliveryTime/List.cshtml @@ -3,7 +3,7 @@ }
    - + @T("Admin.Configuration.DeliveryTimes")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Discount/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Discount/Edit.cshtml index 423fe32eb4..9bd378f0fb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Discount/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Discount/Edit.cshtml @@ -19,7 +19,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Discount/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Discount/_CreateOrUpdate.cshtml index 7aff619be0..d4f58841c8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Discount/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Discount/_CreateOrUpdate.cshtml @@ -409,7 +409,7 @@ name="deleterequirement{{= discountRequirementId }}" id="deleterequirement{{= discountRequirementId }}" onclick="deleteRequirement({{= discountRequirementId }})"> - +
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/EmailAccount/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/EmailAccount/Edit.cshtml index 37f27e47ac..215f4acbd4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/EmailAccount/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/EmailAccount/Edit.cshtml @@ -17,7 +17,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index d7780bc634..928cefbf41 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -22,13 +22,13 @@ if (Model.Id != 0) { } - + @T("Admin.Common.Preview") } @@ -36,7 +36,7 @@ @if (Model.LogFileExists) { - + @T("Admin.Configuration.ActivityLog") } @@ -53,7 +53,7 @@ @if (!Model.IsSystemProfile) { } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml index 08c85de8d2..a63c46efc1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml @@ -22,7 +22,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml index b2866cfa4b..c0a53dc65a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml @@ -19,16 +19,16 @@ @if (Model.LogFileExists) { - + @T("Admin.Configuration.ActivityLog") } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml index e50f06f489..58b01c5355 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml @@ -49,7 +49,7 @@
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml index 8c60028e15..e312ffa95b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml @@ -185,7 +185,7 @@ option = $('select[name="@(Html.FieldNameFor(x => x.DeploymentType))"]').find('option[value="' + item.id + '"]'); } - html += ' '; + html += ' '; html += item.text; html += ''; diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml index 256bed40e3..5b12410204 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml @@ -88,7 +88,7 @@ - + @T("Admin.Configuration.ActivityLog") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml index 5a7b260832..f890ef7440 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -40,7 +40,7 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml index 27941ed1cf..5d00bffdf5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -25,7 +25,7 @@ @if (Model.ColumnMappings.Any()) { } @@ -119,7 +119,7 @@ @@ -410,7 +410,7 @@ leftLabel.attr('title', ''); } - context.find('i').removeClass('fa-globe fa-chain-broken').addClass(isLocalized ? 'fa-globe' : 'fa-chain-broken'); + context.find('i').removeClass('fa-globe fa-unlink').addClass(isLocalized ? 'fa-globe' : 'fa-unlink'); } function initSelectBox(element, isProperty) { @@ -478,7 +478,7 @@ if (option.length === 0) { html += '
    '; html += ''; - html += ' '; + html += ' '; html += ''; } else { @@ -501,7 +501,7 @@ html += ''; html += '' : ' text-warning" />'); html += ''; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Language/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Language/Edit.cshtml index 5e37409a78..98fbea9085 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Language/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Language/Edit.cshtml @@ -27,7 +27,7 @@ @T("Admin.Configuration.Languages.Import")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Language/Partials/AvailableLanguages.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Language/Partials/AvailableLanguages.cshtml index a8c7bb96e8..e2be412332 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Language/Partials/AvailableLanguages.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Language/Partials/AvailableLanguages.cshtml @@ -72,7 +72,7 @@ - + @T("Common.Refresh") } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml index 4cd8d53eae..7409eae3f8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml @@ -13,11 +13,11 @@
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Log/View.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Log/View.cshtml index 0ecad951ca..528364c8bc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Log/View.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Log/View.cshtml @@ -13,7 +13,7 @@
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Edit.cshtml index 4a2c61cbd8..0456f81dc4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Edit.cshtml @@ -13,7 +13,7 @@ @{ Html.RenderWidget("admin_button_toolbar_before"); } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml index b1c4c0e914..ce22947113 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml @@ -5,7 +5,7 @@ }
    - + @T("Admin.Catalog.Manufacturers")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/Edit.cshtml index cfdb659b5f..94d40249ad 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/Edit.cshtml @@ -7,7 +7,7 @@ {
    - + @T("Admin.ContentManagement.MessageTemplates.EditMessageTemplateDetails") - @Model.Name @Html.ActionLink("(" + T("Admin.ContentManagement.MessageTemplates.BackToList") + ")", "List")
    @@ -16,7 +16,7 @@ @* CAUTION: saving a template to XML overwrites the original raw content *@ @**@ - + @T("Admin.Common.Preview") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/List.cshtml index 31f743131c..5b75096ee9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/List.cshtml @@ -6,7 +6,7 @@ }
    - + @T("Admin.ContentManagement.MessageTemplates")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/News/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/News/Edit.cshtml index 06fcf0f6aa..f762b5a245 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/News/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/News/Edit.cshtml @@ -19,7 +19,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml index 39a1707d01..a88278a11e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml @@ -21,7 +21,7 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml index 5f1666018a..6ea6c224f0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml @@ -5,7 +5,7 @@ }
    - + @T("Admin.Promotions.newsLetterSubscriptions")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/OnlineCustomer/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/OnlineCustomer/List.cshtml index 3766723ab5..b46d89452e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/OnlineCustomer/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/OnlineCustomer/List.cshtml @@ -8,7 +8,7 @@ }
    - + @T("Admin.Customers.OnlineCustomers")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml index 520b170a40..6cfd7225f6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml @@ -22,12 +22,12 @@ @if (Model.DisplayPdfInvoice) { - + @T("Admin.Orders.PdfInvoice") } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml index a46bf9a68e..ff4f515541 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml @@ -11,7 +11,7 @@ {
    - + @T("Admin.Orders")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml index 0872b1b569..48ee154e8d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml @@ -18,7 +18,7 @@ }
    @@ -36,7 +36,7 @@ @Html.EditorFor(model => model.TrackingNumber) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.BillingAndShipment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.BillingAndShipment.cshtml index a422025620..9b53e5d2cb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.BillingAndShipment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.BillingAndShipment.cshtml @@ -10,7 +10,7 @@
    @@ -25,11 +25,11 @@
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.Info.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.Info.cshtml index 20dc195c1c..3a226ca316 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.Info.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.Info.cshtml @@ -524,7 +524,7 @@
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml index 043794089b..e6ab7fdeea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml @@ -18,7 +18,7 @@ @Html.SmartStore().TabStrip().Name("product-edit").OnAjaxSuccess("productEditTab_onAjaxSuccess").HtmlAttributes(new { data_product_id = Model.Id }).Style(TabsStyle.Material).Position(TabsPosition.Left).Items(x => { x.Add().Text(T("Admin.Catalog.Products.Info")) - .Icon("fa fa-pencil fa-lg fa-fw") + .Icon("fa fa-pencil-alt fa-lg fa-fw") .LinkHtmlAttributes(new { data_tab_name = "Info" }) .Content(Html.Partial("_CreateOrUpdate.Info", Model).ToHtmlString()) .Selected(true); @@ -54,7 +54,7 @@ if (Model.ProductTypeId != (int)ProductType.GroupedProduct) { x.Add().Text(T("Admin.Catalog.Products.Price").Text) - .Icon("fa fa-euro fa-lg fa-fw") + .Icon("fa fa-euro-sign fa-lg fa-fw") .LinkHtmlAttributes(new { data_tab_name = "Price" }) .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Price" }) .Ajax(); @@ -68,7 +68,7 @@ .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Discounts" }) .Ajax(); x.Add().Text(T("Admin.Catalog.Products.Pictures").Text) - .Icon("fa fa-picture-o fa-lg fa-fw") + .Icon("far fa-image fa-lg fa-fw") .LinkHtmlAttributes(new { data_tab_name = "Pictures" }) .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Pictures" }) .Ajax(); @@ -78,7 +78,7 @@ .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Categories" }) .Ajax(); x.Add().Text(T("Admin.Catalog.Products.Manufacturers").Text) - .Icon("fa fa-building-o fa-lg fa-fw") + .Icon("far fa-building fa-lg fa-fw") .LinkHtmlAttributes(new { data_tab_name = "Manufacturers" }) .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Manufacturers" }) .Ajax(); @@ -91,7 +91,7 @@ if (Model.ProductTypeId != (int)ProductType.BundledProduct) { x.Add().Text(T("Admin.Catalog.Products.ProductVariantAttributes.Attributes").Text) - .Icon("fa fa-list-alt fa-lg fa-fw") + .Icon("far fa-list-alt fa-lg fa-fw") .LinkHtmlAttributes(new { data_tab_name = "Attributes" }) .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Attributes" }) .Ajax(); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/Edit.cshtml index c0e28de45d..6da614eefe 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/Edit.cshtml @@ -17,7 +17,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/Edit.cshtml index 5985378ad4..dad114749a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/Edit.cshtml @@ -17,7 +17,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml index 29d92eaf35..098d1c78dc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml @@ -22,7 +22,7 @@ @T("Admin.Catalog.ProductReviews.DisapproveSelected") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/Edit.cshtml index 41d7320538..965b8600ce 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/Edit.cshtml @@ -17,7 +17,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/Edit.cshtml index 2e6588dedd..7a9991407d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/Edit.cshtml @@ -17,7 +17,7 @@ @T("Admin.Common.SaveContinue") @if (Model.SendManually) @@ -28,7 +28,7 @@ } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/List.cshtml index b31449248f..6a6d0f4123 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/List.cshtml @@ -8,7 +8,7 @@ {
    - + @T("Admin.System.QueuedEmails")
    @@ -27,11 +27,11 @@
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/Edit.cshtml index ed04ef37b0..0eba5eb7ec 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/Edit.cshtml @@ -17,7 +17,7 @@ @T("Admin.Common.SaveContinue")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/List.cshtml index 3aaf4761bc..5e496ca4cb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/List.cshtml @@ -8,7 +8,7 @@ }
    - + @T("Admin.RecurringPayments")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/Edit.cshtml index 6dd81bd552..6241edb6bf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/Edit.cshtml @@ -33,7 +33,7 @@ @T("Admin.Common.SaveContinue")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/RoxyFileManager/Index.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/RoxyFileManager/Index.cshtml index 07e25b370b..62d5175927 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/RoxyFileManager/Index.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/RoxyFileManager/Index.cshtml @@ -38,11 +38,11 @@
    @@ -66,11 +66,11 @@
    @@ -160,7 +160,7 @@ Select - + Preview @@ -177,16 +177,16 @@ Copy - + Paste - + Rename - + Delete @@ -209,16 +209,16 @@ Copy - + Paste - + Rename - + Delete diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml index 2a7590b235..7e4b02b0a1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml @@ -14,7 +14,7 @@
    - + @ViewBag.Title (@T("Common.Back"))
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/List.cshtml index ea74c651fc..6c007ca0ed 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/List.cshtml @@ -7,7 +7,7 @@
    - + @T("Admin.System.ScheduleTasks")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/_MinimalTaskWidget.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/_MinimalTaskWidget.cshtml index c499516c35..e14d009412 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/_MinimalTaskWidget.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/_MinimalTaskWidget.cshtml @@ -27,7 +27,7 @@ {
    @@ -75,7 +75,7 @@ @if (item.CanCancel) { } @@ -125,7 +125,7 @@ diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Partials/OffCanvasShoppingCart.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Partials/OffCanvasShoppingCart.cshtml index 90332d9818..d0b66eddca 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Partials/OffCanvasShoppingCart.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Partials/OffCanvasShoppingCart.cshtml @@ -127,7 +127,7 @@ data-name="@item.ProductName" data-type="cart" data-action="addfromcart"> - + } - + diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Partials/OffCanvasWishlist.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Partials/OffCanvasWishlist.cshtml index 29993c2a65..626744fc56 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Partials/OffCanvasWishlist.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Partials/OffCanvasWishlist.cshtml @@ -15,10 +15,10 @@ @T("ShoppingCart.Mini.EmptyWishlist.Title")

    - +

    - @T("ShoppingCart.Mini.EmptyWishlist.Info", "fa fa-lg fa-heart-o") + @T("ShoppingCart.Mini.EmptyWishlist.Info", "fal fa-lg fa-heart")

    } @@ -127,7 +127,7 @@ data-type="wishlist" data-action="remove" title='@T("Common.Remove")'> - + diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.cshtml index d7a2bb0a33..c547c7a46a 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.cshtml @@ -83,7 +83,7 @@ {
    @foreach (var product in Model.Items) { - var foundProductSpec = product.SpecificationAttributes.Where(psa => psa.SpecificationAttributeId == specificationAttribute.SpecificationAttributeId).FirstOrDefault(); - var specValue = foundProductSpec != null ? foundProductSpec.SpecificationAttributeOption : null; + var foundProductSpecs = product.SpecificationAttributes + .Where(psa => psa.SpecificationAttributeId == specificationAttribute.SpecificationAttributeId); } From 0624d7b414d83c297762c2850d5c83e01b2ef5f8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 23 Jan 2019 21:13:05 +0100 Subject: [PATCH 377/932] Added StoryExportAssetAttribute --- .../Modelling/StoryExportAssetAttribute.cs | 30 +++++++++++++++++++ .../SmartStore.Web.Framework.csproj | 1 + .../Installation/InstallDataSeeder.cs | 1 - 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/Presentation/SmartStore.Web.Framework/Modelling/StoryExportAssetAttribute.cs diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/StoryExportAssetAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/StoryExportAssetAttribute.cs new file mode 100644 index 0000000000..fafd448ad3 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Modelling/StoryExportAssetAttribute.cs @@ -0,0 +1,30 @@ +using System; + +namespace SmartStore.Web.Framework.Modelling +{ + /// + /// Specifies whether a property refers to an asset to be included in the story export. + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public sealed class StoryExportAssetAttribute : Attribute + { + public StoryExportAssetAttribute(StoryExportPropertyType type) + { + Type = type; + } + + /// + /// The property type. + /// + public StoryExportPropertyType Type { get; private set; } + } + + + public enum StoryExportPropertyType + { + /// + /// The property value is a picture identifier. + /// + PictureId = 0 + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index c9c5625a5d..8bf2d23ed1 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -315,6 +315,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs index 65cbc3b6a0..be9399e40d 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs @@ -33,7 +33,6 @@ using SmartStore.Services.Stores; using SmartStore.Utilities; using SmartStore.Web.Framework; -using SmartStore.Core.Infrastructure.DependencyManagement; namespace SmartStore.Web.Infrastructure.Installation { From ecb58eb936c661041fcc19b6c13e6df8e3058617 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 24 Jan 2019 17:00:03 +0100 Subject: [PATCH 378/932] More on StoryExportAssetAttribute --- .../Modelling/StoryExportAssetAttribute.cs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/StoryExportAssetAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/StoryExportAssetAttribute.cs index fafd448ad3..89e30f3613 100644 --- a/src/Presentation/SmartStore.Web.Framework/Modelling/StoryExportAssetAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Modelling/StoryExportAssetAttribute.cs @@ -8,15 +8,34 @@ namespace SmartStore.Web.Framework.Modelling [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public sealed class StoryExportAssetAttribute : Attribute { - public StoryExportAssetAttribute(StoryExportPropertyType type) + public StoryExportAssetAttribute( + StoryExportPropertyType type, + Type root, + string rootProperty) { + Guard.NotNull(root, nameof(root)); + Guard.NotEmpty(rootProperty, nameof(rootProperty)); + Type = type; + Root = root; + RootProperty = rootProperty; } /// - /// The property type. + /// The asset property type. /// public StoryExportPropertyType Type { get; private set; } + + /// + /// The root entity type that contains the model data. + /// + public Type Root { get; private set; } + + /// + /// The root property name that contains the model data. + /// It is expected that the property value is always a Json serialized string. + /// + public string RootProperty { get; private set; } } @@ -27,4 +46,12 @@ public enum StoryExportPropertyType /// PictureId = 0 } + + + /// + /// Required to identify export assets containing models. + /// + public interface IStoryExportModel + { + } } From 02c82c9a8aa3255123c4bf6b27bfaa7e44295be8 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 24 Jan 2019 19:24:19 +0100 Subject: [PATCH 379/932] Story assets: code conventions --- .../SmartStore.Web.Framework.csproj | 2 +- .../UI/Blocks/IBlockEntity.cs | 4 ---- .../Blocks/StoryAssetAttribute.cs} | 16 ++++++++-------- 3 files changed, 9 insertions(+), 13 deletions(-) rename src/Presentation/SmartStore.Web.Framework/{Modelling/StoryExportAssetAttribute.cs => UI/Blocks/StoryAssetAttribute.cs} (78%) diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index 8bf2d23ed1..e432dd216a 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -315,7 +315,7 @@ - + diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs index b7b868f042..ef70797399 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockEntity.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SmartStore.Web.Framework.UI.Blocks { diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/StoryExportAssetAttribute.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs similarity index 78% rename from src/Presentation/SmartStore.Web.Framework/Modelling/StoryExportAssetAttribute.cs rename to src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs index 89e30f3613..9f99442ce9 100644 --- a/src/Presentation/SmartStore.Web.Framework/Modelling/StoryExportAssetAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs @@ -1,22 +1,22 @@ using System; -namespace SmartStore.Web.Framework.Modelling +namespace SmartStore.Web.Framework.UI.Blocks { /// /// Specifies whether a property refers to an asset to be included in the story export. /// [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] - public sealed class StoryExportAssetAttribute : Attribute + public sealed class StoryAssetAttribute : Attribute { - public StoryExportAssetAttribute( - StoryExportPropertyType type, + public StoryAssetAttribute( + StoryAssetKind type, Type root, string rootProperty) { Guard.NotNull(root, nameof(root)); Guard.NotEmpty(rootProperty, nameof(rootProperty)); - Type = type; + Kind = type; Root = root; RootProperty = rootProperty; } @@ -24,7 +24,7 @@ public StoryExportAssetAttribute( /// /// The asset property type. /// - public StoryExportPropertyType Type { get; private set; } + public StoryAssetKind Kind { get; private set; } /// /// The root entity type that contains the model data. @@ -39,12 +39,12 @@ public StoryExportAssetAttribute( } - public enum StoryExportPropertyType + public enum StoryAssetKind { /// /// The property value is a picture identifier. /// - PictureId = 0 + Picture = 0 } From af89a95caa89496ee59c0a0e00b2dc6852d49f9b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 25 Jan 2019 12:58:44 +0100 Subject: [PATCH 380/932] More on StoryExportAssetAttribute --- .../Collections/ConcurrentMultimap.cs | 6 +++- .../UI/Blocks/StoryAssetAttribute.cs | 33 ++----------------- 2 files changed, 8 insertions(+), 31 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs b/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs index 0e02723ec4..3929c80b79 100644 --- a/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs +++ b/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs @@ -81,7 +81,11 @@ public virtual IProducerConsumerCollection this[TKey key] IProducerConsumerCollection value; if (!_items.TryGetValue(key, out value)) { - _items.TryAdd(key, _bagCreator(Enumerable.Empty())); + value = _bagCreator(Enumerable.Empty()); + if (!_items.TryAdd(key, value)) + { + value = null; + } } return value; diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs index 9f99442ce9..95e2956caa 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs @@ -8,34 +8,15 @@ namespace SmartStore.Web.Framework.UI.Blocks [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public sealed class StoryAssetAttribute : Attribute { - public StoryAssetAttribute( - StoryAssetKind type, - Type root, - string rootProperty) + public StoryAssetAttribute(StoryAssetKind kind) { - Guard.NotNull(root, nameof(root)); - Guard.NotEmpty(rootProperty, nameof(rootProperty)); - - Kind = type; - Root = root; - RootProperty = rootProperty; + Kind = kind; } /// - /// The asset property type. + /// The asset property kind. /// public StoryAssetKind Kind { get; private set; } - - /// - /// The root entity type that contains the model data. - /// - public Type Root { get; private set; } - - /// - /// The root property name that contains the model data. - /// It is expected that the property value is always a Json serialized string. - /// - public string RootProperty { get; private set; } } @@ -46,12 +27,4 @@ public enum StoryAssetKind /// Picture = 0 } - - - /// - /// Required to identify export assets containing models. - /// - public interface IStoryExportModel - { - } } From a6394184b7fced304926162310f9acdc31d87b6d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 28 Jan 2019 12:24:54 +0100 Subject: [PATCH 381/932] GMC: fixes custom labels were not exported --- changelog.md | 1 + .../Providers/GmcXmlExportProvider.cs | 23 +++++++++++++------ .../UI/Blocks/StoryAssetAttribute.cs | 7 +++++- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index 7d39f8c679..2f87697c35 100644 --- a/changelog.md +++ b/changelog.md @@ -72,6 +72,7 @@ * Export the product images if no attribute images are defined * Do not export the first image twice for additional images * Export image URL of full size image (not default size) for additional images + * Custom labels were not exported * Media middleware: 0-byte files should be treated as missing. * Megamenu alpha/omega blends do not toggle correctly on touch devices * Summernote HTML editor exceeds parent container width when CodeMirror is activated diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index 84c27f6cf7..1f57fcf317 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -480,16 +480,25 @@ protected override void Export(ExportExecuteContext context) } var customLabel0 = GetAttributeValue(attributeValues, "custom_label_0", languageId, gmc?.CustomLabel0, null); - var customLabel1 = GetAttributeValue(attributeValues, "custom_label_1", languageId, gmc?.CustomLabel1, null); - var customLabel2 = GetAttributeValue(attributeValues, "custom_label_2", languageId, gmc?.CustomLabel2, null); - var customLabel3 = GetAttributeValue(attributeValues, "custom_label_3", languageId, gmc?.CustomLabel3, null); - var customLabel4 = GetAttributeValue(attributeValues, "custom_label_4", languageId, gmc?.CustomLabel4, null); + WriteString(writer, "custom_label_0", gmc.CustomLabel0.NullEmpty()); - ++context.RecordsSucceeded; + var customLabel1 = GetAttributeValue(attributeValues, "custom_label_1", languageId, gmc?.CustomLabel1, null); + WriteString(writer, "custom_label_1", gmc.CustomLabel1.NullEmpty()); + + var customLabel2 = GetAttributeValue(attributeValues, "custom_label_2", languageId, gmc?.CustomLabel2, null); + WriteString(writer, "custom_label_2", gmc.CustomLabel2.NullEmpty()); + + var customLabel3 = GetAttributeValue(attributeValues, "custom_label_3", languageId, gmc?.CustomLabel3, null); + WriteString(writer, "custom_label_3", gmc.CustomLabel3.NullEmpty()); + + var customLabel4 = GetAttributeValue(attributeValues, "custom_label_4", languageId, gmc?.CustomLabel4, null); + WriteString(writer, "custom_label_4", gmc.CustomLabel4.NullEmpty()); + + ++context.RecordsSucceeded; } - catch (Exception exception) + catch (Exception ex) { - context.RecordException(exception, entity.Id); + context.RecordException(ex, entity.Id); } writer.WriteEndElement(); // item diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs index 95e2956caa..707dc55e9d 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs @@ -25,6 +25,11 @@ public enum StoryAssetKind /// /// The property value is a picture identifier. /// - Picture = 0 + Picture = 0, + + /// + /// The property value is a video path. + /// + Video } } From 16278947654a8172c7cae648011cf3b99bba1769 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 31 Jan 2019 21:47:48 +0100 Subject: [PATCH 382/932] More on StoryAssetAttribute --- .../UI/Blocks/StoryAssetAttribute.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs index 707dc55e9d..3257ee879b 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/StoryAssetAttribute.cs @@ -30,6 +30,21 @@ public enum StoryAssetKind /// /// The property value is a video path. /// - Video + Video, + + /// + /// The property value is a product identifier. + /// + Product, + + /// + /// The property value is a category identifier. + /// + Category, + + /// + /// The property value is a manufacturer identifier. + /// + Manufacturer } } From 1179cfd4af49a3f9ef995d535118d8caca1fb27b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Feb 2019 09:36:00 +0100 Subject: [PATCH 383/932] Link builder: what about localized URLs? --- .../Migrations/MigrationsConfiguration.cs | 10 ++ .../Seo/UrlRecordService.cs | 4 +- .../EntityPicker/EntityPickerBuilder.cs | 2 - .../Controllers/EntityController.cs | 115 +++++++++--------- .../SmartStore.Web/SmartStore.Web.csproj | 1 + .../Views/Shared/EditorTemplates/Link.cshtml | 49 ++++++++ 6 files changed, 119 insertions(+), 62 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Link.cshtml diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index c24d77ffd2..5455cd9a51 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -641,6 +641,16 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Languages.AvailableLanguages.Note", "Click Download to install a new language including all localized resources. On translate.smartstore.com you will find more details about available resources.", "Klicken Sie auf Download, um eine neue Sprache mit allen lokalisierten Ressourcen zu installieren. Auf translate.smartstore.com finden Sie weitere Details zu verfügbaren Ressourcen."); + + builder.AddOrUpdate("Common.Entity.Product", "Product", "Produkt"); + builder.AddOrUpdate("Common.Entity.Category", "Category", "Warengruppe"); + builder.AddOrUpdate("Common.Entity.Manufacturer", "Manufacturer", "Hersteller"); + builder.AddOrUpdate("Common.Entity.Topic", "Topic", "Seite"); + + builder.AddOrUpdate("Common.Entity.SelectProduct", "Select product", "Produkt auswählen"); + builder.AddOrUpdate("Common.Entity.SelectCategory", "Select category", "Warengruppe auswählen"); + builder.AddOrUpdate("Common.Entity.SelectManufacturer", "Select manufacturer", "Hersteller auswählen"); + builder.AddOrUpdate("Common.Entity.SelectTopic", "Select topic", "Seite auswählen"); } } } diff --git a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs index bbdb23440b..af827690c3 100644 --- a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs @@ -246,7 +246,9 @@ public virtual UrlRecordCollection GetUrlRecordCollectionInternal(string entityN { if (languageIds.Length == 1) { - query = query.Where(x => x.LanguageId == languageIds[0]); + // Avoid "The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities". + var languageId = languageIds[0]; + query = query.Where(x => x.LanguageId == languageId); } else { diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPickerBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPickerBuilder.cs index a17551a51e..a7b8503942 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPickerBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPickerBuilder.cs @@ -71,8 +71,6 @@ public EntityPickerBuilder For(Expression> public EntityPickerBuilder For(string expression) { - Guard.NotEmpty(expression, nameof(expression)); - base.Component.TargetInputSelector = "#" + this.HtmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(expression); return this; } diff --git a/src/Presentation/SmartStore.Web/Controllers/EntityController.cs b/src/Presentation/SmartStore.Web/Controllers/EntityController.cs index 539ca5080e..4c32bd524f 100644 --- a/src/Presentation/SmartStore.Web/Controllers/EntityController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/EntityController.cs @@ -1,28 +1,27 @@ using System; using System.Collections.Generic; +using System.Data.Entity; using System.Linq; using System.Web.Mvc; -using System.Data.Entity; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Media; using SmartStore.Core.Search; -using SmartStore.Services; using SmartStore.Services.Catalog; +using SmartStore.Services.Customers; using SmartStore.Services.Media; using SmartStore.Services.Search; using SmartStore.Services.Security; +using SmartStore.Services.Seo; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Models.Entity; -using SmartStore.Services.Customers; -using SmartStore.Core.Domain.Customers; namespace SmartStore.Web.Controllers { - public partial class EntityController : PublicControllerBase + public partial class EntityController : PublicControllerBase { - private readonly ICommonServices _services; private readonly ICatalogSearchService _catalogSearchService; private readonly CatalogSettings _catalogSettings; private readonly MediaSettings _mediaSettings; @@ -31,11 +30,9 @@ public partial class EntityController : PublicControllerBase private readonly IManufacturerService _manufacturerService; private readonly ICustomerService _customerService; private readonly ICategoryService _categoryService; - private readonly IProductService _productService; - private readonly CatalogHelper _catalogHelper; + private readonly IUrlRecordService _urlRecordService; public EntityController( - ICommonServices services, ICatalogSearchService catalogSearchService, CatalogSettings catalogSettings, MediaSettings mediaSettings, @@ -44,10 +41,8 @@ public EntityController( IManufacturerService manufacturerService, ICustomerService customerService, ICategoryService categoryService, - IProductService productService, - CatalogHelper catalogHelper) + IUrlRecordService urlRecordService) { - _services = services; _catalogSearchService = catalogSearchService; _catalogSettings = catalogSettings; _mediaSettings = mediaSettings; @@ -56,15 +51,14 @@ public EntityController( _manufacturerService = manufacturerService; _customerService = customerService; _categoryService = categoryService; - _productService = productService; - _catalogHelper = catalogHelper; + _urlRecordService = urlRecordService; } #region Entity Picker public ActionResult Picker(EntityPickerModel model) { - model.PageSize = 96; // _commonSettings.EntityPickerPageSize; + model.PageSize = 96; if (model.EntityType.IsCaseInsensitiveEqual("product")) { @@ -77,7 +71,7 @@ public ActionResult Picker(EntityPickerModel model) .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) .ToList(); - ViewBag.AvailableStores = _services.StoreService.GetAllStores() + ViewBag.AvailableStores = Services.StoreService.GetAllStores() .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) .ToList(); @@ -90,7 +84,7 @@ public ActionResult Picker(EntityPickerModel model) new SelectListItem { Text = "Email", Value = "Email" } }; - if (_services.Settings.GetSettingByKey("CustomerSettings.CustomerNumberMethod") != CustomerNumberMethod.Disabled) + if (Services.Settings.GetSettingByKey("CustomerSettings.CustomerNumberMethod") != CustomerNumberMethod.Disabled) { ViewBag.AvailableCustomerSearchTypes.Add(new SelectListItem { Text = T("Account.Fields.CustomerNumber"), Value = "CustomerNumber" }); } @@ -102,24 +96,22 @@ public ActionResult Picker(EntityPickerModel model) [HttpPost] public ActionResult Picker(EntityPickerModel model, FormCollection form) { - model.PageSize = 96; // _commonSettings.EntityPickerPageSize; + model.PageSize = 96; try { + var language = Services.WorkContext.WorkingLanguage; var disableIf = model.DisableIf.SplitSafe(",").Select(x => x.ToLower().Trim()).ToList(); var disableIds = model.DisableIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); - var selIds = new HashSet(model.PreselectedEntityIds.ToIntArray()); - using (var scope = new DbContextScope(_services.DbContext, autoDetectChanges: false, proxyCreation: true, validateOnSave: false, forceNoTracking: true)) + using (var scope = new DbContextScope(Services.DbContext, autoDetectChanges: false, proxyCreation: true, validateOnSave: false, forceNoTracking: true)) { if (model.EntityType.IsCaseInsensitiveEqual("product")) { - #region Product - model.SearchTerm = model.SearchTerm.TrimSafe(); - var hasPermission = _services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog); + var hasPermission = Services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog); var disableIfNotSimpleProduct = disableIf.Contains("notsimpleproduct"); var disableIfGroupedProduct = disableIf.Contains("groupedproduct"); var labelTextGrouped = T("Admin.Catalog.Products.ProductType.GroupedProduct.Label").Text; @@ -137,7 +129,7 @@ public ActionResult Picker(EntityPickerModel model, FormCollection form) if (!hasPermission) { - searchQuery = searchQuery.VisibleOnly(_services.WorkContext.CurrentCustomer); + searchQuery = searchQuery.VisibleOnly(Services.WorkContext.CurrentCustomer); } if (model.ProductTypeId > 0) @@ -179,28 +171,34 @@ public ActionResult Picker(EntityPickerModel model, FormCollection form) var allPictureIds = products.Select(x => x.MainPictureId.GetValueOrDefault()); var allPictureInfos = _pictureService.GetPictureInfos(allPictureIds); + var slugs = model.ReturnField.IsCaseInsensitiveEqual("link") + ? _urlRecordService.GetUrlRecordCollection(nameof(Product), null, products.Select(x => x.Id).ToArray()) + : null; - model.SearchResult = products + model.SearchResult = products .Select(x => - { - var item = new EntityPickerModel.SearchResultModel - { - Id = x.Id, - ReturnValue = (model.ReturnField.IsCaseInsensitiveEqual("sku") ? x.Sku : x.Id.ToString()), - Title = x.Name, - Summary = x.Sku, - SummaryTitle = "{0}: {1}".FormatInvariant(sku, x.Sku.NaIfEmpty()), - Published = (hasPermission ? x.Published : (bool?)null), - Selected = selIds.Contains(x.Id) - }; + { + var item = new EntityPickerModel.SearchResultModel + { + Id = x.Id, + Title = x.Name, + Summary = x.Sku, + SummaryTitle = "{0}: {1}".FormatInvariant(sku, x.Sku.NaIfEmpty()), + Published = hasPermission ? x.Published : (bool?)null, + Selected = selIds.Contains(x.Id) + }; + + item.ReturnValue = slugs != null + ? Url.RouteUrl("Product", new { SeName = slugs.GetSlug(language.Id, x.Id) }) + : (model.ReturnField.IsCaseInsensitiveEqual("sku") ? x.Sku : x.Id.ToString()); if (disableIfNotSimpleProduct) { - item.Disable = (x.ProductTypeId != (int)ProductType.SimpleProduct); + item.Disable = x.ProductTypeId != (int)ProductType.SimpleProduct; } else if (disableIfGroupedProduct) { - item.Disable = (x.ProductTypeId == (int)ProductType.GroupedProduct); + item.Disable = x.ProductTypeId == (int)ProductType.GroupedProduct; } if (!item.Disable && disableIds.Contains(x.Id)) @@ -230,16 +228,15 @@ public ActionResult Picker(EntityPickerModel model, FormCollection form) return item; }) .ToList(); - - #endregion } else if (model.EntityType.IsCaseInsensitiveEqual("category")) - { - #region Category - + { var categories = _categoryService.GetAllCategories(model.SearchTerm, showHidden: true); var allPictureIds = categories.Select(x => x.PictureId.GetValueOrDefault()); var allPictureInfos = _pictureService.GetPictureInfos(allPictureIds); + var slugs = model.ReturnField.IsCaseInsensitiveEqual("link") + ? _urlRecordService.GetUrlRecordCollection(nameof(Category), null, categories.Select(x => x.Id).ToArray()) + : null; model.SearchResult = categories .Select(x => @@ -247,14 +244,17 @@ public ActionResult Picker(EntityPickerModel model, FormCollection form) var item = new EntityPickerModel.SearchResultModel { Id = x.Id, - ReturnValue = x.Id.ToString(), Title = x.Name, - Summary = x.Description.Truncate(120, "..."), + Summary = x.Description.Truncate(120, "…"), SummaryTitle = x.Name, Published = x.Published, Selected = selIds.Contains(x.Id) }; + item.ReturnValue = slugs != null + ? Url.RouteUrl("Category", new { SeName = slugs.GetSlug(language.Id, x.Id) }) + : x.Id.ToString(); + if (!item.Disable && disableIds.Contains(x.Id)) { item.Disable = true; @@ -271,16 +271,15 @@ public ActionResult Picker(EntityPickerModel model, FormCollection form) return item; }) .ToList(); - - #endregion } else if (model.EntityType.IsCaseInsensitiveEqual("manufacturer")) { - #region Manufacturer - var manufacturers = _manufacturerService.GetAllManufacturers(model.SearchTerm, model.PageIndex, model.PageSize, showHidden: true); var allPictureIds = manufacturers.Select(x => x.PictureId.GetValueOrDefault()); var allPictureInfos = _pictureService.GetPictureInfos(allPictureIds); + var slugs = model.ReturnField.IsCaseInsensitiveEqual("link") + ? _urlRecordService.GetUrlRecordCollection(nameof(Manufacturer), null, manufacturers.Select(x => x.Id).ToArray()) + : null; model.SearchResult = manufacturers .Select(x => @@ -294,7 +293,11 @@ public ActionResult Picker(EntityPickerModel model, FormCollection form) Published = x.Published, Selected = selIds.Contains(x.Id) }; - + + item.ReturnValue = slugs != null + ? Url.RouteUrl("Manufacturer", new { SeName = slugs.GetSlug(language.Id, x.Id) }) + : x.Id.ToString(); + if (!item.Disable && disableIds.Contains(x.Id)) { item.Disable = true; @@ -311,18 +314,14 @@ public ActionResult Picker(EntityPickerModel model, FormCollection form) return item; }) .ToList(); - - #endregion } else if (model.EntityType.IsCaseInsensitiveEqual("customer")) { - #region Customer - var registeredRoleId = _customerService.GetCustomerRoleBySystemName("Registered").Id; - var searchTermName = String.Empty; - var searchTermEmail = String.Empty; - var searchTermCustomerNumber = String.Empty; + var searchTermName = string.Empty; + var searchTermEmail = string.Empty; + var searchTermCustomerNumber = string.Empty; if (model.CustomerSearchType.IsCaseInsensitiveEqual("Name")) searchTermName = model.SearchTerm; @@ -365,8 +364,6 @@ public ActionResult Picker(EntityPickerModel model, FormCollection form) return item; }) .ToList(); - - #endregion } } } diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index a20e6d6eae..0dea0171ff 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -1828,6 +1828,7 @@ + Web.config diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Link.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Link.cshtml new file mode 100644 index 0000000000..e446fd9403 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Link.cshtml @@ -0,0 +1,49 @@ +@functions +{ + private dynamic GetTypeInfo(string type) + { + switch (type) + { + case "product": + return new { icon = "fa fa-cube", title = T("Common.Entity.Product").Text }; + case "category": + return new { icon = "fa fa-sitemap", title = T("Common.Entity.Category").Text }; + case "manufacturer": + return new { icon = "far fa-building", title = T("Common.Entity.Manufacturer").Text }; + case "topic": + return new { icon = "far fa-file", title = T("Common.Entity.Topic").Text }; + default: + return new { icon = "fa fa-link", title = "URL" }; + } + } + + private string Value => ViewData.Model != null ? Convert.ToString(ViewData.Model) : null; +} + +@{ + var type = (GetMetadata("type").NullEmpty() ?? "product").ToLower(); + var typeInfo = GetTypeInfo(type); + var maxItems = GetMetadata("maxItems") ?? 1; + var appendMode = GetMetadata("appendMode") ?? false; +} + +
    +
    + + + +
    + @Html.TextBox("", Value, new { @class = "form-control" }) +
    + @(Html.SmartStore().EntityPicker() + .For(string.Empty) + .EntityType("product") + .MaxItems(maxItems) + .AppendMode(appendMode) + .FieldName("link") + .DialogTitle(T("Common.Entity.SelectProduct").Text.EncodeJsString('\'', false)) + .Caption(T("Common.Search").Text.EncodeJsString('\'', false))) +
    +
    + +@**@ \ No newline at end of file From 404b6cfb9bca62f50e84c600a0c99631e2d5e839 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Feb 2019 12:03:35 +0100 Subject: [PATCH 384/932] Resolves #1570 Filter option "Only deactivated customers" filters deleted instead of deactivated customers Fixes the "apply filter" button has misaligned a bit --- changelog.md | 3 +- .../Migrations/MigrationsConfiguration.cs | 3 ++ .../Customers/CustomerSearchQuery.cs | 13 +++++--- .../Customers/CustomerService.cs | 13 +++++--- .../Extensions/HtmlExtensions.cs | 30 +++++++++++-------- .../Controllers/CustomerController.cs | 9 +++--- .../Models/Customers/CustomerListModel.cs | 8 ++--- .../Views/ActivityLog/ListLogs.cshtml | 2 +- .../Administration/Views/Category/List.cshtml | 2 +- .../Administration/Views/Customer/List.cshtml | 25 ++++++++-------- ...ReportBestCustomersByNumberOfOrders.cshtml | 2 +- .../_ReportBestCustomersByOrderTotal.cshtml | 2 +- .../Administration/Views/GiftCard/List.cshtml | 2 +- .../Administration/Views/Log/List.cshtml | 2 +- .../Views/Manufacturer/List.cshtml | 2 +- .../Views/MessageTemplate/List.cshtml | 2 +- .../Administration/Views/News/List.cshtml | 2 +- .../Views/NewsLetterSubscription/List.cshtml | 2 +- .../Administration/Views/Order/List.cshtml | 2 +- .../Views/Order/NeverSoldReport.cshtml | 4 +-- .../Views/Order/ShipmentList.cshtml | 2 +- .../Views/Product/BulkEdit.cshtml | 2 +- .../Administration/Views/Product/List.cshtml | 2 +- .../Views/ProductReview/List.cshtml | 2 +- .../Views/QueuedEmail/List.cshtml | 2 +- .../Views/ReturnRequest/List.cshtml | 2 +- .../Administration/Views/Topic/List.cshtml | 2 +- .../Views/UrlRecord/List.cshtml | 2 +- 28 files changed, 83 insertions(+), 63 deletions(-) diff --git a/changelog.md b/changelog.md index 2f87697c35..13db42f67b 100644 --- a/changelog.md +++ b/changelog.md @@ -107,7 +107,8 @@ * Import: Fixes invalid conversion "System.Double" to "SmartStore.Core.Domain.Catalog.QuantityControlType". * Topics: Fixes "Cannot insert duplicate key row in object 'dbo.UrlRecord' with unique index 'IX_UrlRecord_Slug'". * #1566 Santander: eliminate the 1 cent rounding difference at amountTotalNet. -* Fixed redirection to the homepage for pages which are loaded while the application is restarted +* Fixed redirection to the homepage for pages which are loaded while the application is restarted. +* #1570 Filter option "Only deactivated customers" filters deleted instead of deactivated customers. ## SmartStore.NET 3.1.5 diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 5455cd9a51..2fc48aa446 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -651,6 +651,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Common.Entity.SelectCategory", "Select category", "Warengruppe auswählen"); builder.AddOrUpdate("Common.Entity.SelectManufacturer", "Select manufacturer", "Hersteller auswählen"); builder.AddOrUpdate("Common.Entity.SelectTopic", "Select topic", "Seite auswählen"); + + builder.Delete("Admin.Customers.Customers.List.SearchDeletedOnly"); + builder.AddOrUpdate("Admin.Customers.Customers.List.SearchActiveOnly", "Only activated customers", "Nur aktivierte Kunden"); } } } diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerSearchQuery.cs b/src/Libraries/SmartStore.Services/Customers/CustomerSearchQuery.cs index 92710cc92e..e0737ac075 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerSearchQuery.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerSearchQuery.cs @@ -86,10 +86,15 @@ public class CustomerSearchQuery : ICloneable /// public bool? Deleted { get; set; } = false; - /// - /// Whether only system account records should be loaded. Default: false (meaning: ignore system accounts) - /// - public bool? IsSystemAccount { get; set; } = false; + /// + /// Whether only active customers should be loaded. + /// + public bool? Active { get; set; } + + /// + /// Whether only system account records should be loaded. Default: false (meaning: ignore system accounts) + /// + public bool? IsSystemAccount { get; set; } = false; /// /// Searches in FullName (FirstName + LastName) and Company fields. Default: null. diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs index db565351e5..3709c7a3e2 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs @@ -182,6 +182,11 @@ select grp query = query.Where(c => c.Deleted == q.Deleted.Value); } + if (q.Active.HasValue) + { + query = query.Where(c => c.Active == q.Active.Value); + } + if (q.IsSystemAccount.HasValue) { query = q.IsSystemAccount.Value == true @@ -200,9 +205,9 @@ select grp { query = query .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) - .Where((z => z.Attribute.KeyGroup == "Customer" && + .Where(z => z.Attribute.KeyGroup == "Customer" && z.Attribute.Key == SystemCustomerAttributeNames.Phone && - z.Attribute.Value.Contains(q.Phone))) + z.Attribute.Value.Contains(q.Phone)) .Select(z => z.Customer); } @@ -211,9 +216,9 @@ select grp { query = query .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) - .Where((z => z.Attribute.KeyGroup == "Customer" && + .Where(z => z.Attribute.KeyGroup == "Customer" && z.Attribute.Key == SystemCustomerAttributeNames.ZipPostalCode && - z.Attribute.Value.Contains(q.ZipPostalCode))) + z.Attribute.Value.Contains(q.ZipPostalCode)) .Select(z => z.Customer); } diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs index 40e05538a9..325144c315 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs @@ -148,20 +148,26 @@ public static MvcHtmlString SmartLabel(this HtmlHelper helper, string expression { var result = new StringBuilder(); - var labelAttrs = new RouteValueDictionary(htmlAttributes); - //labelAttrs.AppendCssClass("col-form-label"); + result.Append("
    "); - var label = helper.Label(expression, labelText, labelAttrs); + if (expression.IsEmpty() && labelText.IsEmpty()) + { + result.Append(""); + } + else + { + var labelAttrs = new RouteValueDictionary(htmlAttributes); + var label = helper.Label(expression, labelText, labelAttrs); - result.Append("
    "); - { - result.Append(label); - if (hint.HasValue()) - { - result.Append(helper.Hint(hint).ToHtmlString()); - } - } - result.Append("
    "); + result.Append(label); + } + + if (hint.HasValue()) + { + result.Append(helper.Hint(hint).ToHtmlString()); + } + + result.Append("
    "); return MvcHtmlString.Create(result.ToString()); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs index bd17596d1a..41f8438212 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs @@ -74,7 +74,6 @@ public partial class CustomerController : AdminControllerBase private readonly IPermissionService _permissionService; private readonly AdminAreaSettings _adminAreaSettings; private readonly IQueuedEmailService _queuedEmailService; - private readonly EmailAccountSettings _emailAccountSettings; private readonly IEmailAccountService _emailAccountService; private readonly ForumSettings _forumSettings; private readonly IForumService _forumService; @@ -108,7 +107,7 @@ public CustomerController( ICustomerActivityService customerActivityService, IPriceCalculationService priceCalculationService, IPermissionService permissionService, AdminAreaSettings adminAreaSettings, - IQueuedEmailService queuedEmailService, EmailAccountSettings emailAccountSettings, + IQueuedEmailService queuedEmailService, IEmailAccountService emailAccountService, ForumSettings forumSettings, IForumService forumService, IOpenAuthenticationService openAuthenticationService, AddressSettings addressSettings, IStoreService storeService, @@ -142,7 +141,6 @@ public CustomerController( _permissionService = permissionService; _adminAreaSettings = adminAreaSettings; _queuedEmailService = queuedEmailService; - _emailAccountSettings = emailAccountSettings; _emailAccountService = emailAccountService; _forumSettings = forumSettings; _forumService = forumService; @@ -501,7 +499,7 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult CustomerList(GridCommand command, CustomerListModel model) { - // We use own own binder for searchCustomerRoleIds property + // We use own own binder for searchCustomerRoleIds property. var gridModel = new GridModel(); if (_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) @@ -516,7 +514,8 @@ public ActionResult CustomerList(GridCommand command, CustomerListModel model) MonthOfBirth = model.SearchMonthOfBirth.ToInt(), Phone = model.SearchPhone, ZipPostalCode = model.SearchZipPostalCode, - Deleted = model.SearchDeletedOnly, + Deleted = false, + Active = model.SearchActiveOnly, PageIndex = command.Page - 1, PageSize = command.PageSize }; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerListModel.cs index b6bea763ea..6bc3c7a0dd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerListModel.cs @@ -8,7 +8,7 @@ namespace SmartStore.Admin.Models.Customers { public class CustomerListModel : ModelBase { - public CustomerListModel () + public CustomerListModel() { AvailableCustomerRoles = new List(); } @@ -53,7 +53,7 @@ public CustomerListModel () public string SearchZipPostalCode { get; set; } public bool ZipPostalCodeEnabled { get; set; } - [SmartResourceDisplayName("Admin.Customers.Customers.List.SearchDeletedOnly")] - public bool SearchDeletedOnly { get; set; } - } + [SmartResourceDisplayName("Admin.Customers.Customers.List.SearchActiveOnly")] + public bool? SearchActiveOnly { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ActivityLog/ListLogs.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ActivityLog/ListLogs.cshtml index 104618e045..f01056f3b5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ActivityLog/ListLogs.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ActivityLog/ListLogs.cshtml @@ -46,7 +46,7 @@
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/List.cshtml index 0d8722653d..a5bd650d60 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Category/List.cshtml @@ -46,7 +46,7 @@
    }
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/List.cshtml index cc55977547..da65f0c747 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/List.cshtml @@ -97,20 +97,15 @@
    @Html.SmartLabelFor(model => model.SearchCustomerRoleIds) - @*@Html.DropDownListFor(model => model.SearchCustomerRoleIds, Model.AvailableCustomerRoles, null, new { multiple = "multiple", @class = "form-control" })*@ @Html.DropDownList("SearchCustomerRoleIds", Model.AvailableCustomerRoles, null, new { multiple = "multiple", @class = "form-control" })
    - -
    - @Html.CheckBoxFor(model => model.SearchDeletedOnly, new { @class = "form-check-input" }) - -
    + @Html.SmartLabelFor(model => model.SearchActiveOnly) + @Html.EditorFor(model => Model.SearchActiveOnly, new { @class = "form-control sf" })
    - @@ -122,7 +117,8 @@ .Name("customers-grid") .ClientEvents(events => events .OnDataBinding("onDataBinding") - .OnDataBound("onDataBound")) + .OnDataBound("onDataBound") + .OnRowDataBound("onRowDataBound")) .Columns(columns => { columns.Bound(x => x.Id) @@ -216,8 +212,7 @@ }); }); - function onDataBound(e) { - + function onDataBound() { $('#customers-grid input[type=checkbox][id!=mastercheckbox]').each(function () { var currentId = $(this).val(); var checked = jQuery.inArray(currentId, selectedIds); @@ -226,7 +221,13 @@ }); updateMasterCheckbox(); - } + } + + function onRowDataBound(e) { + if (!e.dataItem.Active) { + $(e.row).find('td').wrapInner(''); + } + } function updateMasterCheckbox() { var numChkBoxes = $('#customers-grid input[type=checkbox][id!=mastercheckbox]').length; @@ -244,7 +245,7 @@ SearchMonthOfBirth: $('#@Html.FieldIdFor(model => model.SearchMonthOfBirth)').val(), SearchPhone: $('#@Html.FieldIdFor(model => model.SearchPhone)').val(), SearchZipPostalCode: $('#@Html.FieldIdFor(model => model.SearchZipPostalCode)').val(), - SearchDeletedOnly: $('#@Html.FieldIdFor(model => model.SearchDeletedOnly)').prop('checked') + SearchActiveOnly: $('#@Html.FieldIdFor(model => model.SearchActiveOnly)').val() }; e.data = searchModel; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByNumberOfOrders.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByNumberOfOrders.cshtml index 389e0ffc36..5da15e34b7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByNumberOfOrders.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByNumberOfOrders.cshtml @@ -24,7 +24,7 @@
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByOrderTotal.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByOrderTotal.cshtml index 7e07fd7b5c..a693c83bce 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByOrderTotal.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByOrderTotal.cshtml @@ -24,7 +24,7 @@
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/List.cshtml index a22c72cf7e..2cc47b5526 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/List.cshtml @@ -28,7 +28,7 @@ @Html.TextBoxFor(model => model.CouponCode, new { @class = "form-control" })
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml index 7409eae3f8..82a4890ee1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml @@ -45,7 +45,7 @@ @Html.DropDownList("LogLevelId", Model.AvailableLogLevels, T("Admin.Common.All"))
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml index ce22947113..543372a417 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml @@ -33,7 +33,7 @@
    }
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/List.cshtml index 5b75096ee9..7ad2be365a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/List.cshtml @@ -25,7 +25,7 @@
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml index a88278a11e..2a45db4bf7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml @@ -37,7 +37,7 @@ @Html.DropDownListFor(model => model.SearchStoreId, Model.AvailableStores, T("Admin.Common.All"), new { @class = "form-control" })
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml index 6ea6c224f0..982e5f05e2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml @@ -28,7 +28,7 @@
    }
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml index ff4f515541..100b11a8d0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml @@ -100,7 +100,7 @@
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/NeverSoldReport.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/NeverSoldReport.cshtml index 86f13c1590..3762130c58 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/NeverSoldReport.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/NeverSoldReport.cshtml @@ -25,9 +25,9 @@ @Html.SmartLabelFor(model => model.EndDate) @Html.EditorFor(model => model.EndDate)
    - +
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentList.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentList.cshtml index 61330f9f09..a340893b3c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentList.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentList.cshtml @@ -43,7 +43,7 @@
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml index 7e1ebc3ba5..6884fb03d0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml @@ -46,7 +46,7 @@ @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, allString)
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml index 1fe243f910..3b8924e689 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml @@ -84,7 +84,7 @@
    }
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml index 098d1c78dc..6fc0887407 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml @@ -40,7 +40,7 @@
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/List.cshtml index 6a6d0f4123..5038df4680 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/List.cshtml @@ -74,7 +74,7 @@
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml index 1649806229..3ee82d345f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml @@ -30,7 +30,7 @@ @Html.DropDownListFor(model => model.SearchReturnRequestStatusId, Model.AvailableReturnRequestStatus, T("Admin.Common.All"))
    - + @Html.SmartLabel(string.Empty, string.Empty) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml index 9d85d4d644..3da1caad9d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml @@ -48,7 +48,7 @@
    @if (Model.AvailableStores.Count > 1) { - + @Html.SmartLabel(string.Empty, string.Empty) }
    - + @Html.SmartLabel(string.Empty, string.Empty) From f5b62c3a5aea2c5dd76e329851a784ecad2fcb0b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 8 Feb 2019 11:46:12 +0100 Subject: [PATCH 385/932] Fixes broken unit tests --- changelog.md | 1 + .../Discounts/DiscountService.cs | 22 +---- .../Infrastructure/AutoMapperAdminProfile.cs | 24 +++-- .../Discounts/DiscountServiceTests.cs | 92 ++++++++----------- .../Helpers/DateTimeHelperTests.cs | 35 +++---- 5 files changed, 68 insertions(+), 106 deletions(-) diff --git a/changelog.md b/changelog.md index 13db42f67b..2b93b75d58 100644 --- a/changelog.md +++ b/changelog.md @@ -44,6 +44,7 @@ * Added options for alternating price display (in badges). * #1515 Poll: Add result tab with a list of answers and customers for a poll * BMEcat: Added export and import of product tags. +* Santander instalment purchase. ### Improvements * (Perf) Significantly increased query performance for products with a lot of category assignments (> 10). diff --git a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs index 666c53dfdb..660e5f23a1 100644 --- a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs +++ b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs @@ -1,19 +1,16 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Discounts; -using SmartStore.Core.Events; -using SmartStore.Core.Plugins; using SmartStore.Core.Domain.Orders; -using SmartStore.Services.Customers; +using SmartStore.Core.Plugins; using SmartStore.Services.Common; -using SmartStore.Services.Configuration; -using SmartStore.Collections; -using SmartStore.Core.Data.Hooks; +using SmartStore.Services.Customers; namespace SmartStore.Services.Discounts { @@ -28,21 +25,16 @@ public partial class DiscountService : IDiscountService private readonly IRequestCache _requestCache; private readonly IStoreContext _storeContext; private readonly IGenericAttributeService _genericAttributeService; - private readonly IPluginFinder _pluginFinder; - private readonly IEventPublisher _eventPublisher; - private readonly ISettingService _settingService; private readonly IProviderManager _providerManager; private readonly IDictionary _discountValidityCache; - public DiscountService(IRequestCache requestCache, + public DiscountService( + IRequestCache requestCache, IRepository discountRepository, IRepository discountRequirementRepository, IRepository discountUsageHistoryRepository, IStoreContext storeContext, IGenericAttributeService genericAttributeService, - IPluginFinder pluginFinder, - IEventPublisher eventPublisher, - ISettingService settingService, IProviderManager providerManager) { _requestCache = requestCache; @@ -51,9 +43,6 @@ public DiscountService(IRequestCache requestCache, _discountUsageHistoryRepository = discountUsageHistoryRepository; _storeContext = storeContext; _genericAttributeService = genericAttributeService; - _pluginFinder = pluginFinder; - _eventPublisher = eventPublisher; - _settingService = settingService; _providerManager = providerManager; _discountValidityCache = new Dictionary(); } @@ -207,7 +196,6 @@ public virtual Discount GetDiscountByCouponCode(string couponCode, bool showHidd return discount; } - private System.Diagnostics.Stopwatch _watch = new System.Diagnostics.Stopwatch(); public virtual bool IsDiscountValid(Discount discount, Customer customer) { var couponCodeToValidate = ""; diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs index 068081f3dd..7833616bb4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperAdminProfile.cs @@ -431,16 +431,19 @@ public AutoMapperAdminProfile() CreateMap() .ForMember(dest => dest.CreatedOnUtc, mo => mo.Ignore()) .ForMember(dest => dest.NewsLetterSubscriptionGuid, mo => mo.Ignore()) - .ForMember(dest => dest.StoreId, mo => mo.Ignore()); - //forums - CreateMap() - .ForMember(dest => dest.Locales, mo => mo.Ignore()) - .ForMember(dest => dest.SeName, mo => mo.MapFrom(src => src.GetSeName(0, true, false))) - .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) - .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) - .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) - .ForMember(dest => dest.ForumModels, mo => mo.Ignore()); - CreateMap() + .ForMember(dest => dest.StoreId, mo => mo.Ignore()) + .ForMember(dest => dest.WorkingLanguageId, mo => mo.Ignore()); + //forums + CreateMap() + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.SeName, mo => mo.MapFrom(src => src.GetSeName(0, true, false))) + .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) + .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableCustomerRoles, mo => mo.Ignore()) + .ForMember(dest => dest.SelectedCustomerRoleIds, mo => mo.Ignore()) + .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) + .ForMember(dest => dest.ForumModels, mo => mo.Ignore()); + CreateMap() .ForMember(dest => dest.CreatedOnUtc, mo => mo.Ignore()) .ForMember(dest => dest.UpdatedOnUtc, mo => mo.Ignore()) .ForMember(dest => dest.Forums, mo => mo.Ignore()); @@ -499,6 +502,7 @@ public AutoMapperAdminProfile() .ForMember(dest => dest.EndDate, mo => mo.Ignore()) .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableLanguages, mo => mo.Ignore()) .ForMember(dest => dest.UsernamesEnabled, mo => mo.Ignore()) .ForMember(dest => dest.GridPageSize, mo => mo.Ignore()); CreateMap() diff --git a/src/Tests/SmartStore.Services.Tests/Discounts/DiscountServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Discounts/DiscountServiceTests.cs index 5c3fb0e389..44368552fb 100644 --- a/src/Tests/SmartStore.Services.Tests/Discounts/DiscountServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Discounts/DiscountServiceTests.cs @@ -1,21 +1,17 @@ using System; using System.Collections.Generic; using System.Linq; +using NUnit.Framework; +using Rhino.Mocks; +using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Discounts; -using SmartStore.Core.Plugins; +using SmartStore.Core.Domain.Stores; +using SmartStore.Services.Common; using SmartStore.Services.Discounts; -using SmartStore.Core.Events; using SmartStore.Tests; -using NUnit.Framework; -using Rhino.Mocks; -using SmartStore.Core; -using SmartStore.Services.Common; -using SmartStore.Core.Domain.Common; -using SmartStore.Services.Configuration; -using SmartStore.Core.Domain.Stores; namespace SmartStore.Services.Tests.Discounts { @@ -25,11 +21,9 @@ public class DiscountServiceTests : ServiceTest IRepository _discountRepo; IRepository _discountRequirementRepo; IRepository _discountUsageHistoryRepo; - IEventPublisher _eventPublisher; IGenericAttributeService _genericAttributeService; IDiscountService _discountService; IStoreContext _storeContext; - ISettingService _settingService; [SetUp] public new void SetUp() @@ -62,9 +56,6 @@ public class DiscountServiceTests : ServiceTest _discountRepo.Expect(x => x.Table).Return(new List() { discount1, discount2 }.AsQueryable()); - _eventPublisher = MockRepository.GenerateMock(); - _eventPublisher.Expect(x => x.Publish(Arg.Is.Anything)); - _storeContext = MockRepository.GenerateMock(); _storeContext.Expect(x => x.CurrentStore).Return(new Store { @@ -72,17 +63,12 @@ public class DiscountServiceTests : ServiceTest Name = "MyStore" }); - _settingService = MockRepository.GenerateMock(); - - var cacheManager = new NullCache(); _discountRequirementRepo = MockRepository.GenerateMock>(); _discountUsageHistoryRepo = MockRepository.GenerateMock>(); - var pluginFinder = new PluginFinder(); _genericAttributeService = MockRepository.GenerateMock(); _discountService = new DiscountService(NullRequestCache.Instance, _discountRepo, _discountRequirementRepo, - _discountUsageHistoryRepo, _storeContext, _genericAttributeService, pluginFinder, _eventPublisher, - _settingService, base.ProviderManager); + _discountUsageHistoryRepo, _storeContext, _genericAttributeService, ProviderManager); } [Test] @@ -133,17 +119,8 @@ public void Should_accept_valid_discount_code() LastActivityDateUtc = new DateTime(2010, 01, 02) }; - _genericAttributeService.Expect(x => x.GetAttributesForEntity(customer.Id, "Customer")) - .Return(new List() - { - new GenericAttribute() - { - EntityId = customer.Id, - Key = SystemCustomerAttributeNames.DiscountCouponCode, - KeyGroup = "Customer", - Value = "CouponCode 1" - } - }); + _genericAttributeService.Expect(x => x.GetAttribute(nameof(Customer), customer.Id, SystemCustomerAttributeNames.DiscountCouponCode, 0)) + .Return("CouponCode 1"); var result1 = _discountService.IsDiscountValid(discount, customer); result1.ShouldEqual(true); @@ -174,29 +151,30 @@ public void Should_not_accept_wrong_discount_code() LastActivityDateUtc = new DateTime(2010, 01, 02) }; - _genericAttributeService.Expect(x => x.GetAttributesForEntity(customer.Id, "Customer")) - .Return(new List() - { - new GenericAttribute() - { - EntityId = customer.Id, - Key = SystemCustomerAttributeNames.DiscountCouponCode, - KeyGroup = "Customer", - Value = "CouponCode 2" - } - }); - - var result2 = _discountService.IsDiscountValid(discount, customer); + _genericAttributeService.Expect(x => x.GetAttribute(nameof(Customer), customer.Id, SystemCustomerAttributeNames.DiscountCouponCode, 0)) + .Return("CouponCode 2"); + + var result2 = _discountService.IsDiscountValid(discount, customer); result2.ShouldEqual(false); } [Test] public void Can_validate_discount_dateRange() { - var discount = new Discount + var customer = new Customer + { + CustomerGuid = Guid.NewGuid(), + AdminComment = "", + Active = true, + Deleted = false, + CreatedOnUtc = new DateTime(2010, 01, 01), + LastActivityDateUtc = new DateTime(2010, 01, 02) + }; + + var discount1 = new Discount { DiscountType = DiscountType.AssignedToSkus, - Name = "Discount 2", + Name = "Discount 1", UsePercentage = false, DiscountPercentage = 0, DiscountAmount = 5, @@ -206,21 +184,23 @@ public void Can_validate_discount_dateRange() DiscountLimitation = DiscountLimitationType.Unlimited, }; - var customer = new Customer + var discount2 = new Discount { - CustomerGuid = Guid.NewGuid(), - AdminComment = "", - Active = true, - Deleted = false, - CreatedOnUtc = new DateTime(2010, 01, 01), - LastActivityDateUtc = new DateTime(2010, 01, 02) + DiscountType = DiscountType.AssignedToSkus, + Name = "Discount 2", + UsePercentage = false, + DiscountPercentage = 0, + DiscountAmount = 5, + StartDateUtc = DateTime.UtcNow.AddDays(1), + EndDateUtc = DateTime.UtcNow.AddDays(2), + RequiresCouponCode = false, + DiscountLimitation = DiscountLimitationType.Unlimited, }; - var result1 = _discountService.IsDiscountValid(discount, customer); + var result1 = _discountService.IsDiscountValid(discount1, customer); result1.ShouldEqual(true); - discount.StartDateUtc = DateTime.UtcNow.AddDays(1); - var result2 = _discountService.IsDiscountValid(discount, customer); + var result2 = _discountService.IsDiscountValid(discount2, customer); result2.ShouldEqual(false); } } diff --git a/src/Tests/SmartStore.Services.Tests/Helpers/DateTimeHelperTests.cs b/src/Tests/SmartStore.Services.Tests/Helpers/DateTimeHelperTests.cs index 29160fe4b9..ed5a793f53 100644 --- a/src/Tests/SmartStore.Services.Tests/Helpers/DateTimeHelperTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Helpers/DateTimeHelperTests.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Generic; +using NUnit.Framework; +using Rhino.Mocks; using SmartStore.Core; +using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Domain.Common; +using SmartStore.Services.Common; using SmartStore.Services.Configuration; -using SmartStore.Services.Customers; using SmartStore.Services.Helpers; -using SmartStore.Services.Common; using SmartStore.Tests; -using NUnit.Framework; -using Rhino.Mocks; namespace SmartStore.Services.Tests.Helpers { @@ -33,18 +32,17 @@ public class DateTimeHelperTests : ServiceTest _workContext = MockRepository.GenerateMock(); - _store = new Store() { Id = 1 }; + _store = new Store { Id = 1 }; _storeContext = MockRepository.GenerateMock(); _storeContext.Expect(x => x.CurrentStore).Return(_store); - _dateTimeSettings = new DateTimeSettings() + _dateTimeSettings = new DateTimeSettings { AllowCustomersToSetTimeZone = false, DefaultStoreTimeZoneId = "" }; - _dateTimeHelper = new DateTimeHelper(_workContext, _genericAttributeService, - _settingService, _dateTimeSettings); + _dateTimeHelper = new DateTimeHelper(_workContext, _genericAttributeService, _settingService, _dateTimeSettings); } [Test] @@ -67,25 +65,16 @@ public void Can_get_all_systemTimeZones() public void Can_get_customer_timeZone_with_customTimeZones_enabled() { _dateTimeSettings.AllowCustomersToSetTimeZone = true; - _dateTimeSettings.DefaultStoreTimeZoneId = "E. Europe Standard Time"; //(GMT+02:00) Minsk; + _dateTimeSettings.DefaultStoreTimeZoneId = "E. Europe Standard Time"; // (GMT+02:00) Minsk; - var customer = new Customer() + var customer = new Customer { Id = 10 }; - _genericAttributeService.Expect(x => x.GetAttributesForEntity(customer.Id, "Customer")) - .Return(new List() - { - new GenericAttribute() - { - StoreId = 0, - EntityId = customer.Id, - Key = SystemCustomerAttributeNames.TimeZoneId, - KeyGroup = "Customer", - Value = "Russian Standard Time" //(GMT+03:00) Moscow, St. Petersburg, Volgograd - } - }); + _genericAttributeService + .Expect(x => x.GetAttribute(nameof(Customer), customer.Id, SystemCustomerAttributeNames.TimeZoneId, 0)) + .Return("Russian Standard Time"); // (GMT+03:00) Moscow, St. Petersburg, Volgograd var timeZone = _dateTimeHelper.GetCustomerTimeZone(customer); timeZone.ShouldNotBeNull(); From 7fe1ffbd9690b4b82f283ab75d22e8a46cdde2de Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 8 Feb 2019 17:35:10 +0100 Subject: [PATCH 386/932] Resolves #1569 Show login note if no prices are displayed due to customer group permissions --- changelog.md | 1 + .../Domain/Catalog/CatalogSettings.cs | 15 ++- .../Migrations/MigrationsConfiguration.cs | 10 ++ .../Security/PermissionService.cs | 52 +++++---- .../Models/Settings/CatalogSettingsModel.cs | 3 + .../Views/Setting/Catalog.cshtml | 9 ++ .../Controllers/CatalogHelper.cs | 86 +++++++-------- .../Models/Catalog/ProductDetailsModel.cs | 3 +- .../Themes/Flex/Content/_product.scss | 3 +- .../Partials/Product.Offer.Price.cshtml | 103 +++++++++--------- .../Product/Partials/Product.Offer.cshtml | 4 +- 11 files changed, 154 insertions(+), 135 deletions(-) diff --git a/changelog.md b/changelog.md index 2b93b75d58..2a54b1a8c6 100644 --- a/changelog.md +++ b/changelog.md @@ -66,6 +66,7 @@ * OpenTrans: added customer number to parties * Do not filter cookie using resources if cookie usage has not yet been consented to. * #1563 QueuedMessagesClearTask: add a setting for the age of the mails to be deleted. +* #1569 Added a setting to show login note if no prices are displayed due to customer group permissions. ### Bugfixes * In a multi-store environment, multiple topics with the same system name cannot be resolved reliably. diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs index c6d208659f..394e9e4e9e 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs @@ -68,7 +68,7 @@ public CatalogSettings() ShowColorSquaresInLists = true; ShowDiscountSign = true; ShowVariantCombinationPriceAdjustment = true; - ShowLinkedAttributeValueImage = true; + ShowLinkedAttributeValueImage = true; EnableDynamicPriceUpdate = true; ShowProductReviewsInProductDetail = true; EnableHtmlTextCollapser = true; @@ -129,10 +129,15 @@ public CatalogSettings() /// public bool ShowVariantCombinationPriceAdjustment { get; set; } - /// - /// Gets or sets a value indicating whether to display quantity of linked product at attribute values - /// - public bool ShowLinkedAttributeValueQuantity { get; set; } + /// + /// Indicates whether to show a login note if the user is not authorized to see prices. + /// + public bool ShowLoginForPriceNote { get; set; } + + /// + /// Gets or sets a value indicating whether to display quantity of linked product at attribute values + /// + public bool ShowLinkedAttributeValueQuantity { get; set; } /// /// Gets or sets a value indicating whether to display the image of linked product at attribute values diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 2fc48aa446..977bc6ccb4 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -654,6 +654,16 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.Delete("Admin.Customers.Customers.List.SearchDeletedOnly"); builder.AddOrUpdate("Admin.Customers.Customers.List.SearchActiveOnly", "Only activated customers", "Nur aktivierte Kunden"); + + builder.AddOrUpdate("Products.LoginForPrice", + "Prices will be displayed after login.", + "Preise werden nach Anmeldung angezeigt."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowLoginForPriceNote", + "Show login for price note", + "Hinweis \"Preis nach Anmeldung\" anzeigen", + "Specifies whether to display a message stating that prices will not be displayed until login.", + "Legt fest, ob ein Hinweis erscheinen soll, dass Preise erst nach Anmeldung angezeigt werden."); } } } diff --git a/src/Libraries/SmartStore.Services/Security/PermissionService.cs b/src/Libraries/SmartStore.Services/Security/PermissionService.cs index 16d0fa0ac0..dec98fd0fe 100644 --- a/src/Libraries/SmartStore.Services/Security/PermissionService.cs +++ b/src/Libraries/SmartStore.Services/Security/PermissionService.cs @@ -10,12 +10,11 @@ namespace SmartStore.Services.Security { - /// - /// Permission service - /// - public partial class PermissionService : IPermissionService + /// + /// Permission service + /// + public partial class PermissionService : IPermissionService { - #region Constants /// /// Cache key for storing a valie indicating whether a certain customer role has a permission /// @@ -25,20 +24,12 @@ public partial class PermissionService : IPermissionService /// private const string PERMISSIONS_ALLOWED_KEY = "permission:allowed-{0}-{1}"; private const string PERMISSIONS_PATTERN_KEY = "permission:*"; - #endregion - - #region Fields private readonly IRepository _permissionRecordRepository; - private readonly IRepository _customerRoleRepository; private readonly ICustomerService _customerService; private readonly IWorkContext _workContext; private readonly ICacheManager _cacheManager; - #endregion - - #region Ctor - /// /// Ctor /// @@ -48,19 +39,16 @@ public partial class PermissionService : IPermissionService /// Cache manager public PermissionService( IRepository permissionRecordRepository, - IRepository customerRoleRepository, ICustomerService customerService, - IWorkContext workContext, ICacheManager cacheManager) + IWorkContext workContext, + ICacheManager cacheManager) { - this._permissionRecordRepository = permissionRecordRepository; - this._customerRoleRepository = customerRoleRepository; - this._customerService = customerService; - this._workContext = workContext; - this._cacheManager = cacheManager; + _permissionRecordRepository = permissionRecordRepository; + _customerService = customerService; + _workContext = workContext; + _cacheManager = cacheManager; } - #endregion - #region Utilities /// @@ -71,15 +59,21 @@ public PermissionService( /// true - authorized; otherwise, false protected virtual bool Authorize(string permissionRecordSystemName, CustomerRole customerRole) { - if (String.IsNullOrEmpty(permissionRecordSystemName)) + if (string.IsNullOrEmpty(permissionRecordSystemName)) + { return false; + } - string key = string.Format(PERMISSIONS_ALLOWED_KEY, customerRole.Id, permissionRecordSystemName); + var key = string.Format(PERMISSIONS_ALLOWED_KEY, customerRole.Id, permissionRecordSystemName); return _cacheManager.Get(key, () => { foreach (var permission1 in customerRole.PermissionRecords) + { if (permission1.SystemName.Equals(permissionRecordSystemName, StringComparison.InvariantCultureIgnoreCase)) + { return true; + } + } return false; }); @@ -311,16 +305,20 @@ public virtual bool Authorize(string permissionRecordSystemName) /// true - authorized; otherwise, false public virtual bool Authorize(string permissionRecordSystemName, Customer customer) { - if (String.IsNullOrEmpty(permissionRecordSystemName)) + if (string.IsNullOrEmpty(permissionRecordSystemName)) + { return false; + } var customerRoles = customer.CustomerRoles.Where(cr => cr.Active); foreach (var role in customerRoles) + { if (Authorize(permissionRecordSystemName, role)) - //yes, we have such permission + { return true; + } + } - //no permission found return false; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs index 8c2e1acf84..d842fbdda7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs @@ -300,6 +300,9 @@ public CatalogSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowVariantCombinationPriceAdjustment")] public bool ShowVariantCombinationPriceAdjustment { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowLoginForPriceNote")] + public bool ShowLoginForPriceNote { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowLinkedAttributeValueQuantity")] public bool ShowLinkedAttributeValueQuantity { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml index 44f9f4db7f..b0cdb0d98e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml @@ -799,6 +799,15 @@ @Html.ValidationMessageFor(model => model.ShowVariantCombinationPriceAdjustment) +
    + + +
    - + @T("Plugins.Payments.AmazonPay.RegisterNow")
    @Model.FileNamePatternExample @@ -144,7 +144,7 @@ @deployment.Name
    -  @deployment.DeploymentTypeName +  @deployment.DeploymentTypeName
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForum.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForum.cshtml index 29a844c8a7..11fa1679fa 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForum.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForum.cshtml @@ -20,7 +20,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForumGroup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForumGroup.cshtml index de3588e0f0..f0b3ca5909 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForumGroup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForumGroup.cshtml @@ -20,7 +20,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/Edit.cshtml index 640f4f3bdc..22da56cf86 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/Edit.cshtml @@ -18,7 +18,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/_CreateOrUpdate.cshtml index 06a72ac536..14c43c7f65 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/_CreateOrUpdate.cshtml @@ -116,7 +116,7 @@
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Home/Index.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Home/Index.cshtml index f8c5f0b314..750aa84167 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Home/Index.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Home/Index.cshtml @@ -4,7 +4,7 @@
    - + @T("Admin.Dashboard.StoreStatistics")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Home/UaTester.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Home/UaTester.cshtml index f4c0bc92fb..95af86f0b8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Home/UaTester.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Home/UaTester.cshtml @@ -7,7 +7,7 @@
    - + User Agent Tester
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/Edit.cshtml index f4f7e253bd..0889579148 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/Edit.cshtml @@ -17,14 +17,14 @@ @if (Model.Id != 0) { } @if (Model.LogFileExists) { - + @T("Admin.Configuration.ActivityLog") } @@ -36,7 +36,7 @@ @T("Admin.Common.SaveContinue") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml index a01b8aa3c2..2f1d5fdc18 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml @@ -93,7 +93,7 @@ - + @T("Admin.Configuration.ActivityLog")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/Edit.cshtml index acdf321841..d5ec3dcfa6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/Edit.cshtml @@ -17,7 +17,7 @@ @{ Html.RenderWidget("admin_button_toolbar_before"); } - + @T("Admin.Catalog.Products.Copy") @@ -89,7 +89,7 @@ ) .FooterContent(@ ) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml index e12b6bd09d..1fe243f910 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml @@ -35,7 +35,7 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/LowStockReport.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/LowStockReport.cshtml index 9e2ffae784..81921f53e4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/LowStockReport.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/LowStockReport.cshtml @@ -13,7 +13,7 @@ {
    - + @T("Admin.Catalog.LowStockReport")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml index cafb1db22b..b94502e231 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml @@ -197,7 +197,7 @@ .Centered(); columns.Bound(x => x.AllowOutOfStockOrders) .Width(60) - .HeaderTemplate("") + .HeaderTemplate("") .Template(item => @Html.SymbolForBool(item.AllowOutOfStockOrders)) .ClientTemplate(@Html.SymbolForBool("AllowOutOfStockOrders")) .Centered(); @@ -254,7 +254,7 @@ } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Downloads.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Downloads.cshtml index 9850e54922..101f7681e8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Downloads.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Downloads.cshtml @@ -102,7 +102,7 @@ - +
    - + @string.Format(@T("Account.CustomerOrders.RecurringOrders.ViewInitialOrder").Text, item.InitialOrderId) @specificationAttribute.SpecificationAttributeName - @if (specValue != null && specValue.Value.HasValue()) - { - @specValue - } - else - { -   - } + @foreach (var spec in foundProductSpecs) + { + var specValue = spec != null ? spec.SpecificationAttributeOption : null; + + if (specValue != null && specValue.Value.HasValue()) + { +
    @specValue
    + } + else + { +
     
    + } + }
    + @Html.SmartLabelFor(model => model.ShowLoginForPriceNote) + + @Html.SettingEditorFor(model => model.ShowLoginForPriceNote) + @Html.ValidationMessageFor(model => model.ShowLoginForPriceNote) +
    @Html.SmartLabelFor(model => model.ShowLinkedAttributeValueQuantity) diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index 4fffd68631..b563a34d37 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -634,36 +634,34 @@ public ProductDetailsModel PrepareProductDetailModel( IList productBundleItems = null, int selectedQuantity = 1) { - if (product == null) - throw new ArgumentNullException("product"); - - if (model == null) - throw new ArgumentNullException("model"); + Guard.NotNull(model, nameof(model)); + Guard.NotNull(product, nameof(product)); var store = _services.StoreContext.CurrentStore; var customer = _services.WorkContext.CurrentCustomer; var currency = _services.WorkContext.WorkingCurrency; - decimal preSelectedPriceAdjustmentBase = decimal.Zero; - decimal preSelectedWeightAdjustment = decimal.Zero; - bool displayPrices = _services.Permissions.Authorize(StandardPermissionProvider.DisplayPrices); - bool isBundle = (product.ProductType == ProductType.BundledProduct); - bool isBundleItemPricing = (productBundleItem != null && productBundleItem.Item.BundleProduct.BundlePerItemPricing); - bool isBundlePricing = (productBundleItem != null && !productBundleItem.Item.BundleProduct.BundlePerItemPricing); - int bundleItemId = (productBundleItem == null ? 0 : productBundleItem.Item.Id); + var preSelectedPriceAdjustmentBase = decimal.Zero; + var preSelectedWeightAdjustment = decimal.Zero; + var displayPrices = _services.Permissions.Authorize(StandardPermissionProvider.DisplayPrices); + var isBundle = product.ProductType == ProductType.BundledProduct; + var isBundleItemPricing = productBundleItem != null && productBundleItem.Item.BundleProduct.BundlePerItemPricing; + var isBundlePricing = productBundleItem != null && !productBundleItem.Item.BundleProduct.BundlePerItemPricing; + var bundleItemId = productBundleItem == null ? 0 : productBundleItem.Item.Id; - bool hasSelectedAttributesValues = false; - bool hasSelectedAttributes = query.Variants.Count > 0; + var hasSelectedAttributesValues = false; + var hasSelectedAttributes = query.Variants.Count > 0; List selectedAttributeValues = null; - - var variantAttributes = (isBundle ? new List() : _productAttributeService.GetProductVariantAttributesByProductId(product.Id)); + var variantAttributes = isBundle ? new List() : _productAttributeService.GetProductVariantAttributesByProductId(product.Id); model.IsBundlePart = product.ProductType != ProductType.BundledProduct && productBundleItem != null; model.ProductPrice.DynamicPriceUpdate = _catalogSettings.EnableDynamicPriceUpdate; model.ProductPrice.BundleItemShowBasePrice = _catalogSettings.BundleItemShowBasePrice; - if (!model.ProductPrice.DynamicPriceUpdate) - selectedQuantity = 1; + if (!model.ProductPrice.DynamicPriceUpdate) + { + selectedQuantity = 1; + } #region Product attributes @@ -1075,11 +1073,11 @@ public ProductDetailsModel PrepareProductDetailModel( #region Product price model.ProductPrice.ProductId = product.Id; + model.ProductPrice.HidePrices = !displayPrices; + model.ProductPrice.ShowLoginNote = !displayPrices && productBundleItem == null && _catalogSettings.ShowLoginForPriceNote; - if (displayPrices) + if (displayPrices) { - model.ProductPrice.HidePrices = false; - if (product.CustomerEntersPrice && !isBundleItemPricing) { model.ProductPrice.CustomerEntersPrice = true; @@ -1092,26 +1090,25 @@ public ProductDetailsModel PrepareProductDetailModel( } else { - decimal taxRate = decimal.Zero; - decimal oldPrice = decimal.Zero; - decimal finalPriceWithoutDiscountBase = decimal.Zero; - decimal finalPriceWithDiscountBase = decimal.Zero; - decimal attributesTotalPriceBase = decimal.Zero; - decimal attributesTotalPriceBaseOrig = decimal.Zero; - decimal finalPriceWithoutDiscount = decimal.Zero; - decimal finalPriceWithDiscount = decimal.Zero; - - decimal oldPriceBase = _taxService.GetProductPrice(product, product.OldPrice, out taxRate); + var taxRate = decimal.Zero; + var oldPrice = decimal.Zero; + var finalPriceWithoutDiscountBase = decimal.Zero; + var finalPriceWithDiscountBase = decimal.Zero; + var attributesTotalPriceBase = decimal.Zero; + var attributesTotalPriceBaseOrig = decimal.Zero; + var finalPriceWithoutDiscount = decimal.Zero; + var finalPriceWithDiscount = decimal.Zero; + var oldPriceBase = _taxService.GetProductPrice(product, product.OldPrice, out taxRate); if (model.ProductPrice.DynamicPriceUpdate && !isBundlePricing) { if (selectedAttributeValues != null) { selectedAttributeValues.Each(x => attributesTotalPriceBase += _priceCalculationService.GetProductVariantAttributeValuePriceAdjustment(x, - product, _services.WorkContext.CurrentCustomer, null, selectedQuantity)); + product, customer, null, selectedQuantity)); selectedAttributeValues.Each(x => attributesTotalPriceBaseOrig += _priceCalculationService.GetProductVariantAttributeValuePriceAdjustment(x, - product, _services.WorkContext.CurrentCustomer, null, 1)); + product, customer, null, 1)); } else { @@ -1208,36 +1205,32 @@ public ProductDetailsModel PrepareProductDetailModel( } else { - model.ProductPrice.HidePrices = true; - model.ProductPrice.OldPrice = null; + model.ProductPrice.OldPrice = null; model.ProductPrice.Price = null; } + #endregion #region 'Add to cart' model model.AddToCart.ProductId = product.Id; - - //quantity model.AddToCart.EnteredQuantity = product.OrderMinimumQuantity; - model.AddToCart.MinOrderAmount = product.OrderMinimumQuantity; model.AddToCart.MaxOrderAmount = product.OrderMaximumQuantity; model.AddToCart.QuantityUnitName = model.QuantityUnitName; // TODO: (mc) remove 'QuantityUnitName' from parent model later model.AddToCart.QuantityStep = product.QuantityStep > 0 ? product.QuantityStep : 1; model.AddToCart.HideQuantityControl = product.HideQuantityControl; model.AddToCart.QuantiyControlType = product.QuantiyControlType; + model.AddToCart.AvailableForPreOrder = product.AvailableForPreOrder; - //'add to cart', 'add to wishlist' buttons - model.AddToCart.DisableBuyButton = !displayPrices || product.DisableBuyButton || !_services.Permissions.Authorize(StandardPermissionProvider.EnableShoppingCart); - model.AddToCart.DisableWishlistButton = !displayPrices || product.DisableWishlistButton - || !_services.Permissions.Authorize(StandardPermissionProvider.EnableWishlist) - || product.ProductType == ProductType.GroupedProduct; + // 'add to cart', 'add to wishlist' buttons. + model.AddToCart.DisableBuyButton = !displayPrices || product.DisableBuyButton || + !_services.Permissions.Authorize(StandardPermissionProvider.EnableShoppingCart); - //pre-order - model.AddToCart.AvailableForPreOrder = product.AvailableForPreOrder; + model.AddToCart.DisableWishlistButton = !displayPrices || product.DisableWishlistButton + || product.ProductType == ProductType.GroupedProduct + || !_services.Permissions.Authorize(StandardPermissionProvider.EnableWishlist); - //customer entered price model.AddToCart.CustomerEntersPrice = product.CustomerEntersPrice; if (model.AddToCart.CustomerEntersPrice) { @@ -1251,7 +1244,6 @@ public ProductDetailsModel PrepareProductDetailModel( _priceFormatter.FormatPrice(maximumCustomerEnteredPrice, true, false)); } - //allowed quantities var allowedQuantities = product.ParseAllowedQuatities(); foreach (var qty in allowedQuantities) { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs index 0eca3e49ef..dd24d6b44e 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs @@ -200,8 +200,9 @@ public partial class ProductPriceModel : ModelBase public int ProductId { get; set; } public bool HidePrices { get; set; } + public bool ShowLoginNote { get; set; } - public bool DynamicPriceUpdate { get; set; } + public bool DynamicPriceUpdate { get; set; } public bool BundleItemShowBasePrice { get; set; } public string NoteWithDiscount { get; set; } diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_product.scss b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_product.scss index 61eaf65e91..f98a98a2dd 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Content/_product.scss +++ b/src/Presentation/SmartStore.Web/Themes/Flex/Content/_product.scss @@ -240,7 +240,8 @@ $pd-offer-spacing: $grid-gutter-width / 2; color: $danger; } -.pd-callforprice { +.pd-callforprice, +.pd-loginforprice { font-size: 1.2rem; } diff --git a/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.Price.cshtml b/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.Price.cshtml index 082bb6b0e5..aaf802985b 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.Price.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.Price.cshtml @@ -6,71 +6,70 @@ @using SmartStore.Core.Domain.Catalog; @{ - if (Model.AddToCart.DisableBuyButton && Model.AddToCart.DisableWishlistButton) + if (Model.AddToCart.DisableBuyButton && Model.AddToCart.DisableWishlistButton && !Model.ProductPrice.ShowLoginNote) { return; } +} - var isBundleItem = Model.IsBundlePart; - var priceModel = Model.ProductPrice; - - var currentPrice = priceModel.PriceWithDiscount.NullEmpty() ?? priceModel.Price; - var currentPriceValue = priceModel.PriceWithDiscountValue > 0 ? priceModel.PriceWithDiscountValue : priceModel.PriceValue; - var currentPriceLabel = priceModel.NoteWithDiscount; +
    + @if (Model.ProductPrice.ShowLoginNote) + { +

    @T("Products.LoginForPrice")

    + } + else if (Model.ProductPrice.CallForPrice) + { +

    @T("Products.CallForPrice")

    + } + else + { + var isBundleItem = Model.IsBundlePart; + var priceModel = Model.ProductPrice; + var currentPrice = priceModel.PriceWithDiscount.NullEmpty() ?? priceModel.Price; + var currentPriceValue = priceModel.PriceWithDiscountValue > 0 ? priceModel.PriceWithDiscountValue : priceModel.PriceValue; + var currentPriceLabel = priceModel.NoteWithDiscount; - var oldPrice1 = priceModel.OldPrice; - var oldPrice1Label = T("Products.Price.OldPrice").Text; + var oldPrice1 = priceModel.OldPrice; + var oldPrice1Label = T("Products.Price.OldPrice").Text; - var oldPrice2 = priceModel.PriceWithDiscountValue < priceModel.PriceValue ? priceModel.Price : ""; - var oldPrice2Label = priceModel.NoteWithoutDiscount; + var oldPrice2 = priceModel.PriceWithDiscountValue < priceModel.PriceValue ? priceModel.Price : ""; + var oldPrice2Label = priceModel.NoteWithoutDiscount; - var basePriceEnabled = isBundleItem ? priceModel.BundleItemShowBasePrice : Model.IsBasePriceEnabled; + var basePriceEnabled = isBundleItem ? priceModel.BundleItemShowBasePrice : Model.IsBasePriceEnabled; - if (oldPrice2.HasValue() && !oldPrice1.HasValue()) - { - oldPrice1 = oldPrice2; - oldPrice2 = null; - oldPrice2Label = null; - } + if (oldPrice2.HasValue() && !oldPrice1.HasValue()) + { + oldPrice1 = oldPrice2; + oldPrice2 = null; + oldPrice2Label = null; + } - if (priceModel.PriceValue == 0 && Model.DisplayTextForZeroPrices) - { - currentPrice = T("Products.Free").Text; - oldPrice1 = null; - oldPrice2 = null; - oldPrice2Label = null; - } + if (priceModel.PriceValue == 0 && Model.DisplayTextForZeroPrices) + { + currentPrice = T("Products.Free").Text; + oldPrice1 = null; + oldPrice2 = null; + oldPrice2Label = null; + } - var blocksHaveLabel = currentPriceLabel.HasValue() && oldPrice2Label.HasValue(); + var blocksHaveLabel = currentPriceLabel.HasValue() && oldPrice2Label.HasValue(); - var artPriceClasses = "pd-price"; - if (isBundleItem) - { - artPriceClasses += " pd-price-sm"; - } - if (priceModel.OldPrice.HasValue()) - { - artPriceClasses += " pd-price--offer"; - } - if (priceModel.CallForPrice) - { - artPriceClasses += " pd-price--call"; - } + var artPriceClasses = "pd-price"; + if (isBundleItem) + { + artPriceClasses += " pd-price-sm"; + } + if (priceModel.OldPrice.HasValue()) + { + artPriceClasses += " pd-price--offer"; + } - var badgeClasses = ""; - if (Model.PriceDisplayStyle == PriceDisplayStyle.BadgeAll || (Model.PriceDisplayStyle == PriceDisplayStyle.BadgeFreeProductsOnly && priceModel.PriceValue == 0)) - { - badgeClasses = Model.PriceDisplayStyle == PriceDisplayStyle.BadgeAll && priceModel.SavingAmount.HasValue() ? "text-danger" : "text-success"; - } -} + var badgeClasses = ""; + if (Model.PriceDisplayStyle == PriceDisplayStyle.BadgeAll || (Model.PriceDisplayStyle == PriceDisplayStyle.BadgeFreeProductsOnly && priceModel.PriceValue == 0)) + { + badgeClasses = Model.PriceDisplayStyle == PriceDisplayStyle.BadgeAll && priceModel.SavingAmount.HasValue() ? "text-danger" : "text-success"; + } -
    - @if (priceModel.CallForPrice) - { -

    @T("Products.CallForPrice")

    - } - else - {
    diff --git a/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.cshtml b/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.cshtml index 38c74770a6..2f3a25865e 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.cshtml @@ -7,9 +7,9 @@
    - @if (!Model.AddToCart.DisableBuyButton || !Model.AddToCart.DisableWishlistButton) + @if (!Model.AddToCart.DisableBuyButton || !Model.AddToCart.DisableWishlistButton || Model.ProductPrice.ShowLoginNote) { - if (Model.AddToCart.CustomerEntersPrice) + if (Model.AddToCart.CustomerEntersPrice && !Model.ProductPrice.HidePrices) { var dataDictCustomerPrice = new ViewDataDictionary(); dataDictCustomerPrice.TemplateInfo.HtmlFieldPrefix = string.Format("addtocart_{0}", Model.Id); From c2ff8a41f62c05b3dd4e0a1d1c952115d01b842e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 11 Feb 2019 15:18:18 +0100 Subject: [PATCH 387/932] Link builder --- .../Migrations/MigrationsConfiguration.cs | 2 + .../Components/EntityPicker/EntityPicker.cs | 8 +- .../EntityPicker/EntityPickerBuilder.cs | 16 +- .../Controllers/EntityController.cs | 344 ++++++++++-------- .../Models/Entity/EntityPickerModel.cs | 14 +- .../Scripts/smartstore.entityPicker.js | 54 +-- .../Views/Entity/Partials/Picker.List.cshtml | 29 +- .../Views/Entity/Partials/Picker.cshtml | 3 +- .../Shared/Components/EntityPicker.cshtml | 6 +- .../Views/Shared/EditorTemplates/Link.cshtml | 125 +++++-- 10 files changed, 363 insertions(+), 238 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 977bc6ccb4..620cabff20 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -642,6 +642,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Click Download to install a new language including all localized resources. On translate.smartstore.com you will find more details about available resources.", "Klicken Sie auf Download, um eine neue Sprache mit allen lokalisierten Ressourcen zu installieren. Auf translate.smartstore.com finden Sie weitere Details zu verfügbaren Ressourcen."); + builder.AddOrUpdate("Common.Url", "URL", "URL"); + builder.AddOrUpdate("Common.File", "File", "Datei"); builder.AddOrUpdate("Common.Entity.Product", "Product", "Produkt"); builder.AddOrUpdate("Common.Entity.Category", "Category", "Warengruppe"); builder.AddOrUpdate("Common.Entity.Manufacturer", "Manufacturer", "Hersteller"); diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPicker.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPicker.cs index 161f88beca..fcca6a595b 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPicker.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPicker.cs @@ -1,5 +1,4 @@ -using System; -namespace SmartStore.Web.Framework.UI +namespace SmartStore.Web.Framework.UI { public class EntityPicker : Component { @@ -16,8 +15,9 @@ public EntityPicker() } public string EntityType { get; set; } + public int LanguageId { get; set; } - public string TargetInputSelector + public string TargetInputSelector { get { return HtmlAttributes["data-target"] as string; } set { HtmlAttributes["data-target"] = value; } @@ -32,7 +32,7 @@ public string TargetInputSelector public bool DisableGroupedProducts { get; set; } public bool DisableBundleProducts { get; set; } public int[] DisabledEntityIds { get; set; } - public int[] SelectedEntityIds { get; set; } + public string[] Selected { get; set; } public bool EnableThumbZoomer { get; set; } public bool HighlightSearchTerm { get; set; } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPickerBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPickerBuilder.cs index a7b8503942..78b3d4867b 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPickerBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPickerBuilder.cs @@ -1,11 +1,7 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; using System.Web.Mvc; using System.Web.Routing; -using System.Web.WebPages; namespace SmartStore.Web.Framework.UI { @@ -31,7 +27,13 @@ public EntityPickerBuilder EntityType(string value) return this; } - public EntityPickerBuilder Caption(string value) + public EntityPickerBuilder LanguageId(int value) + { + base.Component.LanguageId = value; + return this; + } + + public EntityPickerBuilder Caption(string value) { base.Component.Caption = value; return this; @@ -94,9 +96,9 @@ public EntityPickerBuilder DisabledEntityIds(params int[] values) return this; } - public EntityPickerBuilder SelectedEntityIds(params int[] values) + public EntityPickerBuilder Selected(params string[] values) { - base.Component.SelectedEntityIds = values; + base.Component.Selected = values; return this; } diff --git a/src/Presentation/SmartStore.Web/Controllers/EntityController.cs b/src/Presentation/SmartStore.Web/Controllers/EntityController.cs index 4c32bd524f..84c6ce1e7d 100644 --- a/src/Presentation/SmartStore.Web/Controllers/EntityController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/EntityController.cs @@ -7,6 +7,7 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Media; +using SmartStore.Core.Domain.Topics; using SmartStore.Core.Search; using SmartStore.Services.Catalog; using SmartStore.Services.Customers; @@ -14,6 +15,7 @@ using SmartStore.Services.Search; using SmartStore.Services.Security; using SmartStore.Services.Seo; +using SmartStore.Services.Topics; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Models.Entity; @@ -31,8 +33,9 @@ public partial class EntityController : PublicControllerBase private readonly ICustomerService _customerService; private readonly ICategoryService _categoryService; private readonly IUrlRecordService _urlRecordService; + private readonly ITopicService _topicService; - public EntityController( + public EntityController( ICatalogSearchService catalogSearchService, CatalogSettings catalogSettings, MediaSettings mediaSettings, @@ -41,7 +44,8 @@ public EntityController( IManufacturerService manufacturerService, ICustomerService customerService, ICategoryService categoryService, - IUrlRecordService urlRecordService) + IUrlRecordService urlRecordService, + ITopicService topicService) { _catalogSearchService = catalogSearchService; _catalogSettings = catalogSettings; @@ -52,14 +56,13 @@ public EntityController( _customerService = customerService; _categoryService = categoryService; _urlRecordService = urlRecordService; + _topicService = topicService; } #region Entity Picker public ActionResult Picker(EntityPickerModel model) { - model.PageSize = 96; - if (model.EntityType.IsCaseInsensitiveEqual("product")) { ViewBag.AvailableCategories = _categoryService.GetCategoryTree(includeHidden: true) @@ -79,7 +82,8 @@ public ActionResult Picker(EntityPickerModel model) } else if (model.EntityType.IsCaseInsensitiveEqual("customer")) { - ViewBag.AvailableCustomerSearchTypes = new List { + ViewBag.AvailableCustomerSearchTypes = new List + { new SelectListItem { Text = "Name", Value = "Name", Selected = true }, new SelectListItem { Text = "Email", Value = "Email" } }; @@ -96,87 +100,87 @@ public ActionResult Picker(EntityPickerModel model) [HttpPost] public ActionResult Picker(EntityPickerModel model, FormCollection form) { - model.PageSize = 96; - try { - var language = Services.WorkContext.WorkingLanguage; - var disableIf = model.DisableIf.SplitSafe(",").Select(x => x.ToLower().Trim()).ToList(); + var languageId = model.LanguageId == 0 ? Services.WorkContext.WorkingLanguage.Id : model.LanguageId; + var disableIf = model.DisableIf.SplitSafe(",").Select(x => x.ToLower().Trim()).ToList(); var disableIds = model.DisableIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); - var selIds = new HashSet(model.PreselectedEntityIds.ToIntArray()); + var selected = model.Selected.SplitSafe(","); + var returnLink = model.ReturnField.IsCaseInsensitiveEqual("link"); + var returnSku = model.ReturnField.IsCaseInsensitiveEqual("sku"); - using (var scope = new DbContextScope(Services.DbContext, autoDetectChanges: false, proxyCreation: true, validateOnSave: false, forceNoTracking: true)) + using (var scope = new DbContextScope(Services.DbContext, autoDetectChanges: false, proxyCreation: true, validateOnSave: false, forceNoTracking: true)) { - if (model.EntityType.IsCaseInsensitiveEqual("product")) - { - model.SearchTerm = model.SearchTerm.TrimSafe(); - - var hasPermission = Services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog); - var disableIfNotSimpleProduct = disableIf.Contains("notsimpleproduct"); - var disableIfGroupedProduct = disableIf.Contains("groupedproduct"); - var labelTextGrouped = T("Admin.Catalog.Products.ProductType.GroupedProduct.Label").Text; - var labelTextBundled = T("Admin.Catalog.Products.ProductType.BundledProduct.Label").Text; - var sku = T("Products.Sku").Text; - - var fields = new List { "name" }; - if (_searchSettings.SearchFields.Contains("sku")) - fields.Add("sku"); - if (_searchSettings.SearchFields.Contains("shortdescription")) - fields.Add("shortdescription"); - - var searchQuery = new CatalogSearchQuery(fields.ToArray(), model.SearchTerm) - .HasStoreId(model.StoreId); - - if (!hasPermission) - { - searchQuery = searchQuery.VisibleOnly(Services.WorkContext.CurrentCustomer); - } - - if (model.ProductTypeId > 0) - { - searchQuery = searchQuery.IsProductType((ProductType)model.ProductTypeId); - } - - if (model.ManufacturerId != 0) - { - searchQuery = searchQuery.WithManufacturerIds(null, model.ManufacturerId); - } - - if (model.CategoryId != 0) - { - var node = _categoryService.GetCategoryTree(model.CategoryId, true); - if (node != null) - { - searchQuery = searchQuery.WithCategoryIds(null, node.Flatten(true).Select(x => x.Id).ToArray()); - } - } + if (model.EntityType.IsCaseInsensitiveEqual("product")) + { + model.SearchTerm = model.SearchTerm.TrimSafe(); + + var hasPermission = Services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog); + var disableIfNotSimpleProduct = disableIf.Contains("notsimpleproduct"); + var disableIfGroupedProduct = disableIf.Contains("groupedproduct"); + var labelTextGrouped = T("Admin.Catalog.Products.ProductType.GroupedProduct.Label").Text; + var labelTextBundled = T("Admin.Catalog.Products.ProductType.BundledProduct.Label").Text; + var sku = T("Products.Sku").Text; + + var fields = new List { "name" }; + if (_searchSettings.SearchFields.Contains("sku")) + fields.Add("sku"); + if (_searchSettings.SearchFields.Contains("shortdescription")) + fields.Add("shortdescription"); + + var searchQuery = new CatalogSearchQuery(fields.ToArray(), model.SearchTerm) + .HasStoreId(model.StoreId); + + if (!hasPermission) + { + searchQuery = searchQuery.VisibleOnly(Services.WorkContext.CurrentCustomer); + } + + if (model.ProductTypeId > 0) + { + searchQuery = searchQuery.IsProductType((ProductType)model.ProductTypeId); + } + + if (model.ManufacturerId != 0) + { + searchQuery = searchQuery.WithManufacturerIds(null, model.ManufacturerId); + } + + if (model.CategoryId != 0) + { + var node = _categoryService.GetCategoryTree(model.CategoryId, true); + if (node != null) + { + searchQuery = searchQuery.WithCategoryIds(null, node.Flatten(true).Select(x => x.Id).ToArray()); + } + } var skip = model.PageIndex * model.PageSize; var query = _catalogSearchService.PrepareQuery(searchQuery); - var products = query - .Select(x => new - { - x.Id, - x.Sku, - x.Name, - x.Published, - x.ProductTypeId, - x.MainPictureId - }) - .OrderBy(x => x.Name) - .Skip(() => skip) - .Take(() => model.PageSize) - .ToList(); - - var allPictureIds = products.Select(x => x.MainPictureId.GetValueOrDefault()); - var allPictureInfos = _pictureService.GetPictureInfos(allPictureIds); - var slugs = model.ReturnField.IsCaseInsensitiveEqual("link") + var products = query + .Select(x => new + { + x.Id, + x.Sku, + x.Name, + x.Published, + x.ProductTypeId, + x.MainPictureId + }) + .OrderBy(x => x.Name) + .Skip(() => skip) + .Take(() => model.PageSize) + .ToList(); + + var allPictureIds = products.Select(x => x.MainPictureId.GetValueOrDefault()); + var allPictureInfos = _pictureService.GetPictureInfos(allPictureIds); + var slugs = returnLink ? _urlRecordService.GetUrlRecordCollection(nameof(Product), null, products.Select(x => x.Id).ToArray()) : null; model.SearchResult = products - .Select(x => + .Select(x => { var item = new EntityPickerModel.SearchResultModel { @@ -184,80 +188,94 @@ public ActionResult Picker(EntityPickerModel model, FormCollection form) Title = x.Name, Summary = x.Sku, SummaryTitle = "{0}: {1}".FormatInvariant(sku, x.Sku.NaIfEmpty()), - Published = hasPermission ? x.Published : (bool?)null, - Selected = selIds.Contains(x.Id) + Published = hasPermission ? x.Published : (bool?)null }; - item.ReturnValue = slugs != null - ? Url.RouteUrl("Product", new { SeName = slugs.GetSlug(language.Id, x.Id) }) - : (model.ReturnField.IsCaseInsensitiveEqual("sku") ? x.Sku : x.Id.ToString()); - - if (disableIfNotSimpleProduct) - { - item.Disable = x.ProductTypeId != (int)ProductType.SimpleProduct; - } - else if (disableIfGroupedProduct) - { - item.Disable = x.ProductTypeId == (int)ProductType.GroupedProduct; - } - - if (!item.Disable && disableIds.Contains(x.Id)) - { - item.Disable = true; - } - - if (x.ProductTypeId == (int)ProductType.GroupedProduct) - { - item.LabelText = labelTextGrouped; - item.LabelClassName = "badge-success"; - } - else if (x.ProductTypeId == (int)ProductType.BundledProduct) - { - item.LabelText = labelTextBundled; - item.LabelClassName = "badge-info"; - } - - var pictureInfo = allPictureInfos.Get(x.MainPictureId.GetValueOrDefault()); - var fallbackType = _catalogSettings.HideProductDefaultPictures ? FallbackPictureType.NoFallback : FallbackPictureType.Entity; - - item.ImageUrl = _pictureService.GetUrl( - allPictureInfos.Get(x.MainPictureId.GetValueOrDefault()), - _mediaSettings.ProductThumbPictureSizeOnProductDetailsPage, - fallbackType); - - return item; - }) - .ToList(); - } + if (returnLink) + { + item.ReturnValue = Url.RouteUrl("Product", new { SeName = slugs.GetSlug(languageId, x.Id) }); + } + else if (returnSku) + { + item.ReturnValue = x.Sku; + } + else + { + item.ReturnValue = x.Id.ToString(); + } + + item.Selected = selected.Contains(item.ReturnValue); + + if (disableIfNotSimpleProduct) + { + item.Disable = x.ProductTypeId != (int)ProductType.SimpleProduct; + } + else if (disableIfGroupedProduct) + { + item.Disable = x.ProductTypeId == (int)ProductType.GroupedProduct; + } + + if (!item.Disable && disableIds.Contains(x.Id)) + { + item.Disable = true; + } + + if (x.ProductTypeId == (int)ProductType.GroupedProduct) + { + item.LabelText = labelTextGrouped; + item.LabelClassName = "badge-success"; + } + else if (x.ProductTypeId == (int)ProductType.BundledProduct) + { + item.LabelText = labelTextBundled; + item.LabelClassName = "badge-info"; + } + + var pictureInfo = allPictureInfos.Get(x.MainPictureId.GetValueOrDefault()); + var fallbackType = _catalogSettings.HideProductDefaultPictures ? FallbackPictureType.NoFallback : FallbackPictureType.Entity; + + item.ImageUrl = _pictureService.GetUrl( + allPictureInfos.Get(x.MainPictureId.GetValueOrDefault()), + _mediaSettings.ProductThumbPictureSizeOnProductDetailsPage, + fallbackType); + + return item; + }) + .ToList(); + } else if (model.EntityType.IsCaseInsensitiveEqual("category")) - { + { var categories = _categoryService.GetAllCategories(model.SearchTerm, showHidden: true); var allPictureIds = categories.Select(x => x.PictureId.GetValueOrDefault()); var allPictureInfos = _pictureService.GetPictureInfos(allPictureIds); - var slugs = model.ReturnField.IsCaseInsensitiveEqual("link") + var slugs = returnLink ? _urlRecordService.GetUrlRecordCollection(nameof(Category), null, categories.Select(x => x.Id).ToArray()) : null; model.SearchResult = categories .Select(x => { + var path = ((ICategoryNode)x).GetCategoryPath(_categoryService, languageId, "({0})"); + var returnValue = returnLink + ? Url.RouteUrl("Category", new { SeName = slugs.GetSlug(languageId, x.Id) }) + : x.Id.ToString(); + var item = new EntityPickerModel.SearchResultModel { Id = x.Id, Title = x.Name, - Summary = x.Description.Truncate(120, "…"), - SummaryTitle = x.Name, + Summary = path, + SummaryTitle = path, Published = x.Published, - Selected = selIds.Contains(x.Id) + ReturnValue = returnValue, + Selected = selected.Contains(returnValue), + Disable = disableIds.Contains(x.Id) }; - item.ReturnValue = slugs != null - ? Url.RouteUrl("Category", new { SeName = slugs.GetSlug(language.Id, x.Id) }) - : x.Id.ToString(); - - if (!item.Disable && disableIds.Contains(x.Id)) + if (x.Alias.HasValue()) { - item.Disable = true; + item.LabelText = x.Alias; + item.LabelClassName = "badge-secondary"; } var pictureInfo = allPictureInfos.Get(x.PictureId.GetValueOrDefault()); @@ -272,37 +290,32 @@ public ActionResult Picker(EntityPickerModel model, FormCollection form) }) .ToList(); } - else if (model.EntityType.IsCaseInsensitiveEqual("manufacturer")) + else if (model.EntityType.IsCaseInsensitiveEqual("manufacturer")) { var manufacturers = _manufacturerService.GetAllManufacturers(model.SearchTerm, model.PageIndex, model.PageSize, showHidden: true); var allPictureIds = manufacturers.Select(x => x.PictureId.GetValueOrDefault()); var allPictureInfos = _pictureService.GetPictureInfos(allPictureIds); - var slugs = model.ReturnField.IsCaseInsensitiveEqual("link") + var slugs = returnLink ? _urlRecordService.GetUrlRecordCollection(nameof(Manufacturer), null, manufacturers.Select(x => x.Id).ToArray()) : null; model.SearchResult = manufacturers .Select(x => { + var returnValue = returnLink + ? Url.RouteUrl("Manufacturer", new { SeName = slugs.GetSlug(languageId, x.Id) }) + : x.Id.ToString(); + var item = new EntityPickerModel.SearchResultModel { Id = x.Id, - ReturnValue = x.Id.ToString(), Title = x.Name, - SummaryTitle = x.Name, Published = x.Published, - Selected = selIds.Contains(x.Id) + ReturnValue = returnValue, + Selected = selected.Contains(returnValue), + Disable = disableIds.Contains(x.Id) }; - item.ReturnValue = slugs != null - ? Url.RouteUrl("Manufacturer", new { SeName = slugs.GetSlug(language.Id, x.Id) }) - : x.Id.ToString(); - - if (!item.Disable && disableIds.Contains(x.Id)) - { - item.Disable = true; - } - var pictureInfo = allPictureInfos.Get(x.PictureId.GetValueOrDefault()); var fallbackType = _catalogSettings.HideProductDefaultPictures ? FallbackPictureType.NoFallback : FallbackPictureType.Entity; @@ -318,14 +331,13 @@ public ActionResult Picker(EntityPickerModel model, FormCollection form) else if (model.EntityType.IsCaseInsensitiveEqual("customer")) { var registeredRoleId = _customerService.GetCustomerRoleBySystemName("Registered").Id; - var searchTermName = string.Empty; var searchTermEmail = string.Empty; var searchTermCustomerNumber = string.Empty; if (model.CustomerSearchType.IsCaseInsensitiveEqual("Name")) searchTermName = model.SearchTerm; - else if(model.CustomerSearchType.IsCaseInsensitiveEqual("Email")) + else if (model.CustomerSearchType.IsCaseInsensitiveEqual("Email")) searchTermEmail = model.SearchTerm; else if (model.CustomerSearchType.IsCaseInsensitiveEqual("CustomerNumber")) searchTermCustomerNumber = model.SearchTerm; @@ -341,26 +353,54 @@ public ActionResult Picker(EntityPickerModel model, FormCollection form) }; var customers = _customerService.SearchCustomers(q); - + model.SearchResult = customers .Select(x => { + var fullName = x.GetFullName(); + var item = new EntityPickerModel.SearchResultModel { Id = x.Id, ReturnValue = x.Id.ToString(), - Title = x.Username, - Summary = x.GetFullName(), - SummaryTitle = x.GetFullName(), + Title = x.Username.NullEmpty() ?? x.Email, + Summary = fullName, + SummaryTitle = fullName, Published = true, - Selected = selIds.Contains(x.Id) + Selected = selected.Contains(x.Id.ToString()), + Disable = disableIds.Contains(x.Id) }; - if (!item.Disable && disableIds.Contains(x.Id)) + return item; + }) + .ToList(); + } + else if (model.EntityType.IsCaseInsensitiveEqual("topic")) + { + var topics = _topicService.GetAllTopics(0, model.PageIndex, model.PageSize, true); + var slugs = returnLink + ? _urlRecordService.GetUrlRecordCollection(nameof(Topic), null, topics.Select(x => x.Id).ToArray()) + : null; + + model.SearchResult = topics + .Select(x => + { + var returnValue = returnLink + ? Url.RouteUrl("Topic", new { SeName = slugs.GetSlug(languageId, x.Id) }) + : x.Id.ToString(); + + var item = new EntityPickerModel.SearchResultModel { - item.Disable = true; - } - + Id = x.Id, + Published = x.IsPublished, + Title = x.Title.NullEmpty() ?? x.SystemName, + Summary = x.SystemName, + SummaryTitle = x.SystemName, + ReturnValue = returnValue, + Selected = selected.Contains(returnValue), + Disable = disableIds.Contains(x.Id) + }; + return item; }) .ToList(); diff --git a/src/Presentation/SmartStore.Web/Models/Entity/EntityPickerModel.cs b/src/Presentation/SmartStore.Web/Models/Entity/EntityPickerModel.cs index 643c0128ec..eb6a248e31 100644 --- a/src/Presentation/SmartStore.Web/Models/Entity/EntityPickerModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Entity/EntityPickerModel.cs @@ -6,20 +6,26 @@ namespace SmartStore.Web.Models.Entity { public class EntityPickerModel : ModelBase { - public string EntityType { get; set; } + public EntityPickerModel() + { + PageSize = 96; + } + + public string EntityType { get; set; } public bool HighligtSearchTerm { get; set; } public string DisableIf { get; set; } public string DisableIds { get; set; } public string SearchTerm { get; set; } public string ReturnField { get; set; } public int MaxItems { get; set; } - public string PreselectedEntityIds { get; set; } + public string Selected { get; set; } public int PageIndex { get; set; } public int PageSize { get; set; } + public int LanguageId { get; set; } - public List SearchResult { get; set; } + public List SearchResult { get; set; } - #region SearchProperties + #region Search properties [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] public string ProductName { get; set; } diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js index 856834fc66..e775021c03 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js @@ -76,7 +76,7 @@ returnField: 'id', delim: ',', targetInput: null, - preselectedIds: null, + selected: null, appendMode: true, maxItems: 0, onDialogLoading: null, @@ -84,7 +84,10 @@ onSelectionCompleted: null }; - options = $.extend({}, defaults, options); + // Use self.attr, not self.data! + options = $.extend({}, defaults, options); + options.entityType = self.attr('data-entitytype') || options.entityType; + options.caption = self.attr('data-caption') || options.caption; if (_.isEmpty(options.url)) { options.url = self.attr('data-url'); @@ -98,20 +101,22 @@ options.targetInput = $(self.data('target')).first(); } - if (options.targetInput && !_.isArray(options.preselectedIds)) { - var val = $(options.targetInput).val(); + if (options.targetInput && !_.isArray(options.selected)) { + var val = $(options.targetInput).val(); if (val.length > 0) { - options.preselectedIds = _.map(val.split(options.delim), function (x) { + options.selected = _.map(val.split(options.delim), function (x) { var result = x.trim(); - if (options.returnField.toLowerCase() === 'id') result = toInt(result); + if (options.returnField.toLowerCase() === 'id') { + result = toInt(result); + } return result; - }); + }); } } - if (!_.isArray(options.preselectedIds)) { - options.preselectedIds = []; - } + if (!_.isArray(options.selected)) { + options.selected = []; + } return options; } @@ -132,7 +137,7 @@ } function loadDialog(context /* button */, opt) { - var btn = $(context); + var btn = $(context); var dialog = $('#entpicker-' + opt.entityType + '-dialog'); function showAndFocusDialog() { @@ -151,16 +156,16 @@ if (dialog.length) { showAndFocusDialog(); } - else { + else { $.ajax({ cache: false, type: 'GET', data: { - "EntityType": opt.entityType, + "EntityType": opt.entityType, "HighligtSearchTerm": opt.highligtSearchTerm, "ReturnField": opt.returnField, "MaxItems": opt.maxItems, - "PreselectedEntityIds": opt.preselectedIds.join(), + "Selected": opt.selected.join(), "DisableIf": opt.disableIf, "DisableIds": opt.disableIds }, @@ -238,8 +243,8 @@ // lazy loading dialog.find('.modal-body').on('scroll', function (e) { - if ($('.load-more:not(.loading)').visible(true, false, 'vertical')) { - fillList(this, { append: true }); + if (dialog.find('.load-more:not(.loading)').visible(true, false, 'vertical')) { + fillList(this, { append: true }); } }); @@ -298,25 +303,27 @@ }; }); - var selectedIds = _.uniq(_.map(selectedItems, function (x) { + var selectedValues = _.uniq(_.map(selectedItems, function (x) { var result = x.id; - if (opts.returnField.toLowerCase() === 'id' && !_.isNumber(result)) result = toInt(result); + if (opts.returnField.toLowerCase() === 'id' && !_.isNumber(result)) { + result = toInt(result); + } return result; })); - if (opts.appendMode && _.isArray(opts.preselectedIds)) { - selectedIds = _.union(opts.preselectedIds, selectedIds); + if (opts.appendMode && _.isArray(opts.selected)) { + selectedValues = _.union(opts.selected, selectedValues); } if (opts.targetInput) { $(opts.targetInput) - .val(selectedIds.join(opts.delim)) + .val(selectedValues.join(opts.delim)) .focus() .blur(); } if (_.isFunction(opts.onSelectionCompleted)) { - if (opts.onSelectionCompleted(selectedIds, selectedItems, dialog)) { + if (opts.onSelectionCompleted(selectedValues, selectedItems, dialog)) { dialog.modal('hide'); } } @@ -340,7 +347,7 @@ var pageElement = dialog.find('input[name=PageIndex]'), pageIndex = parseInt(pageElement.val()); - pageElement.val(pageIndex + 1); + pageElement.val(pageIndex + 1); } else { dialog.find('input[name=PageIndex]').val('0'); @@ -370,7 +377,6 @@ list.stop().append(response); if (!opt.append) { - //dialog.find('.entpicker-filter').slideUp(200); showStatus(dialog); } diff --git a/src/Presentation/SmartStore.Web/Views/Entity/Partials/Picker.List.cshtml b/src/Presentation/SmartStore.Web/Views/Entity/Partials/Picker.List.cshtml index a56418423e..d598add3d4 100644 --- a/src/Presentation/SmartStore.Web/Views/Entity/Partials/Picker.List.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Entity/Partials/Picker.List.cshtml @@ -1,6 +1,5 @@ -@model EntityPickerModel - -@using SmartStore.Web.Models.Entity; +@using SmartStore.Web.Models.Entity; +@model EntityPickerModel @helper HighlightSearchTermInTitle(EntityPickerModel.SearchResultModel item) { @@ -18,16 +17,16 @@ {
    -
    -
    - @if (item.ImageUrl.HasValue()) - { -
    - -
    - } -
    -
    + @if (item.ImageUrl.HasValue()) + { +
    +
    +
    + +
    +
    +
    + }
    @@ -43,13 +42,13 @@ } @item.Summary -
    - +
    } +