mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Merge
--HG-- branch : dev
This commit is contained in:
104
src/Orchard.Web/Core/Common/Drivers/CommonDriver.cs
Normal file
104
src/Orchard.Web/Core/Common/Drivers/CommonDriver.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Drivers;
|
||||
using Orchard.Core.Common.Models;
|
||||
using Orchard.Core.Common.ViewModels;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Security;
|
||||
using Orchard.Services;
|
||||
|
||||
namespace Orchard.Core.Common.Drivers {
|
||||
public class CommonDriver : ContentPartDriver<CommonAspect> {
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly IAuthenticationService _authenticationService;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly IMembershipService _membershipService;
|
||||
private readonly IClock _clock;
|
||||
|
||||
public CommonDriver(
|
||||
IContentManager contentManager,
|
||||
IAuthenticationService authenticationService,
|
||||
IAuthorizationService authorizationService,
|
||||
IMembershipService membershipService,
|
||||
IClock clock) {
|
||||
_contentManager = contentManager;
|
||||
_authenticationService = authenticationService;
|
||||
_authorizationService = authorizationService;
|
||||
_membershipService = membershipService;
|
||||
_clock = clock;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
||||
protected override DriverResult Editor(CommonAspect part) {
|
||||
return Combined(OwnerEditor(part, null), ContainerEditor(part, null));
|
||||
}
|
||||
|
||||
protected override DriverResult Editor(CommonAspect instance, ContentManagement.IUpdateModel updater) {
|
||||
// this event is hooked so the modified timestamp is changed when an edit-post occurs.
|
||||
instance.ModifiedUtc = _clock.UtcNow;
|
||||
instance.VersionModifiedUtc = _clock.UtcNow;
|
||||
|
||||
return Combined(OwnerEditor(instance, updater), ContainerEditor(instance, updater));
|
||||
}
|
||||
|
||||
DriverResult OwnerEditor(CommonAspect part, IUpdateModel updater) {
|
||||
var currentUser = _authenticationService.GetAuthenticatedUser();
|
||||
if (!_authorizationService.TryCheckAccess(Permissions.ChangeOwner, currentUser, part)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var model = new OwnerEditorViewModel();
|
||||
if (part.Owner != null)
|
||||
model.Owner = part.Owner.UserName;
|
||||
|
||||
if (updater != null) {
|
||||
var priorOwner = model.Owner;
|
||||
updater.TryUpdateModel(model, "CommonAspect", null, null);
|
||||
|
||||
if (model.Owner != null && model.Owner != priorOwner) {
|
||||
var newOwner = _membershipService.GetUser(model.Owner);
|
||||
if (newOwner == null) {
|
||||
updater.AddModelError("CommonAspect.Owner", T("Invalid user name"));
|
||||
}
|
||||
else {
|
||||
part.Owner = newOwner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ContentPartTemplate(model, "Parts/Common.Owner", "CommonAspect").Location("primary", "10");
|
||||
}
|
||||
|
||||
DriverResult ContainerEditor(CommonAspect part, IUpdateModel updater) {
|
||||
var currentUser = _authenticationService.GetAuthenticatedUser();
|
||||
if (!_authorizationService.TryCheckAccess(Permissions.ChangeOwner, currentUser, part)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var model = new ContainerEditorViewModel();
|
||||
if (part.Container != null)
|
||||
model.ContainerId = part.Container.ContentItem.Id;
|
||||
|
||||
if (updater != null) {
|
||||
var priorContainerId = model.ContainerId;
|
||||
updater.TryUpdateModel(model, "CommonAspect", null, null);
|
||||
|
||||
if (model.ContainerId != null && model.ContainerId != priorContainerId) {
|
||||
var newContainer = _contentManager.Get((int)model.ContainerId, VersionOptions.Latest);
|
||||
if (newContainer == null) {
|
||||
updater.AddModelError("CommonAspect.ContainerId", T("Invalid container"));
|
||||
}
|
||||
else {
|
||||
part.Container = newContainer;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ContentPartTemplate(model, "Parts/Common.Container", "CommonAspect").Location("primary", "10.1");
|
||||
}
|
||||
}
|
||||
}
|
@@ -52,8 +52,8 @@ namespace Orchard.Core.Common.Handlers {
|
||||
OnPublishing<ContentPart<CommonVersionRecord>>(AssignPublishingDates);
|
||||
|
||||
//OnGetDisplayViewModel<CommonAspect>();
|
||||
OnGetEditorViewModel<CommonAspect>(GetEditor);
|
||||
OnUpdateEditorViewModel<CommonAspect>(UpdateEditor);
|
||||
//OnGetEditorViewModel<CommonAspect>(GetEditor);
|
||||
//OnUpdateEditorViewModel<CommonAspect>(UpdateEditor);
|
||||
|
||||
OnIndexing<CommonAspect>((context, commonAspect) => context.IndexDocument
|
||||
.Add("type", commonAspect.ContentItem.ContentType).Analyze(false)
|
||||
@@ -157,48 +157,48 @@ namespace Orchard.Core.Common.Handlers {
|
||||
}
|
||||
|
||||
|
||||
private void GetEditor(BuildEditorModelContext context, CommonAspect instance) {
|
||||
var currentUser = _authenticationService.GetAuthenticatedUser();
|
||||
if (!_authorizationService.TryCheckAccess(Permissions.ChangeOwner, currentUser, instance)) {
|
||||
return;
|
||||
}
|
||||
var viewModel = new OwnerEditorViewModel();
|
||||
if (instance.Owner != null)
|
||||
viewModel.Owner = instance.Owner.UserName;
|
||||
//private void GetEditor(BuildEditorModelContext context, CommonAspect instance) {
|
||||
// var currentUser = _authenticationService.GetAuthenticatedUser();
|
||||
// if (!_authorizationService.TryCheckAccess(Permissions.ChangeOwner, currentUser, instance)) {
|
||||
// return;
|
||||
// }
|
||||
// var viewModel = new OwnerEditorViewModel();
|
||||
// if (instance.Owner != null)
|
||||
// viewModel.Owner = instance.Owner.UserName;
|
||||
|
||||
context.AddEditor(new TemplateViewModel(viewModel, "CommonAspect") { TemplateName = "Parts/Common.Owner", ZoneName = "primary", Position = "999" });
|
||||
}
|
||||
// context.AddEditor(new TemplateViewModel(viewModel, "CommonAspect") { TemplateName = "Parts/Common.Owner", ZoneName = "primary", Position = "999" });
|
||||
//}
|
||||
|
||||
|
||||
private void UpdateEditor(UpdateEditorModelContext context, CommonAspect instance) {
|
||||
// this event is hooked so the modified timestamp is changed when an edit-post occurs.
|
||||
// kind of a loose rule of thumb. may not be sufficient
|
||||
instance.ModifiedUtc = _clock.UtcNow;
|
||||
instance.VersionModifiedUtc = _clock.UtcNow;
|
||||
//private void UpdateEditor(UpdateEditorModelContext context, CommonAspect instance) {
|
||||
// // this event is hooked so the modified timestamp is changed when an edit-post occurs.
|
||||
// // kind of a loose rule of thumb. may not be sufficient
|
||||
// instance.ModifiedUtc = _clock.UtcNow;
|
||||
// instance.VersionModifiedUtc = _clock.UtcNow;
|
||||
|
||||
var currentUser = _authenticationService.GetAuthenticatedUser();
|
||||
if (!_authorizationService.TryCheckAccess(Permissions.ChangeOwner, currentUser, instance)) {
|
||||
return;
|
||||
}
|
||||
// var currentUser = _authenticationService.GetAuthenticatedUser();
|
||||
// if (!_authorizationService.TryCheckAccess(Permissions.ChangeOwner, currentUser, instance)) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
var viewModel = new OwnerEditorViewModel();
|
||||
if (instance.Owner != null)
|
||||
viewModel.Owner = instance.Owner.UserName;
|
||||
// var viewModel = new OwnerEditorViewModel();
|
||||
// if (instance.Owner != null)
|
||||
// viewModel.Owner = instance.Owner.UserName;
|
||||
|
||||
var priorOwner = viewModel.Owner;
|
||||
context.Updater.TryUpdateModel(viewModel, "CommonAspect", null, null);
|
||||
// var priorOwner = viewModel.Owner;
|
||||
// context.Updater.TryUpdateModel(viewModel, "CommonAspect", null, null);
|
||||
|
||||
if (viewModel.Owner != null && viewModel.Owner != priorOwner) {
|
||||
var newOwner = _membershipService.GetUser(viewModel.Owner);
|
||||
if (newOwner == null) {
|
||||
context.Updater.AddModelError("CommonAspect.Owner", T("Invalid user name"));
|
||||
}
|
||||
else {
|
||||
instance.Owner = newOwner;
|
||||
}
|
||||
}
|
||||
// if (viewModel.Owner != null && viewModel.Owner != priorOwner) {
|
||||
// var newOwner = _membershipService.GetUser(viewModel.Owner);
|
||||
// if (newOwner == null) {
|
||||
// context.Updater.AddModelError("CommonAspect.Owner", T("Invalid user name"));
|
||||
// }
|
||||
// else {
|
||||
// instance.Owner = newOwner;
|
||||
// }
|
||||
// }
|
||||
|
||||
context.AddEditor(new TemplateViewModel(viewModel, "CommonAspect") { TemplateName = "Parts/Common.Owner", ZoneName = "primary", Position = "999" });
|
||||
}
|
||||
// context.AddEditor(new TemplateViewModel(viewModel, "CommonAspect") { TemplateName = "Parts/Common.Owner", ZoneName = "primary", Position = "999" });
|
||||
//}
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Orchard.Core.Common.ViewModels {
|
||||
public class ContainerEditorViewModel {
|
||||
|
||||
public int? ContainerId { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<ContainerEditorViewModel>" %>
|
||||
<%@ Import Namespace="Orchard.Core.Common.ViewModels" %>
|
||||
<fieldset>
|
||||
<%=Html.LabelFor(m=>m.ContainerId) %>
|
||||
<%=Html.EditorFor(m=>m.ContainerId) %>
|
||||
<%=Html.ValidationMessageFor(m=>m.ContainerId) %>
|
||||
</fieldset>
|
@@ -66,9 +66,11 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Common\Drivers\BodyDriver.cs" />
|
||||
<Compile Include="Common\Drivers\CommonDriver.cs" />
|
||||
<Compile Include="Common\Drivers\RoutableDriver.cs" />
|
||||
<Compile Include="Common\Controllers\RoutableController.cs" />
|
||||
<Compile Include="Common\Handlers\RoutableAspectHandler.cs" />
|
||||
<Compile Include="Common\ViewModels\ContainerEditorViewModel.cs" />
|
||||
<Compile Include="Contents\Controllers\ItemController.cs" />
|
||||
<Compile Include="Contents\Handlers\ContentsModuleHandler.cs" />
|
||||
<Compile Include="Localization\Drivers\LocalizedDriver.cs" />
|
||||
@@ -155,8 +157,11 @@
|
||||
<Compile Include="Navigation\ViewModels\NavigationManagementViewModel.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Routable\Routes.cs" />
|
||||
<Compile Include="Routable\Services\IRoutableService.cs" />
|
||||
<Compile Include="Routable\Services\RoutablePathConstraint.cs" />
|
||||
<Compile Include="Routable\Services\RoutablePathConstraintUpdator.cs" />
|
||||
<Compile Include="Routable\Services\RoutableService.cs" />
|
||||
<Compile Include="Routable\ViewModels\RoutableEditorViewModel.cs" />
|
||||
<Compile Include="Routable\ViewModels\RoutableDisplayViewModel.cs" />
|
||||
<Compile Include="Scheduling\Models\ScheduledTaskRecord.cs" />
|
||||
<Compile Include="Scheduling\Services\PublishingTaskHandler.cs" />
|
||||
@@ -202,6 +207,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Common\Module.txt" />
|
||||
<Content Include="Common\Views\EditorTemplates\Parts\Common.Container.ascx" />
|
||||
<Content Include="Contents\Module.txt" />
|
||||
<Content Include="Contents\Views\Admin\Types.aspx" />
|
||||
<Content Include="Contents\Views\Admin\List.aspx" />
|
||||
@@ -215,6 +221,8 @@
|
||||
<Content Include="Indexing\Module.txt" />
|
||||
<Content Include="Localization\Module.txt" />
|
||||
<Content Include="Routable\Module.txt" />
|
||||
<Content Include="Routable\Scripts\jquery.slugify.js" />
|
||||
<Content Include="Routable\Views\EditorTemplates\Parts\Routable.IsRoutable.ascx" />
|
||||
<Content Include="Routable\Views\Item\Display.aspx" />
|
||||
<Content Include="Settings\Module.txt" />
|
||||
<Content Include="Settings\Styles\admin.css" />
|
||||
|
@@ -6,16 +6,20 @@ using Orchard.ContentManagement.Aspects;
|
||||
using Orchard.Core.Common.Models;
|
||||
using Orchard.Core.Routable.Models;
|
||||
using Orchard.Core.Routable.ViewModels;
|
||||
using Orchard.Data;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Mvc.ViewModels;
|
||||
|
||||
namespace Orchard.Core.Routable.Controllers {
|
||||
[ValidateInput(false)]
|
||||
public class ItemController : Controller {
|
||||
public class ItemController : Controller, IUpdateModel {
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly ITransactionManager _transactionManager;
|
||||
private readonly IRoutablePathConstraint _routablePathConstraint;
|
||||
|
||||
public ItemController(IContentManager contentManager, IRoutablePathConstraint routablePathConstraint) {
|
||||
public ItemController(IContentManager contentManager, ITransactionManager transactionManager, IRoutablePathConstraint routablePathConstraint) {
|
||||
_contentManager = contentManager;
|
||||
_transactionManager = transactionManager;
|
||||
_routablePathConstraint = routablePathConstraint;
|
||||
}
|
||||
|
||||
@@ -47,5 +51,39 @@ namespace Orchard.Core.Routable.Controllers {
|
||||
itemViewModel.TemplateName = "Items/Contents.Item";
|
||||
}
|
||||
}
|
||||
|
||||
public ActionResult Slugify(string contentType, int? id, int? containerId) {
|
||||
const string slug = "";
|
||||
ContentItem contentItem = null;
|
||||
|
||||
if (string.IsNullOrEmpty(contentType))
|
||||
return Json(slug);
|
||||
|
||||
if (id != null)
|
||||
contentItem = _contentManager.Get((int)id, VersionOptions.Latest);
|
||||
|
||||
if (contentItem == null) {
|
||||
contentItem = _contentManager.New(contentType);
|
||||
|
||||
if (containerId != null) {
|
||||
var containerItem = _contentManager.Get((int)containerId);
|
||||
contentItem.As<ICommonAspect>().Container = containerItem;
|
||||
}
|
||||
}
|
||||
|
||||
_contentManager.UpdateEditorModel(contentItem, this);
|
||||
_transactionManager.Cancel();
|
||||
|
||||
return Json(contentItem.As<IRoutableAspect>().Slug ?? slug);
|
||||
}
|
||||
|
||||
|
||||
bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) {
|
||||
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
|
||||
}
|
||||
|
||||
void IUpdateModel.AddModelError(string key, LocalizedString errorMessage) {
|
||||
ModelState.AddModelError(key, errorMessage.ToString());
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,61 +1,99 @@
|
||||
using JetBrains.Annotations;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Aspects;
|
||||
using Orchard.ContentManagement.Drivers;
|
||||
using Orchard.Core.Common.Models;
|
||||
using Orchard.Core.Common.ViewModels;
|
||||
using Orchard.Core.Common.Services;
|
||||
using Orchard.Core.Routable.Models;
|
||||
using Orchard.Core.Routable.Services;
|
||||
using Orchard.Core.Routable.ViewModels;
|
||||
using Orchard.Localization;
|
||||
using Orchard.UI.Notify;
|
||||
|
||||
namespace Orchard.Core.Routable.Drivers {
|
||||
public class RoutableDriver : ContentPartDriver<IsRoutable> {
|
||||
protected override DriverResult Editor(IsRoutable part, IUpdateModel updater) {
|
||||
part.Record.Title = "Routable #" + part.ContentItem.Id;
|
||||
part.Record.Slug = "routable" + part.ContentItem.Id;
|
||||
part.Record.Path = "routable" + part.ContentItem.Id;
|
||||
return base.Editor(part, updater);
|
||||
private readonly IOrchardServices _services;
|
||||
private readonly IRoutableService _routableService;
|
||||
|
||||
public RoutableDriver(IOrchardServices services, IRoutableService routableService) {
|
||||
_services = services;
|
||||
_routableService = routableService;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
//private const string TemplateName = "Parts/Common.Routable";
|
||||
|
||||
//private readonly IOrchardServices _services;
|
||||
//private readonly IRoutableService _routableService;
|
||||
//public Localizer T { get; set; }
|
||||
private const string TemplateName = "Parts/Routable.IsRoutable";
|
||||
|
||||
//protected override string Prefix {
|
||||
// get { return "Routable"; }
|
||||
//}
|
||||
public Localizer T { get; set; }
|
||||
|
||||
//public Routable(IOrchardServices services, IRoutableService routableService)
|
||||
//{
|
||||
// _services = services;
|
||||
// _routableService = routableService;
|
||||
protected override string Prefix {
|
||||
get { return "Routable"; }
|
||||
}
|
||||
|
||||
// T = NullLocalizer.Instance;
|
||||
//}
|
||||
int? GetContainerId(IContent item) {
|
||||
var commonAspect = item.As<ICommonAspect>();
|
||||
if (commonAspect != null && commonAspect.Container != null) {
|
||||
return commonAspect.Container.ContentItem.Id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//protected override DriverResult Editor(RoutableAspect part) {
|
||||
// var model = new RoutableEditorViewModel { Prefix = Prefix, RoutableAspect = part };
|
||||
// return ContentPartTemplate(model, TemplateName, Prefix).Location("primary", "before.5");
|
||||
//}
|
||||
string GetContainerSlug(IContent item) {
|
||||
var commonAspect = item.As<ICommonAspect>();
|
||||
if (commonAspect != null && commonAspect.Container != null) {
|
||||
var routable = commonAspect.Container.As<IRoutableAspect>();
|
||||
if (routable != null) {
|
||||
return routable.Slug;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//protected override DriverResult Editor(RoutableAspect part, IUpdateModel updater) {
|
||||
// var model = new RoutableEditorViewModel { Prefix = Prefix, RoutableAspect = part };
|
||||
// updater.TryUpdateModel(model, Prefix, null, null);
|
||||
protected override DriverResult Editor(IsRoutable part) {
|
||||
var model = new RoutableEditorViewModel {
|
||||
ContentType = part.ContentItem.ContentType,
|
||||
Id = part.ContentItem.Id,
|
||||
Slug = part.Slug,
|
||||
Title = part.Title,
|
||||
ContainerId = GetContainerId(part),
|
||||
};
|
||||
|
||||
// if (!_routableService.IsSlugValid(part.Slug)){
|
||||
// updater.AddModelError("Routable.Slug", T("Please do not use any of the following characters in your slugs: \"/\", \":\", \"?\", \"#\", \"[\", \"]\", \"@\", \"!\", \"$\", \"&\", \"'\", \"(\", \")\", \"*\", \"+\", \",\", \";\", \"=\". No spaces are allowed (please use dashes or underscores instead).").ToString());
|
||||
// }
|
||||
// TEMP: path format patterns replaces this logic
|
||||
var path = part.Record.Path;
|
||||
if (path.EndsWith(part.Slug)) {
|
||||
model.DisplayLeadingPath = path.Substring(0, path.Length - part.Slug.Length);
|
||||
}
|
||||
|
||||
// string originalSlug = part.Slug;
|
||||
// if(!_routableService.ProcessSlug(part)) {
|
||||
// _services.Notifier.Warning(T("Slugs in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"",
|
||||
// originalSlug, part.Slug, part.ContentItem.ContentType));
|
||||
// }
|
||||
|
||||
// return ContentPartTemplate(model, TemplateName, Prefix).Location("primary", "before.5");
|
||||
//}
|
||||
return ContentPartTemplate(model, TemplateName, Prefix).Location("primary", "before.5");
|
||||
}
|
||||
|
||||
protected override DriverResult Editor(IsRoutable part, IUpdateModel updater) {
|
||||
|
||||
var model = new RoutableEditorViewModel();
|
||||
updater.TryUpdateModel(model, Prefix, null, null);
|
||||
part.Title = model.Title;
|
||||
part.Slug = model.Slug;
|
||||
|
||||
// TEMP: path format patterns replaces this logic
|
||||
var containerSlug = GetContainerSlug(part);
|
||||
if (string.IsNullOrEmpty(containerSlug)) {
|
||||
part.Record.Path = model.Slug;
|
||||
}
|
||||
else {
|
||||
part.Record.Path = containerSlug + "/" + model.Slug;
|
||||
}
|
||||
|
||||
if (!_routableService.IsSlugValid(part.Slug)) {
|
||||
updater.AddModelError("Routable.Slug", T("Please do not use any of the following characters in your slugs: \"/\", \":\", \"?\", \"#\", \"[\", \"]\", \"@\", \"!\", \"$\", \"&\", \"'\", \"(\", \")\", \"*\", \"+\", \",\", \";\", \"=\". No spaces are allowed (please use dashes or underscores instead).").ToString());
|
||||
}
|
||||
|
||||
string originalSlug = part.Slug;
|
||||
if (!_routableService.ProcessSlug(part)) {
|
||||
_services.Notifier.Warning(T("Slugs in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"",
|
||||
originalSlug, part.Slug, part.ContentItem.ContentType));
|
||||
}
|
||||
|
||||
return Editor(part);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
24
src/Orchard.Web/Core/Routable/Scripts/jquery.slugify.js
Normal file
24
src/Orchard.Web/Core/Routable/Scripts/jquery.slugify.js
Normal file
@@ -0,0 +1,24 @@
|
||||
jQuery.fn.extend({
|
||||
slugify: function(options) {
|
||||
//todo: (heskew) need messaging system
|
||||
if (!options.target || !options.url)
|
||||
return;
|
||||
|
||||
var args = {
|
||||
"contentType": options.contentType,
|
||||
"id": options.id,
|
||||
"containerId": options.containerId,
|
||||
__RequestVerificationToken: $("input[name=__RequestVerificationToken]").val()
|
||||
};
|
||||
args[$(this).attr("name")] = $(this).val();
|
||||
|
||||
jQuery.post(
|
||||
options.url,
|
||||
args,
|
||||
function(data) {
|
||||
options.target.val(data);
|
||||
},
|
||||
"json"
|
||||
);
|
||||
}
|
||||
});
|
28
src/Orchard.Web/Core/Routable/Services/IRoutableService.cs
Normal file
28
src/Orchard.Web/Core/Routable/Services/IRoutableService.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Core.Routable.Models;
|
||||
|
||||
namespace Orchard.Core.Routable.Services {
|
||||
public interface IRoutableService : IDependency {
|
||||
void FillSlug<TModel>(TModel model) where TModel : IsRoutable;
|
||||
void FillSlug<TModel>(TModel model, Func<string, string> generateSlug) where TModel : IsRoutable;
|
||||
string GenerateUniqueSlug(string slugCandidate, IEnumerable<string> existingSlugs);
|
||||
|
||||
/// <summary>
|
||||
/// Returns any content item of the specified content type with similar slugs
|
||||
/// </summary>
|
||||
IEnumerable<IsRoutable> GetSimilarSlugs(string contentType, string slug);
|
||||
|
||||
/// <summary>
|
||||
/// Validates the given slug
|
||||
/// </summary>
|
||||
bool IsSlugValid(string slug);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the slug of a RoutableAspect and validate its unicity
|
||||
/// </summary>
|
||||
/// <returns>True if the slug has been created, False if a conflict occured</returns>
|
||||
bool ProcessSlug(IsRoutable part);
|
||||
|
||||
}
|
||||
}
|
113
src/Orchard.Web/Core/Routable/Services/RoutableService.cs
Normal file
113
src/Orchard.Web/Core/Routable/Services/RoutableService.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using JetBrains.Annotations;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Core.Common.Models;
|
||||
using Orchard.Core.Routable.Models;
|
||||
using Orchard.Localization;
|
||||
using Orchard.UI.Notify;
|
||||
|
||||
namespace Orchard.Core.Routable.Services {
|
||||
[UsedImplicitly]
|
||||
public class RoutableService : IRoutableService {
|
||||
private readonly IContentManager _contentManager;
|
||||
|
||||
public RoutableService(IContentManager contentManager) {
|
||||
_contentManager = contentManager;
|
||||
}
|
||||
|
||||
public void FillSlug<TModel>(TModel model) where TModel : IsRoutable {
|
||||
if (!string.IsNullOrEmpty(model.Slug) || string.IsNullOrEmpty(model.Title))
|
||||
return;
|
||||
|
||||
var slug = model.Title;
|
||||
var dissallowed = new Regex(@"[/:?#\[\]@!$&'()*+,;=\s]+");
|
||||
|
||||
slug = dissallowed.Replace(slug, "-");
|
||||
slug = slug.Trim('-');
|
||||
|
||||
if (slug.Length > 1000)
|
||||
slug = slug.Substring(0, 1000);
|
||||
|
||||
model.Slug = slug.ToLowerInvariant();
|
||||
}
|
||||
|
||||
public void FillSlug<TModel>(TModel model, Func<string, string> generateSlug) where TModel : IsRoutable {
|
||||
if (!string.IsNullOrEmpty(model.Slug) || string.IsNullOrEmpty(model.Title))
|
||||
return;
|
||||
|
||||
model.Slug = generateSlug(model.Title).ToLowerInvariant();
|
||||
}
|
||||
|
||||
public string GenerateUniqueSlug(string slugCandidate, IEnumerable<string> existingSlugs) {
|
||||
if (existingSlugs == null || !existingSlugs.Contains(slugCandidate))
|
||||
return slugCandidate;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public IEnumerable<IsRoutable> GetSimilarSlugs(string contentType, string slug)
|
||||
{
|
||||
return
|
||||
_contentManager.Query(contentType).Join<RoutableRecord>()
|
||||
.List()
|
||||
.Select(i => i.As<IsRoutable>())
|
||||
.Where(routable => routable.Slug.StartsWith(slug, StringComparison.OrdinalIgnoreCase)) // todo: for some reason the filter doesn't work within the query, even without StringComparison or StartsWith
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public bool IsSlugValid(string slug) {
|
||||
// see http://tools.ietf.org/html/rfc3987 for prohibited chars
|
||||
return slug == null || String.IsNullOrEmpty(slug.Trim()) || Regex.IsMatch(slug, @"^[^/:?#\[\]@!$&'()*+,;=\s]+$");
|
||||
}
|
||||
|
||||
public bool ProcessSlug(IsRoutable part)
|
||||
{
|
||||
FillSlug(part);
|
||||
|
||||
if (string.IsNullOrEmpty(part.Slug))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var slugsLikeThis = GetSimilarSlugs(part.ContentItem.ContentType, part.Slug);
|
||||
|
||||
// If the part is already a valid content item, don't include it in the list
|
||||
// of slug to consider for conflict detection
|
||||
if (part.ContentItem.Id != 0)
|
||||
slugsLikeThis = slugsLikeThis.Where(p => p.ContentItem.Id != part.ContentItem.Id);
|
||||
|
||||
//todo: (heskew) need better messages
|
||||
if (slugsLikeThis.Count() > 0)
|
||||
{
|
||||
var originalSlug = part.Slug;
|
||||
//todo: (heskew) make auto-uniqueness optional
|
||||
part.Slug = GenerateUniqueSlug(part.Slug, slugsLikeThis.Select(p => p.Slug));
|
||||
|
||||
if (originalSlug != part.Slug) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
using Orchard.Mvc.ViewModels;
|
||||
|
||||
namespace Orchard.Core.Routable.ViewModels {
|
||||
public class RoutableDisplayViewModel : BaseViewModel {
|
||||
public class RoutableDisplayViewModel : BaseViewModel {
|
||||
public ContentItemViewModel<IRoutableAspect> Routable {get;set;}
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Orchard.Core.Routable.ViewModels {
|
||||
public class RoutableEditorViewModel {
|
||||
|
||||
public int Id { get; set; }
|
||||
public string ContentType { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Title { get; set; }
|
||||
public string Slug { get; set; }
|
||||
public int? ContainerId { get; set; }
|
||||
|
||||
public string DisplayLeadingPath { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<Orchard.Core.Routable.ViewModels.RoutableEditorViewModel>" %>
|
||||
<%@ Import Namespace="Orchard.Utility.Extensions"%>
|
||||
<%@ Import Namespace="Orchard.ContentManagement.Extenstions"%>
|
||||
|
||||
<% Html.RegisterFootScript("jquery.slugify.js"); %>
|
||||
<fieldset>
|
||||
<%=Html.LabelFor(m => m.Title) %>
|
||||
<%=Html.TextBoxFor(m => m.Title, new { @class = "large text" }) %>
|
||||
</fieldset>
|
||||
<fieldset class="permalink">
|
||||
<label class="sub" for="Slug"><%=_Encoded("Permalink")%><br /><span><%=Html.Encode(Request.ToRootUrlString())%>/<%:Model.DisplayLeadingPath %></span></label>
|
||||
<span><%=Html.TextBoxFor(m => m.Slug, new { @class = "text" })%></span>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<% using (this.Capture("end-of-page-scripts")) { %>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
//pull slug input from tab order
|
||||
$("#<%:Html.FieldIdFor(m=>m.Slug)%>").attr("tabindex",-1);
|
||||
$("#<%:Html.FieldIdFor(m=>m.Title)%>").blur(function(){
|
||||
$(this).slugify({
|
||||
target:$("#<%:Html.FieldIdFor(m=>m.Slug)%>"),
|
||||
url:"<%=Url.Action("Slugify","Item",new RouteValueDictionary{{"Area","Routable"}})%>",
|
||||
contentType:"<%=Model.ContentType %>",
|
||||
id:"<%=Model.Id %>" <%if (Model.ContainerId != null) { %>,
|
||||
containerId:<%=Model.ContainerId %><%} %>
|
||||
})
|
||||
})
|
||||
})</script>
|
||||
<% } %>
|
@@ -10,16 +10,18 @@ if (Model.Entries.Count() > 0) { %>
|
||||
entry.ContentItemViewModel.Zones.AddAction("meta", html => {
|
||||
int draftCount = entry.TotalPostCount - entry.ContentItemViewModel.Item.PostCount;
|
||||
int totalPostCount = entry.TotalPostCount;
|
||||
var draftText = (draftCount == 0 ? "": string.Format(" ({0} draft{1})", draftCount, draftCount == 1 ? "" : "s"));
|
||||
|
||||
var linkText = T.Plural("1 post", "{0} posts", totalPostCount).ToString();
|
||||
if (draftCount==0){
|
||||
linkText = linkText + " (" + T.Plural("1 draft", "{0} drafts", draftCount).ToString() + ")";
|
||||
}
|
||||
|
||||
var linkContent = _Encoded("{0} post{1}{2}", totalPostCount, totalPostCount == 1 ? "" : "s", draftText);
|
||||
|
||||
html.ViewContext.Writer.Write(html.Link(linkContent.ToString(), Url.BlogForAdmin(entry.ContentItemViewModel.Item.Slug)));
|
||||
html.ViewContext.Writer.Write(html.Link(linkText, Url.BlogForAdmin(entry.ContentItemViewModel.Item.Slug)));
|
||||
});
|
||||
|
||||
// Display the summary for the blog post
|
||||
return Html.DisplayForItem(entry.ContentItemViewModel).ToHtmlString();
|
||||
}, "blogs contentItems")%><%
|
||||
} else { %>
|
||||
<div class="info message"><%=T("There are no blogs for you to see. Want to <a href=\"{0}\">add one</a>?", Url.BlogCreate()).ToString()%></div><%
|
||||
<div class="info message"><%:T("There are no blogs for you to see. Want to <a href=\"{0}\">add one</a>?", Url.BlogCreate())%></div><%
|
||||
} %>
|
@@ -4,4 +4,4 @@
|
||||
<%@ Import Namespace="Orchard.Blogs.Models"%>
|
||||
<h2><%=Html.Link(Html.Encode(Model.Item.Name), Url.Blog(Model.Item.Slug)) %></h2>
|
||||
<% if (!string.IsNullOrEmpty(Model.Item.Description)) { %><p><%: Model.Item.Description %></p><% } %>
|
||||
<div class="blog metadata"><%: T("{0} post{1}", Model.Item.PostCount, Model.Item.PostCount == 1 ? "" : "s")%> | <%Html.Zone("meta");%></div>
|
||||
<div class="blog metadata"><%: T.Plural("1 post", "{0} posts", Model.Item.PostCount)%> | <%Html.Zone("meta");%></div>
|
||||
|
@@ -2,6 +2,7 @@ using System.Web.Mvc;
|
||||
using System.Web.Mvc.Html;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Mvc.Html;
|
||||
using Orchard.Utility.Extensions;
|
||||
|
||||
namespace Orchard.Comments.Extensions {
|
||||
@@ -11,14 +12,14 @@ namespace Orchard.Comments.Extensions {
|
||||
|
||||
if (item.Id != 0) {
|
||||
var totalCommentCount = commentCount + pendingCount;
|
||||
|
||||
var totalCommentText = T.Plural("1 comment", "{0} comments", totalCommentCount);
|
||||
if (totalCommentCount == 0) {
|
||||
commentText += html.Encode(T("0 comments"));
|
||||
commentText += totalCommentText.ToString();
|
||||
}
|
||||
else {
|
||||
commentText +=
|
||||
html.ActionLink(
|
||||
T("{0} comment{1}", totalCommentCount, totalCommentCount == 1 ? "" : "s").ToString(),
|
||||
totalCommentText.ToString(),
|
||||
"Details",
|
||||
new {
|
||||
Area = "Orchard.Comments",
|
||||
|
@@ -1,3 +1,3 @@
|
||||
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<CommentCountViewModel>" %>
|
||||
<%@ Import Namespace="Orchard.Comments.ViewModels"%>
|
||||
<span class="commentcount"><%: T("{0} Comment{1}", Model.CommentCount, Model.CommentCount == 1 ? "" : "s")%></span>
|
||||
<span class="commentcount"><%: T.Plural("1 Comment", "{0} Comments", Model.CommentCount)%></span>
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<%@ Import Namespace="Orchard.Utility.Extensions" %>
|
||||
<%-- todo: clean up this template - waaay too much going on in here :/ --%><%
|
||||
if (Model.Comments.Count > 0) { %>
|
||||
<h2 id="comments"><%: T("{0} Comment{1}", Model.Comments.Count, Model.Comments.Count == 1 ? "" : "s")%></h2>
|
||||
<h2 id="comments"><%: T.Plural("1 Comment", "{0} Comments", Model.Comments.Count)%></h2>
|
||||
<% Html.RenderPartial("ListOfComments", Model.Comments);
|
||||
}
|
||||
|
||||
|
@@ -4,5 +4,5 @@
|
||||
<%@ Import Namespace="Orchard.Blogs.Models"%>
|
||||
|
||||
<h3><%=Html.Link(Html.Encode(Model.Item.Name), Url.Blog(Model.Item.Slug)) %></h3>
|
||||
<div class="blog meta"><a href="<%=Url.Blog(Model.Item.Slug) %>"><%: T("{0} post{1}", Model.Item.PostCount, Model.Item.PostCount == 1 ? "" : "s")%></a></div>
|
||||
<div class="blog meta"><a href="<%=Url.Blog(Model.Item.Slug) %>"><%: T.Plural("1 post", "{0} posts", Model.Item.PostCount)%></a></div>
|
||||
<div class="blogdescription"><p><%: Model.Item.Description %></p></div>
|
||||
|
@@ -4,5 +4,5 @@
|
||||
<%@ Import Namespace="Orchard.Blogs.Models"%>
|
||||
|
||||
<h3><%=Html.Link(Html.Encode(Model.Item.Name), Url.Blog(Model.Item.Slug)) %></h3>
|
||||
<div class="blog meta"><a href="<%=Url.Blog(Model.Item.Slug) %>"><%: T("{0} post{1}", Model.Item.PostCount, Model.Item.PostCount == 1 ? "" : "s")%></a></div>
|
||||
<div class="blog meta"><a href="<%=Url.Blog(Model.Item.Slug) %>"><%: T.Plural("1 post", "{0} posts", Model.Item.PostCount)%></a></div>
|
||||
<div class="blogdescription"><p><%: Model.Item.Description %></p></div>
|
||||
|
@@ -2,9 +2,10 @@
|
||||
<%@ Import Namespace="Orchard.Comments"%>
|
||||
<%@ Import Namespace="Orchard.Security" %>
|
||||
<%@ Import Namespace="Orchard.Comments.Models" %>
|
||||
|
||||
<%-- todo: clean up this template - waaay too much going on in here :/ --%><%
|
||||
if (Model.Comments.Count > 0) { %>
|
||||
<h2 id="comments"><%: T("{0} Comment{1}", Model.Comments.Count, Model.Comments.Count == 1 ? "" : "s")%></h2>
|
||||
<h2 id="comments"><%: T.Plural("1 Comment", "{0} Comments", Model.Comments.Count)%></h2>
|
||||
<% Html.RenderPartial("ListOfComments", Model.Comments);
|
||||
}
|
||||
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<%@ Import Namespace="Orchard.Comments.Models" %>
|
||||
<%-- todo: clean up this template - waaay too much going on in here :/ --%><%
|
||||
if (Model.Comments.Count > 0) { %>
|
||||
<h2 id="comments"><%: T("{0} Comment{1}", Model.Comments.Count, Model.Comments.Count == 1 ? "" : "s")%></h2>
|
||||
<h2 id="comments"><%: T.Plural("1 Comment", "{0} Comments", Model.Comments.Count)%></h2>
|
||||
<% Html.RenderPartial("ListOfComments", Model.Comments);
|
||||
}
|
||||
|
||||
|
@@ -3,5 +3,5 @@
|
||||
<%@ Import Namespace="Orchard.Blogs.Extensions"%>
|
||||
<%@ Import Namespace="Orchard.Blogs.Models"%>
|
||||
<h3><%=Html.Link(Html.Encode(Model.Item.Name), Url.Blog(Model.Item.Slug)) %></h3>
|
||||
<div class="blog meta"><a href="<%=Url.Blog(Model.Item.Slug) %>"><%: T("{0} post{1}", Model.Item.PostCount, Model.Item.PostCount == 1 ? "" : "s")%></a></div>
|
||||
<div class="blog meta"><a href="<%=Url.Blog(Model.Item.Slug) %>"><%: T.Plural("1 post", "{0} posts", Model.Item.PostCount)%></a></div>
|
||||
<div class="blogdescription"><p><%: Model.Item.Description %></p></div>
|
||||
|
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
|
||||
namespace Orchard.Localization {
|
||||
public class LocalizedString : MarshalByRefObject {
|
||||
public class LocalizedString : MarshalByRefObject, IHtmlString {
|
||||
private readonly string _localized;
|
||||
|
||||
public LocalizedString(string localized) {
|
||||
@@ -20,6 +21,10 @@ namespace Orchard.Localization {
|
||||
return _localized;
|
||||
}
|
||||
|
||||
string IHtmlString.ToHtmlString() {
|
||||
return _localized;
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
var hashCode = 0;
|
||||
if (_localized != null)
|
||||
@@ -31,8 +36,9 @@ namespace Orchard.Localization {
|
||||
if (obj == null || obj.GetType() != GetType())
|
||||
return false;
|
||||
|
||||
var that = (LocalizedString) obj;
|
||||
var that = (LocalizedString)obj;
|
||||
return string.Equals(_localized, that._localized);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,15 @@
|
||||
using System.Linq;
|
||||
using Orchard.Localization;
|
||||
|
||||
namespace Orchard.Localization {
|
||||
public delegate LocalizedString Localizer(string text, params object[] args);
|
||||
|
||||
}
|
||||
|
||||
namespace Orchard.Mvc.Html {
|
||||
public static class LocalizerExtensions {
|
||||
public static LocalizedString Plural(this Localizer T, string textSingular, string textPlural, int count, params object[] args) {
|
||||
return T(count == 1 ? textSingular : textPlural, new object[] { count }.Concat(args).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Orchard.Localization.Services;
|
||||
using Orchard.Logging;
|
||||
@@ -23,9 +26,25 @@ namespace Orchard.Localization {
|
||||
string currentCulture = _cultureManager.GetCurrentCulture(HttpContext.Current);
|
||||
var localizedFormat = _resourceManager.GetLocalizedString(_scope, textHint, currentCulture);
|
||||
|
||||
return args.Length < 1
|
||||
? new LocalizedString(localizedFormat)
|
||||
: new LocalizedString(string.Format(localizedFormat, args));
|
||||
return args.Length == 0
|
||||
? new LocalizedString(localizedFormat)
|
||||
: string.Format(GetFormatProvider(currentCulture), localizedFormat, args.Select(Encode).ToArray());
|
||||
}
|
||||
|
||||
private static IFormatProvider GetFormatProvider(string currentCulture) {
|
||||
try {
|
||||
return CultureInfo.GetCultureInfoByIetfLanguageTag(currentCulture);
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static object Encode(object arg) {
|
||||
if (arg is IFormattable || arg is IHtmlString) {
|
||||
return arg;
|
||||
}
|
||||
return HttpUtility.HtmlEncode(arg);
|
||||
}
|
||||
}
|
||||
}
|
@@ -23,6 +23,14 @@ namespace Orchard.Mvc.Html {
|
||||
return Reflect.NameOf(html.ViewData.Model, expression);
|
||||
}
|
||||
|
||||
public static string FieldNameFor<T, TResult>(this HtmlHelper<T> html, Expression<Func<T, TResult>> expression) {
|
||||
return html.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression));
|
||||
}
|
||||
public static string FieldIdFor<T, TResult>(this HtmlHelper<T> html, Expression<Func<T, TResult>> expression) {
|
||||
return html.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(expression));
|
||||
}
|
||||
|
||||
|
||||
public static MvcHtmlString SelectOption<T>(this HtmlHelper html, T currentValue, T optionValue, string text) {
|
||||
return SelectOption(html, optionValue, object.Equals(optionValue, currentValue), text);
|
||||
}
|
||||
|
Reference in New Issue
Block a user