Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Business/Grand.Business.Cms/Services/BlogService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public virtual Task<BlogPost> GetBlogPostById(string blogPostId)
/// <param name="blogPostName">Blog post name</param>
/// <param name="categoryId">Category ident</param>
/// <returns>Blog posts</returns>
public virtual async Task<IPagedList<BlogPost>> GetAllBlogPosts(string storeId = "",
public virtual async Task<IPagedList<BlogPost>> GetAllBlogPosts(string storeId,
DateTime? dateFrom = null, DateTime? dateTo = null,
int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false, string tag = null,
string blogPostName = "", string categoryId = "")
Expand Down Expand Up @@ -123,7 +123,7 @@ public virtual async Task<IPagedList<BlogPost>> GetAllBlogPosts(string storeId =
/// <param name="pageSize">Page size</param>
/// <param name="showHidden">A value indicating whether to show hidden records</param>
/// <returns>Blog posts</returns>
public virtual async Task<IPagedList<BlogPost>> GetAllBlogPostsByTag(string storeId = "",
public virtual async Task<IPagedList<BlogPost>> GetAllBlogPostsByTag(string storeId,
string tag = "",
int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false)
{
Expand Down Expand Up @@ -336,7 +336,7 @@ public virtual async Task<BlogCategory> GetBlogCategoryBySeName(string blogCateg
/// Get all blog categories
/// </summary>
/// <returns></returns>
public virtual async Task<IList<BlogCategory>> GetAllBlogCategories(string storeId = "")
public virtual async Task<IList<BlogCategory>> GetAllBlogCategories(string storeId)
{
var query = from c in _blogCategoryRepository.Table
select c;
Expand Down
2 changes: 1 addition & 1 deletion src/Business/Grand.Business.Cms/Services/NewsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public virtual Task<NewsItem> GetNewsById(string newsId)
/// <param name="showHidden">A value indicating whether to show hidden records</param>
/// <param name="newsTitle">News title</param>
/// <returns>News items</returns>
public virtual async Task<IPagedList<NewsItem>> GetAllNews(string storeId = "",
public virtual async Task<IPagedList<NewsItem>> GetAllNews(string storeId,
int pageIndex = 0, int pageSize = int.MaxValue, bool ignoreAcl = false, bool showHidden = false,
string newsTitle = "")
{
Expand Down
21 changes: 13 additions & 8 deletions src/Business/Grand.Business.Cms/Services/PageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@ public virtual Task<Page> GetPageById(string pageId)
/// Gets a page
/// </summary>
/// <param name="systemName">The page system name</param>
/// <param name="storeId">Store identifier; pass 0 to ignore filtering by store and load the first one</param>
/// <returns>Page</returns>
public virtual async Task<Page> GetPageBySystemName(string systemName, string storeId = "")
/// <param name="storeId">Store identifier; pass "" to ignore filtering by store and load the first one</param>
/// <returns>
/// The page a store overrode this system name with, if it has one; otherwise the page shared by every store
/// </returns>
public virtual async Task<Page> GetPageBySystemName(string systemName, string storeId)
{
if (string.IsNullOrEmpty(systemName))
return null;
Expand All @@ -78,8 +80,11 @@ public virtual async Task<Page> GetPageBySystemName(string systemName, string st

query = query.Where(t => t.SystemName.ToLower() == systemName.ToLower());
query = query.OrderBy(t => t.Id);
var pages = await Task.FromResult(query.ToList());
if (!string.IsNullOrEmpty(storeId)) pages = pages.Where(x => _aclService.Authorize(x, storeId)).ToList();
IEnumerable<Page> pages = await _pageRepository.ToListAsync(query);
if (!string.IsNullOrEmpty(storeId))
//a page this store was given for itself is the one it means, even though the shared page is older
pages = pages.Where(x => _aclService.Authorize(x, storeId))
.OrderByDescending(x => x.LimitedToStores && x.Stores.Contains(storeId));
return pages.FirstOrDefault();
});
}
Expand All @@ -101,7 +106,7 @@ public virtual async Task<IList<Page>> GetAllPages(string storeId, bool ignoreAc
query = query.OrderBy(t => t.DisplayOrder).ThenBy(t => t.SystemName);

if ((string.IsNullOrEmpty(storeId) || _accessControlConfig.IgnoreStoreLimitations) &&
(ignoreAcl || _accessControlConfig.IgnoreAcl)) return await Task.FromResult(query.ToList());
(ignoreAcl || _accessControlConfig.IgnoreAcl)) return await _pageRepository.ToListAsync(query);
{
if (!ignoreAcl && !_accessControlConfig.IgnoreAcl)
{
Expand All @@ -113,14 +118,14 @@ public virtual async Task<IList<Page>> GetAllPages(string storeId, bool ignoreAc

//Store acl
if (string.IsNullOrEmpty(storeId) || _accessControlConfig.IgnoreStoreLimitations)
return await Task.FromResult(query.ToList());
return await _pageRepository.ToListAsync(query);
query = from p in query
where !p.LimitedToStores || p.Stores.Contains(storeId)
select p;

query = query.OrderBy(t => t.SystemName);
}
return await Task.FromResult(query.ToList());
return await _pageRepository.ToListAsync(query);
});
}

Expand Down
42 changes: 42 additions & 0 deletions src/Business/Grand.Business.Core/Extensions/PageExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Grand.Domain.Pages;

namespace Grand.Business.Core.Extensions;

public static class PageExtensions
{
/// <summary>
/// Drops the page shared by every store wherever this store was given its own page under the same system name.
/// A store panel copies a shared page to edit it for one store, which leaves two pages carrying one system name
/// visible to that store; a list rendered for the storefront means the store's own one.
/// </summary>
/// <param name="pages">Pages already filtered to what the store may see</param>
/// <param name="storeId">Store identifier; pass "" to keep every page</param>
/// <returns>The pages to render for the store</returns>
public static IList<Page> PreferStoreOverrides(this IEnumerable<Page> pages, string storeId)
{
ArgumentNullException.ThrowIfNull(pages);

var all = pages as IList<Page> ?? pages.ToList();
if (string.IsNullOrEmpty(storeId))
return all;

var overriddenSystemNames = all
.Where(p => IsOwnedBy(p, storeId) && !string.IsNullOrEmpty(p.SystemName))
.Select(p => p.SystemName)
.ToHashSet(StringComparer.OrdinalIgnoreCase);

if (overriddenSystemNames.Count == 0)
return all;

return all
.Where(p => IsOwnedBy(p, storeId) ||
string.IsNullOrEmpty(p.SystemName) ||
!overriddenSystemNames.Contains(p.SystemName))
.ToList();
}

private static bool IsOwnedBy(Page page, string storeId)
{
return page.LimitedToStores && page.Stores.Contains(storeId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public interface IBlogService
/// <param name="blogPostName">Blog post name</param>
/// <param name="categoryId">Category id</param>
/// <returns>Blog posts</returns>
Task<IPagedList<BlogPost>> GetAllBlogPosts(string storeId = "",
Task<IPagedList<BlogPost>> GetAllBlogPosts(string storeId,
DateTime? dateFrom = null, DateTime? dateTo = null,
int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false, string tag = null,
string blogPostName = "", string categoryId = "");
Expand All @@ -42,7 +42,7 @@ Task<IPagedList<BlogPost>> GetAllBlogPosts(string storeId = "",
/// <param name="pageSize">Page size</param>
/// <param name="showHidden">A value indicating whether to show hidden records</param>
/// <returns>Blog posts</returns>
Task<IPagedList<BlogPost>> GetAllBlogPostsByTag(string storeId = "",
Task<IPagedList<BlogPost>> GetAllBlogPostsByTag(string storeId,
string tag = "",
int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false);

Expand Down Expand Up @@ -122,7 +122,7 @@ Task<IPagedList<BlogPost>> GetAllBlogPostsByTag(string storeId = "",
/// Get all blog categories
/// </summary>
/// <returns></returns>
Task<IList<BlogCategory>> GetAllBlogCategories(string storeId = "");
Task<IList<BlogCategory>> GetAllBlogCategories(string storeId);

/// <summary>
/// Inserts an blog category
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public interface INewsService
/// <param name="showHidden">A value indicating whether to show hidden records</param>
/// <param name="newsTitle">News title</param>
/// <returns>News items</returns>
Task<IPagedList<NewsItem>> GetAllNews(string storeId = "",
Task<IPagedList<NewsItem>> GetAllNews(string storeId,
int pageIndex = 0, int pageSize = int.MaxValue, bool ignoreAcl = false, bool showHidden = false,
string newsTitle = "");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public interface IPageService
/// <param name="systemName">The page system name</param>
/// <param name="storeId">Store identifier; pass 0 to ignore filtering by store and load the first one</param>
/// <returns>Page</returns>
Task<Page> GetPageBySystemName(string systemName, string storeId = "");
Task<Page> GetPageBySystemName(string systemName, string storeId);

/// <summary>
/// Gets all pages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ private async Task<IEnumerable<SitemapUrl>> GetPagesUrls(Language language, Stor
{
var now = DateTime.UtcNow;
return (await _pageService.GetAllPages(store.Id))
.PreferStoreOverrides(store.Id)
.Where(t => t.IncludeInSitemap && (!t.StartDateUtc.HasValue || t.StartDateUtc < now) &&
(!t.EndDateUtc.HasValue || t.EndDateUtc > now))
.Select(topic =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ public virtual Task<MessageTemplate> GetMessageTemplateById(string messageTempla
/// </summary>
/// <param name="messageTemplateName">Message template name</param>
/// <param name="storeId">Store identifier</param>
/// <returns>Message template</returns>
/// <returns>
/// The template a store overrode this name with, if it has one; otherwise the template shared by every store
/// </returns>
public virtual async Task<MessageTemplate> GetMessageTemplateByName(string messageTemplateName, string storeId)
{
if (string.IsNullOrWhiteSpace(messageTemplateName))
Expand All @@ -121,13 +123,14 @@ public virtual async Task<MessageTemplate> GetMessageTemplateByName(string messa

query = query.Where(t => t.Name == messageTemplateName);
query = query.OrderBy(t => t.Id);
var templates = await Task.FromResult(query.ToList());
IEnumerable<MessageTemplate> templates = await _messageTemplateRepository.ToListAsync(query);

//store acl
if (!string.IsNullOrEmpty(storeId))
//a template this store was given for itself is the one it means, even though the shared template is older
templates = templates
.Where(t => _aclService.Authorize(t, storeId))
.ToList();
.OrderByDescending(t => t.LimitedToStores && t.Stores.Contains(storeId));

return templates.FirstOrDefault();
});
Expand Down
124 changes: 124 additions & 0 deletions src/Tests/Grand.Business.Cms.Tests/Extensions/PageExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using Grand.Business.Core.Extensions;
using Grand.Domain.Pages;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Grand.Business.Cms.Tests.Extensions;

[TestClass]
public class PageExtensionsTests
{
private static Page Shared(string systemName, string id)
{
return new Page { Id = id, SystemName = systemName, LimitedToStores = false };
}

private static Page OwnedBy(string systemName, string id, string storeId)
{
return new Page { Id = id, SystemName = systemName, LimitedToStores = true, Stores = { storeId } };
}

[TestMethod]
public void PreferStoreOverrides_StoreCopiedTheSharedPage_KeepsOnlyTheCopy()
{
var pages = new List<Page> { Shared("about", "1"), OwnedBy("about", "2", "store-1") };

var result = pages.PreferStoreOverrides("store-1");

Assert.HasCount(1, result);
Assert.AreEqual("2", result[0].Id);
}

[TestMethod]
public void PreferStoreOverrides_NoCopyForThisStore_KeepsTheSharedPage()
{
var pages = new List<Page> { Shared("about", "1") };

var result = pages.PreferStoreOverrides("store-1");

Assert.HasCount(1, result);
Assert.AreEqual("1", result[0].Id);
}

/// <summary>
/// Only the system name the store overrode is affected; everything else it may see stays.
/// </summary>
[TestMethod]
public void PreferStoreOverrides_OtherSystemNames_AreUntouched()
{
var pages = new List<Page> {
Shared("about", "1"),
OwnedBy("about", "2", "store-1"),
Shared("contact", "3"),
OwnedBy("terms", "4", "store-1")
};

var result = pages.PreferStoreOverrides("store-1");

CollectionAssert.AreEqual(new[] { "2", "3", "4" }, result.Select(p => p.Id).ToArray());
}

/// <summary>
/// A store panel creates the copy with the system name it was copied from, but nothing stops the
/// casing from differing, and the storefront treats one page as one page regardless of casing.
/// </summary>
[TestMethod]
public void PreferStoreOverrides_SystemNameCasingDiffers_StillCountsAsAnOverride()
{
var pages = new List<Page> { Shared("About", "1"), OwnedBy("about", "2", "store-1") };

var result = pages.PreferStoreOverrides("store-1");

Assert.HasCount(1, result);
Assert.AreEqual("2", result[0].Id);
}

/// <summary>
/// The order the caller was given is the order it renders in, so collapsing must not reshuffle.
/// </summary>
[TestMethod]
public void PreferStoreOverrides_PreservesTheIncomingOrder()
{
var pages = new List<Page> {
Shared("c", "1"),
OwnedBy("a", "2", "store-1"),
Shared("b", "3")
};

var result = pages.PreferStoreOverrides("store-1");

CollectionAssert.AreEqual(new[] { "1", "2", "3" }, result.Select(p => p.Id).ToArray());
}

/// <summary>
/// Without a store there is no override to prefer - the admin panel reads pages this way.
/// </summary>
[TestMethod]
public void PreferStoreOverrides_NoStore_KeepsEveryPage()
{
var pages = new List<Page> { Shared("about", "1"), OwnedBy("about", "2", "store-1") };

var result = pages.PreferStoreOverrides("");

Assert.HasCount(2, result);
}

/// <summary>
/// Another store's copy is not this store's override, and a page limited to another store should
/// not have reached this method at all.
/// </summary>
[TestMethod]
public void PreferStoreOverrides_CopyBelongsToAnotherStore_KeepsTheSharedPage()
{
var pages = new List<Page> { Shared("about", "1"), OwnedBy("about", "2", "store-2") };

var result = pages.PreferStoreOverrides("store-1");

CollectionAssert.AreEqual(new[] { "1", "2" }, result.Select(p => p.Id).ToArray());
}

[TestMethod]
public void PreferStoreOverrides_NullPages_Throws()
{
Assert.ThrowsExactly<ArgumentNullException>(() => ((IEnumerable<Page>)null).PreferStoreOverrides("store-1"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public async Task GetAllNewsTest()
var newsItem = new NewsItem { Published = true };
await _repository.InsertAsync(newsItem);
//Act
var result = await _newsService.GetAllNews();
var result = await _newsService.GetAllNews(storeId: "");
//Assert
Assert.IsTrue(result.Any());
}
Expand Down
Loading
Loading