From ab134ced5fade523d5aeab53cf5f6b3aa232010c Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Sat, 22 Sep 2018 04:20:35 +0200 Subject: [PATCH 01/71] 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 { - /// <summary> /// Instructs the repository to eager load entities that may be in the type's association path. /// </summary> @@ -40,6 +39,5 @@ public static IQueryable<T> Expand<T, TProperty>(this IQueryable<T> 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 <home@footwear-box.de> Date: Mon, 24 Sep 2018 09:58:36 +0200 Subject: [PATCH 02/71] 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 { - /// <summary> - /// Facebook externalAuth processor - /// </summary> - public class FacebookExternalAuthMethod : BasePlugin, IExternalAuthenticationMethod, IConfigurable + /// <summary> + /// Facebook externalAuth processor + /// </summary> + 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 @@ <Language Name="Deutsch" IsDefault="true" IsRightToLeft="false"> <LocaleResource Name="Plugins.FriendlyName.SmartStore.FacebookAuth" AppendRootKey="false"> - <Value>Facebook</Value> + <Value>Facebook Login</Value> </LocaleResource> <LocaleResource Name="AdminInstruction"> <Value> 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 @@ <Language Name="English" IsDefault="false" IsRightToLeft="false"> <LocaleResource Name="Plugins.FriendlyName.SmartStore.FacebookAuth" AppendRootKey="false"> - <Value>Facebook</Value> + <Value>Facebook Login</Value> </LocaleResource> <LocaleResource Name="AdminInstruction"> <Value> 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 <home@footwear-box.de> Date: Mon, 24 Sep 2018 15:23:29 +0200 Subject: [PATCH 03/71] 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<Forum> 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<SyndicationItem>(); 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 <muratc@smartstore.de> Date: Mon, 24 Sep 2018 21:30:54 +0200 Subject: [PATCH 04/71] 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 @@ </div> } - @if (IsSectionDefined("header")) - { - @RenderSection("header") - } - else - { - <header> - @if (IsSectionDefined("navbar")) - { - @RenderSection("navbar") - } - else - { - Html.RenderWidget("admin_navbar_before"); - Html.RenderAction("Navbar", "Common", new { area = "Admin" }); - Html.RenderWidget("admin_navbar_after"); - } + <div class="page-main"> + @if (IsSectionDefined("header")) + { + @RenderSection("header") + } + else + { + <header id="header"> + @if (IsSectionDefined("navbar")) + { + @RenderSection("navbar") + } + else + { + Html.RenderWidget("admin_navbar_before"); + Html.RenderAction("Navbar", "Common", new { area = "Admin" }); + Html.RenderWidget("admin_navbar_after"); + } - <div id="breadcrumb"> - <!-- SiteMapPath here--> - @{ Html.RenderWidget("admin_breadcrumb"); } - </div> - </header> - } + <div id="breadcrumb"> + <!-- SiteMapPath here--> + @{ Html.RenderWidget("admin_breadcrumb"); } + </div> + </header> + } - @{ Html.RenderWidget("admin_content_before"); } - <div id="content"> - <div class="cph"> - @RenderBody() - </div> - <div id="ajax-busy"> - <div class="bar"></div> - <div class="bar"></div> - <div class="bar"></div> + <div id="content"> + <div class="cph"> + @{ Html.RenderWidget("admin_content_before"); } + @RenderBody() + </div> + <div id="ajax-busy"> + <div class="bar"></div> + <div class="bar"></div> + <div class="bar"></div> + </div> </div> - </div> + </div> + @{ Html.RenderPartial("_Notifications"); } @{ Html.RenderWidget("admin_content_after"); } </div> 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 <muratc@smartstore.de> Date: Tue, 25 Sep 2018 03:26:35 +0200 Subject: [PATCH 05/71] 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('<label class="switch"></label>') .after('<span class="switch-toggle" data-on="' + window.Res['Common.On'] + '" data-off="' + window.Res['Common.Off'] + '"></span>'); 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 @@ </header> } + @{ Html.RenderZone("beforemaincontent"); } + <div id="content"> <div class="cph"> @{ Html.RenderWidget("admin_content_before"); } @@ -70,6 +72,8 @@ <div class="bar"></div> </div> </div> + + @{ Html.RenderZone("aftermaincontent"); } </div> @{ 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<string>(); + } + + return cls; + } + } } @{ @@ -41,7 +56,7 @@ } } -<div class="input-group date datetimepicker-group" id="@id-parent" data-date="@value" data-target-input="nearest"> +<div class="@CssClass" id="@id-parent" data-date="@value" data-target-input="nearest"> @Html.TextBox("", value, new { @class = "form-control datetimepicker-input", data_target = "#" + id + "-parent", data_format = format }) <div class="input-group-append input-group-addon" data-target="#@(id)-parent" data-toggle="datetimepicker"> <span class="input-group-text"><i class="fa fa-calendar"></i></span> From 522ec887ca25d374b81774f42d34e95a4841f39c Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Tue, 25 Sep 2018 11:05:09 +0200 Subject: [PATCH 06/71] 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 /// <summary> /// Represents a forum group /// </summary> - public partial class ForumGroup : BaseEntity, IAuditable, IStoreMappingSupported, ILocalizedEntity, ISlugSupported + public partial class ForumGroup : BaseEntity, IAuditable, IStoreMappingSupported, IAclSupported, ILocalizedEntity, ISlugSupported { private ICollection<Forum> _forums; @@ -43,6 +44,11 @@ public partial class ForumGroup : BaseEntity, IAuditable, IStoreMappingSupported /// </summary> public bool LimitedToStores { get; set; } + /// <summary> + /// Gets or sets a value indicating whether the entity is subject to ACL + /// </summary> + public bool SubjectToAcl { get; set; } + /// <summary> /// Gets or sets the collection of Forums /// </summary> 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 @@ +// <auto-generated /> +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 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="Target" xml:space="preserve"> + <value>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</value> + </data> + <data name="DefaultSchema" xml:space="preserve"> + <value>dbo</value> + </data> +</root> \ 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 @@ <Compile Include="Migrations\201809171309522_NewsletterSubscriptionLanguage.Designer.cs"> <DependentUpon>201809171309522_NewsletterSubscriptionLanguage.cs</DependentUpon> </Compile> + <Compile Include="Migrations\201809241947314_ForumGroupAcl.cs" /> + <Compile Include="Migrations\201809241947314_ForumGroupAcl.Designer.cs"> + <DependentUpon>201809241947314_ForumGroupAcl.cs</DependentUpon> + </Compile> <Compile Include="ObjectContextBase.SaveChanges.cs" /> <Compile Include="Setup\Builder\ActivityLogTypeMigrator.cs" /> <Compile Include="Setup\Builder\PermissionMigrator.cs" /> @@ -1120,6 +1124,9 @@ <EmbeddedResource Include="Migrations\201809171309522_NewsletterSubscriptionLanguage.resx"> <DependentUpon>201809171309522_NewsletterSubscriptionLanguage.cs</DependentUpon> </EmbeddedResource> + <EmbeddedResource Include="Migrations\201809241947314_ForumGroupAcl.resx"> + <DependentUpon>201809241947314_ForumGroupAcl.cs</DependentUpon> + </EmbeddedResource> <EmbeddedResource Include="Sql\Indexes.sql" /> <EmbeddedResource Include="Sql\StoredProcedures.sql" /> </ItemGroup> 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<ForumPost> _forumPostRepository; private readonly IRepository<PrivateMessage> _forumPrivateMessageRepository; private readonly IRepository<ForumSubscription> _forumSubscriptionRepository; + private readonly IRepository<AclRecord> _aclRepository; private readonly ForumSettings _forumSettings; private readonly IRepository<Customer> _customerRepository; private readonly IGenericAttributeService _genericAttributeService; @@ -33,6 +35,7 @@ public ForumService( IRepository<ForumPost> forumPostRepository, IRepository<PrivateMessage> forumPrivateMessageRepository, IRepository<ForumSubscription> forumSubscriptionRepository, + IRepository<AclRecord> aclRepository, ForumSettings forumSettings, IRepository<Customer> 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<ForumGroup> GetAllForumGroups(int storeId = 0) + public virtual IList<ForumGroup> 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<ForumTopic> GetAllTopics(int forumId, int pageIndex, i return topics; } - public virtual IList<ForumTopic> GetActiveTopics(int forumId, int count) + public virtual IList<ForumTopic> 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 /// </summary> /// <param name="storeId">Store identifier</param> + /// <param name="showHidden">Whether to load hidden records</param> /// <returns>Forum groups</returns> - IList<ForumGroup> GetAllForumGroups(int storeId = 0); + IList<ForumGroup> GetAllForumGroups(int storeId = 0, bool showHidden = false); /// <summary> /// Deletes a forum group @@ -112,8 +113,9 @@ public partial interface IForumService /// </summary> /// <param name="forumId">The forum identifier</param> /// <param name="topicCount">Count of forum topics to return</param> + /// <param name="showHidden">Whether to load hidden records</param> /// <returns>Forum Topics</returns> - IList<ForumTopic> GetActiveTopics(int forumId, int topicCount); + IList<ForumTopic> GetActiveTopics(int forumId, int topicCount, bool showHidden = false); /// <summary> /// 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<ForumGroupLocalizedModel>, IStoreSelector + public class ForumGroupModel : EntityModelBase, ILocalizedModel<ForumGroupLocalizedModel>, IStoreSelector, IAclSelector { public ForumGroupModel() { @@ -41,8 +41,11 @@ public ForumGroupModel() public IEnumerable<SelectListItem> AvailableStores { get; set; } public int[] SelectedStoreIds { get; set; } - public IList<ForumModel> ForumModels { get; set; } + public bool SubjectToAcl { get; set; } + public IEnumerable<SelectListItem> AvailableCustomerRoles { get; set; } + public int[] SelectedCustomerRoleIds { get; set; } + public IList<ForumModel> ForumModels { get; set; } public IList<ForumGroupLocalizedModel> 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<IEventPublisher>().Publish(new TabStripCreated(x, "forumgroup-edit", this.Html, this.Model)); }) @@ -98,6 +97,11 @@ </table> } +@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 <home@footwear-box.de> Date: Tue, 25 Sep 2018 13:03:51 +0200 Subject: [PATCH 07/71] 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<ForumPost> _forumPostRepository; private readonly IRepository<PrivateMessage> _forumPrivateMessageRepository; private readonly IRepository<ForumSubscription> _forumSubscriptionRepository; + private readonly IRepository<StoreMapping> _storeMappingRepository; private readonly IRepository<AclRecord> _aclRepository; private readonly ForumSettings _forumSettings; private readonly IRepository<Customer> _customerRepository; private readonly IGenericAttributeService _genericAttributeService; private readonly ICustomerService _customerService; - private readonly IRepository<StoreMapping> _storeMappingRepository; private readonly ICommonServices _services; public ForumService( @@ -35,12 +35,12 @@ public ForumService( IRepository<ForumPost> forumPostRepository, IRepository<PrivateMessage> forumPrivateMessageRepository, IRepository<ForumSubscription> forumSubscriptionRepository, + IRepository<StoreMapping> storeMappingRepository, IRepository<AclRecord> aclRepository, ForumSettings forumSettings, IRepository<Customer> customerRepository, IGenericAttributeService genericAttributeService, ICustomerService customerService, - IRepository<StoreMapping> 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<ForumPost> _forumPostRepository; + private readonly IRepository<ForumTopic> _forumTopicRepository; + private readonly IRepository<Forum> _forumRepository; + private readonly IRepository<ForumGroup> _forumGroupRepository; private readonly IRepository<StoreMapping> _storeMappingRepository; + private readonly IRepository<AclRecord> _aclRepository; private readonly IForumService _forumService; private readonly ICommonServices _services; private readonly CustomerSettings _customerSettings; public LinqForumSearchService( IRepository<ForumPost> forumPostRepository, + IRepository<ForumTopic> forumTopicRepository, + IRepository<Forum> forumRepository, + IRepository<ForumGroup> forumGroupRepository, IRepository<StoreMapping> storeMappingRepository, + IRepository<AclRecord> 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<ForumPost> 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 <home@footwear-box.de> Date: Tue, 25 Sep 2018 15:06:06 +0200 Subject: [PATCH 08/71] 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 /// </summary> public DateTime UpdatedOnUtc { get; set; } + /// <summary> + /// Gets or sets a value indicating whether the entity is published + /// </summary> + public bool Published { get; set; } + /// <summary> /// Gets the topic /// </summary> 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 /// </summary> public DateTime UpdatedOnUtc { get; set; } + /// <summary> + /// Gets or sets a value indicating whether the entity is published + /// </summary> + public bool Published { get; set; } + /// <summary> /// Gets or sets the forum topic type /// </summary> 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 @@ -<?xml version="1.0" encoding="utf-8"?> -<root> - <!-- - Microsoft ResX Schema - - Version 2.0 - - The primary goals of this format is to allow a simple XML format - that is mostly human readable. The generation and parsing of the - various data types are done through the TypeConverter classes - associated with the data types. - - Example: - - ... ado.net/XML headers & schema ... - <resheader name="resmimetype">text/microsoft-resx</resheader> - <resheader name="version">2.0</resheader> - <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> - <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> - <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> - <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> - <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> - <value>[base64 mime encoded serialized .NET Framework object]</value> - </data> - <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> - <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> - <comment>This is a comment</comment> - </data> - - There are any number of "resheader" rows that contain simple - name/value pairs. - - Each data row contains a name, and value. The row also contains a - type or mimetype. Type corresponds to a .NET class that support - text/value conversion through the TypeConverter architecture. - Classes that don't support this are serialized and stored with the - mimetype set. - - The mimetype is used for serialized objects, and tells the - ResXResourceReader how to depersist the object. This is currently not - extensible. For a given mimetype the value must be set accordingly: - - Note - application/x-microsoft.net.object.binary.base64 is the format - that the ResXResourceWriter will generate, however the reader can - read any of the formats listed below. - - mimetype: application/x-microsoft.net.object.binary.base64 - value : The object must be serialized with - : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter - : and then encoded with base64 encoding. - - mimetype: application/x-microsoft.net.object.soap.base64 - value : The object must be serialized with - : System.Runtime.Serialization.Formatters.Soap.SoapFormatter - : and then encoded with base64 encoding. - - mimetype: application/x-microsoft.net.object.bytearray.base64 - value : The object must be serialized into a byte array - : using a System.ComponentModel.TypeConverter - : and then encoded with base64 encoding. - --> - <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> - <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> - <xsd:element name="root" msdata:IsDataSet="true"> - <xsd:complexType> - <xsd:choice maxOccurs="unbounded"> - <xsd:element name="metadata"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" /> - </xsd:sequence> - <xsd:attribute name="name" use="required" type="xsd:string" /> - <xsd:attribute name="type" type="xsd:string" /> - <xsd:attribute name="mimetype" type="xsd:string" /> - <xsd:attribute ref="xml:space" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="assembly"> - <xsd:complexType> - <xsd:attribute name="alias" type="xsd:string" /> - <xsd:attribute name="name" type="xsd:string" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="data"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> - <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> - </xsd:sequence> - <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> - <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> - <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> - <xsd:attribute ref="xml:space" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="resheader"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> - </xsd:sequence> - <xsd:attribute name="name" type="xsd:string" use="required" /> - </xsd:complexType> - </xsd:element> - </xsd:choice> - </xsd:complexType> - </xsd:element> - </xsd:schema> - <resheader name="resmimetype"> - <value>text/microsoft-resx</value> - </resheader> - <resheader name="version"> - <value>2.0</value> - </resheader> - <resheader name="reader"> - <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> - </resheader> - <resheader name="writer"> - <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> - </resheader> - <data name="Target" xml:space="preserve"> - <value>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</value> - </data> - <data name="DefaultSchema" xml:space="preserve"> - <value>dbo</value> - </data> -</root> \ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="Target" xml:space="preserve"> + <value>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</value> + </data> + <data name="DefaultSchema" xml:space="preserve"> + <value>dbo</value> + </data> +</root> \ 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 @@ <Compile Include="Migrations\201809171309522_NewsletterSubscriptionLanguage.Designer.cs"> <DependentUpon>201809171309522_NewsletterSubscriptionLanguage.cs</DependentUpon> </Compile> - <Compile Include="Migrations\201809241947314_ForumGroupAcl.cs" /> - <Compile Include="Migrations\201809241947314_ForumGroupAcl.Designer.cs"> - <DependentUpon>201809241947314_ForumGroupAcl.cs</DependentUpon> + <Compile Include="Migrations\201809251233170_ForumGroupAcl.cs" /> + <Compile Include="Migrations\201809251233170_ForumGroupAcl.Designer.cs"> + <DependentUpon>201809251233170_ForumGroupAcl.cs</DependentUpon> </Compile> <Compile Include="ObjectContextBase.SaveChanges.cs" /> <Compile Include="Setup\Builder\ActivityLogTypeMigrator.cs" /> @@ -1124,8 +1124,8 @@ <EmbeddedResource Include="Migrations\201809171309522_NewsletterSubscriptionLanguage.resx"> <DependentUpon>201809171309522_NewsletterSubscriptionLanguage.cs</DependentUpon> </EmbeddedResource> - <EmbeddedResource Include="Migrations\201809241947314_ForumGroupAcl.resx"> - <DependentUpon>201809241947314_ForumGroupAcl.cs</DependentUpon> + <EmbeddedResource Include="Migrations\201809251233170_ForumGroupAcl.resx"> + <DependentUpon>201809251233170_ForumGroupAcl.cs</DependentUpon> </EmbeddedResource> <EmbeddedResource Include="Sql\Indexes.sql" /> <EmbeddedResource Include="Sql\StoredProcedures.sql" /> 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 <home@footwear-box.de> Date: Tue, 25 Sep 2018 21:26:42 +0200 Subject: [PATCH 09/71] 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; } /// <summary> 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 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.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 t�glich bereinigt: Maximal {0} Eintr�ge 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<Forum> 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<ForumTopic> GetTopicsByIds(int[] topicIds) return query.OrderBySequence(topicIds).ToList(); } - public virtual IPagedList<ForumTopic> GetAllTopics(int forumId, int pageIndex, int pageSize) + public virtual IPagedList<ForumTopic> 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<ForumTopic> GetAllTopics(int forumId, int pageIndex, i public virtual IList<ForumTopic> 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<ForumPost> GetPostsByIds(int[] postIds) return query.OrderBySequence(postIds).ToList(); } - public virtual IPagedList<ForumPost> GetAllPosts(int forumTopicId, int customerId, bool ascSort, int pageIndex, int pageSize) + public virtual IPagedList<ForumPost> 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<ForumPost> 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 /// <returns>Forum</returns> Forum GetForumById(int forumId); - /// <summary> - /// Gets forums by group identifier - /// </summary> - /// <param name="forumGroupId">The forum group identifier</param> - /// <returns>Forums</returns> - IList<Forum> GetAllForumsByGroupId(int forumGroupId); - /// <summary> /// Deletes a forum /// </summary> @@ -105,8 +98,9 @@ public partial interface IForumService /// <param name="forumId">The forum identifier</param> /// <param name="pageIndex">Page index</param> /// <param name="pageSize">Page size</param> + /// <param name="showHidden">Whether to load hidden records</param> /// <returns>Forum Topics</returns> - IPagedList<ForumTopic> GetAllTopics(int forumId, int pageIndex, int pageSize); + IPagedList<ForumTopic> GetAllTopics(int forumId, int pageIndex, int pageSize, bool showHidden = false); /// <summary> /// Gets active forum topics @@ -179,8 +173,9 @@ public partial interface IForumService /// <param name="ascSort">Sort order</param> /// <param name="pageIndex">Page index</param> /// <param name="pageSize">Page size</param> + /// <param name="showHidden">Whether to load hidden records</param> /// <returns>Forum Posts</returns> - IPagedList<ForumPost> GetAllPosts(int forumTopicId, int customerId, bool ascSort, int pageIndex, int pageSize); + IPagedList<ForumPost> GetAllPosts(int forumTopicId, int customerId, bool ascSort, int pageIndex, int pageSize, bool showHidden = false); /// <summary> /// 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<SelectListItem> 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<int>(SystemCustomerAttributeNames.ForumPostCount), - ShowCustomersJoinDate = _customerSettings.ShowCustomersJoinDate, - CustomerJoinDate = post.Customer.CreatedOnUtc, - AllowPrivateMessages = _forumSettings.AllowPrivateMessages, - SignaturesEnabled = _forumSettings.SignaturesEnabled, - FormattedSignature = post.Customer.GetAttribute<string>(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<int>(SystemCustomerAttributeNames.ForumPostCount), + ShowCustomersJoinDate = _customerSettings.ShowCustomersJoinDate, + CustomerJoinDate = post.Customer.CreatedOnUtc, + AllowPrivateMessages = _forumSettings.AllowPrivateMessages, + SignaturesEnabled = _forumSettings.SignaturesEnabled, + FormattedSignature = post.Customer.GetAttribute<string>(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<int>(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<int>(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<string> 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 @@ <button type="submit" class="btn btn-primary"> <span>@T("Forum.Submit")</span> </button> - <button class="btn btn-danger" onclick="setLocation('@Url.RouteUrl("TopicSlug", new { id = Model.Id, slug = Model.TopicSeName })')"> + <button class="btn btn-secondary" onclick="setLocation('@Url.RouteUrl("TopicSlug", new { id = Model.Id, slug = Model.TopicSeName })')"> <span>@T("Forum.Cancel")</span> </button> </div> From dd6c3de5b3a209dd4df98b9baffa7a99d26abff0 Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Wed, 26 Sep 2018 03:22:55 +0200 Subject: [PATCH 10/71] 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<T> 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<T> 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<T> element, string[] templates, } } - protected virtual void RenderCore(IBlockContainer<T> 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<T> 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<T> element, string[] templates } } - protected abstract RouteInfo GetRoute(IBlockContainer<T> element, string template); + protected abstract RouteInfo GetRoute(IBlockContainer element, string template); /// <summary> /// 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<out T> where T : IBlock + public interface IBlockContainer { string BlockType { get; } - T Block { get; } + IBlock Block { get; } IBlockMetadata Metadata { get; } //IBlockHandler<T> 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<T> : IBlockHandler where T : IBlock @@ -13,8 +15,5 @@ public interface IBlockHandler<T> : IBlockHandler where T : IBlock T Create(IBlockEntity entity); T Load(IBlockEntity entity, bool editMode); void Save(T block, IBlockEntity entity); - - void Render(IBlockContainer<T> element, string[] templates, HtmlHelper htmlHeper); - IHtmlString ToHtmlString(IBlockContainer<T> 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()) { <div class="section-header"> 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()) { <div class="section-header"> 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 @@ <sessionState configSource="Config\SessionState.config" /> <trace enabled="true" localOnly="true" pageOutput="true" requestLimit="40" /> <httpRuntime targetFramework="4.5.2" maxRequestLength="1536000" executionTimeout="5400" maxQueryStringLength="16384" fcnMode="Single" /> - <compilation debug="true" targetFramework="4.6.1" numRecompilesBeforeAppRestart="254" batch="true" optimizeCompilations="true"> + <compilation debug="true" targetFramework="4.6.1" numRecompilesBeforeAppRestart="256" batch="true" optimizeCompilations="true"> <assemblies> <add assembly="System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <add assembly="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> From aedfad773e34cc20636bdbc3b0a982875b4c0380 Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Wed, 26 Sep 2018 12:39:41 +0200 Subject: [PATCH 11/71] 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 /// <summary> /// Gets or sets the forum group identifier /// </summary> + [Index("IX_ForumGroupId_DisplayOrder", Order = 0)] public int ForumGroupId { get; set; } /// <summary> @@ -57,6 +59,7 @@ public partial class Forum : BaseEntity, IAuditable, ILocalizedEntity, ISlugSupp /// <summary> /// Gets or sets the display order /// </summary> + [Index("IX_ForumGroupId_DisplayOrder", Order = 1)] public int DisplayOrder { get; set; } /// <summary> 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 /// <summary> /// Gets or sets the display order /// </summary> + [Index] public int DisplayOrder { get; set; } /// <summary> @@ -39,14 +41,16 @@ public partial class ForumGroup : BaseEntity, IAuditable, IStoreMappingSupported /// </summary> public DateTime UpdatedOnUtc { get; set; } - /// <summary> - /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores - /// </summary> - public bool LimitedToStores { get; set; } + /// <summary> + /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores + /// </summary> + [Index] + public bool LimitedToStores { get; set; } /// <summary> /// Gets or sets a value indicating whether the entity is subject to ACL /// </summary> + [Index] public bool SubjectToAcl { get; set; } /// <summary> 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 /// <summary> /// Gets or sets the date and time of instance creation /// </summary> + [Index] public DateTime CreatedOnUtc { get; set; } /// <summary> @@ -41,6 +43,7 @@ public partial class ForumPost : BaseEntity, IAuditable /// <summary> /// Gets or sets a value indicating whether the entity is published /// </summary> + [Index] public bool Published { get; set; } /// <summary> @@ -52,6 +55,5 @@ public partial class ForumPost : BaseEntity, IAuditable /// Gets the customer /// </summary> 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 /// <summary> /// Gets or sets the forum identifier /// </summary> + [Index("IX_ForumId_Published", Order = 0)] public int ForumId { get; set; } /// <summary> @@ -21,16 +23,19 @@ public partial class ForumTopic : BaseEntity, IAuditable /// <summary> /// Gets or sets the topic type identifier /// </summary> + [Index("IX_TopicTypeId_LastPostTime", Order = 0)] public int TopicTypeId { get; set; } /// <summary> /// Gets or sets the subject /// </summary> + [Index] public string Subject { get; set; } /// <summary> /// Gets or sets the number of posts /// </summary> + [Index] public int NumPosts { get; set; } /// <summary> @@ -57,11 +62,13 @@ public partial class ForumTopic : BaseEntity, IAuditable /// <summary> /// Gets or sets the last post date and time /// </summary> + [Index("IX_TopicTypeId_LastPostTime", Order = 1)] public DateTime? LastPostTime { get; set; } /// <summary> /// Gets or sets the date and time of instance creation /// </summary> + [Index] public DateTime CreatedOnUtc { get; set; } /// <summary> @@ -72,6 +79,7 @@ public partial class ForumTopic : BaseEntity, IAuditable /// <summary> /// Gets or sets a value indicating whether the entity is published /// </summary> + [Index("IX_ForumId_Published", Order = 1)] public bool Published { get; set; } /// <summary> 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 @@ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <data name="Target" xml:space="preserve"> - <value>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</value> + <value>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</value> </data> <data name="DefaultSchema" xml:space="preserve"> <value>dbo</value> 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 @@ <Compile Include="Migrations\201809171309522_NewsletterSubscriptionLanguage.Designer.cs"> <DependentUpon>201809171309522_NewsletterSubscriptionLanguage.cs</DependentUpon> </Compile> - <Compile Include="Migrations\201809251233170_ForumGroupAcl.cs" /> - <Compile Include="Migrations\201809251233170_ForumGroupAcl.Designer.cs"> - <DependentUpon>201809251233170_ForumGroupAcl.cs</DependentUpon> + <Compile Include="Migrations\201809261026134_ForumGroupAcl.cs" /> + <Compile Include="Migrations\201809261026134_ForumGroupAcl.Designer.cs"> + <DependentUpon>201809261026134_ForumGroupAcl.cs</DependentUpon> </Compile> <Compile Include="ObjectContextBase.SaveChanges.cs" /> <Compile Include="Setup\Builder\ActivityLogTypeMigrator.cs" /> @@ -1124,8 +1124,8 @@ <EmbeddedResource Include="Migrations\201809171309522_NewsletterSubscriptionLanguage.resx"> <DependentUpon>201809171309522_NewsletterSubscriptionLanguage.cs</DependentUpon> </EmbeddedResource> - <EmbeddedResource Include="Migrations\201809251233170_ForumGroupAcl.resx"> - <DependentUpon>201809251233170_ForumGroupAcl.cs</DependentUpon> + <EmbeddedResource Include="Migrations\201809261026134_ForumGroupAcl.resx"> + <DependentUpon>201809261026134_ForumGroupAcl.cs</DependentUpon> </EmbeddedResource> <EmbeddedResource Include="Sql\Indexes.sql" /> <EmbeddedResource Include="Sql\StoredProcedures.sql" /> From a2e806384754615db9c19b7055d5af5a76c5fc35 Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Wed, 26 Sep 2018 16:04:42 +0200 Subject: [PATCH 12/71] 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<ForumSubscription> GetAllSubscriptions(int customerId, int forumId, /// <returns>True if allowed, otherwise false</returns> bool IsCustomerAllowedToDeletePost(Customer customer, ForumPost post); - /// <summary> - /// Check whether customer is allowed to set topic priority - /// </summary> - /// <param name="customer">Customer</param> - /// <returns>True if allowed, otherwise false</returns> - bool IsCustomerAllowedToSetTopicPriority(Customer customer); - /// <summary> /// Check whether customer is allowed to watch topics /// </summary> 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<EditForumPostModel> 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<string> 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<SelectListItem> 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<EditForumTopicModel> 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<SelectListItem> 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 @@ </div> </div> + @if (Model.IsModerator) + { + <div class="row justify-content-end"> + <div class="col-sm-9"> + <div class="form-check"> + @Html.CheckBoxFor(model => model.Published, new { @class = "form-check-input" }) + <label class="form-check-label" for="@Html.IdFor(model => model.Published)"> + @T("Common.Published") + </label> + </div> + </div> + </div> + } + @if (Model.IsCustomerAllowedToSubscribe) { <div class="row justify-content-end"> <div class="col-sm-9"> <div class="form-check"> - @Html.CheckBox("Subscribed", @Model.Subscribed, new { @class = "form-check-input" }) + @Html.CheckBoxFor(model => model.Subscribed, new { @class = "form-check-input" }) <label class="form-check-label" for="Subscribed">@T("Forum.NotifyWhenSomeonePostsInThisTopic")</label> </div> </div> @@ -72,10 +86,10 @@ <i class="fa fa-reply"></i> <span>@T("Forum.Submit")</span> </button> - <button class="btn btn-secondary" onclick="setLocation('@Url.RouteUrl("TopicSlug", new { id = Model.ForumTopicId, slug = Model.ForumTopicSeName })'); return false;"> + <a class="btn btn-secondary" href="@Url.RouteUrl("TopicSlug", new { id = Model.ForumTopicId, slug = Model.ForumTopicSeName })"> <i class="fa fa-times"></i> <span>@T("Forum.Cancel")</span> - </button> + </a> </div> </div> </div> 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 @@ </div> </div> - @if (Model.IsCustomerAllowedToSetTopicPriority) + @if (Model.IsModerator) { <div class="form-group row"> <label class="col-sm-3 col-form-label">@T("Forum.Priority")</label> @@ -49,14 +54,22 @@ @Html.DropDownList("TopicTypeId", new SelectList(Model.TopicPriorities, "Value", "Text", Model.TopicTypeId), new { @class = "form-control" } ) </div> </div> - } - - @if (Model.IsCustomerAllowedToSubscribe) - { + + <div class="row justify-content-end"> + <div class="col-sm-9"> + <div class="form-check"> + @Html.CheckBoxFor(model => model.Published, new { @class = "form-check-input" }) + <label class="form-check-label" for="@Html.IdFor(model => model.Published)"> + @T("Common.Published") + </label> + </div> + </div> + </div> + <div class="row justify-content-end"> <div class="col-sm-9"> <div class="form-check"> - @Html.CheckBox("Subscribed", @Model.Subscribed, new { @class = "form-check-input" }) + @Html.CheckBoxFor(model => model.Subscribed, new { @class = "form-check-input" }) <label class="form-check-label" for="Subscribed">@T("Forum.NotifyWhenSomeonePostsInThisTopic")</label> </div> </div> @@ -86,10 +99,10 @@ <i class="fa fa-reply"></i> <span>@T("Forum.Submit")</span> </button> - <button class="btn btn-secondary" onclick="setLocation('@Url.RouteUrl("ForumSlug", new { id = Model.ForumId, slug = Model.ForumSeName })'); return false;"> + <a class="btn btn-secondary" href="@cancelUrl"> <i class="fa fa-times"></i> <span>@T("Forum.Cancel")</span> - </button> + </a> </div> </div> </div> From 56ad33c908d4f20713b64ff4bc789d9ca0e462d9 Mon Sep 17 00:00:00 2001 From: Michael Herzog <herzog@smartstore.com> Date: Wed, 26 Sep 2018 20:22:04 +0200 Subject: [PATCH 13/71] 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 @@ </div> </div> + <div class="form-group row"> + <div class="col col-sm-auto offset-sm-4 text-muted"> + @T("Common.FormFields.Required.Hint") + </div> + </div> + <div class="form-group row"> <div class="col"> <button type="submit" class="btn btn-lg btn-primary"> diff --git a/src/Presentation/SmartStore.Web/Views/Customer/Info.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/Info.cshtml index 1d85d0d82c..4f099e776a 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/Info.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/Info.cshtml @@ -286,6 +286,12 @@ </fieldset> } + <div class="form-group row"> + <div class="col-12 text-muted"> + @T("Common.FormFields.Required.Hint") + </div> + </div> + <div class="form-group row"> <div class="col"> <button type="submit" name="save-info-button" class="btn btn-primary btn-lg save-customer-info-button"> diff --git a/src/Presentation/SmartStore.Web/Views/Customer/Register.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/Register.cshtml index b2346da2e7..0f8f0b1cd6 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/Register.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/Register.cshtml @@ -247,6 +247,12 @@ </fieldset> } + <div class="form-group row"> + <div class="col-12 text-muted"> + @T("Common.FormFields.Required.Hint") + </div> + </div> + <div class="form-group row"> <div class="col"> <button type="submit" class="btn btn-primary btn-lg" name="register-button"> diff --git a/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml b/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml index c729ae5157..c48eada596 100644 --- a/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml @@ -51,6 +51,13 @@ @Html.ValidationMessageFor(model => model.Enquiry) </div> </div> + + <div class="form-group row"> + <div class="col col-sm-auto offset-sm-3 text-muted"> + @T("Common.FormFields.Required.Hint") + </div> + </div> + <div class="row justify-content-end"> <div class="col-sm-9"> @{ 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" }) </div> + <div class="form-group row"> + <div class="col-12 text-muted"> + @T("Common.FormFields.Required.Hint") + </div> + </div> + @{ 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 @@ </p> <div class="form-group"> - @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" }) </div> <div class="form-group"> - @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 @@ </div> <div class="form-group"> - @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" }) </div> + <div class="form-group"> + <div class="col-12 text-muted"> + @T("Common.FormFields.Required.Hint") + </div> + </div> + @{ 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 @@ </div> </div> <div class="form-group row"> - @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" }) <div class="col-md-9"> @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) </div> </div> <div class="form-group row"> - @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" }) <div class="col-md-9"> @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) </div> </div> + + <div class="form-group"> + <div class="col col-sm-auto offset-sm-3 text-muted"> + @T("Common.FormFields.Required.Hint") + </div> + </div> <div class="row justify-content-end"> <div class="col-sm-9"> 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 @@ <div class="form-group row"> <div class="offset-@breakpoint-3 col-@breakpoint-9 text-muted address-required-hint"> - @T("Address.Fields.Required.Hint") + @T("Common.FormFields.Required.Hint") </div> </div> 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 @@ </p> <div class="form-group"> - @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) </div> <div class="form-group"> - @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) </div> <div class="form-group"> - @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) </div> + <div class="form-group row"> + <div class="col-12 text-muted"> + @T("Common.FormFields.Required.Hint") + </div> + </div> + @{ Html.RenderWidget("gdpr_consent"); } @if (Model.DisplayCaptcha) From 22dcc7683b73103741ec9cf07204c23f2fde0649 Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Wed, 26 Sep 2018 21:40:58 +0200 Subject: [PATCH 14/71] 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 @@ <div> <div class="actions my-3 row sm-gutters"> <div class="col col-sm-auto mt-1 mt-sm-0"> - <a href='@Url.Action("TopicCreate", new { id = Model.Id })' class="btn btn-primary newtopic btn-block" rel="nofollow"> + <a href="@Url.Action("TopicCreate", new { id = Model.Id })" class="btn btn-primary newtopic btn-block" rel="nofollow"> <i class="fa fa-plus"></i> <span>@T("Forum.NewTopic")</span> </a> @@ -101,7 +101,7 @@ } @foreach (var topic in Model.ForumTopics) { - <tr class="topic"> + <tr class="topic@(topic.Published ? "" : " disabled")"> <td class="image"> @{ Html.RenderPartial("Customer.Avatar", topic.Avatar); } </td> 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) { - <button class="btn btn-outline-info btn-flat" onclick="setLocation('@Url.Action("ActiveDiscussions")')"> + <a class="btn btn-outline-info btn-flat" href="@Url.Action("ActiveDiscussions")"> <i class="fa fa-th-list"></i> <span>@T("Forum.ActiveDiscussions.ViewAll")</span> - </button> + </a> } </div> </div> 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()) { <div class="page post-edit"> @@ -86,7 +93,7 @@ <i class="fa fa-reply"></i> <span>@T("Forum.Submit")</span> </button> - <a class="btn btn-secondary" href="@Url.RouteUrl("TopicSlug", new { id = Model.ForumTopicId, slug = Model.ForumTopicSeName })"> + <a class="btn btn-secondary" href="@cancelUrl"> <i class="fa fa-times"></i> <span>@T("Forum.Cancel")</span> </a> 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("<a name=\"{0}\"></a>".FormatInvariant(Model.Id)) -<div class="block block-bordered forum-post card" id="@Html.Encode(String.Format("post{0}", Model.Id))"> +<div class="block block-bordered forum-post card" id="post@(Model.Id)"> <div class="block-title card-header"> @if (Model.CustomerId > 0) { @@ -17,7 +17,6 @@ } <div class="d-flex align-items-center"> - @if (Model.IsCurrentCustomerAllowedToEditPost) { <i class="fa fa-pencil-square-o"></i> @@ -41,7 +40,7 @@ </div> </div> - <div class="block-body card-body row"> + <div class="block-body card-body row@(Model.Published ? "" : " disabled")"> <div class="post-info col-2 d-none d-sm-block"> <div class="user-info mt-3 text-center"> <div class="avatar"> @@ -69,7 +68,7 @@ <span class="stat-value">@Model.CustomerJoinDate.ToNativeString("d")</span> </div> } - @if (Model.ShowCustomersLocation && !Model.IsCustomerGuest & !String.IsNullOrEmpty(Model.CustomerLocation)) + @if (Model.ShowCustomersLocation && !Model.IsCustomerGuest && Model.CustomerLocation.HasValue()) { <div class="location"> <span class="pr-1">@T("Forum.Location"):</span> @@ -78,7 +77,7 @@ } @if (Model.AllowPrivateMessages && !Model.IsCustomerGuest) { - <a class="btn btn btn-outline-info btn-flat button-pm mt-2" rel="nofollow" href='@Url.Action("Send", "PrivateMessages", new { id = Model.CustomerId })'> + <a class="btn btn btn-outline-info btn-flat button-pm mt-2" rel="nofollow" href="@Url.Action("Send", "PrivateMessages", new { id = Model.CustomerId })"> <i class="fa fa-user"></i> <span>@T("Forum.PrivateMessages.PM")</span> </a> @@ -98,7 +97,7 @@ @Html.Hidden("Id", Model.Id) </div> - @if (Model.SignaturesEnabled & !String.IsNullOrEmpty(Model.FormattedSignature)) + @if (Model.SignaturesEnabled && Model.FormattedSignature.HasValue()) { <div class="signature text-muted" dir="auto"> @Html.Raw(Model.FormattedSignature) @@ -112,10 +111,10 @@ <a href="#" class="btn btn-link btn-sm font-weight-600"> @T("Forum.Top") </a> - <button class="btn btn-outline-info btn-flat btn-sm" onclick="setLocation('@Url.Action("PostCreate", new { id = Model.ForumTopicId, quote = Model.Id })')"> + <a class="btn btn-outline-info btn-flat btn-sm" href="@Url.Action("PostCreate", new { id = Model.ForumTopicId, quote = Model.Id })"> <i class="fa fa-quote-left"></i> <span>@T("Forum.QuotePost")</span> - </button> + </a> </div> </div> </div> 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() <div class="posts"> - @foreach (var post in @Model.ForumPostModels) + @foreach (var post in Model.ForumPostModels) { Html.RenderPartial("_ForumPost", post); } </div> @ButtonBar() - </div> @helper ButtonBar() @@ -72,12 +71,11 @@ <div class="topic-options row my-2"> <div class="col"> - <button class="btn btn-primary" onclick="setLocation('@Url.Action("PostCreate", new { id = @Model.Id })')"> + <a class="btn btn-primary" href="@Url.Action("PostCreate", new { id = Model.Id })"> <i class="fa fa-share"></i> <span>@T("Forum.Reply")</span> - </button> + </a> </div> - @if (Model.IsCustomerAllowedToSubscribe) { @@ -85,7 +83,7 @@ <i class="fa fa-bookmark"></i> @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" }) </div> 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 @@ <div class="page-title"> <h2>@T("Forum.MoveTopic")</h2> </div> - <div class="page-body"> <h4 class="my-3">@T("Forum.SelectTheForumToMoveTopic")</h4> - <div> @Html.DropDownList("ForumSelected", new SelectList(Model.ForumList, "Value", "Text"), new { @class = "form-control" }) </div> @@ -23,9 +21,9 @@ <button type="submit" class="btn btn-primary"> <span>@T("Forum.Submit")</span> </button> - <button class="btn btn-secondary" onclick="setLocation('@Url.RouteUrl("TopicSlug", new { id = Model.Id, slug = Model.TopicSeName })')"> + <a class="btn btn-secondary" href="@Url.RouteUrl("TopicSlug", new { id = Model.Id, slug = Model.TopicSeName })"> <span>@T("Forum.Cancel")</span> - </button> + </a> </div> </div> </div> From 7924067fbd0b0a6856491786be38e7e03fc50d77 Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Thu, 27 Sep 2018 01:08:01 +0200 Subject: [PATCH 15/71] 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 + /// <summary> /// Creates a single fast constructor invoker. The result is not cached. /// </summary> @@ -78,8 +80,6 @@ public static Func<object[], object> MakeFastInvoker(ConstructorInfo constructor return lambda.Compile(); } - #region Static - /// <summary> /// Creates and caches fast constructor invokers /// </summary> 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<MethodKey, FastInvoker> _invokersCache = new ConcurrentDictionary<MethodKey, FastInvoker>(); + + public FastInvoker(MethodInfo methodInfo) + { + Guard.NotNull(methodInfo, nameof(methodInfo)); + + Method = methodInfo; + Invoker = MakeFastInvoker(methodInfo); + ParameterTypes = methodInfo.GetParameters().Select(p => p.ParameterType).ToArray(); + } + + /// <summary> + /// Gets the backing <see cref="MethodInfo"/>. + /// </summary> + public MethodInfo Method { get; private set; } + + /// <summary> + /// Gets the parameter types from the backing <see cref="MethodInfo"/> + /// </summary> + public Type[] ParameterTypes { get; private set; } + + /// <summary> + /// Gets the method invoker. + /// </summary> + public Func<object, object[], object> Invoker { get; private set; } + + /// <summary> + /// Invokes the method using the specified parameters. + /// </summary> + /// <returns>The method invocation result.</returns> + public object Invoke(object obj, params object[] parameters) + { + return Invoker(obj, parameters); + } + + #region Static + + /// <summary> + /// Creates a single fast method invoker. The result is not cached. + /// </summary> + /// <param name="method">Method to create invoker for.</param> + /// <returns>The fast method invoker delegate.</returns> + public static Func<object, object[], object> 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<Func<object, object[], object>>( + finalExpression, + instanceParameterExpression, + argumentsParameterExpression + ); + + var lamdba = lambdaExpression.Compile(); + return lamdba; + } + + /// <summary> + /// Invokes a method using the specified object and parameter instances. + /// </summary> + /// <param name="obj">The objectinstance</param> + /// <param name="methodName">Method name</param> + /// <param name="parameterTypes">Argument types of the matching method overload (in exact order)</param> + /// <param name="parameters">Parameter instances to pass to invocation</param> + /// <returns>The method invocation result.</returns> + 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<object>()); + } + + /// <summary> + /// Creates and caches a fast method invoker. + /// </summary> + /// <param name="methodInfo">Method info instance to create an invoker for.</param> + /// <returns>The fast method invoker.</returns> + public static FastInvoker GetInvoker(MethodInfo methodInfo) + { + Guard.NotNull(methodInfo, nameof(methodInfo)); + + return GetInvoker( + methodInfo.DeclaringType, + methodInfo.Name, + methodInfo.GetParameters().Select(x => x.ParameterType).ToArray()); + } + + /// <summary> + /// Creates and caches a fast method invoker. + /// </summary> + /// <param name="methodName">Name of method to create an invoker for.</param> + /// <param name="argTypes">Argument types of method to create an invoker for.</param> + /// <returns>The fast method invoker.</returns> + public static FastInvoker GetInvoker<T>(string methodName, params Type[] argTypes) + { + return GetInvoker(typeof(T), methodName, argTypes); + } + + /// <summary> + /// Creates and caches a fast method invoker. + /// </summary> + /// <param name="type">The type to extract fast method invoker for.</param> + /// <param name="methodName">Name of method to create an invoker for.</param> + /// <param name="argTypes">Argument types of method to create an invoker for.</param> + /// <returns>The fast method invoker.</returns> + 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<Type, string, int> + { + 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<Type> 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<string, FastProperty> 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 @@ <Compile Include="Collections\Querystring.cs" /> <Compile Include="Collections\TopologicalSorter.cs" /> <Compile Include="Collections\TreeNodeBase.cs" /> + <Compile Include="ComponentModel\FastInvoker.cs" /> <Compile Include="ComponentModel\MiniMapper.cs" /> <Compile Include="Collections\ReferenceEqualityComparer.cs" /> <Compile Include="ComponentModel\TypeConversion\BooleanConverter.cs" /> 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 <muratc@smartstore.de> Date: Thu, 27 Sep 2018 03:14:41 +0200 Subject: [PATCH 16/71] 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 @@ /// <reference path="../../Content/shared/_mixins.scss" /> /// <reference path="../../Content/bs4/scss/bootstrap.scss" /> +$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 <home@footwear-box.de> Date: Thu, 27 Sep 2018 09:30:15 +0200 Subject: [PATCH 17/71] 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() /// </summary> public int AvatarMaximumSizeBytes { get; set; } - /// <summary> - /// Gets or sets a value indicating whether to display default user avatar. - /// </summary> - public bool DefaultAvatarEnabled { get; set; } - /// <summary> /// Gets or sets a value indicating whether customers location is shown /// </summary> 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, + /// <summary> /// Entities (products, categories, manufacturers) /// </summary> Entity = 1, - /// <summary> - /// Avatar - /// </summary> - 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<IMediaStorageProvider> _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<CustomerUserSettingsLocalizedModel> + public partial class CustomerUserSettingsModel : ModelBase, ILocalizedModel<CustomerUserSettingsLocalizedModel> { 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 @@ </td> <td class="adminData"> @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) </td> </tr> - <tr id="pnlDefaultAvatarEnabled"> - <td class="adminTitle"> - @Html.SmartLabelFor(model => model.CustomerSettings.DefaultAvatarEnabled) - </td> - <td class="adminData"> - @Html.SettingEditorFor(model => model.CustomerSettings.DefaultAvatarEnabled) - @Html.ValidationMessageFor(model => model.CustomerSettings.DefaultAvatarEnabled) - </td> - </tr> <tr> <td class="adminTitle"> @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=<NpH&0WUXCHwH#VMur3+WcYuZ!I^=Bjg6g+m4ls~os*M;i${c)hnt&6Qb?Fz zL{>^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<<Uft5x!pKI?*u)5A2qPyaCl5D|1TU|I zk)n*E5y{~H0R}-127d;BW=16jCP7AKLB{__7~~lk7+Ha?1vwZB7@3$^SlQS)IJvli z3bqO`FflSSGqEtUva+xMRo4RL8CV2ag%k}P*@OcV*_8@Kj2b5{<WP3ncu+Lx;s+Ju zq@pHHE-`TlNhwt|bq!4|6H_yD3rj0!7gslT4^OY)kkGL3h{&kql+?8JjLfX!lG3vB zipr|yme#iRj?S)0lc!9bHhsp-S&J4gS-Ncbij}K2ZQinV+x8thcO5!><mj>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_bamA3<IN z`;0h`HId~rxW^Fwy2Zf5%m|D;W<dsfhMSAJG#FN-?(9=;K3&BX-C47uQ@-MZw((kq znBV1pjsIr3?&<h?>RPP`<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?<l~ zFTu|oWzH;KU~-B<>+aSQflqfn-neQ?0NYW~nX*SFe#x`tjct0ECUH!nYysyApnEhY rZ96fuJwWqo)F-EnZ{?3Vh<NIFs;gXrdO~E?(P)JSU0xc@|8D{Sv;swo diff --git a/src/Presentation/SmartStore.Web/Extensions/MappingExtensions.cs b/src/Presentation/SmartStore.Web/Extensions/MappingExtensions.cs index 328539186e..a08cce463b 100644 --- a/src/Presentation/SmartStore.Web/Extensions/MappingExtensions.cs +++ b/src/Presentation/SmartStore.Web/Extensions/MappingExtensions.cs @@ -97,11 +97,6 @@ public static CustomerAvatarModel ToAvatarModel( { model.AvatarLetter = 'G'; model.AvatarColor = "light"; - - if (customerSettings.DefaultAvatarEnabled) - { - model.PictureUrl = pictureService.GetFallbackUrl(mediaSettings.AvatarPictureSize, FallbackPictureType.Avatar); - } } else { @@ -143,14 +138,7 @@ public static CustomerAvatarModel ToAvatarModel( if (model.PictureUrl.IsEmpty()) { - if (customerSettings.DefaultAvatarEnabled) - { - model.PictureUrl = pictureService.GetFallbackUrl(mediaSettings.AvatarPictureSize, FallbackPictureType.Avatar); - } - else - { - model.AvatarColor = customer.GetAvatarColor(genericAttributeService); - } + model.AvatarColor = customer.GetAvatarColor(genericAttributeService); } } diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index b1ab3ddc0d..5f87fa935b 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -851,7 +851,6 @@ <Content Include="Content\editors\summernote\summernote-bs4.min.js" /> <Content Include="Content\fontastic\fontastic.css" /> <Content Include="Content\fontastic\fonts\fontastic.svg" /> - <Content Include="Content\Images\default-avatar.jpg" /> <Content Include="Content\placeholder.txt" /> <Content Include="Content\print-rtl.css" /> <Content Include="Content\vendors\drift\Drift.js" /> From 4445e932f3f3265b9858c911db9f53efa9e0fad1 Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Thu, 27 Sep 2018 12:28:35 +0200 Subject: [PATCH 18/71] 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<ForumPost> GetPostQuery(ForumSearchQuery searchQuer var cnf = _customerSettings.CustomerNameFormat; var fields = searchQuery.Fields; var filters = new List<ISearchFilter>(); + var customer = _services.WorkContext.CurrentCustomer; var query = baseQuery ?? _forumPostRepository.TableUntracked.Expand(x => x.ForumTopic); // Apply search term. @@ -95,8 +96,31 @@ protected virtual IQueryable<ForumPost> 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<IAttributeSearchFilter>().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) { - <tr class="topic" data-id="@topic.Id"> + <tr data-id="@topic.Id" class="topic@(topic.Published ? "" : " disabled")"> <td class="image"> @{ Html.RenderPartial("Customer.Avatar", topic.Avatar); } </td> From 75cfe5c40edcfd8f5ab6e48f8a0af1098283e521 Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Thu, 27 Sep 2018 17:07:37 +0200 Subject: [PATCH 19/71] 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<Customer> _customerRepository; private readonly IGenericAttributeService _genericAttributeService; private readonly ICustomerService _customerService; - private readonly ICommonServices _services; + private readonly ICommonServices _services; public ForumService( IRepository<ForumGroup> forumGroupRepository, @@ -41,7 +41,7 @@ public ForumService( IRepository<Customer> 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<ForumGroup> 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<ForumTopic> 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<ForumPost> 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<ForumPost> GetPostsByIds(int[] postIds) } public virtual IPagedList<ForumPost> 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<ForumPost>(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<PrivateMessage> 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 /// <summary> /// Gets a forum group /// </summary> - /// <param name="forumGroupId">The forum group identifier</param> + /// <param name="groupId">The forum group identifier</param> /// <returns>Forum group</returns> - ForumGroup GetForumGroupById(int forumGroupId); + ForumGroup GetForumGroupById(int groupId); /// <summary> /// Gets all forum groups @@ -30,20 +30,20 @@ public partial interface IForumService /// <summary> /// Deletes a forum group /// </summary> - /// <param name="forumGroup">Forum group</param> - void DeleteForumGroup(ForumGroup forumGroup); + /// <param name="group">Forum group</param> + void DeleteForumGroup(ForumGroup group); /// <summary> /// Inserts a forum group /// </summary> - /// <param name="forumGroup">Forum group</param> - void InsertForumGroup(ForumGroup forumGroup); + /// <param name="group">Forum group</param> + void InsertForumGroup(ForumGroup group); /// <summary> /// Updates the forum group /// </summary> - /// <param name="forumGroup">Forum group</param> - void UpdateForumGroup(ForumGroup forumGroup); + /// <param name="group">Forum group</param> + void UpdateForumGroup(ForumGroup group); #endregion @@ -81,9 +81,9 @@ public partial interface IForumService /// <summary> /// Gets a forum topic /// </summary> - /// <param name="forumTopicId">The forum topic identifier</param> + /// <param name="topicId">The forum topic identifier</param> /// <returns>Forum Topic</returns> - ForumTopic GetTopicById(int forumTopicId); + ForumTopic GetTopicById(int topicId); /// <summary> /// Gets forum topics by identifiers @@ -114,38 +114,38 @@ public partial interface IForumService /// <summary> /// Deletes a forum topic /// </summary> - /// <param name="forumTopic">Forum topic</param> - void DeleteTopic(ForumTopic forumTopic); + /// <param name="topic">Forum topic</param> + void DeleteTopic(ForumTopic topic); /// <summary> /// Inserts a forum topic /// </summary> - /// <param name="forumTopic">Forum topic</param> + /// <param name="topic">Forum topic</param> /// <param name="sendNotifications">A value indicating whether to send notifications to subscribed customers</param> - void InsertTopic(ForumTopic forumTopic, bool sendNotifications); + void InsertTopic(ForumTopic topic, bool sendNotifications); /// <summary> /// Updates the forum topic /// </summary> - /// <param name="forumTopic">Forum topic</param> - void UpdateTopic(ForumTopic forumTopic); + /// <param name="topic">Forum topic</param> + void UpdateTopic(ForumTopic topic); /// <summary> /// Moves the forum topic /// </summary> - /// <param name="forumTopicId">The forum topic identifier</param> + /// <param name="topicId">The forum topic identifier</param> /// <param name="newForumId">New forum identifier</param> /// <returns>Moved forum topic</returns> - ForumTopic MoveTopic(int forumTopicId, int newForumId); + ForumTopic MoveTopic(int topicId, int newForumId); /// <summary> /// Calculates topic page index by post identifier /// </summary> - /// <param name="forumTopicId">Topic identifier</param> + /// <param name="topicId">Topic identifier</param> /// <param name="pageSize">Page size</param> /// <param name="postId">Post identifier</param> /// <returns>Page index</returns> - 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 /// <summary> /// Gets a forum post /// </summary> - /// <param name="forumPostId">The forum post identifier</param> + /// <param name="postId">The forum post identifier</param> /// <returns>Forum Post</returns> - ForumPost GetPostById(int forumPostId); + ForumPost GetPostById(int postId); /// <summary> /// Gets forum posts by identifiers. @@ -168,33 +168,33 @@ public partial interface IForumService /// <summary> /// Gets all forum posts /// </summary> - /// <param name="forumTopicId">The forum topic identifier</param> + /// <param name="topicId">The forum topic identifier</param> /// <param name="customerId">The customer identifier</param> /// <param name="ascSort">Sort order</param> /// <param name="pageIndex">Page index</param> /// <param name="pageSize">Page size</param> /// <param name="showHidden">Whether to load hidden records</param> /// <returns>Forum Posts</returns> - IPagedList<ForumPost> GetAllPosts(int forumTopicId, int customerId, bool ascSort, int pageIndex, int pageSize, bool showHidden = false); + IPagedList<ForumPost> GetAllPosts(int topicId, int customerId, bool ascSort, int pageIndex, int pageSize, bool showHidden = false); /// <summary> /// Deletes a forum post /// </summary> - /// <param name="forumPost">Forum post</param> - void DeletePost(ForumPost forumPost); + /// <param name="post">Forum post</param> + void DeletePost(ForumPost post); /// <summary> /// Inserts a forum post /// </summary> - /// <param name="forumPost">The forum post</param> + /// <param name="post">The forum post</param> /// <param name="sendNotifications">A value indicating whether to send notifications to subscribed customers</param> - void InsertPost(ForumPost forumPost, bool sendNotifications); + void InsertPost(ForumPost post, bool sendNotifications); /// <summary> /// Updates the forum post /// </summary> - /// <param name="forumPost">Forum post</param> - void UpdatePost(ForumPost forumPost); + /// <param name="post">Forum post</param> + void UpdatePost(ForumPost post); #endregion @@ -203,9 +203,9 @@ public partial interface IForumService /// <summary> /// Gets a private message /// </summary> - /// <param name="privateMessageId">The private message identifier</param> + /// <param name="messageId">The private message identifier</param> /// <returns>Private message</returns> - PrivateMessage GetPrivateMessageById(int privateMessageId); + PrivateMessage GetPrivateMessageById(int messageId); /// <summary> /// Gets private messages @@ -232,20 +232,20 @@ IPagedList<PrivateMessage> GetAllPrivateMessages( /// <summary> /// Deletes a private message /// </summary> - /// <param name="privateMessage">Private message</param> - void DeletePrivateMessage(PrivateMessage privateMessage); + /// <param name="message">Private message</param> + void DeletePrivateMessage(PrivateMessage message); /// <summary> /// Inserts a private message /// </summary> - /// <param name="privateMessage">Private message</param> - void InsertPrivateMessage(PrivateMessage privateMessage); + /// <param name="message">Private message</param> + void InsertPrivateMessage(PrivateMessage message); /// <summary> /// Updates the private message /// </summary> - /// <param name="privateMessage">Private message</param> - void UpdatePrivateMessage(PrivateMessage privateMessage); + /// <param name="message">Private message</param> + void UpdatePrivateMessage(PrivateMessage message); #endregion From 00a35732cd48e5024d927fe36719915c161b0863 Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Thu, 27 Sep 2018 20:59:05 +0200 Subject: [PATCH 20/71] 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 /// </summary> /// <param name="topic">Forum topic</param> - void UpdateTopic(ForumTopic topic); + /// <param name="updateStatistics">A value indicating whether to update counter.</param> + void UpdateTopic(ForumTopic topic, bool updateStatistics); /// <summary> /// Moves the forum topic @@ -194,7 +195,8 @@ public partial interface IForumService /// Updates the forum post /// </summary> /// <param name="post">Forum post</param> - void UpdatePost(ForumPost post); + /// <param name="updateStatistics">A value indicating whether to update counter.</param> + 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 })) } </div> - </td> <td class="replies d-none d-md-table-cell"> @topic.NumReplies.ToString("N0") From 389a7cef1b3f8ae54ca720c942ee4f348a68e58d Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Fri, 28 Sep 2018 03:16:40 +0200 Subject: [PATCH 21/71] 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 <muratc@smartstore.de> Date: Fri, 28 Sep 2018 03:17:24 +0200 Subject: [PATCH 22/71] 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 <muratc@smartstore.de> Date: Fri, 28 Sep 2018 03:17:43 +0200 Subject: [PATCH 23/71] 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<string>(); + } + + return cls; + } + } } -<div class="numerictextbox-group flex-grow-1"> +<div class="@CssClass"> @(Html.Telerik().NumericTextBox<byte>() .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<string>(); + cls += " input-group-" + ViewData["size"].Convert<string>(); } 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<string>(); + } + + return cls; + } + } } -<div class="numerictextbox-group flex-grow-1"> +<div class="@CssClass"> @(Html.Telerik().NumericTextBox<decimal>() .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<string>(); + } + + return cls; + } + } } -<div class="numerictextbox-group flex-grow-1"> +<div class="@CssClass"> @(Html.Telerik().NumericTextBox<double>() .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<string>(); + } + + return cls; + } + } } -<div class="numerictextbox-group flex-grow-1"> +<div class="@CssClass"> @(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<string>(); + } + + return cls; + } + } } @{ From ee5d99d9372c5f094bd8edf250ca40182bdc9476 Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Fri, 28 Sep 2018 10:30:33 +0200 Subject: [PATCH 24/71] 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 /// <summary> /// Formats the forum post text /// </summary> - /// <param name="forumPost">Forum post</param> + /// <param name="post">Forum post</param> /// <returns>Formatted text</returns> - 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<ForumSettings>().ForumEditor) { @@ -43,27 +45,26 @@ public static string FormatPostText(this ForumPost forumPost) /// <summary> /// Strips the topic subject /// </summary> - /// <param name="forumTopic">Forum topic</param> + /// <param name="topic">Forum topic</param> /// <returns>Formatted subject</returns> - 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<ForumSettings>().StrippedTopicMaxLength; - if (strippedTopicMaxLength > 0) + var strippedTopicMaxLength = EngineContext.Current.Resolve<ForumSettings>().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) /// <returns>Formatted text</returns> 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); } /// <summary> /// Formats the private message text /// </summary> - /// <param name="pm">Private message</param> + /// <param name="message">Private message</param> /// <returns>Formatted text</returns> - 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); } /// <summary> @@ -109,8 +112,8 @@ public static string FormatPrivateMessageText(this PrivateMessage pm) /// <returns>Forum topic</returns> 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 /// <returns>Forum topic</returns> 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 /// <returns>Customer</returns> 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 /// <summary> /// Get first post /// </summary> - /// <param name="forumTopic">Forum topic</param> + /// <param name="topic">Forum topic</param> /// <param name="forumService">Forum service</param> /// <returns>Forum post</returns> - 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 /// <summary> /// Get last post /// </summary> - /// <param name="forumTopic">Forum topic</param> + /// <param name="topic">Forum topic</param> /// <param name="forumService">Forum service</param> /// <returns>Forum post</returns> - 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); } /// <summary> /// Get forum last post customer /// </summary> - /// <param name="forumTopic">Forum topic</param> + /// <param name="topic">Forum topic</param> /// <param name="customerService">Customer service</param> /// <returns>Customer</returns> - 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 @@ </div> </div> - @if (Model.IsModerator) + @if (Model.IsModerator && !Model.IsFirstPost) { <div class="row justify-content-end"> <div class="col-sm-9"> From 6adb4dbef60a4d50b4facb1113a905d6cb36a92d Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Fri, 28 Sep 2018 11:31:16 +0200 Subject: [PATCH 25/71] 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 @@ <Content Include="Views\Boards\Partials\_ActiveTopics.cshtml" /> <Content Include="Views\Boards\Partials\_CreateUpdatePost.cshtml" /> <Content Include="Views\Boards\Partials\_CreateUpdateTopic.cshtml" /> - <Content Include="Views\Boards\Partials\_ForumGroupPartial.cshtml" /> + <Content Include="Views\Boards\Partials\ForumGroup.cshtml" /> <Content Include="Views\Boards\Partials\_ForumPost.cshtml" /> <Content Include="Views\Catalog\Partials\CategoryMenu.cshtml" /> <Content Include="Views\Catalog\CategoryTemplate.ProductsInGridOrLines.cshtml" /> 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<ForumTopicPagerRenderer>() .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"); } </div> <div class="forum-group"> - @{ Html.RenderPartial("_ForumGroupPartial", Model); } + @{ Html.RenderPartial("ForumGroup", Model); } </div> </div> 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<ForumTopicPagerRenderer>() .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<ForumTopicPagerRenderer>() .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) { <i class="fa fa-pencil-square-o"></i> - @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) { <i class="fa fa-times"></i> - @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 }); } + <a class="post-link" title="@T("Forum.PostLinkTitle")" href="@(Url.RouteUrl("TopicSlug", postLinkRoutValues))#@(Model.Id)"> + #@(Model.Id) + </a> </div> </div> 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 @@ <div class="manage btn-group my-3"> @if (Model.IsCustomerAllowedToEditTopic) { - <a href='@Url.Action("TopicEdit", new { id = Model.Id })' class="btn btn-secondary" rel="nofollow"> + <a href="@Url.Action("TopicEdit", new { id = Model.Id })" class="btn btn-secondary" rel="nofollow"> <i class="fa fa-pencil-square-o"></i> <span>@T("Forum.EditTopic")</span> </a> } @if (Model.IsCustomerAllowedToDeleteTopic) { - <a href='@Url.Action("TopicDelete", new { id = Model.Id })' class="btn btn-secondary" rel="nofollow" onclick='@(String.Format("return confirm({0});", T("Common.AreYouSure").JsText))'> + <a href="@Url.Action("TopicDelete", new { id = Model.Id })" class="btn btn-secondary" rel="nofollow" onclick='return confirm(@T("Common.AreYouSure").JsText);'> <i class="fa fa-trash-o"></i> <span>@T("Forum.DeleteTopic")</span> </a> } @if (Model.IsCustomerAllowedToMoveTopic) { - <a href='@Url.Action("TopicMove", new { id = Model.Id })' class="btn btn-secondary" rel="nofollow"> + <a href="@Url.Action("TopicMove", new { id = Model.Id })" class="btn btn-secondary" rel="nofollow"> <i class="fa fa-arrows"></i> <span>@T("Forum.MoveTopic")</span> </a> @@ -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 <home@footwear-box.de> Date: Fri, 28 Sep 2018 13:56:12 +0200 Subject: [PATCH 26/71] 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<int, ForumPos return forumModel; } - private ForumGroupModel PrepareForumGroupModel(ForumGroup forumGroup) + private ForumGroupModel PrepareForumGroupModel(ForumGroup group) { var forumGroupModel = new ForumGroupModel { - Id = forumGroup.Id, - Name = forumGroup.GetLocalized(x => 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<SelectListItem> ForumTopicTypesList() return list; } - private IEnumerable<SelectListItem> ForumGroupsForumsList() - { - var forumsList = new List<SelectListItem>(); - 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<SelectListItem>(); + 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<SelectListItem>(); - } - - public int ForumSelected { get; set; } public string TopicSeName { get; set; } public bool IsCustomerAllowedToEdit { get; set; } public int CustomerId { get; set; } - public IEnumerable<SelectListItem> ForumList { get; set; } + public int ForumSelected { get; set; } + public IList<SelectListItem> 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 @@ <h2>@T("Forum.MoveTopic")</h2> </div> <div class="page-body"> - <h4 class="my-3">@T("Forum.SelectTheForumToMoveTopic")</h4> - <div> - @Html.DropDownList("ForumSelected", new SelectList(Model.ForumList, "Value", "Text"), new { @class = "form-control" }) + <div class="form-group"> + <label for="@Html.IdFor(x => x.ForumSelected)">@T("Forum.SelectTheForumToMoveTopic")</label> + @Html.DropDownListFor(x => x.ForumSelected, Model.Forums, new { @class = "form-control" }) </div> - <div class="options my-3"> <button type="submit" class="btn btn-primary"> + <i class="fa fa-reply"></i> <span>@T("Forum.Submit")</span> </button> <a class="btn btn-secondary" href="@Url.RouteUrl("TopicSlug", new { id = Model.Id, slug = Model.TopicSeName })"> + <i class="fa fa-times"></i> <span>@T("Forum.Cancel")</span> </a> </div> From 904f60cbf145032c0a3f341a5299c874f1253269 Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Fri, 28 Sep 2018 16:09:02 +0200 Subject: [PATCH 27/71] 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 @@ </tbody> @if (Model.PagedList.TotalCount == 0 || !Model.PagedList.HasNextPage) { - <tfoot class="border-0"> - <tr> - <td colspan="5"> - <span class="text-muted">@T("Search.NoMoreHitsFound")</span> - </td> - </tr> - </tfoot> + if (Model.PagedList.TotalPages > 1) + { + <tfoot class="border-0"> + <tr> + <td colspan="5"> + <span class="text-muted">@T("Search.NoMoreHitsFound")</span> + </td> + </tr> + </tfoot> + } } 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 @@ <script type="text/javascript"> $(document).ready(function () { - var result = $('#ForumSearchResult'); + var result = $('#ForumSearchResult'), + loading = false; // Sorting changed. $('.fsearch-actions').on('change', '.artlist-action-select', function (e) { - var select = $(this), - qname = select.data('qname'), - url = select.data('url'), - val = select.val(); + var select = $(this), + qname = select.data('qname'), + url = select.data('url'), + val = select.val(); - var url = window.modifyUrl(url, qname, val); - window.setLocation(url); - }); + url = window.modifyUrl(url, qname, val); + window.setLocation(url); + }); // Auto load more search hits. $(document).scroll( function() { var container = result.find('.load-more:not(.loading)'); - if (container.visible(true, false, 'vertical')) { + if (container.visible(true, false, 'vertical') && !loading) { var newPage = container.data('page'), - url = window.modifyUrl(window.location.href, 'i', newPage); + url = window.modifyUrl(window.location.href, 'i', newPage), + html, processImmediately = false; // IDs of already rendered topics, to avoid rendering them again when the list is extended. var renderedTopicIds = result.find('tr.topic').map(function() { @@ -167,6 +169,12 @@ //console.log(page + '. ' + url + '. ' + renderedTopicIds.toString()); + setTimeout(function () { + processImmediately = true; + processSearchResult(html, container, newPage); + html = null; + }, 100); + $.ajax({ cache: false, type: 'POST', @@ -174,31 +182,46 @@ url: url, data: { renderedTopicIds: renderedTopicIds }, beforeSend: function () { + loading = true; container.addClass('loading').append(createCircularSpinner(20, true)); }, success: function (response) { - // Replace empty tbody at table end by page representing tbody. - container.replaceWith(response); - - // Update cumulative hit counter. - $counter = $('#SearchHitsCount'); - $pageBody = result.find('tbody[data-page=' + newPage + ']'); - $counter.find('.cumulative-count').text($pageBody.data('cumulativehits')); - if (!$pageBody.data('hasnextpage')) { - $counter.find('.more-hits-sign').hide(); + if (processImmediately) { + processSearchResult(response, container, newPage); + } + else { + html = response; } }, + complete: function () { + loading = false; + }, error: function (objXml) { result.find('.load-more:not(.loading)').removeClass('loading'); - if (objXml != null && objXml.responseText != null && objXml.responseText !== '') { + if (objXml && objXml.responseText.length > 0) { displayNotification(objXml.responseText, 'error'); - } + } } - }); + }); } }); + function processSearchResult(html, container, newPage) { + if (html && html.length > 0) { + // Replace empty tbody at table end by page representing tbody. + container.replaceWith(html); + + // Update cumulative hit counter. + $counter = $('#SearchHitsCount'); + $pageBody = result.find('tbody[data-page=' + newPage + ']'); + $counter.find('.cumulative-count').text($pageBody.data('cumulativehits')); + if (!$pageBody.data('hasnextpage')) { + $counter.find('.more-hits-sign').hide(); + } + } + } + }); </script> \ No newline at end of file From 0b33722bd581eee640345b640ab32aad38953d50 Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Sat, 29 Sep 2018 04:06:21 +0200 Subject: [PATCH 28/71] 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<Customer> { private static readonly HashSet<string> _candidateProps = new HashSet<string>(new string[] { - TypeHelper.NameOf<Customer>(x => x.Title), - TypeHelper.NameOf<Customer>(x => x.Salutation), - TypeHelper.NameOf<Customer>(x => x.FirstName), - TypeHelper.NameOf<Customer>(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<Money>(x => x.Amount), - TypeHelper.NameOf<Money>(x => x.RoundedAmount), - TypeHelper.NameOf<Money>(x => x.TruncatedAmount), - TypeHelper.NameOf<Money>(x => x.Formatted), - TypeHelper.NameOf<Money>(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 <herzog@smartstore.com> Date: Mon, 1 Oct 2018 10:54:55 +0200 Subject: [PATCH 29/71] 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 <home@footwear-box.de> Date: Mon, 1 Oct 2018 11:15:31 +0200 Subject: [PATCH 30/71] 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 <home@footwear-box.de> Date: Mon, 1 Oct 2018 13:04:53 +0200 Subject: [PATCH 31/71] 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<EmailAddress>(); } From 141c665f3821a9c19ac7661c9844139a52e3a38a Mon Sep 17 00:00:00 2001 From: Michael Herzog <herzog@smartstore.com> Date: Mon, 1 Oct 2018 14:37:34 +0200 Subject: [PATCH 32/71] 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 +{ + /// <summary> + /// An event message, which will be published after customer has logged in + /// </summary> + 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 { /// <summary> - /// An event message, which gets published after customer was registered + /// An event message, which will be published after customer has registered /// </summary> 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 @@ <Compile Include="Customers\CustomerSearchQuery.cs" /> <Compile Include="Customers\Events\CustomerAnonymizedEvent.cs" /> <Compile Include="Customers\Events\CustomerExportedEvent.cs" /> + <Compile Include="Customers\Events\CustomerLogedInEvent.cs" /> <Compile Include="Customers\Events\CustomerRegisteredEvent.cs" /> <Compile Include="Customers\GdprTool.cs" /> <Compile Include="Customers\IGdprTool.cs" /> 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 <herzog@smartstore.com> Date: Mon, 1 Oct 2018 15:44:51 +0200 Subject: [PATCH 33/71] 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 <herzog@smartstore.com> Date: Mon, 1 Oct 2018 15:44:51 +0200 Subject: [PATCH 34/71] 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 <home@footwear-box.de> Date: Mon, 1 Oct 2018 22:00:19 +0200 Subject: [PATCH 35/71] 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 { /// <summary> - /// Represents a product review helpfulness + /// Represents a product review helpfulness. /// </summary> public partial class ProductReviewHelpfulness : CustomerContent { /// <summary> - /// Gets or sets the product review identifier + /// Gets or sets the product review identifier. /// </summary> public int ProductReviewId { get; set; } /// <summary> - /// A value indicating whether a review a helpful + /// A value indicating whether a review is helpful. /// </summary> public bool WasHelpful { get; set; } /// <summary> - /// Gets the product + /// Gets the product review. /// </summary> 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 /// </summary> public partial class ForumPost : BaseEntity, IAuditable { + private ICollection<ForumPostVote> _forumPostVotes; + /// <summary> /// Gets or sets the forum topic identifier /// </summary> @@ -55,5 +58,14 @@ public partial class ForumPost : BaseEntity, IAuditable /// Gets the customer /// </summary> public virtual Customer Customer { get; set; } + + /// <summary> + /// Forum post votes. + /// </summary> + public virtual ICollection<ForumPostVote> ForumPostVotes + { + get { return _forumPostVotes ?? (_forumPostVotes = new HashSet<ForumPostVote>()); } + 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 +{ + /// <summary> + /// Represents a vote for a forum post. + /// </summary> + public partial class ForumPostVote : CustomerContent + { + /// <summary> + /// Forum post identifier. + /// </summary> + public int ForumPostId { get; set; } + + /// <summary> + /// A value indicating whether the customer voted for or against a forum post. + /// </summary> + public bool Vote { get; set; } + + /// <summary> + /// Forum post entity. + /// </summary> + 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; + } /// <summary> /// Gets or sets a value indicating whether forums are enabled @@ -64,6 +65,16 @@ public ForumSettings() /// </summary> public bool AllowCustomersToDeletePosts { get; set; } + /// <summary> + /// Gets or sets a value indicating whether customer can vote on posts + /// </summary> + public bool AllowCustomersToVoteOnPosts { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether guests are allowed to vote on posts + /// </summary> + public bool AllowGuestsToVoteOnPosts { get; set; } + /// <summary> /// Gets or sets maximum length of topic subject /// </summary> 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 @@ <Compile Include="Domain\DataExchange\ExportFilter.cs" /> <Compile Include="Domain\DataExchange\ExportProfile.cs" /> <Compile Include="Domain\Forums\ForumDateFilter.cs" /> + <Compile Include="Domain\Forums\ForumPostVote.cs" /> <Compile Include="Domain\Forums\ForumTopicSorting.cs" /> <Compile Include="Domain\Media\MediaStorage.cs" /> <Compile Include="Domain\Media\IHasMedia.cs" /> 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<ForumPostVote> + { + 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 @@ +// <auto-generated /> +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 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="Target" xml:space="preserve"> + <value>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</value> + </data> + <data name="DefaultSchema" xml:space="preserve"> + <value>dbo</value> + </data> +</root> \ 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 @@ <Compile Include="Mapping\DataExchange\ExportDeploymentMap.cs" /> <Compile Include="Mapping\Directory\QuantityUnitMap.cs" /> <Compile Include="Mapping\DataExchange\ExportProfileMap.cs" /> + <Compile Include="Mapping\Forums\ForumPostVoteMap.cs" /> <Compile Include="Mapping\Media\MediaStorageMap.cs" /> <Compile Include="Mapping\Messages\QueuedEmailAttachmentMap.cs" /> <Compile Include="Mapping\Payments\PaymentMethodMap.cs" /> @@ -627,6 +628,10 @@ <Compile Include="Migrations\201809261026134_ForumGroupAcl.Designer.cs"> <DependentUpon>201809261026134_ForumGroupAcl.cs</DependentUpon> </Compile> + <Compile Include="Migrations\201810011954195_ForumPostVote.cs" /> + <Compile Include="Migrations\201810011954195_ForumPostVote.Designer.cs"> + <DependentUpon>201810011954195_ForumPostVote.cs</DependentUpon> + </Compile> <Compile Include="ObjectContextBase.SaveChanges.cs" /> <Compile Include="Setup\Builder\ActivityLogTypeMigrator.cs" /> <Compile Include="Setup\Builder\PermissionMigrator.cs" /> @@ -1127,6 +1132,9 @@ <EmbeddedResource Include="Migrations\201809261026134_ForumGroupAcl.resx"> <DependentUpon>201809261026134_ForumGroupAcl.cs</DependentUpon> </EmbeddedResource> + <EmbeddedResource Include="Migrations\201810011954195_ForumPostVote.resx"> + <DependentUpon>201810011954195_ForumPostVote.cs</DependentUpon> + </EmbeddedResource> <EmbeddedResource Include="Sql\Indexes.sql" /> <EmbeddedResource Include="Sql\StoredProcedures.sql" /> </ItemGroup> 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<string>(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<int>(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("<a name=\"{0}\"></a>".FormatInvariant(Model.Id)) -<div class="block block-bordered forum-post card" id="post@(Model.Id)"> +<div class="block block-bordered forum-post card" id="post@(Model.Id)" data-id="@Model.Id"> <div class="block-title card-header"> @if (Model.CustomerId > 0) { @@ -114,6 +114,17 @@ <i class="fa fa-quote-left"></i> <span>@T("Forum.QuotePost")</span> </a> + @if (Model.AllowVoting) + { + <button type="button" class="btn btn-outline-success btn-flat btn-sm post-vote-button post-vote-up@(Model.Vote ? " hide" : "")" value="true"> + <i class="fa fa-thumbs-up"></i> + <span>@T("Common.LikeIt")</span> + </button> + <button type="button" class="btn btn-outline-danger btn-flat btn-sm post-vote-button post-vote-down@(Model.Vote ? "" : " hide")" value="false"> + <i class="fa fa-thumbs-down"></i> + <span>@T("Common.DoNotLikeIt")</span> + </button> + } </div> </div> </div> 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 </div> -} \ No newline at end of file +} + +<script type="text/javascript"> + jQuery(document).ready(function () { + + // Post voting. + $('button.post-vote-button').on('click', function () { + var self = $(this), + postId = self.closest('.forum-post').data('id'); + + $.ajax({ + type: 'POST', + url: '@Url.Action("PostVote", "Boards")', + data: { "id": postId, "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(); + } + + displayNotification(resp.message, resp.success ? 'success' : 'error'); + } + }); + }); + + }); +</script> From 7c836ab2659f1740635d5a24794355f378bb4579 Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Tue, 2 Oct 2018 02:51:21 +0200 Subject: [PATCH 36/71] 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 <home@footwear-box.de> Date: Tue, 2 Oct 2018 10:45:49 +0200 Subject: [PATCH 37/71] 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"; <div class="form-group row"> @Html.LabelFor(model => model.CountryId, new { @class = "col-{0}-3 col-form-label{1}".FormatInvariant(breakpoint, Model.CountryRequired ? " required" : "") }) <div class="col-@breakpoint-9"> @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) </div> </div> From 4743fd650b22ccd28381c93c0b1eded74c44a2f4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Tue, 2 Oct 2018 10:54:10 +0200 Subject: [PATCH 38/71] 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 <home@footwear-box.de> Date: Tue, 2 Oct 2018 12:41:28 +0200 Subject: [PATCH 39/71] 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) </td> </tr> + <tr> + <td class="adminTitle"> + @Html.SmartLabelFor(model => model.AllowCustomersToVoteOnPosts) + </td> + <td class="adminData"> + @Html.SettingEditorFor(model => model.AllowCustomersToVoteOnPosts, Html.CheckBoxFor(x => x.AllowCustomersToVoteOnPosts, new { data_toggler_for = "#pnlForumsEnabled .post-voting" })) + @Html.ValidationMessageFor(model => model.AllowCustomersToVoteOnPosts) + </td> + </tr> + <tr class="post-voting"> + <td class="adminTitle"> + @Html.SmartLabelFor(model => model.AllowGuestsToVoteOnPosts) + </td> + <td class="adminData"> + @Html.SettingEditorFor(model => model.AllowGuestsToVoteOnPosts) + @Html.ValidationMessageFor(model => model.AllowGuestsToVoteOnPosts) + </td> + </tr> <tr> <td class="adminTitle"> @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 @@ </div> </div> <div class="post-content col col-sm-10"> - <div class="posttime text-muted"> - <span class="pr-1">@T("Forum.Posted"):</span> - <span class="stat-value">@Model.PostCreatedOnStr</span> + <div class="post-stats text-muted mb-1"> + <span class="posttime"> + <span class="pr-1">@T("Forum.Posted"):</span> + <span class="stat-value">@Model.PostCreatedOnStr</span> + </span> + <span class="votes@(Model.VoteCount > 0 ? "" : " hide")" title="@T("Common.Liked")"> + <span class="pl-1 pr-1">·</span> + <span><i class="fa fa-thumbs-up"></i></span> + <span class="vote-count stat-value">@Model.VoteCount.ToString("N0")</span> + </span> </div> <div class="post-body"> <div class="posttext" dir="auto"> 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 <home@footwear-box.de> Date: Tue, 2 Oct 2018 12:49:42 +0200 Subject: [PATCH 40/71] 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 <home@footwear-box.de> Date: Tue, 2 Oct 2018 14:29:51 +0200 Subject: [PATCH 41/71] 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 <home@footwear-box.de> Date: Tue, 2 Oct 2018 15:06:36 +0200 Subject: [PATCH 42/71] 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<string, object> ExportCustomer(Customer customer) model["ForumPosts"] = forumPosts.Select(x => _messageModelProvider.CreateModelPart(x, true)).ToList(); } - // Product reviews - var productReviews = customer.CustomerContent.OfType<ProductReview>(); + // Forum post votes + var forumPostVotes = customer.CustomerContent.OfType<ForumPostVote>(); + 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<ProductReview>(); 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<string, object> + { + { "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 <home@footwear-box.de> Date: Tue, 2 Oct 2018 20:38:21 +0200 Subject: [PATCH 43/71] 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 <muratc@smartstore.de> Date: Tue, 2 Oct 2018 19:37:51 +0200 Subject: [PATCH 44/71] 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<string> _visibilityAffectingProductProps = new HashSet<string>(); - - 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<string> props, params Expression<Func<Product, object>>[] lambdas) + private static readonly HashSet<string> _visibilityAffectingProductProps = new HashSet<string> { - foreach (var lambda in lambdas) - { - props.Add(lambda.ExtractPropertyInfo().Name); - } - } - - public static HashSet<string> 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<string> GetVisibilityAffectingPropertyNames() { return _visibilityAffectingProductProps; } From 4c278344f65451f2bb662396031535cbb0bd0ec9 Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Wed, 3 Oct 2018 03:31:58 +0200 Subject: [PATCH 45/71] 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; } /// <summary> - /// Gets or sets the product specification attribute + /// Gets or sets the product specification attributes /// </summary> [DataMember] public virtual ICollection<ProductSpecificationAttribute> 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<T>(); } - 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<T> : 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 <home@footwear-box.de> Date: Wed, 3 Oct 2018 11:16:17 +0200 Subject: [PATCH 46/71] 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<LocalizedProperty> 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 <home@footwear-box.de> Date: Wed, 3 Oct 2018 11:20:51 +0200 Subject: [PATCH 47/71] 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 <home@footwear-box.de> Date: Thu, 4 Oct 2018 09:30:15 +0200 Subject: [PATCH 48/71] 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<LocalizedProperty> 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 <home@footwear-box.de> Date: Thu, 4 Oct 2018 12:15:11 +0200 Subject: [PATCH 49/71] 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 /// <param name="customerId">Customer identifier</param> /// <returns>Result</returns> bool AlreadyVoted(int pollId, int customerId); + + /// <summary> + /// Get voting records for a poll. + /// </summary> + /// <param name="pollId">Poll identifier.</param> + /// <param name="pageIndex">Page index.</param> + /// <param name="pageSize">Page size.</param> + /// <returns>List of voting records.</returns> + IPagedList<PollVotingRecord> 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<PollVotingRecord> GetVotingRecords(int pollId, int pageIndex, int pageSize) + { + if (pollId == 0) + { + return new PagedList<PollVotingRecord>(new List<PollVotingRecord>(), 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<PollVotingRecord>(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<PollVotingRecordModel>(); + + 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<PollVotingRecordModel>(); + + 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 @@ <Compile Include="Models\Plugins\LicensePluginModel.cs" /> <Compile Include="Models\Plugins\LocalPluginsModel.cs" /> <Compile Include="Models\Plugins\ProviderModelList.cs" /> + <Compile Include="Models\Polls\PollVotingRecordModel.cs" /> <Compile Include="Models\Settings\ForumSearchSettingsModel.cs" /> <Compile Include="Models\Settings\PaymentSettingsModel.cs" /> <Compile Include="Models\Settings\SearchSettingsModel.cs" /> 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<IEventPublisher>().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)) - - <script type="text/javascript"> - function grid_onError(e) { - alert(e.XMLHttpRequest.responseText); - e.preventDefault(); - } - </script> </div> } else @@ -156,6 +151,39 @@ } } +@helper TabVoting() +{ + if (Model.Id > 0) + { + <div> + @(Html.Telerik().Grid<PollVotingRecordModel>() + .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("<a href=\"" + Url.Content("~/Admin/Customer/Edit/") + "<#= CustomerId #>\"><#= Email #></a>"); + 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)) + </div> + } + 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 <home@footwear-box.de> Date: Thu, 4 Oct 2018 16:09:10 +0200 Subject: [PATCH 50/71] 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<PollAnswer> PollAnswers() public IList<Poll> Polls() { var defaultLanguage = _ctx.Set<Language>().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<Poll> { 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<PollModel, Poll>() .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<SelectListItem> 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) </td> <td class="adminData"> - @Html.DropDownListFor(model => model.LanguageId, new SelectList(ViewBag.AllLanguages, "Id", "Name")) + @Html.DropDownListFor(model => model.LanguageId, Model.AvailableLanguages) </td> </tr> <tr> From 0b52c83f529e14f8e1139f519bf2a368c23616c2 Mon Sep 17 00:00:00 2001 From: Michael Herzog <herzog@smartstore.com> Date: Fri, 5 Oct 2018 20:42:36 +0200 Subject: [PATCH 51/71] 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 @@ <Content Include="Scripts\smartstore.viewport.js" /> <Content Include="Content\vendors\underscore\underscore.min.js" /> <Content Include="Content\vendors\underscore\underscore.js" /> + <Content Include="Content\vendors\wow\wow.js" /> <Content Include="Themes\FlexBlue\preview.png" /> <Content Include="Themes\FlexBlack\preview.png" /> <Content Include="Scripts\smartstore.entitypicker.js" /> From dfd33f8c779323eece23e1af32daf040e790f78c Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Fri, 5 Oct 2018 21:32:32 +0200 Subject: [PATCH 52/71] 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> _privacySettings; private readonly Lazy<TaxSettings> _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> privacySettings, Lazy<TaxSettings> 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 <muratc@smartstore.de> Date: Sat, 6 Oct 2018 03:50:19 +0200 Subject: [PATCH 53/71] 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<Store>)ViewBag.Stores; + var stores = ((IList<Store>)ViewBag.Stores) ?? new List<Store>(); 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 @@ <Content Include="Views\Boards\Partials\InstantSearch.cshtml" /> <Content Include="Views\Boards\Partials\SearchHits.cshtml" /> <Content Include="Views\Shared\Partials\Customer.Avatar.cshtml" /> + <Content Include="Views\Shared\Layouts\_Layout.Bare.cshtml" /> <None Include="Web.EFMigrations.config"> <DependentUpon>Web.config</DependentUpon> </None> 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 @@ <sessionState configSource="Config\SessionState.config" /> <trace enabled="true" localOnly="true" pageOutput="true" requestLimit="40" /> <httpRuntime targetFramework="4.5.2" maxRequestLength="1536000" executionTimeout="5400" maxQueryStringLength="16384" fcnMode="Single" /> - <compilation debug="true" targetFramework="4.6.1" numRecompilesBeforeAppRestart="256" batch="true" optimizeCompilations="true"> + <compilation debug="true" targetFramework="4.6.1" numRecompilesBeforeAppRestart="257" batch="true" optimizeCompilations="true"> <assemblies> <add assembly="System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <add assembly="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> From 6adbcae87ac38676c907cb24149676745c2bc6f1 Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Sun, 7 Oct 2018 10:42:44 +0200 Subject: [PATCH 54/71] 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 @@ </td> <td class="adminData"> @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) </td> </tr> @@ -588,7 +588,7 @@ </td> <td class="adminData"> @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) </td> </tr> @@ -645,7 +645,7 @@ </td> <td class="adminData"> @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) </td> </tr> From fa7606cb963269c87bb05d7deb7662eb067e3da2 Mon Sep 17 00:00:00 2001 From: Marcus Gesing <home@footwear-box.de> Date: Mon, 8 Oct 2018 12:22:55 +0200 Subject: [PATCH 55/71] 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<OrganizedShoppingCartItem> 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 <home@footwear-box.de> Date: Mon, 8 Oct 2018 12:50:27 +0200 Subject: [PATCH 56/71] #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 <home@footwear-box.de> Date: Mon, 8 Oct 2018 19:56:31 +0200 Subject: [PATCH 57/71] 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 <home@footwear-box.de> Date: Tue, 9 Oct 2018 11:47:52 +0200 Subject: [PATCH 58/71] 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 { - /// <summary> - /// A context object which contains all required and optional information - /// for the creation of message templates. - /// </summary> - public class MessageContext + /// <summary> + /// A context object which contains all required and optional information + /// for the creation of message templates. + /// </summary> + 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 <muratc@smartstore.de> Date: Wed, 10 Oct 2018 02:45:31 +0200 Subject: [PATCH 59/71] 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 <home@footwear-box.de> Date: Thu, 11 Oct 2018 20:02:00 +0200 Subject: [PATCH 60/71] 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 </div> } -<form method="POST" action="@Url.Action("Create", "Import", new { area = "Admin" })"> - @{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(@<text> +@{Html.SmartStore().Window() + .Name("import-profile-dialog") + .Title(T("Common.Profile") + " - " + T("Admin.Common.AddNew")) + .HtmlAttribute("data-profile", T("Common.Profile").Text) + .Content(@<text> + <form method="POST" action="@Url.Action("Create", "Import", new { area = "Admin" })" class="import-profile-form"> <p class="text-muted"> @T("Admin.DataExchange.Import.ProfileCreationNote") </p> @@ -146,14 +145,14 @@ else </div> </div> - </text>) - .FooterContent(@<text> - <button class="btn btn-secondary btn-flat" data-dismiss="modal">@T("Common.Cancel")</button> - <button type="submit" class="btn btn-primary" disabled="disabled">@T("Common.OK")</button> - </text>) - .Render(); - } -</form> + </form> + </text>) + .FooterContent(@<text> + <button class="btn btn-secondary btn-flat" data-dismiss="modal">@T("Common.Cancel")</button> + <button type="button" class="btn btn-primary" disabled="disabled">@T("Common.OK")</button> + </text>) + .Render(); +} <script> window['onUploadCompleted'] = function (e, el, data) { @@ -175,6 +174,11 @@ else e.preventDefault(); $('#import-profile-dialog').modal('show'); return false; + }); + + // submit dialog form + $('#import-profile-dialog').on('click', '.btn-primary', function () { + $('#import-profile-dialog').find('.import-profile-form').submit(); }); // start task From 48996e3062a04342379a3151e3cb03dbb2b13fcd Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Fri, 12 Oct 2018 03:35:21 +0200 Subject: [PATCH 61/71] min, max, step params for numeric textbox editor --- .../SmartStore.Web/Views/Shared/EditorTemplates/Boolean.cshtml | 3 +-- .../SmartStore.Web/Views/Shared/EditorTemplates/Byte.cshtml | 1 + .../SmartStore.Web/Views/Shared/EditorTemplates/Decimal.cshtml | 3 +++ .../SmartStore.Web/Views/Shared/EditorTemplates/Double.cshtml | 3 +++ .../SmartStore.Web/Views/Shared/EditorTemplates/Int32.cshtml | 3 +++ 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Boolean.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Boolean.cshtml index 23e4e0c0fc..7ca9bc1e7f 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Boolean.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Boolean.cshtml @@ -1,5 +1,4 @@ -@functions{ - +@functions{ private bool? Value { get diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Byte.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Byte.cshtml index e1085d1bf2..c7360886a3 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Byte.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Byte.cshtml @@ -44,6 +44,7 @@ .EmptyMessage(T("Common.EnterValue")) .MinValue(0) .MaxValue(255) + .IncrementStep(ViewData["step"].Convert<byte?>() ?? 1) .Value(Value) ) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Decimal.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Decimal.cshtml index 4c93f01990..efce4fc607 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Decimal.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Decimal.cshtml @@ -43,6 +43,9 @@ .Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)) .EmptyMessage(T("Common.EnterValue")) .Value(Value) + .MinValue(ViewData["min"].Convert<decimal?>()) + .MaxValue(ViewData["max"].Convert<decimal?>()) + .IncrementStep(ViewData["step"].Convert<decimal?>() ?? 1) .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 8946e81ae3..2396030733 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Double.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Double.cshtml @@ -43,6 +43,9 @@ .Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)) .EmptyMessage(T("Common.EnterValue")) .Value(Value) + .MinValue(ViewData["min"].Convert<double?>()) + .MaxValue(ViewData["max"].Convert<double?>()) + .IncrementStep(ViewData["step"].Convert<double?>() ?? 1) .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 e70f0e6446..166826c1c8 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Int32.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Int32.cshtml @@ -42,6 +42,9 @@ @(Html.Telerik().IntegerTextBox() .Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)) .EmptyMessage(T("Common.EnterValue")) + .MinValue(ViewData["min"].Convert<int?>()) + .MaxValue(ViewData["max"].Convert<int?>()) + .IncrementStep(ViewData["step"].Convert<int?>() ?? 1) .Value(Value) ) From 456887d8219120df978b1e9445cb62db1b5385c7 Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Fri, 12 Oct 2018 03:35:54 +0200 Subject: [PATCH 62/71] New EditorTemplate "Range" --- .../Extensions/RouteExtensions.cs | 3 +- .../Extensions/TypeExtensions.cs | 29 +++++++++++++++- .../Views/Shared/EditorTemplates/Range.cshtml | 34 +++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Range.cshtml diff --git a/src/Libraries/SmartStore.Core/Extensions/RouteExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/RouteExtensions.cs index 70bbe2eb28..e57d2ca275 100644 --- a/src/Libraries/SmartStore.Core/Extensions/RouteExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/RouteExtensions.cs @@ -9,8 +9,7 @@ public static class RouteExtensions { public static string GetAreaName(this RouteData routeData) { - object area; - if (routeData.DataTokens.TryGetValue("area", out area)) + if (routeData.DataTokens.TryGetValue("area", out object area)) { return (area as string); } diff --git a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs index d2b270c7e5..41b1e64e44 100644 --- a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs @@ -26,7 +26,34 @@ public static string AssemblyQualifiedNameWithoutVersion(this Type type) return null; } - public static bool IsSequenceType(this Type type) + public static bool IsNumericType(this Type type) + { + switch (Type.GetTypeCode(type)) + { + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Decimal: + case TypeCode.Double: + case TypeCode.Single: + return true; + case TypeCode.Object: + if (type.IsNullable(out var innerType)) + { + return innerType.IsNumericType(); + } + return false; + default: + return false; + } + } + + public static bool IsSequenceType(this Type type) { if (type == typeof(string)) return false; diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Range.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Range.cshtml new file mode 100644 index 0000000000..f532432349 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Range.cshtml @@ -0,0 +1,34 @@ +@model object + +@using System.Globalization; + +@{ + if (Model == null || !Model.GetType().IsNumericType() || Model.GetType().IsGenericType) + { + //throw new InvalidOperationException("The model item to be used in a range editor must be numeric and non-nullable."); + } + + var containerCssClass = "range-slider"; + if (ViewData.ContainsKey("class")) + { + containerCssClass += " " + ViewData["class"].ToString(); + } + + var sliderCssClass = "form-control{0} form-control-range px-0".FormatInvariant(ViewData.ContainsKey("size") ? "-" + ViewData["size"].ToString() : ""); + + var id = ViewData.TemplateInfo.GetFullHtmlFieldId(string.Empty); + var name = ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty); + + var metadataValues = ViewData.ModelMetadata.AdditionalValues; + + var min = (ViewData["min"].Convert<decimal?>() ?? metadataValues.Get("min").Convert<decimal?>() ?? 0m).ToString(CultureInfo.InvariantCulture); + var max = (ViewData["max"].Convert<decimal?>() ?? metadataValues.Get("max").Convert<decimal?>() ?? 100m).ToString(CultureInfo.InvariantCulture); + var step = (ViewData["step"].Convert<decimal?>() ?? metadataValues.Get("step").Convert<decimal?>() ?? 1m).ToString(CultureInfo.InvariantCulture); + + var invariantValue = ViewData.Model.Convert<decimal>().ToString(CultureInfo.InvariantCulture); +} + +<div class="@containerCssClass" style="--slider-value: @invariantValue"> + @Html.Hidden(string.Empty, ViewData.Model) + <input type="range" class="@sliderCssClass" data-target="#@id" min="@min" max="@max" step="@step" value="@invariantValue" /> +</div> \ No newline at end of file From dda751d4ef5e3c2ba4f7b99f52f96b49f0af2d6b Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Fri, 12 Oct 2018 03:36:20 +0200 Subject: [PATCH 63/71] Compact version of FileUploader component --- .../UI/Components/FileUploader/FileUploader.cs | 1 + .../UI/Components/FileUploader/FileUploaderBuilder.cs | 6 ++++++ .../Views/Shared/Components/FileUploader.cshtml | 8 ++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploader.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploader.cs index 0616d36231..73f3d991e3 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploader.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploader.cs @@ -37,6 +37,7 @@ public string UploadUrl public ButtonStyle ButtonStyle { get; set; } public bool ButtonOutlineStyle { get; set; } public bool ShowRemoveButton { get; set; } + public bool Compact { get; set; } public string CancelText { get; set; } public string RemoveText { get; set; } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploaderBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploaderBuilder.cs index 761a51bcf1..2784575e45 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploaderBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploaderBuilder.cs @@ -53,6 +53,12 @@ public FileUploaderBuilder<TModel> ShowRemoveButtonAfterUpload(bool value) return this; } + public FileUploaderBuilder<TModel> Compact(bool value) + { + base.Component.Compact = value; + return this; + } + public FileUploaderBuilder<TModel> AcceptedFileTypes(string value) { if (value.IsEmpty()) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Components/FileUploader.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Components/FileUploader.cshtml index 6f568e86b9..83db6b39cf 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Components/FileUploader.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Components/FileUploader.cshtml @@ -16,11 +16,11 @@ <div @Html.Attrs(Model.HtmlAttributes)> <div class="col-auto"> - <button class="btn btn-danger remove@(Model.ShowRemoveButton ? "" : " hide")"> + <button class="btn btn-danger remove@(Model.ShowRemoveButton ? "" : " hide")@(Model.Compact ? " btn-sm" : "")"> <span>@Model.RemoveText</span> </button> - <span class="btn btn-@(Model.ButtonOutlineStyle ? "outline-" : "")@Model.ButtonStyle.ToString().ToLower() fileinput-button"> + <span class="btn btn-@(Model.ButtonOutlineStyle ? "outline-" : "")@Model.ButtonStyle.ToString().ToLower() fileinput-button@(Model.Compact ? " btn-sm" : "")"> @if (Model.IconCssClass.HasValue()) { <i class="@Model.IconCssClass"></i> @@ -29,7 +29,7 @@ <input type="file" id="@(Model.Id)-file" name="@(Model.Id)-file" /> </span> - <button class="btn btn-outline-secondary cancel hide"> + <button class="btn btn-outline-secondary cancel hide@(Model.Compact ? " btn-sm" : "")"> <span>@Model.CancelText</span> </button> </div> @@ -39,7 +39,7 @@ <div class="fileupload-progress fade"> <!-- The global progress bar --> <div class="progress"> - <div class="progress-bar x-bg-gray" role="progressbar" style="width: 0%" aria-valuemin="0" aria-valuemax="100"></div> + <div class="progress-bar" role="progressbar" style="width: 0%" aria-valuemin="0" aria-valuemax="100"></div> </div> <!-- The extended global progress information --> From b6e437fbe1a4f05dbf7873f60e6c0bf3fe78bad8 Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Fri, 12 Oct 2018 03:37:02 +0200 Subject: [PATCH 64/71] Backend theming: made buttons slightly rounded --- .../Administration/Content/_variables.scss | 12 ++++++------ .../SmartStore.Web/Content/shared/_forms.scss | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss b/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss index cd940a8f7d..5e0ee904b4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss +++ b/src/Presentation/SmartStore.Web/Administration/Content/_variables.scss @@ -133,9 +133,9 @@ $input-btn-line-height: 1.5; $input-box-shadow: none; $input-focus-border-color: lighten($sm-blue2, 30%); $input-focus-box-shadow: none; -$input-border-radius: 0; -$input-border-radius-lg: 0; -$input-border-radius-sm: 0; +$input-border-radius: 2px; +$input-border-radius-lg: 3px; +$input-border-radius-sm: 2px; $input-transition: none; $input-group-addon-bg: $gray-100; @@ -145,9 +145,9 @@ $btn-box-shadow: inset 0 0 0 rgba(#fff, 0.15), 0 0 0 rgba(#000, 0.075); $btn-focus-box-shadow: $input-btn-focus-box-shadow; //$btn-active-box-shadow: inset 0 2px 3px rgba(#000, .125); $btn-transition: background-color .05s ease-in-out, border-color .05s ease-in-out, box-shadow .05s ease-in-out; -$btn-border-radius: 0; //.125rem; -$btn-border-radius-lg: 0; //.1875rem; -$btn-border-radius-sm: 0; //.125rem; +$btn-border-radius: $input-border-radius; //.125rem; +$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); diff --git a/src/Presentation/SmartStore.Web/Content/shared/_forms.scss b/src/Presentation/SmartStore.Web/Content/shared/_forms.scss index 3745cec8f7..940816a2ad 100644 --- a/src/Presentation/SmartStore.Web/Content/shared/_forms.scss +++ b/src/Presentation/SmartStore.Web/Content/shared/_forms.scss @@ -133,7 +133,6 @@ fieldset.content-group { } -// // Validation // ------------------------------------------------------ From c0b973daac5c755058758cb30285815de43369b0 Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Fri, 12 Oct 2018 03:37:31 +0200 Subject: [PATCH 65/71] Color opacity mode for new Range editor --- .../Scripts/admin.globalinit.js | 13 +++++++++++++ .../smartstore.globalization.adapter.js | 19 +++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js index d66b1ead3f..5e3296d96c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js +++ b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.globalinit.js @@ -117,6 +117,19 @@ } }); + // Range slider + $(document).on('input', '.range-slider > .form-control-range[data-target]', function (e) { + // move invariant value from slider to an associated hidden field + // as formatted value. Client validation will fail otherwise. + var el = $(this); + + if (el.parent().is('.color-opacity-slider')) { + el.parent().css('--slider-value', el.val()); + } + + $(el.data('target')).val(SmartStore.globalization.formatNumber(parseFloat(el.val()))); + }); + // Because we restyled the grid, the filter dropdown does not position // correctly anymore. We have to reposition it. Hacks.Telerik.handleGridFilter(); diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.globalization.adapter.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.globalization.adapter.js index 92586dc4fa..e76aeb9c75 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.globalization.adapter.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.globalization.adapter.js @@ -55,18 +55,25 @@ } // Adapt to jQuery validate - if (typeof $.validator !== undefined) { - $.extend($.validator.methods, { - number: function (value, element) { - return this.optional(element) || !isNaN(g.parseFloat(value)); + if (typeof $.validator !== undefined) { + + function _getValue(value, element) { + return $(element).is('[type=range]') + ? parseFloat(value) + : g.parseFloat(value); + } + + $.extend($.validator.methods, { + number: function (value, element) { + return this.optional(element) || !isNaN(_getValue(value, element)); }, date: function (value, element) { if (this.optional(element)) return true; var validPatterns = ['L LTS', 'L LT', 'L', 'LTS', 'LT']; return moment(value, $(element).data('format') || validPatterns, true /* exact */).isValid(); }, - range: function (value, element, param) { - var val = g.parseFloat(value); + range: function (value, element, param) { + var val = _getValue(value, element); return this.optional(element) || (val >= param[0] && val <= param[1]); } }); From 849efdf0f69a928317fe3321ba054275d7d37176 Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Fri, 12 Oct 2018 03:39:22 +0200 Subject: [PATCH 66/71] AdminControllerBase now takes new "NonAdminAttribute" into account before setting IWorkContext.IsAdmin to true --- .../Controllers/AdminControllerBase.cs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs index 8f7c1412fd..0511597b70 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs @@ -19,6 +19,11 @@ namespace SmartStore.Web.Framework.Controllers { + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class NonAdminAttribute : Attribute + { + } + [AdminValidateIpAddress(Order = 100)] [RequireHttpsByConfig(SslRequirement.Yes, Order = 110)] [CustomerLastActivity(Order = 100)] @@ -26,27 +31,22 @@ namespace SmartStore.Web.Framework.Controllers [AdminThemed(Order = -1)] public abstract class AdminControllerBase : SmartController { - /// <summary> - /// Initialize controller - /// </summary> - /// <param name="requestContext">Request context</param> - protected override void Initialize(RequestContext requestContext) - { - var routeData = requestContext.RouteData; - if (routeData != null && !routeData.DataTokens.ContainsKey("ParentActionViewContext")) - { - EngineContext.Current.Resolve<IWorkContext>().IsAdmin = true; - } - base.Initialize(requestContext); - } - - /// <summary> - /// Add locales for localizable entities - /// </summary> - /// <typeparam name="TLocalizedModelLocal">Localizable model</typeparam> - /// <param name="languageService">Language service</param> - /// <param name="locales">Locales</param> - protected virtual void AddLocales<TLocalizedModelLocal>(ILanguageService languageService, IList<TLocalizedModelLocal> locales) where TLocalizedModelLocal : ILocalizedModelLocal + protected override void OnActionExecuting(ActionExecutingContext filterContext) + { + if (filterContext.IsChildAction) + return; + + var isNonAdmin = filterContext.ActionDescriptor.HasAttribute<NonAdminAttribute>(true); + Services.Resolve<IWorkContext>().IsAdmin = !isNonAdmin; + } + + /// <summary> + /// Add locales for localizable entities + /// </summary> + /// <typeparam name="TLocalizedModelLocal">Localizable model</typeparam> + /// <param name="languageService">Language service</param> + /// <param name="locales">Locales</param> + protected virtual void AddLocales<TLocalizedModelLocal>(ILanguageService languageService, IList<TLocalizedModelLocal> locales) where TLocalizedModelLocal : ILocalizedModelLocal { AddLocales(languageService, locales, null); } From eb909bdcff676d7cd2f4990e3e85f543c12f6324 Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Fri, 12 Oct 2018 03:40:50 +0200 Subject: [PATCH 67/71] "nowidgets" query bypasses rendering of request scoped widgets (required for frontend pages embedded in a backend IFrame) --- .../SmartStore.Web.Framework/UI/WidgetProvider.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web.Framework/UI/WidgetProvider.cs b/src/Presentation/SmartStore.Web.Framework/UI/WidgetProvider.cs index 425a8ec920..5e842f0af4 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/WidgetProvider.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/WidgetProvider.cs @@ -14,13 +14,15 @@ namespace SmartStore.Web.Framework.UI public class WidgetProvider : IWidgetProvider { private readonly IApplicationEnvironment _env; + private readonly HttpRequestBase _httpRequest; private Multimap<string, WidgetRouteInfo> _zoneWidgetsMap = new Multimap<string, WidgetRouteInfo>(); private Multimap<Regex, WidgetRouteInfo> _zoneExpressionWidgetsMap = new Multimap<Regex, WidgetRouteInfo>(); - public WidgetProvider(IApplicationEnvironment env) + public WidgetProvider(IApplicationEnvironment env, HttpRequestBase httpRequest) { _env = env; + _httpRequest = httpRequest; } public void RegisterAction(string[] widgetZones, string actionName, string controllerName, RouteValueDictionary routeValues, int order = 0) @@ -29,6 +31,11 @@ public void RegisterAction(string[] widgetZones, string actionName, string contr Guard.NotEmpty(actionName, nameof(actionName)); Guard.NotEmpty(controllerName, nameof(controllerName)); + if (_httpRequest.QueryString["nowidgets"] != null) + { + return; + } + if (_zoneWidgetsMap == null) { _zoneWidgetsMap = new Multimap<string, WidgetRouteInfo>(); @@ -55,6 +62,11 @@ public void RegisterAction(Regex widgetZoneExpression, string actionName, string Guard.NotEmpty(actionName, nameof(actionName)); Guard.NotEmpty(controllerName, nameof(controllerName)); + if (_httpRequest.QueryString["nowidgets"] != null) + { + return; + } + if (_zoneExpressionWidgetsMap == null) { _zoneExpressionWidgetsMap = new Multimap<Regex, WidgetRouteInfo>(); From 2288225bf581d332f66722860fec8f9a964c6a3a Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Fri, 12 Oct 2018 03:42:51 +0200 Subject: [PATCH 68/71] (perf) prevent libSass file globbing mess (checking files again and again even if they exist) --- .../Assets/BundlingVirtualPathProvider.cs | 169 +++++++++++++++++- .../Theming/ThemeHelper.cs | 77 ++++---- .../Administration/Content/_admin.scss | 37 +++- src/Presentation/SmartStore.Web/Global.asax | 2 +- .../SmartStore.Web/SmartStore.Web.csproj | 1 + .../Shared/EditorTemplates/Picture.cshtml | 9 + src/Presentation/SmartStore.Web/Web.config | 6 +- 7 files changed, 249 insertions(+), 52 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/BundlingVirtualPathProvider.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/BundlingVirtualPathProvider.cs index af308fdb66..7ae70c3a2c 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/BundlingVirtualPathProvider.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/BundlingVirtualPathProvider.cs @@ -1,27 +1,71 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Linq; +using System.IO; using System.Web.Caching; using System.Web.Hosting; +using SmartStore.Core.Infrastructure; namespace SmartStore.Web.Framework.Theming.Assets { - public sealed class BundlingVirtualPathProvider : ThemingVirtualPathProvider - { - public BundlingVirtualPathProvider(VirtualPathProvider previous) + public sealed class BundlingVirtualPathProvider : ThemingVirtualPathProvider + { + private readonly SassCheckedPathStack _sassCheckedPathStack; + + private string _yoooooo; + + public BundlingVirtualPathProvider(VirtualPathProvider previous) : base(previous) { - } + _sassCheckedPathStack = new SassCheckedPathStack(); + } public override bool FileExists(string virtualPath) { + var exists = false; var styleResult = ThemeHelper.IsStyleSheet(virtualPath); - if (styleResult != null && (styleResult.IsThemeVars || styleResult.IsModuleImports)) + + TokenizedSassPath sassPath = null; + + if (styleResult != null) { - return true; + exists = _sassCheckedPathStack.Check(styleResult, out sassPath); + if (exists) + { + // It seems awkward to return false when check result actually says true. + // But true in this context means: a check for THIS sass file pattern (e.g. _slick.scss.sass) + // yielded true previously (because _slick.scss really exists on disks), therefore _slick.scss.sass + // CANNOT exist (or SHOULD not by convention). By returning false we prevent that a real filesystem check + // is made against _slick.scss.sass (which is huge performace saver considering that IThemeFileResolver + // also does some additional checks). + return false; + } + + if (styleResult.IsThemeVars || styleResult.IsModuleImports) + { + _sassCheckedPathStack.PushExistingPath(sassPath); + return true; + } + } + + //if (virtualPath.Contains("slick.scss")) + //{ + // var xxx = true; + //// throw new Exception("dfsfs"); + //} + + //System.Diagnostics.Debug.WriteLine("VPATH: " + virtualPath); + _yoooooo += virtualPath + Environment.NewLine; + + exists = base.FileExists(virtualPath); + + if (exists && sassPath != null) + { + _sassCheckedPathStack.PushExistingPath(sassPath); } - return base.FileExists(virtualPath); + return exists; } public override VirtualFile GetFile(string virtualPath) @@ -118,4 +162,115 @@ public override CacheDependency GetCacheDependency(string virtualPath, IEnumerab return null; } } + + internal class SassCheckedPathStack + { + private readonly string[] _styleExtensions = new[] { ".scss", ".sass", ".css" }; + + private readonly ContextState<Stack<TokenizedSassPath>> _state + = new ContextState<Stack<TokenizedSassPath>>("SassCheckedPathStack.State", () => new Stack<TokenizedSassPath>()); + + public SassCheckedPathStack() + { + } + + /// <summary> + /// Checks last path existence + /// </summary> + /// <returns>true = does exist, no need to check | false = not checked yet</returns> + public bool Check(StyleSheetResult styleResult, out TokenizedSassPath path) + { + path = new TokenizedSassPath(styleResult); + + var state = _state.GetState(); + if (state.Count == 0) + return false; + + var currentPath = path; + var lastPath = _state.GetState().Peek(); + + if (currentPath.Extension.IsEmpty()) + { + // We dont't allow extension-less Sass files, so no need to check. + return true; + } + + if (lastPath.Dir != currentPath.Dir) + { + return false; + } + + if (currentPath.StyleResult.IsExplicit && lastPath.StyleResult.IsExplicit && currentPath.FileName == lastPath.FileName) + { + return true; + } + + if (currentPath.StyleResult.IsModuleImports && lastPath.StyleResult.IsModuleImports) + { + return true; + } + + if (currentPath.StyleResult.IsThemeVars && lastPath.StyleResult.IsThemeVars) + { + return true; + } + + // slick.scss.(scss|sass|css) > slick.scss + if (Path.GetExtension(currentPath.FileNameWithoutExtension) == ".scss") + { + return true; + } + + // slick.(sass|css) > slick.scss + if (!currentPath.StyleResult.IsExplicit && _styleExtensions.Contains(currentPath.Extension) && currentPath.FileNameWithoutExtension == lastPath.FileNameWithoutExtension) + { + return true; + } + + // _slick.scss > slick.scss + if (currentPath.FileName.StartsWith("_")) + { + if (currentPath.FileName.Substring(1) == lastPath.FileName) + { + return true; + } + } + + // slick.(scss|sass|css) > _slick.scss + if (lastPath.FileNameWithoutExtension.StartsWith("_")) + { + if (lastPath.FileNameWithoutExtension == "_" + currentPath.FileNameWithoutExtension) + { + return true; + } + } + + return false; + } + + public void PushExistingPath(TokenizedSassPath path) + { + _state.GetState().Push(path); + } + } + + internal class TokenizedSassPath + { + public TokenizedSassPath(StyleSheetResult styleResult) + { + StyleResult = styleResult; + VirtualPath = styleResult.Path; + Extension = styleResult.Extension.EmptyNull(); + FileName = Path.GetFileName(styleResult.Path); + FileNameWithoutExtension = FileName.Substring(0, FileName.Length - Extension.Length); + Dir = VirtualPath.Substring(0, VirtualPath.Length - FileName.Length); + } + + public StyleSheetResult StyleResult { get; set; } + public string VirtualPath { get; private set; } + public string Dir { get; private set; } + public string FileName { get; private set; } + public string FileNameWithoutExtension { get; private set; } + public string Extension { get; private set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHelper.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHelper.cs index 9e5474c48e..949190ae40 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHelper.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHelper.cs @@ -28,8 +28,8 @@ static ThemeHelper() var pattern = @"^{0}(.*)/(.+)(\.)(png|gif|jpg|jpeg|css|scss|js|cshtml|svg|json|liquid)(\?explicit)*$".FormatInvariant(ThemesBasePath); s_inheritableThemeFilePattern = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); - s_themeVarsPattern = new Regex(@"\.(db|app)/themevars(.scss)$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); - s_moduleImportsPattern = new Regex(@"\.app/moduleimports.scss$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + s_themeVarsPattern = new Regex(@"\.(db|app)/[_]?themevars(.scss)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + s_moduleImportsPattern = new Regex(@"\.app/[_]?moduleimports.scss", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); s_extensionlessPathPattern = new Regex(@"~/(.+)/([^/.]*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); } @@ -114,7 +114,7 @@ internal static bool IsStyleValidationRequest() return HttpContext.Current?.Request?.QueryString["validate"] != null; } - internal static IsStyleSheetResult IsStyleSheet(string path) + internal static StyleSheetResult IsStyleSheet(string path) { var extension = Path.GetExtension(path).ToLowerInvariant(); @@ -125,14 +125,20 @@ internal static IsStyleSheetResult IsStyleSheet(string path) } else if (extension == ".css") { - return new IsStyleSheetResult { Path = path, IsCss = true }; + return new StyleSheetResult { Path = path, IsCss = true, Extension = extension }; } - else if (extension == ".scss") + else if (extension == ".scss" || extension == ".sass") { - return new IsStyleSheetResult { Path = path, IsSass = true }; + return new StyleSheetResult { Path = path, IsSass = true, Extension = extension }; } else if (extension.IsEmpty()) { + if (path.Contains("/scss/")) + { + // Bootstrap and other libaries may import SASS files without extension + return new StyleSheetResult { Path = path, IsSass = true }; + } + // StyleBundles are extension-less, so we have to ask 'BundleTable' // if a style bundle has been registered for the given path. if (s_extensionlessPathPattern.IsMatch(path)) @@ -140,18 +146,20 @@ internal static IsStyleSheetResult IsStyleSheet(string path) var bundle = BundleTable.Bundles.GetBundleFor(path); if (bundle != null && ((bundle is SmartStyleBundle || bundle is StyleBundle))) { - return new IsStyleSheetResult { Path = path, IsBundle = true }; + return new StyleSheetResult { Path = path, IsBundle = true }; } } } - else if (extension.EndsWith("?explicit")) + else if (extension.Contains("?explicit")) { // Handle virtual Sass imports with '?explicit' query // TBD: (mc) other query params could exist // Process again, this time without query var pathWithoutQuery = path.Substring(0, path.IndexOf('?')); - return IsStyleSheet(pathWithoutQuery); + var result = IsStyleSheet(pathWithoutQuery); + result.IsExplicit = true; + return result; } return null; @@ -187,41 +195,30 @@ internal static string TokenizePath(string virtualPath, out string themeName, ou // strip out query return "{0}{1}/{2}".FormatCurrent(ThemesBasePath, themeName, relativePath); } + } - internal class IsStyleSheetResult - { - public string Path { get; set; } - public bool IsCss { get; set; } - public bool IsSass { get; set; } - public bool IsBundle { get; set; } - - public string Extension - { - get - { - if (IsSass) - return ".scss"; - else if (IsBundle) - return ""; - - return ".css"; - } - } + internal class StyleSheetResult + { + public string Path { get; set; } + public string Extension { get; set; } + public bool IsCss { get; set; } + public bool IsSass { get; set; } + public bool IsBundle { get; set; } + public bool IsExplicit { get; set; } - public bool IsPreprocessor - { - get { return IsSass; } - } + public bool IsPreprocessor + { + get { return IsSass; } + } - public bool IsThemeVars - { - get { return IsPreprocessor && ThemeHelper.PathIsThemeVars(Path); } - } + public bool IsThemeVars + { + get { return IsPreprocessor && ThemeHelper.PathIsThemeVars(Path); } + } - public bool IsModuleImports - { - get { return IsSass && ThemeHelper.PathIsModuleImports(Path); } - } + public bool IsModuleImports + { + get { return IsSass && ThemeHelper.PathIsModuleImports(Path); } } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss b/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss index c0180abd6b..da3b95273e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss +++ b/src/Presentation/SmartStore.Web/Administration/Content/_admin.scss @@ -432,7 +432,6 @@ legend { transition: all .1s ease-in-out; opacity: 0; text-align: center; - /*@extend .col-form-label;*/ &:hover { color: $gray-600; @@ -448,6 +447,42 @@ legend { } } +.range-slider { + position: relative; + + &.color-opacity-slider { + /*border: $input-border-width solid $input-border-color; + border-radius: $input-border-radius;*/ + + &:after, + &:before { + position: absolute; + content: ' '; + left: 0; + top: 0; + right: 0; + bottom: 0; + z-index: -1; + } + + &:after { + border: $input-border-width solid $input-border-color; + border-radius: $input-border-radius; + } + + &:before { + left: 1px; + top: 1px; + right: 1px; + bottom: 1px; + overflow: hidden; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3ggRDQA1Yl77tAAAAN1JREFUeNrt1rEJwCAURVEVFwruv4BEMtGHdGJpF4LHLqQ7+OTm3u+0nNau9XOMx995SnK2DyxYsD4/OSK86Jt/3SwzhAXrT6d60RW8GcKCpeAVvPtihrBgKXgFb4YIYMFS8AreDGEhgKXgFbwZwoKFQMEreDOEBUvBe9EVvBnCgqXgFbz7YoawYCl4BW+GCGDBUvAK3gxhIYCl4BW8GcKChUDBK3gzhAVLwXvRFbwZwoKl4BW8+2KGsGApeAVvhghgwVLwCt4MYSGApeAVvBnCgoVAwSt4M4QF6/jzAlCrcKXDcpR1AAAAAElFTkSuQmCC'); + background-repeat: repeat; + opacity: calc(1 - var(--slider-value, 0)); + } + } +} + // // Multi-store settings 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/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index 0a9ee64ef5..b0e5cc8985 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -1768,6 +1768,7 @@ <Content Include="Views\Boards\Partials\SearchHits.cshtml" /> <Content Include="Views\Shared\Partials\Customer.Avatar.cshtml" /> <Content Include="Views\Shared\Layouts\_Layout.Bare.cshtml" /> + <Content Include="Views\Shared\EditorTemplates\Range.cshtml" /> <None Include="Web.EFMigrations.config"> <DependentUpon>Web.config</DependentUpon> </None> diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Picture.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Picture.cshtml index 5bd494ff9a..3b4294d2a5 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Picture.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Picture.cshtml @@ -23,6 +23,14 @@ return true; } } + + private bool Compact + { + get + { + return ViewData["compact"].Convert<bool?>() == true; + } + } } @{ @@ -46,6 +54,7 @@ .AcceptedFileTypes("gif|jpe?g|png") .ShowRemoveButton(picture != null) .ShowRemoveButtonAfterUpload(true) + .Compact(ViewData["compact"].Convert<bool?>() == true) .OnUploadCompletedHandlerName("onUploadCompleted_" + random) ) </div> diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index ad41f1171d..1d04a910d6 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -11,7 +11,7 @@ </sectionGroup> </configSections> <appSettings> - <add key="webpages:Version" value="3.0.0.0" /> + <add key="webpages:Version" value="3.0.0.0" /> <add key="webpages:Enabled" value="false" /> <add key="PreserveLoginUrl" value="true" /> <add key="ClientValidationEnabled" value="true" /> @@ -22,7 +22,7 @@ <add key="GoogleMerchantKey" value="" /> <add key="GoogleEnvironment" value="Sandbox" /> <add key="GoogleAuthenticateCallback" value="True" /> - <add key="log4net.Config" value="Config\log4net.config" /> + <add key="log4net.Config" value="Config\log4net.config" /> <add key="sm:ApplicationName" value="SmartStore.NET" /> <add key="sm:EnableDynamicDiscovery" value="true" /> <add key="sm:EngineType" value="" /> @@ -79,7 +79,7 @@ <sessionState configSource="Config\SessionState.config" /> <trace enabled="true" localOnly="true" pageOutput="true" requestLimit="40" /> <httpRuntime targetFramework="4.5.2" maxRequestLength="1536000" executionTimeout="5400" maxQueryStringLength="16384" fcnMode="Single" /> - <compilation debug="true" targetFramework="4.6.1" numRecompilesBeforeAppRestart="257" batch="true" optimizeCompilations="true"> + <compilation debug="true" targetFramework="4.6.1" numRecompilesBeforeAppRestart="258" batch="true" optimizeCompilations="true"> <assemblies> <add assembly="System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <add assembly="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> From 027a554e91f41a7837b888d6b7075eba318c2c3a Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Fri, 12 Oct 2018 17:38:45 +0200 Subject: [PATCH 69/71] Minor refactoring in block interfaces --- .../SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs | 6 +++--- .../SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/BlockHandlerBase.cs index b94ab59af3..a43347aa1d 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, IEnumerable<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, IEnumerable<string> templates, HtmlHelper htmlHelper) { using (var writer = new StringWriter(CultureInfo.CurrentCulture)) { @@ -76,7 +76,7 @@ public IHtmlString ToHtmlString(IBlockContainer element, string[] templates, Htm } } - protected virtual void RenderCore(IBlockContainer element, string[] templates, HtmlHelper htmlHelper, TextWriter textWriter) + protected virtual void RenderCore(IBlockContainer element, IEnumerable<string> templates, HtmlHelper htmlHelper, TextWriter textWriter) { Guard.NotNull(element, nameof(element)); Guard.NotNull(templates, nameof(templates)); diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs index 867d97ed5d..bb932356f1 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Blocks/IBlockHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Web; using System.Web.Mvc; @@ -14,8 +15,8 @@ public enum StoryViewMode public interface IBlockHandler { - void Render(IBlockContainer element, string[] templates, HtmlHelper htmlHeper); - IHtmlString ToHtmlString(IBlockContainer element, string[] templates, HtmlHelper htmlHelper); + void Render(IBlockContainer element, IEnumerable<string> templates, HtmlHelper htmlHeper); + IHtmlString ToHtmlString(IBlockContainer element, IEnumerable<string> templates, HtmlHelper htmlHelper); } public interface IBlockHandler<T> : IBlockHandler where T : IBlock From f506c117d8f308b0832429bd29d01b4bc2e17c16 Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Sat, 13 Oct 2018 01:15:58 +0200 Subject: [PATCH 70/71] Fixed EfDbCache mem leak (hopefully) --- .../SmartStore.Core/Caching/RequestCache.cs | 8 +- .../Caching/DbCacheExtensions.cs | 82 +++++++------------ .../SmartStore.Data/Caching/IDbCache.cs | 2 +- 3 files changed, 38 insertions(+), 54 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Caching/RequestCache.cs b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs index cb05f3f288..5fb74d2400 100644 --- a/src/Libraries/SmartStore.Core/Caching/RequestCache.cs +++ b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs @@ -8,7 +8,7 @@ namespace SmartStore.Core.Caching { - public class RequestCache : IRequestCache + public class RequestCache : DisposableObject, IRequestCache { const string RegionName = "SmartStoreNET:"; @@ -124,5 +124,11 @@ private string BuildKey(string key) { return RegionName + key.EmptyNull(); } + + protected override void OnDispose(bool disposing) + { + if (disposing) + Clear(); + } } } diff --git a/src/Libraries/SmartStore.Data/Caching/DbCacheExtensions.cs b/src/Libraries/SmartStore.Data/Caching/DbCacheExtensions.cs index a53b2597af..cdb68ddb14 100644 --- a/src/Libraries/SmartStore.Data/Caching/DbCacheExtensions.cs +++ b/src/Libraries/SmartStore.Data/Caching/DbCacheExtensions.cs @@ -116,8 +116,7 @@ internal static DbCacheEntry GetCacheEntry<T>(this IQueryable<T> source, string return null; } - DbCacheEntry entry; - if (!cache.RequestTryGet(cacheKey.Key, out entry)) + if (!cache.RequestTryGet(cacheKey.Key, out var entry)) { entry = cache.RequestPut(cacheKey.Key, valueFactory(), cacheKey.AffectedEntitySets); } @@ -196,73 +195,52 @@ public static IQueryable<T> Cached<T>(this IQueryable<T> source) private class CacheKey<T> { - private readonly IQueryable<T> _source; - private string _key; - private string[] _affectedEntitySets; - private ObjectQuery _objectQuery; - private bool _objectQueryResolved; - public CacheKey(IQueryable<T> source, string customKey = null) { Guard.NotNull(source, nameof(source)); - _source = source; - _key = customKey.NullEmpty(); + GenerateKeyAndAffectedEntitySets(source, customKey); } - private void EnsureObjectQuery() + private void GenerateKeyAndAffectedEntitySets(IQueryable<T> source, string customKey = null) { - if (!_objectQueryResolved && _objectQuery == null) - { - _objectQuery = DbCacheUtil.GetObjectQuery(_source) ?? _source as ObjectQuery; - _objectQueryResolved = true; - } - } + var objectQuery = DbCacheUtil.GetObjectQuery(source) ?? source as ObjectQuery; - public string Key - { - get + var key = customKey.NullEmpty(); + if (objectQuery != null && string.IsNullOrEmpty(key)) { - if (_key == null && !_objectQueryResolved) + var commandInfo = objectQuery.GetCommandInfo(); + + var sb = new StringBuilder(); + sb.AppendLine(commandInfo.Sql); + + foreach (DbParameter parameter in commandInfo.Parameters) { - EnsureObjectQuery(); - if (_objectQuery != null) - { - var commandInfo = _objectQuery.GetCommandInfo(); - - var sb = new StringBuilder(); - sb.AppendLine(commandInfo.Sql); - - foreach (DbParameter parameter in commandInfo.Parameters) - { - sb.Append(parameter.ParameterName); - sb.Append(";"); - sb.Append(parameter.Value); - sb.AppendLine(";"); - } - - _key = sb.ToString(); - } + sb.Append(parameter.ParameterName); + sb.Append(";"); + sb.Append(parameter.Value); + sb.AppendLine(";"); } - return _key; + key = sb.ToString(); } + + Key = key; + AffectedEntitySets = objectQuery != null + ? objectQuery.GetAffectedEntitySets() + : new string[0]; } - public string[] AffectedEntitySets + public string Key { - get - { - if (_affectedEntitySets == null) - { - EnsureObjectQuery(); - _affectedEntitySets = _objectQuery != null - ? _objectQuery.GetAffectedEntitySets() - : new string[0]; - } + get; + private set; + } - return _affectedEntitySets; - } + public string[] AffectedEntitySets + { + get; + private set; } } } diff --git a/src/Libraries/SmartStore.Data/Caching/IDbCache.cs b/src/Libraries/SmartStore.Data/Caching/IDbCache.cs index 3ca2c570fb..086fdf447c 100644 --- a/src/Libraries/SmartStore.Data/Caching/IDbCache.cs +++ b/src/Libraries/SmartStore.Data/Caching/IDbCache.cs @@ -77,7 +77,7 @@ public interface IDbCache void RequestInvalidateItem(string key); } - internal class NullDbCache : IDbCache + public class NullDbCache : IDbCache { public bool Enabled { From 950027633b0876d998b6ece27b8cdfdefa6fd189 Mon Sep 17 00:00:00 2001 From: Murat Cakir <muratc@smartstore.de> Date: Sat, 13 Oct 2018 02:43:55 +0200 Subject: [PATCH 71/71] Redesigned file uploader component --- .../Components/FileUploader/FileUploader.cs | 2 +- .../Content/skinning/_fileupload.scss | 70 ++++++++++++++++- .../jquery.fileupload-single-ui.js | 78 +++++++++---------- .../Shared/Components/FileUploader.cshtml | 25 +++--- .../Shared/EditorTemplates/Picture.cshtml | 12 +-- .../Views/Shared/Partials/_ClientRes.cshtml | 1 + 6 files changed, 123 insertions(+), 65 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploader.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploader.cs index 73f3d991e3..15e4d6915f 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploader.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploader.cs @@ -13,7 +13,7 @@ public FileUploader() public FileUploader(Localizer localizer) { - HtmlAttributes.AppendCssClass("fileupload form-row align-items-center"); + HtmlAttributes.AppendCssClass("fileupload"); HtmlAttributes.Add("data-accept", "gif|jpe?g|png"); HtmlAttributes.Add("data-show-remove-after-upload", "false"); IconCssClass = "fa fa-upload"; diff --git a/src/Presentation/SmartStore.Web/Content/skinning/_fileupload.scss b/src/Presentation/SmartStore.Web/Content/skinning/_fileupload.scss index 5ea03a6c59..f10367867c 100644 --- a/src/Presentation/SmartStore.Web/Content/skinning/_fileupload.scss +++ b/src/Presentation/SmartStore.Web/Content/skinning/_fileupload.scss @@ -18,11 +18,49 @@ * http://www.opensource.org/licenses/MIT */ + .fileupload-container { + position: relative; + display: flex; + flex-wrap: nowrap; + border: $input-border-width solid $input-border-color; + background-color: $input-bg; + border-radius: $input-border-radius; + overflow: hidden; + + .fileupload-thumb-stage { + position: relative; + width: 64px; + max-width: 64px; + border-right: 1px solid rgba(#000, 0.2); + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3ggRDQA1Yl77tAAAAN1JREFUeNrt1rEJwCAURVEVFwruv4BEMtGHdGJpF4LHLqQ7+OTm3u+0nNau9XOMx995SnK2DyxYsD4/OSK86Jt/3SwzhAXrT6d60RW8GcKCpeAVvPtihrBgKXgFb4YIYMFS8AreDGEhgKXgFbwZwoKFQMEreDOEBUvBe9EVvBnCgqXgFbz7YoawYCl4BW+GCGDBUvAK3gxhIYCl4BW8GcKChUDBK3gzhAVLwXvRFbwZwoKl4BW8+2KGsGApeAVvhghgwVLwCt4MYSGApeAVvBnCgoVAwSt4M4QF6/jzAlCrcKXDcpR1AAAAAElFTkSuQmCC') repeat; + } + + .fileupload-thumb { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-size: contain; + background-position: center; + background-repeat: no-repeat; + } + + .fileupload-controls { + flex-grow: 1; + padding: 0.5rem 0.5rem; + } + } + .fileupload { .btn.cancel { display: none; } + .fileupload-buttons { + position: relative; + } + .fileinput-button { position: relative; overflow: hidden; @@ -42,9 +80,35 @@ cursor: pointer; } - .fileupload-progress .progress { - margin-bottom: 6px; - } + > .fileupload-progress { + position: absolute; + left: 0; + bottom: -1px; + right: 0; + + .progress { + height: 0.25rem; + background: transparent; + margin: 0; + border-radius: 0; + border-bottom-left-radius: $input-border-radius; + border-bottom-right-radius: $input-border-radius; + box-shadow: none; + + .progress-bar { + background-color: $primary; + transition: width 0.25s ease, background-color 0.25s ease; + } + } + + &.success { + transition-delay: 0.4s; + } + + &.success .progress-bar { + background-color: $success !important; + } + } } diff --git a/src/Presentation/SmartStore.Web/Content/vendors/fileuploader/jquery.fileupload-single-ui.js b/src/Presentation/SmartStore.Web/Content/vendors/fileuploader/jquery.fileupload-single-ui.js index f99e4d4cad..8277cf3a04 100644 --- a/src/Presentation/SmartStore.Web/Content/vendors/fileuploader/jquery.fileupload-single-ui.js +++ b/src/Presentation/SmartStore.Web/Content/vendors/fileuploader/jquery.fileupload-single-ui.js @@ -39,22 +39,15 @@ _transition: function (node) { var dfd = $.Deferred(); - if ($.support.transition && node.hasClass('fade')) { - node.on( - $.support.transitionEnd, - function (e) { - // Make sure we don't respond to other transitions events - // in the container element, e.g. from button elements: - if (e.target === node[0]) { - node.off($.support.transitionEnd); - dfd.resolveWith(node); - } - } - ).toggleClass('show in'); - } else { - node.toggleClass('show in'); - dfd.resolveWith(node); - } + node.on(Prefixer.event.transitionEnd, function (e) { + // Make sure we don't respond to other transitions events + // in the container element, e.g. from button elements: + if (e.target === node[0]) { + node.off(Prefixer.event.transitionEnd); + dfd.resolveWith(node); + } + }).addClass('success').removeClass("show"); + return dfd; }, @@ -128,7 +121,7 @@ } toggleButtons(true); - el.find('.fileupload-progress').addClass("show in"); + el.find('.fileupload-progress').addClass("show"); if (data.dataType && data.dataType.substr(0, 6) === 'iframe') { // Iframe Transport does not support progress events. @@ -143,36 +136,41 @@ //console.log("fail"); }) .on(pre + 'always.' + ns, function (e, data) { - var elProgress = el.find('.fileupload-progress'); - if (!elProgress.hasClass("show in")) { + var elProgress = el.find('.fileupload-progress'); + + if (!elProgress.hasClass("show")) { return - } - self._transition(elProgress).done( - function () { - toggleButtons(false); - elProgress - .find('.progress-bar').attr('aria-valuenow', 0) - .css("width", "0%"); - elProgress - .find('.progress-extended').html(' '); - } - ); + } + + self._transition(elProgress) + .done(function () { + toggleButtons(false); + elProgress + .removeClass('success') + .find('.progress-bar') + .attr('aria-valuenow', 0) + .css("width", "0%") + .find('.progress-extended').html(' '); + }); }) .on(pre + 'progressall.' + ns, function (e, data) { var elProgress = el.find('.fileupload-progress'), progress = parseInt(data.loaded / data.total * 100, 10), extendedProgressNode = elProgress.find('.progress-extended'); - if (extendedProgressNode.length) { - extendedProgressNode.html(self._getProgressInfo(data)); - } + if (extendedProgressNode.length) { + extendedProgressNode.html(self._getProgressInfo(data)); + } + else { + console.debug(self._getProgressInfo(data)); + } elProgress .find('.progress-bar') .attr('aria-valuenow', progress) .css('width', progress + '%'); }) - // cancel button + // cancel button .on('click.' + ns, 'button.cancel', eventData, function (e) { e.preventDefault(); var data = el.data('data') || {}; @@ -204,10 +202,6 @@ .parent().addClass('disabled'); }, - /*_create: function () { - parentWidget.prototype._create.call(this); - },*/ - enable: function () { var wasDisabled = false; if (this.options.disabled) { @@ -247,7 +241,7 @@ send: function (e, data) { if (options.onUploading) options.onUploading.apply(this, [e, el, data]); }, - done: function (e, data) { + done: function (e, data) { var result = data.result; if (result.success) { @@ -256,7 +250,7 @@ } var cnt = el.closest('.fileupload-container'); - cnt.find('.img-thumbnail').attr('src', data.result.imageUrl); + cnt.find('.fileupload-thumb').css('background-image', 'url("' + data.result.imageUrl + '")'); cnt.find('.hidden').val(data.result.pictureId); elCancel.addClass("hide"); @@ -273,7 +267,7 @@ if (options.onError) options.onError.apply(this, [el, textStatus, errorThrown]); } }, - always: function (e, data) { + always: function (e, data) { if (options.onCompleted) options.onCompleted.apply(this, [e, el, data]); } }; @@ -286,7 +280,7 @@ e.preventDefault(); var cnt = el.closest('.fileupload-container'); - cnt.find('.img-thumbnail').attr('src', el.data('fallback-url')); + cnt.find('.fileupload-thumb').css('background-image', 'url("' + el.data('fallback-url') + '")'); cnt.find('.hidden').val(0); $(this).addClass("hide"); if (options.onFileRemove) options.onFileRemove.apply(this, [e, el]); diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Components/FileUploader.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Components/FileUploader.cshtml index 83db6b39cf..c41bef7f05 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Components/FileUploader.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Components/FileUploader.cshtml @@ -15,7 +15,17 @@ } <div @Html.Attrs(Model.HtmlAttributes)> - <div class="col-auto"> + <!-- The global progress information --> + <div class="fileupload-progress fade"> + <!-- The global progress bar --> + <div class="progress"> + <div class="progress-bar" role="progressbar" style="width: 0%" aria-valuemin="0" aria-valuemax="100"></div> + </div> + + @*<div class="progress-extended fs-xs text-muted text-truncate"> </div>*@ + </div> + + <div class="fileupload-buttons"> <button class="btn btn-danger remove@(Model.ShowRemoveButton ? "" : " hide")@(Model.Compact ? " btn-sm" : "")"> <span>@Model.RemoveText</span> </button> @@ -33,19 +43,6 @@ <span>@Model.CancelText</span> </button> </div> - - <div class="col"> - <!-- The global progress information --> - <div class="fileupload-progress fade"> - <!-- The global progress bar --> - <div class="progress"> - <div class="progress-bar" role="progressbar" style="width: 0%" aria-valuemin="0" aria-valuemax="100"></div> - </div> - - <!-- The extended global progress information --> - <div class="progress-extended fs-xs text-muted text-truncate"> </div> - </div> - </div> </div> @using (Html.BeginZoneContent("end")) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Picture.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Picture.cshtml index 3b4294d2a5..f2d04cbe0c 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Picture.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/EditorTemplates/Picture.cshtml @@ -41,13 +41,15 @@ var picture = pictureService.GetPictureById(pictureId); } -<div class="form-row align-items-center fileupload-container"> - <div class="col-auto"> - @Html.HiddenFor(x => x, new { @class = "hidden" }) - <img src="@Url.Picture(pictureId, mediaSettings.ProductThumbPictureSize)" style="max-width: 100px; max-height: 64px" class="img-thumbnail" alt="" /> +<div class="fileupload-container"> + <div class="fileupload-thumb-stage"> + <div class="fileupload-thumb" style="background-image: url('@Url.Picture(pictureId, mediaSettings.ProductThumbPictureSize)')"> + @Html.HiddenFor(x => x, new { @class = "hidden" }) + @*<img src="@Url.Picture(pictureId, mediaSettings.ProductThumbPictureSize)" style="max-width: 64px; max-height: 64px" class="yimg-thumbnail" alt="" />*@ + </div> </div> - <div class="col"> + <div class="fileupload-controls"> @(Html.SmartStore().FileUploader() .UploadUrl(Url.Action("AsyncUpload", "Picture", new { isTransient = TransientUpload, validate = ValidatePicture, area = "Admin" })) .HtmlAttribute("data-fallback-url", pictureService.GetFallbackUrl(mediaSettings.ProductThumbPictureSize)) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Partials/_ClientRes.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Partials/_ClientRes.cshtml index 4e609a7d06..11c2cba0b9 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Partials/_ClientRes.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Partials/_ClientRes.cshtml @@ -25,6 +25,7 @@ "jquery.Validate.Range", "Jquery.Validate.Max", "Jquery.Validate.Min", + "Admin.Common.AreYouSure" }; }