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:
skewed
2010-01-22 22:25:52 +00:00
parent 040a6e0305
commit d8db9e9c99
6 changed files with 103 additions and 25 deletions

View File

@@ -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[] {

View File

@@ -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;
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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"));

View File

@@ -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));
}
}
}
}

View File

@@ -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>