From dab064171cd48361f5582adac1f63bfca1b0ee7f Mon Sep 17 00:00:00 2001 From: Alexey Prokhorov Date: Wed, 8 Jun 2022 17:16:39 +0300 Subject: [PATCH 1/2] Create README.md --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..24cf69c --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# qBoSlider +Hi everyone๐Ÿ‘‹! + +Today I want to present you my small slider project! +I had good expirience of work with [nopCommerce](https://www.nopcommerce.com/en/) CMS and sometimes I get multilanguage slider tasks. I work few years and received slider task often. nopCommerce plugins marketplace has no free plugin for multilanguage slides. That's why I start work on my own multilanguage slider for nopCommerce. +Slider user interface features implemented via [JSSOR slider](https://github.com/jssor). + +Today qBoSlider is absolutely free nopCommerce slider with next features: + +* Unlimited widget zones quantity support. You can create slider for each widget zone in your store. +* Unlimited slides quantity. +* Multilanguage slide content. +* HTML content supported. +* Fast content loading via cache. +* Multilanguage slide images. You can select slide background image for each store language. +* Simple configuration and customization. +* Responsive design support. +* ACL support. You can select which customer roles will see the slide. +* Multistore support. You can display slides only in needed stores. +* Slide activity time support. You can set date from which slide will be visible and when it become invisible again. + +### Installation: +* Uninstall and remove previous plugin version if you have it. Previous plugin versions aren't compatible with multiwidget zone version; +* Download plugin archive. +* Go to admin area > configuration > local plugins. +* Upload the plugin archive using the "Upload plugin or theme" plugin. +Scroll down through the list of plugins to find the newly installed plugin. And click on the "Install" button to install the plugin. +* Find plugin in list or at admin area > configuration > widgets and mark plugin as active. +Congratulations! You're installed qBoSlider. By default slider displays three slides on site homepage. + +Hi everyone. Here is the simple instruction how to use updated qBoSlider. +As you can see we change slider configuration page. At now slider configuration page contains only **Switch on static cache** property, but don't panic, other slider configurations you can find at nopCommerce admin panel menu ๐Ÿ˜‰. Just open admin side -> menu -> Plugins -> qBoSlider and you can configure all that you want. + +![](https://1drv.ms/u/s!ArjbDezvZ070grM2hwDV5nbCc-A5hg) + +## Menu +The plugin menu contains three points: +* **[Widget zones](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone)**. Here you can create new or update already existing sliders in your store. Each slider is linked to one special [widget zone](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone). By default plugin has one [slider](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone) on home page; +* **[Slides](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Slide)**. Here you can create or update slides for your store. Each slide can be linked to any sliders any times. +* **Configuration**. Here you can configure plugin. + +## Slide +First of all we need to create some slides for our store, before publish them in [slider](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone). We can do this by next way: +* Open: Admin -> Admin menu -> Plugins -> qBoSlider -> Slides. +* Push '_Add new slide_' button; +* Upload background picture for your slide and save slide; +* * You can also add slide HTML content in '_Description_' field. It's not required option; +* Congratulations! You already create your first [slide](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Slide)!๐ŸŽ‰๐Ÿ˜‰ + +Previously we just create slide and after clearing cache we can see it on home page. In multi-widget zone slider we can select which widget zone will displays our slide. And we are we going to next step ๐Ÿ˜Š. + +## Widget zone +As we know, the widget zone represents a [slider](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone). Each [slider has some common properties and slide collection](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone). So lets create a new slider for our store ๐Ÿ˜‰. + +* Open: Admin -> Admin menu -> Plugins -> qBoSlider -> Widget zones; +* Push '_Add widget zone_' button; +* Write widget zone name. This property is using just for widget zone searching. That's why you we can write anything that will help us to find widget zone faster; +* Write widget zone system name, for example '_home_page_bottom_'. We add autocomplete to help our users write standard nopCommerce widget zone names; +* Put other widget zone properties and push button '_Save and continue edit_'; +* Great! Now you're create a new slider for widget zone๐ŸŽ‰๐Ÿ‘; +* Now add our new slides, which we add previously, to our widget zone slides collection; +* Param-pam-pam-pam ๐ŸŽ‰๐Ÿ˜. Kudos! Now you've new [slider](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone) with your own [slides](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Slide). Clear nopCommerce cache and go to page where widget zone locate (for example: '_home_page_bottom_' slider locates at bottom side of site home page); + +## Few additional moments: +* qBoSlider supports so much widget zones and slides as you can create; +* One slide can be add to any widget zones. We can add slide to all store widget zones if it's really needed; +* We can create two or more widget zones with same system names. nopCommerce will display slider for first created widget zone; + From a6c1fa72da4aa25a921471cecc1b179d6d275e6f Mon Sep 17 00:00:00 2001 From: iAlexeyProkhorov Date: Thu, 11 Aug 2022 00:42:52 +0300 Subject: [PATCH 2/2] Update qBoSlider for nopCommerce 4.50.2 to version 1.4.1 --- Content/localization.en-US.xml | 47 +++++++++++ Controllers/qBoSlideController.cs | 9 ++- Controllers/qBoWidgetZoneController.cs | 6 +- Factories/Admin/ISearchModelFactory.cs | 35 ++++++++ Factories/Admin/SearchModelFactory.cs | 79 +++++++++++++++++++ Factories/Admin/SlideModelFactory.cs | 9 ++- .../Admin/WidgetZoneSlideModelFactory.cs | 11 ++- Infrastructure/NopStartup.cs | 1 + Models/Admin/ISlideSearchModel.cs | 70 ++++++++++++++++ Models/Admin/Slides/SlideModel.cs | 3 + Models/Admin/Slides/SlideSearchModel.cs | 53 ++++++++++++- .../WidgetZones/AddWidgetZoneSlideModel.cs | 54 ++++++++++++- .../WidgetZones/WidgetZoneSlideSearchModel.cs | 5 ++ Nop.Plugin.Widgets.qBoSlider.csproj | 3 + README.md | 68 ++++++++++++++++ Service/ISlideService.cs | 15 +++- Service/SlideService.cs | 68 ++++++++++------ Views/Admin/Slide/List.cshtml | 7 ++ Views/Admin/Slide/_CreateOrUpdate.Info.cshtml | 9 +++ .../WidgetZone/AddWidgetZoneSlidePopup.cshtml | 5 ++ .../WidgetZone/_CreateOrUpdate.Slides.cshtml | 4 + Views/Admin/_SlideSearch.cshtml | 70 ++++++++++++++++ plugin.json | 2 +- qBoOptions.cs | 10 +++ qBoSliderPlugin.cs | 17 ++-- 25 files changed, 618 insertions(+), 42 deletions(-) create mode 100644 Factories/Admin/ISearchModelFactory.cs create mode 100644 Factories/Admin/SearchModelFactory.cs create mode 100644 Models/Admin/ISlideSearchModel.cs create mode 100644 README.md create mode 100644 Views/Admin/_SlideSearch.cshtml diff --git a/Content/localization.en-US.xml b/Content/localization.en-US.xml index de80ebf..1b1449c 100644 --- a/Content/localization.en-US.xml +++ b/Content/localization.en-US.xml @@ -156,6 +156,9 @@ Picture + + Name + Start date @@ -239,6 +242,9 @@ Picture + + Name + Start date @@ -402,4 +408,45 @@ Put here HTML content to override original slide HTML content. Keep empty if you're don't want to chnage content. + + + Name + + + Name + + + Slide search name. This property is using only for search purposes. + + + + Slide name + + + Put here part of searchable slide name. + + + Widget zone + + + Select searchable widnget zone + + + Start date + + + Put slide start publication date + + + Finish date + + + Put slide finish publication date + + + Published + + + Select searchable slide publication state. + diff --git a/Controllers/qBoSlideController.cs b/Controllers/qBoSlideController.cs index 77f942d..0be2841 100644 --- a/Controllers/qBoSlideController.cs +++ b/Controllers/qBoSlideController.cs @@ -52,6 +52,7 @@ public class qBoSlideController : BasePluginController private readonly INotificationService _notificationService; private readonly IPermissionService _permissionService; private readonly IPictureService _pictureService; + private readonly ISearchModelFactory _searchModelFactory; private readonly ISettingService _settingService; private readonly ISlideModelFactory _slideModelFactory; private readonly ISlideWidgetZoneModelFactory _slideWidgetZoneModelFactory; @@ -75,6 +76,7 @@ public qBoSlideController(IAclService aclService, INotificationService notificationService, IPermissionService permissionService, IPictureService pictureService, + ISearchModelFactory searchModelFactory, ISettingService settingService, ISlideModelFactory slideModelFactory, ISlideWidgetZoneModelFactory slideWidgetZoneModelFactory, @@ -94,6 +96,7 @@ public qBoSlideController(IAclService aclService, this._localizedEntityService = localizedEntityService; this._notificationService = notificationService; this._permissionService = permissionService; + _searchModelFactory = searchModelFactory; this._pictureService = pictureService; this._settingService = settingService; this._slideModelFactory = slideModelFactory; @@ -197,10 +200,10 @@ protected virtual async Task SaveCustomerRolesAclAsync(Slide slide, SlideModel m #region Slides List / Create / Update / Delete - public virtual IActionResult List() + public virtual async Task List() { var model = new SlideSearchModel(); - model.SetGridPageSize(); + await _searchModelFactory.PrepareSlideSearchModelAsync(model); return View("~/Plugins/Widgets.qBoSlider/Views/Admin/Slide/List.cshtml", model); } @@ -248,6 +251,7 @@ public virtual async Task Create(SlideModel model, bool continueE var slide = new Slide() { + Name = model.Name, Description = model.Description, HyperlinkAddress = model.Hyperlink, PictureId = model.PictureId, @@ -324,6 +328,7 @@ public virtual async Task Edit(SlideModel model, bool continueEdi } //set values + slide.Name = model.Name; slide.Description = model.Description; slide.HyperlinkAddress = model.Hyperlink; slide.PictureId = model.PictureId; diff --git a/Controllers/qBoWidgetZoneController.cs b/Controllers/qBoWidgetZoneController.cs index 059997f..557bdd5 100644 --- a/Controllers/qBoWidgetZoneController.cs +++ b/Controllers/qBoWidgetZoneController.cs @@ -49,6 +49,7 @@ public class qBoWidgetZoneController : BasePluginController private readonly ILocalizedEntityService _localizedEntityService; private readonly INotificationService _notificationService; private readonly IPermissionService _permissionService; + private readonly ISearchModelFactory _searchModelFactory; private readonly ISettingService _settingService; private readonly IStaticCacheManager _staticCacheManager; private readonly IStoreMappingService _storeMappingService; @@ -68,6 +69,7 @@ public qBoWidgetZoneController(IAclService aclService, ILocalizedEntityService localizedEntityService, INotificationService notificationService, IPermissionService permissionService, + ISearchModelFactory searchModelFactory, ISettingService settingService, IStoreMappingService storeMappingService, IStoreService storeService, @@ -82,6 +84,7 @@ public qBoWidgetZoneController(IAclService aclService, _localizedEntityService = localizedEntityService; _notificationService = notificationService; _permissionService = permissionService; + _searchModelFactory = searchModelFactory; _settingService = settingService; _storeMappingService = storeMappingService; _storeService = storeService; @@ -478,8 +481,7 @@ public virtual async Task AddWidgetZoneSlidePopup(int widgetZoneI { WidgetZoneId = widgetZoneId }; - - model.SetPopupGridPageSize(); + await _searchModelFactory.PrepareSlideSearchModelAsync(model); return View("~/Plugins/Widgets.qBoSlider/Views/Admin/WidgetZone/AddWidgetZoneSlidePopup.cshtml", model); } diff --git a/Factories/Admin/ISearchModelFactory.cs b/Factories/Admin/ISearchModelFactory.cs new file mode 100644 index 0000000..65550f2 --- /dev/null +++ b/Factories/Admin/ISearchModelFactory.cs @@ -0,0 +1,35 @@ +๏ปฟ//Copyright 2020 Alexey Prokhorov + +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +using Nop.Plugin.Widgets.qBoSlider.Models.Admin; +using Nop.Web.Framework.Models; +using System.Threading.Tasks; + +namespace Nop.Plugin.Widgets.qBoSlider.Factories.Admin +{ + /// + /// Represents search model factory interface. + /// Stores methods which prepares common search box properties + /// + public interface ISearchModelFactory + { + /// + /// Prepares slide search box model + /// + /// Slide search model + /// Search box model instance + /// Model instance + Task PrepareSlideSearchModelAsync(TModel searchModel) where TModel : BaseSearchModel, ISlideSearchModel; + } +} diff --git a/Factories/Admin/SearchModelFactory.cs b/Factories/Admin/SearchModelFactory.cs new file mode 100644 index 0000000..607aa25 --- /dev/null +++ b/Factories/Admin/SearchModelFactory.cs @@ -0,0 +1,79 @@ +๏ปฟ//Copyright 2020 Alexey Prokhorov + +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +using Microsoft.AspNetCore.Mvc.Rendering; +using Nop.Plugin.Widgets.qBoSlider.Models.Admin; +using Nop.Plugin.Widgets.qBoSlider.Service; +using Nop.Services; +using Nop.Services.Localization; +using Nop.Web.Framework.Models; +using System.Linq; +using System.Threading.Tasks; + +namespace Nop.Plugin.Widgets.qBoSlider.Factories.Admin +{ + /// + /// Represents search boxes model factory + /// + public class SearchModelFactory : ISearchModelFactory + { + #region Fields + + private readonly ILocalizationService _localizationService; + private readonly IWidgetZoneService _widgetZoneService; + + #endregion + + #region Constructor + + public SearchModelFactory( + ILocalizationService localizationService, + IWidgetZoneService widgetZoneService) + { + _localizationService = localizationService; + _widgetZoneService = widgetZoneService; + } + + #endregion + + #region Methods + + /// + /// Prepares slide search model + /// + /// Slide search model + /// Slide search model + public virtual async Task PrepareSlideSearchModelAsync(TModel model) where TModel : BaseSearchModel, ISlideSearchModel + { + model.AvailableWidgetZones = _widgetZoneService.GetWidgetZones().Select(x => + { + return new SelectListItem() + { + Value = x.Id.ToString(), + Text = $"{x.Name} ({x.SystemName})" + }; + }).ToList(); + model.AvailablePublicationStates = (await PublicationState.All.ToSelectListAsync(useLocalization: true)).ToList(); + model.SetGridPageSize(); + model.AvailableWidgetZones.Insert(0, + new SelectListItem() + { + Value = "0", + Text = await _localizationService.GetResourceAsync("admin.common.all") + }); + } + + #endregion + } +} diff --git a/Factories/Admin/SlideModelFactory.cs b/Factories/Admin/SlideModelFactory.cs index 251d04c..c474fbd 100644 --- a/Factories/Admin/SlideModelFactory.cs +++ b/Factories/Admin/SlideModelFactory.cs @@ -151,7 +151,12 @@ public virtual async Task PrepareAclModelAsync(SlideModel model, Slide slide, bo /// Slide paged list model public virtual async Task PrepareSlideListPagedModelAsync(SlideSearchModel searchModel) { - var slides = await _slideService.GetAllSlidesAsync(showHidden: true, pageIndex: searchModel.Page - 1, pageSize: searchModel.PageSize); + var slides = await _slideService.GetAllSlidesAsync(searchModel.SearchName, + searchModel.SearchWidgetZoneId > 0 ? new int[1] { searchModel.SearchWidgetZoneId } : null, + searchModel.SearchStartDateOnUtc, + searchModel.SearchFinishDateOnUtc, + (PublicationState)searchModel.SearchPublicationStateId, pageIndex: searchModel.Page - 1, pageSize: searchModel.PageSize); + var gridModel = await new SlideSearchModel.SlidePagedListModel().PrepareToGridAsync(searchModel, slides, () => { return slides.SelectAwait(async slide => @@ -160,6 +165,7 @@ public virtual async Task PrepareAclModelAsync(SlideModel model, Slide slide, bo return new SlideSearchModel.SlideListItemModel() { Id = slide.Id, + Name = slide.Name, Picture = await _pictureService.GetPictureUrlAsync(pictureId, 300), Hyperlink = slide.HyperlinkAddress, StartDateUtc = slide.StartDateUtc, @@ -186,6 +192,7 @@ public virtual async Task PrepareSlideModelAsync(SlideModel model, S if(slide != null) { model.Id = slide.Id; + model.Name = slide.Name; model.Hyperlink = slide.HyperlinkAddress; model.PictureId = slide.PictureId.GetValueOrDefault(0); model.Description = slide.Description; diff --git a/Factories/Admin/WidgetZoneSlideModelFactory.cs b/Factories/Admin/WidgetZoneSlideModelFactory.cs index 6ec8d37..9e2dbc5 100644 --- a/Factories/Admin/WidgetZoneSlideModelFactory.cs +++ b/Factories/Admin/WidgetZoneSlideModelFactory.cs @@ -81,6 +81,7 @@ public WidgetZoneSlideModelFactory(ILanguageService languageService, { Id = widgetZoneSlide.Id, PictureUrl = pictureUrl, + Name = slide.Name, StartDateUtc = slide.StartDateUtc, EndDateUtc = slide.EndDateUtc, Published = slide.Published, @@ -99,7 +100,14 @@ public WidgetZoneSlideModelFactory(ILanguageService languageService, /// Add widget zone slide model public virtual async Task PrepareAddWidgetZoneSlideModelAsync(AddWidgetZoneSlideModel searchModel) { - var slides = await _slideService.GetAllSlidesAsync(showHidden: true, pageIndex: searchModel.Page - 1, pageSize: searchModel.PageSize); + var slides = await _slideService.GetAllSlidesAsync(name: searchModel.SearchName, + widgetZoneIds: searchModel.SearchWidgetZoneId > 0 ? new int[1] { searchModel.SearchWidgetZoneId } : null, + startDate: searchModel.SearchStartDateOnUtc, + endDate: searchModel.SearchFinishDateOnUtc, + publicationState: (PublicationState)searchModel.SearchPublicationStateId, + pageIndex: searchModel.Page - 1, + pageSize: searchModel.PageSize); + var gridModel = await new AddWidgetZoneSlideModel.SlidePagedListModel().PrepareToGridAsync(searchModel, slides, () => { return slides.SelectAwait(async slide => @@ -110,6 +118,7 @@ public WidgetZoneSlideModelFactory(ILanguageService languageService, return new AddWidgetZoneSlideModel.SlideModel() { Id = slide.Id, + Name = slide.Name, PictureUrl = pictureUrl, StartDateUtc = slide.StartDateUtc, EndDateUtc = slide.EndDateUtc, diff --git a/Infrastructure/NopStartup.cs b/Infrastructure/NopStartup.cs index f38de0a..6da3123 100644 --- a/Infrastructure/NopStartup.cs +++ b/Infrastructure/NopStartup.cs @@ -41,6 +41,7 @@ public void ConfigureServices(IServiceCollection services, IConfiguration config services.AddScoped(); //factories + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Models/Admin/ISlideSearchModel.cs b/Models/Admin/ISlideSearchModel.cs new file mode 100644 index 0000000..8acafbb --- /dev/null +++ b/Models/Admin/ISlideSearchModel.cs @@ -0,0 +1,70 @@ +๏ปฟ//Copyright 2020 Alexey Prokhorov + +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +using Microsoft.AspNetCore.Mvc.Rendering; +using Nop.Web.Framework.Mvc.ModelBinding; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Nop.Plugin.Widgets.qBoSlider.Models.Admin +{ + /// + /// Represents slide search model + /// + public interface ISlideSearchModel + { + /// + /// Gets or sets slide search name + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.SearchName")] + string SearchName { get; set; } + + /// + /// Gets or sets search slide widget zone + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.SearchWidgetZoneId")] + int SearchWidgetZoneId { get; set; } + + /// + /// Gets or sets available widget zones + /// + IList AvailableWidgetZones { get; set; } + + /// + /// Gets or sets slide search start date + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.SearchStartDateOnUtc")] + [UIHint("DateTimeNullable")] + DateTime? SearchStartDateOnUtc { get; set; } + + /// + /// Gets or sets slide search finish date + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.SearchFinishDateOnUtc")] + [UIHint("DateTimeNullable")] + DateTime? SearchFinishDateOnUtc { get; set; } + + /// + /// Gets or sets slide search publication status + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.PublicationStateId")] + int SearchPublicationStateId { get; set; } + + /// + /// Gets or sets list of available publication states + /// + IList AvailablePublicationStates { get; set; } + } +} diff --git a/Models/Admin/Slides/SlideModel.cs b/Models/Admin/Slides/SlideModel.cs index 4785443..6523ee8 100644 --- a/Models/Admin/Slides/SlideModel.cs +++ b/Models/Admin/Slides/SlideModel.cs @@ -16,6 +16,9 @@ public record SlideModel : BaseNopEntityModel, ILocalizedModel /// Represent slide search model /// - public record SlideSearchModel : BaseSearchModel + public record SlideSearchModel : BaseSearchModel, ISlideSearchModel { + /// + /// Gets or sets slide search name + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.SearchName")] + public string SearchName { get; set; } + + /// + /// Gets or sets search slide widget zone + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.SearchWidgetZoneId")] + public int SearchWidgetZoneId { get; set; } + + /// + /// Gets or sets available widget zones + /// + public IList AvailableWidgetZones { get; set; } = new List(); + + /// + /// Gets or sets slide search start date + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.SearchStartDateOnUtc")] + [UIHint("DateTimeNullable")] + public DateTime? SearchStartDateOnUtc { get; set; } + + /// + /// Gets or sets slide search finish date + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.SearchFinishDateOnUtc")] + [UIHint("DateTimeNullable")] + public DateTime? SearchFinishDateOnUtc { get; set; } + + /// + /// Gets or sets slide search publication status + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.PublicationStateId")] + public int SearchPublicationStateId { get; set; } + + /// + /// Gets or sets list of available publication states + /// + public IList AvailablePublicationStates { get; set; } = new List(); + /// /// Represent slide page list model /// @@ -41,6 +87,11 @@ public record SlideListItemModel : BaseNopEntityModel /// public string Picture { get; set; } + /// + /// Gets or sets slide name + /// + public string Name { get; set; } + /// /// Gets or sets slide hyperlink /// diff --git a/Models/Admin/WidgetZones/AddWidgetZoneSlideModel.cs b/Models/Admin/WidgetZones/AddWidgetZoneSlideModel.cs index 7cae897..049e861 100644 --- a/Models/Admin/WidgetZones/AddWidgetZoneSlideModel.cs +++ b/Models/Admin/WidgetZones/AddWidgetZoneSlideModel.cs @@ -1,14 +1,16 @@ -๏ปฟusing Nop.Web.Framework.Models; +๏ปฟusing Microsoft.AspNetCore.Mvc.Rendering; +using Nop.Web.Framework.Models; using Nop.Web.Framework.Mvc.ModelBinding; using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; namespace Nop.Plugin.Widgets.qBoSlider.Models.Admin.WidgetZones { /// /// Represents add widget zone slide model /// - public record AddWidgetZoneSlideModel : BaseSearchModel + public record AddWidgetZoneSlideModel : BaseSearchModel, ISlideSearchModel { /// /// Gets or sets widget zone unique id number @@ -20,6 +22,48 @@ public record AddWidgetZoneSlideModel : BaseSearchModel /// public IList SelecetedSlideIds { get; set; } = new List(); + /// + /// Gets or sets slide search name + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.SearchName")] + public string SearchName { get; set; } + + /// + /// Gets or sets search slide widget zone + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.SearchWidgetZoneId")] + public int SearchWidgetZoneId { get; set; } + + /// + /// Gets or sets available widget zones + /// + public IList AvailableWidgetZones { get; set; } = new List(); + + /// + /// Gets or sets slide search start date + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.SearchStartDateOnUtc")] + [UIHint("DateTimeNullable")] + public DateTime? SearchStartDateOnUtc { get; set; } + + /// + /// Gets or sets slide search finish date + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.SearchFinishDateOnUtc")] + [UIHint("DateTimeNullable")] + public DateTime? SearchFinishDateOnUtc { get; set; } + + /// + /// Gets or sets slide search publication status + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.SlideSearch.PublicationStateId")] + public int SearchPublicationStateId { get; set; } + + /// + /// Gets or sets list of available publication states + /// + public IList AvailablePublicationStates { get; set; } = new List(); + /// /// Represents slide list model /// @@ -39,6 +83,12 @@ public record SlideModel : BaseNopEntityModel [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.AddWidgetZoneSlide.Fields.PictureUrl")] public string PictureUrl { get; set; } + /// + /// Gets or sets slide search name + /// + [NopResourceDisplayName("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.AddWidgetZoneSlide.Fields.Name")] + public string Name { get; set; } + /// /// Gets or sets slide publishing start date /// diff --git a/Models/Admin/WidgetZones/WidgetZoneSlideSearchModel.cs b/Models/Admin/WidgetZones/WidgetZoneSlideSearchModel.cs index 57972b0..de23463 100644 --- a/Models/Admin/WidgetZones/WidgetZoneSlideSearchModel.cs +++ b/Models/Admin/WidgetZones/WidgetZoneSlideSearchModel.cs @@ -31,6 +31,11 @@ public record SlideListItem : BaseNopEntityModel /// public string PictureUrl { get; set; } + /// + /// Gets or sets slide search name + /// + public string Name { get; set; } + /// /// Gets or sets slide displaying start date /// diff --git a/Nop.Plugin.Widgets.qBoSlider.csproj b/Nop.Plugin.Widgets.qBoSlider.csproj index 0c4390d..205b950 100644 --- a/Nop.Plugin.Widgets.qBoSlider.csproj +++ b/Nop.Plugin.Widgets.qBoSlider.csproj @@ -153,6 +153,9 @@ Always + + Always + Always diff --git a/README.md b/README.md new file mode 100644 index 0000000..24cf69c --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# qBoSlider +Hi everyone๐Ÿ‘‹! + +Today I want to present you my small slider project! +I had good expirience of work with [nopCommerce](https://www.nopcommerce.com/en/) CMS and sometimes I get multilanguage slider tasks. I work few years and received slider task often. nopCommerce plugins marketplace has no free plugin for multilanguage slides. That's why I start work on my own multilanguage slider for nopCommerce. +Slider user interface features implemented via [JSSOR slider](https://github.com/jssor). + +Today qBoSlider is absolutely free nopCommerce slider with next features: + +* Unlimited widget zones quantity support. You can create slider for each widget zone in your store. +* Unlimited slides quantity. +* Multilanguage slide content. +* HTML content supported. +* Fast content loading via cache. +* Multilanguage slide images. You can select slide background image for each store language. +* Simple configuration and customization. +* Responsive design support. +* ACL support. You can select which customer roles will see the slide. +* Multistore support. You can display slides only in needed stores. +* Slide activity time support. You can set date from which slide will be visible and when it become invisible again. + +### Installation: +* Uninstall and remove previous plugin version if you have it. Previous plugin versions aren't compatible with multiwidget zone version; +* Download plugin archive. +* Go to admin area > configuration > local plugins. +* Upload the plugin archive using the "Upload plugin or theme" plugin. +Scroll down through the list of plugins to find the newly installed plugin. And click on the "Install" button to install the plugin. +* Find plugin in list or at admin area > configuration > widgets and mark plugin as active. +Congratulations! You're installed qBoSlider. By default slider displays three slides on site homepage. + +Hi everyone. Here is the simple instruction how to use updated qBoSlider. +As you can see we change slider configuration page. At now slider configuration page contains only **Switch on static cache** property, but don't panic, other slider configurations you can find at nopCommerce admin panel menu ๐Ÿ˜‰. Just open admin side -> menu -> Plugins -> qBoSlider and you can configure all that you want. + +![](https://1drv.ms/u/s!ArjbDezvZ070grM2hwDV5nbCc-A5hg) + +## Menu +The plugin menu contains three points: +* **[Widget zones](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone)**. Here you can create new or update already existing sliders in your store. Each slider is linked to one special [widget zone](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone). By default plugin has one [slider](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone) on home page; +* **[Slides](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Slide)**. Here you can create or update slides for your store. Each slide can be linked to any sliders any times. +* **Configuration**. Here you can configure plugin. + +## Slide +First of all we need to create some slides for our store, before publish them in [slider](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone). We can do this by next way: +* Open: Admin -> Admin menu -> Plugins -> qBoSlider -> Slides. +* Push '_Add new slide_' button; +* Upload background picture for your slide and save slide; +* * You can also add slide HTML content in '_Description_' field. It's not required option; +* Congratulations! You already create your first [slide](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Slide)!๐ŸŽ‰๐Ÿ˜‰ + +Previously we just create slide and after clearing cache we can see it on home page. In multi-widget zone slider we can select which widget zone will displays our slide. And we are we going to next step ๐Ÿ˜Š. + +## Widget zone +As we know, the widget zone represents a [slider](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone). Each [slider has some common properties and slide collection](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone). So lets create a new slider for our store ๐Ÿ˜‰. + +* Open: Admin -> Admin menu -> Plugins -> qBoSlider -> Widget zones; +* Push '_Add widget zone_' button; +* Write widget zone name. This property is using just for widget zone searching. That's why you we can write anything that will help us to find widget zone faster; +* Write widget zone system name, for example '_home_page_bottom_'. We add autocomplete to help our users write standard nopCommerce widget zone names; +* Put other widget zone properties and push button '_Save and continue edit_'; +* Great! Now you're create a new slider for widget zone๐ŸŽ‰๐Ÿ‘; +* Now add our new slides, which we add previously, to our widget zone slides collection; +* Param-pam-pam-pam ๐ŸŽ‰๐Ÿ˜. Kudos! Now you've new [slider](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Widget-zone) with your own [slides](https://github.com/iAlexeyProkhorov/qBoSlider/wiki/Slide). Clear nopCommerce cache and go to page where widget zone locate (for example: '_home_page_bottom_' slider locates at bottom side of site home page); + +## Few additional moments: +* qBoSlider supports so much widget zones and slides as you can create; +* One slide can be add to any widget zones. We can add slide to all store widget zones if it's really needed; +* We can create two or more widget zones with same system names. nopCommerce will display slider for first created widget zone; + diff --git a/Service/ISlideService.cs b/Service/ISlideService.cs index 78d88d9..f232b49 100644 --- a/Service/ISlideService.cs +++ b/Service/ISlideService.cs @@ -33,14 +33,23 @@ public interface ISlideService /// /// Get all slides for current store /// + /// Searching slider name + /// Slides for widget zones /// Publication start date /// Publication end date - /// Show unpublished slides too + /// Searching slides publication state /// Page index /// Page sizer /// Store id number - /// - Task> GetAllSlidesAsync(DateTime? startDate = null, DateTime? endDate = null, bool showHidden = false, int pageIndex = 0, int pageSize = int.MaxValue, int storeId = 0); + /// Paged slides list + Task> GetAllSlidesAsync(string name = null, + int[] widgetZoneIds = null, + DateTime? startDate = null, + DateTime? endDate = null, + PublicationState publicationState = 0, + int pageIndex = 0, + int pageSize = int.MaxValue, + int storeId = 0); /// /// Insert slide diff --git a/Service/SlideService.cs b/Service/SlideService.cs index cc696cf..0c66829 100644 --- a/Service/SlideService.cs +++ b/Service/SlideService.cs @@ -31,7 +31,7 @@ public partial class SlideService : ISlideService #region Fields private readonly IRepository _slideRepository; - + private readonly IRepository _widgetZoneSlideRepository; private readonly IStoreMappingService _storeMappingService; #endregion @@ -39,11 +39,12 @@ public partial class SlideService : ISlideService #region Constructor public SlideService(IRepository slideRepository, + IRepository widgetZoneSlideRepository, IStoreMappingService storeMappingService) { - this._slideRepository = slideRepository; - - this._storeMappingService = storeMappingService; + _slideRepository = slideRepository; + _widgetZoneSlideRepository = widgetZoneSlideRepository; + _storeMappingService = storeMappingService; } #endregion @@ -63,36 +64,59 @@ public virtual async Task GetSlideByIdAsync(int id) /// /// Get all slides for current store /// + /// Searching slider name + /// Slides for widget zones /// Publication start date /// Publication end date - /// Show unpublished slides too + /// Searching slides publication state /// Page index /// Page sizer /// Store id number - /// - public virtual async Task> GetAllSlidesAsync(DateTime? startDate = null, DateTime? endDate = null, bool showHidden = false, int pageIndex = 0, int pageSize = int.MaxValue, int storeId = 0) + /// Paged slides list + public virtual async Task> GetAllSlidesAsync(string name = null, + int[] widgetZoneIds = null, + DateTime? startDate = null, + DateTime? endDate = null, + PublicationState publicationState = 0, + int pageIndex = 0, + int pageSize = int.MaxValue, + int storeId = 0) { - var query = _slideRepository.Table; + var result = await _slideRepository.GetAllPagedAsync(async query => + { + //filter slides, which contains part of searchable slide name + if (!string.IsNullOrEmpty(name)) + query = query.Where(x => x.Name.Contains(name)); + + //filter by widget zones + if (widgetZoneIds != null && widgetZoneIds.Any()) + query = from slide in query + join widgetZoneSlide in _widgetZoneSlideRepository.Table on slide.Id equals widgetZoneSlide.SlideId + where widgetZoneIds.Contains(widgetZoneSlide.WidgetZoneId) + select slide; + + //filter by start date + if (startDate.HasValue) + query = query.Where(x => !x.StartDateUtc.HasValue || x.StartDateUtc >= startDate.Value); - //filter unpublished slides - if (!showHidden) - query = query.Where(x => x.Published); + //filter by end publishing date + if (endDate.HasValue) + query = query.Where(x => !x.EndDateUtc.HasValue || x.EndDateUtc <= endDate.Value); - //filter by start date - if (startDate.HasValue) - query = query.Where(x => x.StartDateUtc <= DateTime.UtcNow); + //filter unpublished slides + if (publicationState == PublicationState.Published) + query = query.Where(x => x.Published); - //filter by end publishing date - if (endDate.HasValue) - query = query.Where(x => x.EndDateUtc >= DateTime.UtcNow); + if (publicationState == PublicationState.Unpublished) + query = query.Where(x => !x.Published); - var result = new List(); + if (storeId > 0) + query = await _storeMappingService.ApplyStoreMapping(query, storeId); - foreach (var s in query) - if (await _storeMappingService.AuthorizeAsync(s, storeId)) - result.Add(s); + return query; + }); - return new PagedList(result, pageIndex, pageSize); + return result; } /// diff --git a/Views/Admin/Slide/List.cshtml b/Views/Admin/Slide/List.cshtml index d73bf36..9f8548b 100644 --- a/Views/Admin/Slide/List.cshtml +++ b/Views/Admin/Slide/List.cshtml @@ -26,6 +26,7 @@
+ @await Html.PartialAsync("~/Plugins/Widgets.qBoSlider/Views/Admin/_SlideSearch.cshtml", Model)
@await Html.PartialAsync("Table", new DataTablesModel @@ -44,6 +45,12 @@ Visible = true, Render = new RenderPicture() }, + new ColumnProperty(nameof(SlideSearchModel.SlideListItemModel.Name)) + { + Title = T("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.Slide.List.Name").Text, + Width = "300", + Visible = true + }, new ColumnProperty(nameof(SlideSearchModel.SlideListItemModel.StartDateUtc)) { Title = T("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.Slide.List.StartDateUtc").Text, diff --git a/Views/Admin/Slide/_CreateOrUpdate.Info.cshtml b/Views/Admin/Slide/_CreateOrUpdate.Info.cshtml index 374dc7b..5a38a55 100644 --- a/Views/Admin/Slide/_CreateOrUpdate.Info.cshtml +++ b/Views/Admin/Slide/_CreateOrUpdate.Info.cshtml @@ -59,6 +59,15 @@
)) +
+
+ +
+
+ + +
+
diff --git a/Views/Admin/WidgetZone/AddWidgetZoneSlidePopup.cshtml b/Views/Admin/WidgetZone/AddWidgetZoneSlidePopup.cshtml index c5c8346..ce858bd 100644 --- a/Views/Admin/WidgetZone/AddWidgetZoneSlidePopup.cshtml +++ b/Views/Admin/WidgetZone/AddWidgetZoneSlidePopup.cshtml @@ -34,6 +34,7 @@ else
+ @await Html.PartialAsync("~/Plugins/Widgets.qBoSlider/Views/Admin/_SlideSearch.cshtml", Model)
@await Html.PartialAsync("Table", new DataTablesModel @@ -58,6 +59,10 @@ else Title = T("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.AddWidgetZoneSlide.Fields.PictureUrl").Text, Width = "300" }, + new ColumnProperty(nameof(AddWidgetZoneSlideModel.SlideModel.Name)) + { + Title = T("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.AddWidgetZoneSlide.Fields.Name").Text + }, new ColumnProperty(nameof(AddWidgetZoneSlideModel.SlideModel.StartDateUtc)) { Title = T("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.AddWidgetZoneSlide.Fields.StartDateUtc").Text, diff --git a/Views/Admin/WidgetZone/_CreateOrUpdate.Slides.cshtml b/Views/Admin/WidgetZone/_CreateOrUpdate.Slides.cshtml index 902ff4b..2b49a2b 100644 --- a/Views/Admin/WidgetZone/_CreateOrUpdate.Slides.cshtml +++ b/Views/Admin/WidgetZone/_CreateOrUpdate.Slides.cshtml @@ -18,6 +18,10 @@ Title = T("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.WidgetZone.SlideList.PictureUrl").Text, Render = new RenderPicture() }, + new ColumnProperty(nameof(WidgetZoneSlideSearchModel.SlideListItem.Name)) + { + Title = T("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.WidgetZone.SlideList.Name").Text + }, new ColumnProperty(nameof(WidgetZoneSlideSearchModel.SlideListItem.StartDateUtc)) { Title = T("Nop.Plugin.Baroque.Widgets.qBoSlider.Admin.WidgetZone.SlideList.StartDateUtc").Text, diff --git a/Views/Admin/_SlideSearch.cshtml b/Views/Admin/_SlideSearch.cshtml new file mode 100644 index 0000000..9ec78dc --- /dev/null +++ b/Views/Admin/_SlideSearch.cshtml @@ -0,0 +1,70 @@ +๏ปฟ@model ISlideSearchModel +@{ + const string hideSearchBlockAttributeName = "Baroque.qBoSlider.Slide.HideSearchBlock"; + var hideSearchBlock = await genericAttributeService.GetAttributeAsync(await workContext.GetCurrentCustomerAsync(), hideSearchBlockAttributeName); +} + + \ No newline at end of file diff --git a/plugin.json b/plugin.json index 2ec1577..49ce518 100644 --- a/plugin.json +++ b/plugin.json @@ -2,7 +2,7 @@ "Group": "Widgets", "FriendlyName": "qBoSlider", "SystemName": "Widgets.qBoSlider", - "Version": "1.3.1", + "Version": "1.4.1", "SupportedVersions": [ "4.50" ], "Author": "Baroque team", "DisplayOrder": 1, diff --git a/qBoOptions.cs b/qBoOptions.cs index bed9a88..6bc8ba8 100644 --- a/qBoOptions.cs +++ b/qBoOptions.cs @@ -46,4 +46,14 @@ public enum DragOrientation : int Vertical = 2, Both = 3 } + + /// + /// Represents entity publication state variants + /// + public enum PublicationState : int + { + All = 0, + Published = 5, + Unpublished = 10 + } } diff --git a/qBoSliderPlugin.cs b/qBoSliderPlugin.cs index 83e92c9..8ab303a 100644 --- a/qBoSliderPlugin.cs +++ b/qBoSliderPlugin.cs @@ -111,7 +111,7 @@ public async Task> GetWidgetZonesAsync() activeWidgetZones = await activeWidgetZones //process only authorized by ACL widget zones .WhereAwait(async widgetZone => await _aclService.AuthorizeAsync(widgetZone)).ToListAsync(); - //prepare of store mapped widget zones + //prepare of store mapped widget zones var widgetZoneSystemNames = await activeWidgetZones .WhereAwait(async widgetZone => await _storeMappingService.AuthorizeAsync(widgetZone)) .Select(x => x.SystemName).Distinct().ToListAsync(); @@ -218,36 +218,39 @@ public override async Task InstallAsync() //get sample pictures path var sampleImagesPath = CommonHelper.DefaultFileProvider.MapPath("~/Plugins/Widgets.qBoSlider/Content/sample-images/"); - var picture1 = (await _pictureService.InsertPictureAsync(File.ReadAllBytes(string.Format("{0}banner1.jpg", sampleImagesPath)), "image/pjpeg", "qboslide-1")).Id; + var picture1Id = (await _pictureService.InsertPictureAsync(File.ReadAllBytes(string.Format("{0}banner1.jpg", sampleImagesPath)), "image/pjpeg", "qboslide-1")).Id; var slide1 = new Slide() { + Name = "New comfort mouse", Description = "
" + "

NEW COMFORT MOUSE

" + "

CHOOSE FROM HUNDREDS

" + "

OF MODELS

" + "

FROM ONLY $59.00

" + "

SHOP NOW

", - PictureId = picture1, + PictureId = picture1Id, Published = true }; await _slideService.InsertSlideAsync(slide1); - var picture2 = _pictureService.InsertPictureAsync(File.ReadAllBytes(string.Format("{0}banner2.jpg", sampleImagesPath)), "image/pjpeg", "qboslide-2").GetAwaiter().GetResult().Id; + var picture2Id = (await _pictureService.InsertPictureAsync(File.ReadAllBytes(string.Format("{0}banner2.jpg", sampleImagesPath)), "image/pjpeg", "qboslide-2")).Id; var slide2 = new Slide() { + Name = "HD PRO WEBCAM H320", Description = "
" + "

HD PRO WEBCAM H320

" + "

720P FOR TRUE HD-QUALITY
VIDEO CHAT

" + "

ONLY $79.00

" + "

SHOP NOW

", - PictureId = picture2, + PictureId = picture2Id, Published = true, }; await _slideService.InsertSlideAsync(slide2); - var picture3 = _pictureService.InsertPictureAsync(File.ReadAllBytes(string.Format("{0}banner3.jpg", sampleImagesPath)), "image/pjpeg", "qboslide-3").GetAwaiter().GetResult().Id; + var picture3Id = (await _pictureService.InsertPictureAsync(File.ReadAllBytes(string.Format("{0}banner3.jpg", sampleImagesPath)), "image/pjpeg", "qboslide-3")).Id; var slide3 = new Slide() { + Name = "Compact camera SP120", Description = "
" + "

COMPACT CAMERA SP120

" + "

20X WIDE ZOOM, 2.5 LCD,

" + @@ -255,7 +258,7 @@ public override async Task InstallAsync() "

ONLY $159.00

" + "

SHOP NOW

" + "
", - PictureId = picture3, + PictureId = picture3Id, Published = true }; await _slideService.InsertSlideAsync(slide3);