mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-01-22 21:02:08 +08:00
Adding auto slug conflict management ("versioning") on pages bulk publish
--HG-- extra : convert_revision : svn%3A5ff7c347-ad56-4c35-b696-ccb81de16e03/trunk%4045864
This commit is contained in:
@@ -1,20 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Autofac;
|
||||
using Autofac.Builder;
|
||||
using JetBrains.Annotations;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Drivers;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.ContentManagement.Records;
|
||||
using Orchard.Core.Common.Models;
|
||||
using Orchard.Core.Common.Providers;
|
||||
using Orchard.Core.Common.Records;
|
||||
using Orchard.Core.Common.Services;
|
||||
using Orchard.Data;
|
||||
using Orchard.Security;
|
||||
using Orchard.Tests.Packages;
|
||||
|
||||
namespace Orchard.Core.Tests.Common.Services {
|
||||
@@ -67,6 +62,42 @@ namespace Orchard.Core.Tests.Common.Services {
|
||||
Assert.That(thing.Slug.Length, Is.EqualTo(1000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NoExistingLikeSlugsGeneratesSameSlug() {
|
||||
string slug = _routableService.GenerateUniqueSlug("woohoo", null);
|
||||
Assert.That(slug, Is.EqualTo("woohoo"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExistingSingleLikeSlugThatsAConflictGeneratesADash2() {
|
||||
string slug = _routableService.GenerateUniqueSlug("woohoo", new List<string> { "woohoo" });
|
||||
Assert.That(slug, Is.EqualTo("woohoo-2"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExistingSingleLikeSlugThatsNotAConflictGeneratesSameSlug() {
|
||||
string slug = _routableService.GenerateUniqueSlug("woohoo", new List<string> { "woohoo-2" });
|
||||
Assert.That(slug, Is.EqualTo("woohoo"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExistingLikeSlugsWithAConflictGeneratesADashVNext() {
|
||||
string slug = _routableService.GenerateUniqueSlug("woohoo", new List<string> { "woohoo", "woohoo-2" });
|
||||
Assert.That(slug, Is.EqualTo("woohoo-3"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExistingSlugsWithVersionGapsAndNoMatchGeneratesSameSlug() {
|
||||
string slug = _routableService.GenerateUniqueSlug("woohoo", new List<string> { "woohoo-2", "woohoo-4", "woohoo-5" });
|
||||
Assert.That(slug, Is.EqualTo("woohoo"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExistingSlugsWithVersionGapsAndAMatchGeneratesADash2() {
|
||||
string slug = _routableService.GenerateUniqueSlug("woohoo-2", new List<string> { "woohoo-2", "woohoo-4", "woohoo-5" });
|
||||
Assert.That(slug, Is.EqualTo("woohoo-2-2"));
|
||||
}
|
||||
|
||||
protected override IEnumerable<Type> DatabaseTypes {
|
||||
get {
|
||||
return new[] {
|
||||
|
||||
@@ -32,24 +32,26 @@ namespace Orchard.Core.Common.Services {
|
||||
|
||||
|
||||
public string GenerateUniqueSlug(string slugCandidate, IEnumerable<string> existingSlugs) {
|
||||
int? version = existingSlugs
|
||||
.Select(s => {
|
||||
int v;
|
||||
string[] slugParts = s.Split(new[] { slugCandidate }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (slugParts.Length == 0) {
|
||||
return 2;
|
||||
}
|
||||
if (existingSlugs == null || !existingSlugs.Contains(slugCandidate))
|
||||
return slugCandidate;
|
||||
|
||||
return int.TryParse(slugParts[0].TrimStart('-'), out v)
|
||||
? (int?) ++v
|
||||
: null;
|
||||
})
|
||||
.OrderBy(i => i)
|
||||
.LastOrDefault();
|
||||
int? version = existingSlugs.Select(s => GetSlugVersion(slugCandidate, s)).OrderBy(i => i).LastOrDefault();
|
||||
|
||||
return version != null
|
||||
? string.Format("{0}-{1}", slugCandidate, version)
|
||||
: slugCandidate;
|
||||
}
|
||||
|
||||
private static int? GetSlugVersion(string slugCandidate, string slug) {
|
||||
int v;
|
||||
string[] slugParts = slug.Split(new []{slugCandidate}, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (slugParts.Length == 0)
|
||||
return 2;
|
||||
|
||||
return int.TryParse(slugParts[0].TrimStart('-'), out v)
|
||||
? (int?)++v
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,9 @@ namespace Orchard.Blogs.Models {
|
||||
//todo: (heskew) need better messages
|
||||
var originalSlug = post.Slug;
|
||||
post.Slug = _routableService.GenerateUniqueSlug(post.Slug, slugsLikeThis);
|
||||
_orchardServices.Notifier.Warning(T("A different blog post is already published with this same slug ({0}) so a unique slug ({1}) was generated for this post.", originalSlug, post.Slug));
|
||||
|
||||
if (originalSlug != post.Slug)
|
||||
_orchardServices.Notifier.Warning(T("A different blog post is already published with this same slug ({0}) so a unique slug ({1}) was generated for this post.", originalSlug, post.Slug));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace Orchard.Pages.Controllers {
|
||||
foreach (PageEntry entry in checkedEntries) {
|
||||
var page = _pageService.GetLatest(entry.PageId);
|
||||
_pageService.Publish(page);
|
||||
Services.ContentManager.Flush();
|
||||
}
|
||||
break;
|
||||
case PagesBulkAction.Unpublish:
|
||||
@@ -72,6 +73,7 @@ namespace Orchard.Pages.Controllers {
|
||||
foreach (PageEntry entry in checkedEntries) {
|
||||
var page = _pageService.GetLatest(entry.PageId);
|
||||
_pageService.Unpublish(page);
|
||||
Services.ContentManager.Flush();
|
||||
}
|
||||
break;
|
||||
case PagesBulkAction.Delete:
|
||||
@@ -81,6 +83,7 @@ namespace Orchard.Pages.Controllers {
|
||||
foreach (PageEntry entry in checkedEntries) {
|
||||
var page = _pageService.GetLatest(entry.PageId);
|
||||
_pageService.Delete(page);
|
||||
Services.ContentManager.Flush();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -138,7 +141,10 @@ namespace Orchard.Pages.Controllers {
|
||||
return View(model);
|
||||
}
|
||||
|
||||
Services.ContentManager.Create(model.Page.Item.ContentItem, publishNow ? VersionOptions.Published : VersionOptions.Draft);
|
||||
Services.ContentManager.Create(model.Page.Item.ContentItem, VersionOptions.Draft);
|
||||
|
||||
if (publishNow)
|
||||
Services.ContentManager.Publish(model.Page.Item.ContentItem);
|
||||
|
||||
if (publishNow)
|
||||
Services.Notifier.Information(T("Page has been published"));
|
||||
|
||||
@@ -1,21 +1,58 @@
|
||||
using JetBrains.Annotations;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Core.Common.Records;
|
||||
using Orchard.Core.Common.Services;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Pages.Controllers;
|
||||
using Orchard.Core.Common.Models;
|
||||
using Orchard.Data;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Pages.Services;
|
||||
using Orchard.UI.Notify;
|
||||
|
||||
namespace Orchard.Pages.Models {
|
||||
[UsedImplicitly]
|
||||
public class PageHandler : ContentHandler {
|
||||
public PageHandler(IRepository<CommonVersionRecord> commonRepository) {
|
||||
private readonly IPageService _pageService;
|
||||
private readonly IRoutableService _routableService;
|
||||
private readonly IOrchardServices _orchardServices;
|
||||
|
||||
public PageHandler(IRepository<CommonVersionRecord> commonRepository, IPageService pageService, IRoutableService routableService, IOrchardServices orchardServices) {
|
||||
_pageService = pageService;
|
||||
_routableService = routableService;
|
||||
_orchardServices = orchardServices;
|
||||
T = NullLocalizer.Instance;
|
||||
|
||||
Filters.Add(new ActivatingFilter<Page>(PageDriver.ContentType.Name));
|
||||
Filters.Add(new ActivatingFilter<CommonAspect>(PageDriver.ContentType.Name));
|
||||
Filters.Add(new ActivatingFilter<ContentPart<CommonVersionRecord>>(PageDriver.ContentType.Name));
|
||||
Filters.Add(new ActivatingFilter<RoutableAspect>(PageDriver.ContentType.Name));
|
||||
Filters.Add(new ActivatingFilter<BodyAspect>(PageDriver.ContentType.Name));
|
||||
Filters.Add(new StorageFilter<CommonVersionRecord>(commonRepository));
|
||||
|
||||
OnPublished<Page>((context, p) => ProcessSlug(p));
|
||||
}
|
||||
|
||||
Localizer T { get; set; }
|
||||
|
||||
private void ProcessSlug(Page page) {
|
||||
_routableService.FillSlug(page.As<RoutableAspect>());
|
||||
|
||||
var slugsLikeThis = _pageService.Get(PageStatus.Published).Where(
|
||||
p => p.Slug.StartsWith(page.Slug, StringComparison.OrdinalIgnoreCase) &&
|
||||
p.Id != page.Id).Select(p => p.Slug);
|
||||
|
||||
//todo: (heskew) need better messages
|
||||
if (slugsLikeThis.Count() > 0) {
|
||||
//todo: (heskew) need better messages
|
||||
var originalSlug = page.Slug;
|
||||
page.Slug = _routableService.GenerateUniqueSlug(page.Slug, slugsLikeThis);
|
||||
|
||||
if (originalSlug != page.Slug)
|
||||
_orchardServices.Notifier.Warning(T("A different page is already published with this same slug ({0}) so a unique slug ({1}) was generated for this page.", originalSlug, page.Slug));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
<%@ Import Namespace="Orchard.Pages.ViewModels"%>
|
||||
<h1><%=Html.TitleForPage(T("Manage Pages").ToString())%></h1>
|
||||
<%-- todo: Add helper text here when ready. <p><%=_Encoded("Possible text about setting up a page goes here.")%></p>--%>
|
||||
<div class="manage"><%=Html.ActionLink(T("Add a page").ToString(), "Create", new { }, new { @class = "button" })%></div>
|
||||
<% using (Html.BeginFormAntiForgeryPost())
|
||||
{ %>
|
||||
<%=Html.ValidationSummary()%>
|
||||
@@ -25,7 +26,6 @@
|
||||
</select>
|
||||
<input class="button" type="submit" name="submit.Filter" value="<%=_Encoded("Apply") %>"/>
|
||||
</fieldset>
|
||||
<div class="manage"><%=Html.ActionLink(T("Add a page").ToString(), "Create", new { }, new { @class = "button" })%></div>
|
||||
<fieldset>
|
||||
<table class="items" summary="<%=_Encoded("This is a table of the PageEntries currently available for use in your application.") %>">
|
||||
<colgroup>
|
||||
@@ -102,5 +102,5 @@ pageIndex++;
|
||||
} %>
|
||||
</table>
|
||||
</fieldset>
|
||||
<div class="manage"><%=Html.ActionLink(T("Add a page").ToString(), "Create", new { }, new { @class = "button" })%></div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<div class="manage"><%=Html.ActionLink(T("Add a page").ToString(), "Create", new { }, new { @class = "button" })%></div>
|
||||
Reference in New Issue
Block a user