mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-25 10:59:18 +08:00
Localization improvements (#7596)
Updates to handling of MasterContentItem (as they are in 1.10.x) Synchronization of CultureNeutral Fields and Parts (based on Cloning) Support for multi-language Blogs (in its own feature in the Blogs module) Support for multi-language Taxonomies (in its own feature in the Taxonomies module)
This commit is contained in:
committed by
Sébastien Ros
parent
a9066c0ade
commit
4cb0b3b5c4
@@ -0,0 +1,83 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web.Routing;
|
||||
|
||||
using Orchard.Autoroute.Models;
|
||||
using Orchard.Autoroute.Services;
|
||||
using Orchard.Blogs.Models;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Aspects;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Core.Common.Models;
|
||||
using Orchard.Core.Title.Models;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Localization.Models;
|
||||
using Orchard.Localization.Services;
|
||||
using Orchard.UI.Notify;
|
||||
|
||||
namespace Orchard.Blogs.BlogsLocalizationExtensions.Handlers {
|
||||
[OrchardFeature("Orchard.Blogs.LocalizationExtensions")]
|
||||
public class BlogPostPartHandler : ContentHandler {
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly IAutorouteService _routeService;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
|
||||
public BlogPostPartHandler(RequestContext requestContext, IContentManager contentManager, IAutorouteService routeService, ILocalizationService localizationService, INotifier notifier) {
|
||||
_contentManager = contentManager;
|
||||
_routeService = routeService;
|
||||
_localizationService = localizationService;
|
||||
Notifier = notifier;
|
||||
T = NullLocalizer.Instance;
|
||||
//move posts when created, updated or published
|
||||
//changed OnCreating and OnUpdating in OnCreated and OnUpdated so LocalizationPart is already populated
|
||||
OnCreated<BlogPostPart>((context, part) => MigrateBlogPost(context.ContentItem));
|
||||
OnUpdated<BlogPostPart>((context, part) => MigrateBlogPost(context.ContentItem));
|
||||
OnPublishing<BlogPostPart>((context, part) => MigrateBlogPost(context.ContentItem));
|
||||
}
|
||||
|
||||
public INotifier Notifier { get; set; }
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
||||
//This Method checks the blog post's culture and it's parent blog's culture and moves it to the correct blog if they aren't equal.
|
||||
private void MigrateBlogPost(ContentItem blogPost) {
|
||||
if (!blogPost.Has<LocalizationPart>() || !blogPost.Has<BlogPostPart>()) {
|
||||
return;
|
||||
}
|
||||
//bolgPost just cloned for translation, never saved
|
||||
if (blogPost.As<CommonPart>().Container == null) {
|
||||
return;
|
||||
}
|
||||
var blog = _contentManager.Get(blogPost.As<CommonPart>().Container.Id);
|
||||
if (!blog.Has<LocalizationPart>() || blog.As<LocalizationPart>().Culture == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//get our 2 cultures for comparison
|
||||
var blogCulture = blog.As<LocalizationPart>().Culture;
|
||||
var blogPostCulture = blogPost.As<LocalizationPart>().Culture;
|
||||
|
||||
//if the post is a different culture than the parent blog change the post's parent blog to the right localization...
|
||||
if (blogPostCulture != null && (blogPostCulture.Id != blogCulture.Id)) {
|
||||
//Get the id of the current blog
|
||||
var blogids = new HashSet<int> { blog.As<BlogPart>().ContentItem.Id };
|
||||
|
||||
//seek for same culture blog
|
||||
var realBlog = _localizationService.GetLocalizations(blog).SingleOrDefault(w => w.As<LocalizationPart>().Culture == blogPostCulture);
|
||||
if (realBlog.Has<LocalizationPart>() && realBlog.As<LocalizationPart>().Culture.Id == blogPostCulture.Id) {
|
||||
blogPost.As<ICommonPart>().Container = realBlog;
|
||||
if (blogPost.Has<AutoroutePart>()) {
|
||||
_routeService.RemoveAliases(blogPost.As<AutoroutePart>());
|
||||
blogPost.As<AutoroutePart>().DisplayAlias = _routeService.GenerateAlias(blogPost.As<AutoroutePart>());
|
||||
_routeService.PublishAlias(blogPost.As<AutoroutePart>());
|
||||
}
|
||||
Notifier.Information(T("Your Post has been moved under the \"{0}\" Blog", realBlog.As<TitlePart>().Title));
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.Data.Migration;
|
||||
using Orchard.Environment.Extensions;
|
||||
|
||||
namespace Orchard.Blogs.BlogsLocalizationExtensions.Migrations {
|
||||
[OrchardFeature("Orchard.Blogs.LocalizationExtensions")]
|
||||
public class Migrations : DataMigrationImpl {
|
||||
public int Create() {
|
||||
ContentDefinitionManager.AlterTypeDefinition("Blog",
|
||||
cfg => cfg
|
||||
.WithPart("LocalizationPart"));
|
||||
ContentDefinitionManager.AlterTypeDefinition("BlogPost",
|
||||
cfg => cfg
|
||||
.WithPart("LocalizationPart"));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,3 +14,8 @@ Features:
|
||||
Description: Blog easier using a dedicated MetaWeblogAPI-compatible publishing tool.
|
||||
Dependencies: XmlRpc, Orchard.Autoroute, Orchard.ContentPicker
|
||||
Category: Content Publishing
|
||||
Orchard.Blogs.LocalizationExtensions:
|
||||
Name: Blog multi-language support
|
||||
Description: Extend Orchard Blogs module with fully integrated multi-language support.
|
||||
Dependencies: Orchard.Localization
|
||||
Category: Content
|
||||
|
||||
@@ -93,6 +93,8 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AdminMenu.cs" />
|
||||
<Compile Include="BlogsLocalizationExtensions\Handlers\BlogPostPartHandler.cs" />
|
||||
<Compile Include="BlogsLocalizationExtensions\Migrations\Migrations.cs" />
|
||||
<Compile Include="Commands\BlogWidgetCommands.cs" />
|
||||
<Compile Include="Controllers\RemoteBlogPublishingController.cs" />
|
||||
<Compile Include="Drivers\BlogArchivesPartDriver.cs" />
|
||||
@@ -193,6 +195,10 @@
|
||||
<Project>{f301ef7d-f19c-4d83-aa94-cb64f29c037d}</Project>
|
||||
<Name>Orchard.ContentPicker</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.Localization\Orchard.Localization.csproj">
|
||||
<Project>{fbc8b571-ed50-49d8-8d9d-64ab7454a0d6}</Project>
|
||||
<Name>Orchard.Localization</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.Widgets\Orchard.Widgets.csproj">
|
||||
<Project>{194d3ccc-1153-474d-8176-fde8d7d0d0bd}</Project>
|
||||
<Name>Orchard.Widgets</Name>
|
||||
|
||||
@@ -58,21 +58,21 @@ namespace Orchard.Localization.Controllers
|
||||
// pass a dummy content to the authorization check to check for "own" variations
|
||||
var dummyContent = _contentManager.New(masterContentItem.ContentType);
|
||||
|
||||
var contentItemTranslation = _contentManager.Clone(masterContentItem);
|
||||
|
||||
if (!Services.Authorizer.Authorize(Permissions.EditContent, contentItemTranslation, T("Couldn't create translated content")))
|
||||
if (!Services.Authorizer.Authorize(Permissions.EditContent, dummyContent, T("Couldn't create translated content")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
var contentItemTranslation = _contentManager.Clone(masterContentItem);
|
||||
|
||||
var localizationPart = contentItemTranslation.As<LocalizationPart>();
|
||||
if(localizationPart != null) {
|
||||
localizationPart.MasterContentItem = masterContentItem;
|
||||
localizationPart.MasterContentItem = masterLocalizationPart.MasterContentItem == null ? masterContentItem : masterLocalizationPart.MasterContentItem;
|
||||
localizationPart.Culture = string.IsNullOrWhiteSpace(to) ? null : _cultureManager.GetCultureByName(to);
|
||||
}
|
||||
|
||||
Services.Notifier.Success(T("Successfully cloned. The translated content was saved as a draft."));
|
||||
|
||||
var adminRouteValues = _contentManager.GetItemMetadata(contentItemTranslation).AdminRouteValues;
|
||||
return RedirectToRoute(adminRouteValues);
|
||||
var editorRouteValues = _contentManager.GetItemMetadata(contentItemTranslation).EditorRouteValues;
|
||||
return RedirectToRoute(editorRouteValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Globalization;
|
||||
using Orchard.ContentManagement.MetaData.Builders;
|
||||
using Orchard.Environment.Extensions;
|
||||
|
||||
namespace Orchard.Localization.Extensions {
|
||||
[OrchardFeature("Orchard.Localization.CultureNeutralPartsAndFields")]
|
||||
public static class MetaDataExtensions {
|
||||
/// <summary>
|
||||
/// Sets the ContentField being built as CultureNeutral. This field will then be synchronized across elements of a localization set.
|
||||
/// </summary>
|
||||
/// <param name="builder"></param>
|
||||
/// <param name="cultureNeutral"></param>
|
||||
/// <returns></returns>
|
||||
public static ContentPartFieldDefinitionBuilder CultureNeutral(this ContentPartFieldDefinitionBuilder builder, bool cultureNeutral = true) {
|
||||
return builder.WithSetting("LocalizationCultureNeutralitySettings.CultureNeutral", cultureNeutral.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets the ContentPart being built as CultureNeutral. This part will then be synchronized across elements of a localization set.
|
||||
/// </summary>
|
||||
/// <param name="builder"></param>
|
||||
/// <param name="cultureNeutral"></param>
|
||||
/// <returns></returns>
|
||||
public static ContentTypePartDefinitionBuilder CultureNeutral(this ContentTypePartDefinitionBuilder builder, bool cultureNeutral = true) {
|
||||
return builder.WithSetting("LocalizationCultureNeutralitySettings.CultureNeutral", cultureNeutral.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Drivers;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Localization.Models;
|
||||
using Orchard.Localization.Services;
|
||||
using Orchard.Localization.Settings;
|
||||
|
||||
namespace Orchard.Localization.Handlers {
|
||||
[OrchardFeature("Orchard.Localization.CultureNeutralPartsAndFields")]
|
||||
public class LocalizationCultureNeutralityHandler : ContentHandler {
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IEnumerable<IContentFieldDriver> _fieldDrivers;
|
||||
private readonly IEnumerable<IContentPartDriver> _partDrivers;
|
||||
public LocalizationCultureNeutralityHandler(ILocalizationService localizationService,
|
||||
IEnumerable<IContentFieldDriver> fieldDrivers,
|
||||
IEnumerable<IContentPartDriver> partDrivers) {
|
||||
_localizationService = localizationService;
|
||||
_fieldDrivers = fieldDrivers;
|
||||
_partDrivers = partDrivers;
|
||||
|
||||
OnPublished<IContent>(SynchronizeOnPublish);
|
||||
}
|
||||
|
||||
protected void SynchronizeOnPublish(PublishContentContext context, IContent part) {
|
||||
//Conditions to try and start a synchronization:
|
||||
// && The content item is localizable
|
||||
// - The content item has a LocalizationPart
|
||||
// - We can check this on either the type or the item itself
|
||||
// && The part has the CultureNeutral setting set to true
|
||||
//After eventually synchronizing the part, we check whether we should be synchronizing any of its fields
|
||||
// - Go through all the fields and check the CultureNeutral setting.
|
||||
var locPart = part.ContentItem.As<LocalizationPart>();
|
||||
if (locPart != null) {
|
||||
//given the LocalizationPart, get the localization set (all the ContentItems on which we'll try to synchronize)
|
||||
var lSet = GetSynchronizationSet(locPart);
|
||||
//cycle through all parts
|
||||
foreach (var pa in part.ContentItem.Parts) {
|
||||
if (pa.Settings.GetModel<LocalizationCultureNeutralitySettings>().CultureNeutral) {
|
||||
Synchronize(pa, locPart, lSet);
|
||||
}
|
||||
foreach (var field in pa.Fields.Where(fi => fi.PartFieldDefinition.Settings.GetModel<LocalizationCultureNeutralitySettings>().CultureNeutral)) {
|
||||
Synchronize(field, locPart, lSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method attempts to synchronize a part across the localization set
|
||||
/// </summary>
|
||||
/// <param name="part">The part that has just been published and that we wish to use to update all corresponding parts from
|
||||
/// the other elements of the localization set.</param>
|
||||
/// <param name="localizationPart">The localization part of the ContentItem that was just published.</param>
|
||||
/// <param name="lSet">The localization set for the synchronization</param>
|
||||
private void Synchronize(ContentPart part, LocalizationPart localizationPart, List<LocalizationPart> lSet) {
|
||||
if (lSet.Count > 0) {
|
||||
var partDrivers = _partDrivers.Where(cpd => cpd.GetPartInfo().FirstOrDefault().PartName == part.PartDefinition.Name);
|
||||
//use cloning
|
||||
foreach (var target in lSet.Select(lp => lp.ContentItem)) {
|
||||
var context = new CloneContentContext(localizationPart.ContentItem, target);
|
||||
partDrivers.Invoke(driver => driver.Cloning(context), context.Logger);
|
||||
partDrivers.Invoke(driver => driver.Cloned(context), context.Logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// This method attempts to synchronize a field across the localization set
|
||||
/// </summary>
|
||||
/// <param name="field">The field that has just been published and that we wish to use to update all corresponding parts from
|
||||
/// the other elements of the localization set.</param>
|
||||
/// <param name="localizationPart">The localization part of the ContentItem that was just published.</param>
|
||||
/// <param name="lSet">The localization set for the synchronization</param>
|
||||
private void Synchronize(ContentField field, LocalizationPart localizationPart, List<LocalizationPart> lSet) {
|
||||
if (lSet.Count > 0) {
|
||||
var fieldDrivers = _fieldDrivers.Where(cfd => cfd.GetFieldInfo().FirstOrDefault().FieldTypeName == field.FieldDefinition.Name);
|
||||
//use cloning
|
||||
foreach (var target in lSet.Select(lp => lp.ContentItem)) {
|
||||
var context = new CloneContentContext(localizationPart.ContentItem, target);
|
||||
context.FieldName = field.Name;
|
||||
fieldDrivers.Invoke(driver => driver.Cloning(context), context.Logger);
|
||||
fieldDrivers.Invoke(driver => driver.Cloned(context), context.Logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<LocalizationPart> GetSynchronizationSet(LocalizationPart lPart) {
|
||||
var lSet = _localizationService.GetLocalizations(
|
||||
content: lPart.ContentItem,
|
||||
versionOptions: VersionOptions.Published).ToList();
|
||||
lSet.AddRange(_localizationService.GetLocalizations(
|
||||
content: lPart.ContentItem,
|
||||
versionOptions: VersionOptions.Latest));
|
||||
return lSet.Distinct().Where(lp => lp.Id != lPart.Id).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ AntiForgery: enabled
|
||||
Author: The Orchard Team
|
||||
Website: http://orchardproject.net
|
||||
Version: 1.10.1
|
||||
OrchardVersion: 1.9
|
||||
OrchardVersion: 1.10
|
||||
Description: The localization module enables the localization of content items.
|
||||
Features:
|
||||
Orchard.Localization:
|
||||
@@ -31,3 +31,8 @@ Features:
|
||||
Category: Content
|
||||
Name: URL Transliteration
|
||||
Dependencies: Orchard.Localization.Transliteration, Orchard.Autoroute
|
||||
Orchard.Localization.CultureNeutralPartsAndFields:
|
||||
Description: Enables the synchronization among localizations of parts and fields specifically marked as "Culture Neutral".
|
||||
Category: Content
|
||||
Name: Culture Neutral Synchronizations
|
||||
Dependencies: Orchard.Localization
|
||||
|
||||
@@ -94,6 +94,8 @@
|
||||
<Compile Include="Controllers\AdminController.cs" />
|
||||
<Compile Include="Controllers\TransliterationAdminController.cs" />
|
||||
<Compile Include="Controllers\AdminCultureSelectorController.cs" />
|
||||
<Compile Include="Extensions\MetaDataExtensions.cs" />
|
||||
<Compile Include="Handlers\LocalizationCultureNeutralityHandler.cs" />
|
||||
<Compile Include="Helpers\ContextHelpers.cs" />
|
||||
<Compile Include="Models\TransliterationSpecificationRecord.cs" />
|
||||
<Compile Include="Providers\ContentLocalizationTokens.cs" />
|
||||
@@ -120,6 +122,8 @@
|
||||
<Compile Include="Services\LocalizationService.cs" />
|
||||
<Compile Include="Services\TransliterationService.cs" />
|
||||
<Compile Include="Events\TransliterationSlugEventHandler.cs" />
|
||||
<Compile Include="Settings\LocalizationCultureNeutralityEditorEvents.cs" />
|
||||
<Compile Include="Settings\LocalizationCultureNeutralitySettings.cs" />
|
||||
<Compile Include="ViewModels\ContentLocalizationsViewModel.cs" />
|
||||
<Compile Include="ViewModels\EditLocalizationViewModel.cs" />
|
||||
<Compile Include="ViewModels\CreateTransliterationViewModel.cs" />
|
||||
@@ -195,6 +199,9 @@
|
||||
<ItemGroup>
|
||||
<Content Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\DefinitionTemplates\LocalizationCultureNeutralitySettings.cshtml" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Orchard.Localization.Services {
|
||||
|
||||
LocalizationPart ILocalizationService.GetLocalizedContentItem(IContent content, string culture) {
|
||||
// Warning: Returns only the first of same culture localizations.
|
||||
return ((ILocalizationService) this).GetLocalizedContentItem(content, culture, null);
|
||||
return ((ILocalizationService)this).GetLocalizedContentItem(content, culture, null);
|
||||
}
|
||||
|
||||
LocalizationPart ILocalizationService.GetLocalizedContentItem(IContent content, string culture, VersionOptions versionOptions) {
|
||||
@@ -56,18 +56,24 @@ namespace Orchard.Localization.Services {
|
||||
|
||||
IEnumerable<LocalizationPart> ILocalizationService.GetLocalizations(IContent content) {
|
||||
// Warning: May contain more than one localization of the same culture.
|
||||
return ((ILocalizationService) this).GetLocalizations(content, null);
|
||||
return ((ILocalizationService)this).GetLocalizations(content, null);
|
||||
}
|
||||
|
||||
IEnumerable<LocalizationPart> ILocalizationService.GetLocalizations(IContent content, VersionOptions versionOptions) {
|
||||
if (content.ContentItem.Id == 0)
|
||||
return Enumerable.Empty<LocalizationPart>();
|
||||
|
||||
var localized = content.As<LocalizationPart>();
|
||||
|
||||
var query = versionOptions == null
|
||||
IContentQuery<LocalizationPart> query;
|
||||
if (content.ContentItem.TypeDefinition.Parts.Any(x => x.PartDefinition.Name == "TermPart")) { // terms translations can be contained on different TermContentType linked to taxonomies translations
|
||||
query = versionOptions == null
|
||||
? _contentManager.Query<LocalizationPart>()
|
||||
: _contentManager.Query<LocalizationPart>(versionOptions);
|
||||
}
|
||||
else {
|
||||
query = versionOptions == null
|
||||
? _contentManager.Query<LocalizationPart>(localized.ContentItem.ContentType)
|
||||
: _contentManager.Query<LocalizationPart>(versionOptions, localized.ContentItem.ContentType);
|
||||
}
|
||||
|
||||
int contentItemId = localized.ContentItem.Id;
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.ContentManagement.MetaData.Builders;
|
||||
using Orchard.ContentManagement.MetaData.Models;
|
||||
using Orchard.ContentManagement.ViewModels;
|
||||
using Orchard.Environment.Extensions;
|
||||
|
||||
namespace Orchard.Localization.Settings {
|
||||
[OrchardFeature("Orchard.Localization.CultureNeutralPartsAndFields")]
|
||||
public class LocalizationCultureNeutralityEditorEvents : ContentDefinitionEditorEventsBase {
|
||||
//The CultureNeutral Setting may be attached to either parts or fields.
|
||||
//The CultureNeutral Setting only makes sense if the ContentType can be localized.
|
||||
private readonly IContentDefinitionManager _contentDefinitionManager;
|
||||
private bool _typeHasLocalizationPart { get; set; }
|
||||
public LocalizationCultureNeutralityEditorEvents(IContentDefinitionManager contentDefinitionManager) {
|
||||
_contentDefinitionManager = contentDefinitionManager;
|
||||
}
|
||||
//Fields
|
||||
public override IEnumerable<TemplateViewModel> PartFieldEditor(ContentPartFieldDefinition definition) {
|
||||
if (_typeHasLocalizationPart) {
|
||||
var settings = definition.Settings.GetModel<LocalizationCultureNeutralitySettings>();
|
||||
yield return DefinitionTemplate(settings);
|
||||
}
|
||||
}
|
||||
public override IEnumerable<TemplateViewModel> PartFieldEditorUpdate(ContentPartFieldDefinitionBuilder builder, IUpdateModel updateModel) {
|
||||
var typeDefinition = _contentDefinitionManager.GetTypeDefinition(builder.PartName);
|
||||
if (typeDefinition != null && (_typeHasLocalizationPart || typeDefinition.Parts.Any(ctpd => ctpd.PartDefinition.Name == "LocalizationPart"))) {
|
||||
_typeHasLocalizationPart = true;
|
||||
var settings = new LocalizationCultureNeutralitySettings();
|
||||
if (updateModel.TryUpdateModel(settings, "LocalizationCultureNeutralitySettings", null, null)) {
|
||||
builder.WithSetting("LocalizationCultureNeutralitySettings.CultureNeutral", settings.CultureNeutral.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
yield return DefinitionTemplate(settings);
|
||||
}
|
||||
}
|
||||
//Parts
|
||||
public override IEnumerable<TemplateViewModel> TypePartEditor(ContentTypePartDefinition definition) {
|
||||
if (_typeHasLocalizationPart || definition.ContentTypeDefinition.Parts.Any(ctpd => ctpd.PartDefinition.Name == "LocalizationPart")) {
|
||||
_typeHasLocalizationPart = true;
|
||||
var settings = definition.Settings.GetModel<LocalizationCultureNeutralitySettings>();
|
||||
yield return DefinitionTemplate(settings);
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<TemplateViewModel> TypePartEditorUpdate(ContentTypePartDefinitionBuilder builder, IUpdateModel updateModel) {
|
||||
var typeDefinition = _contentDefinitionManager.GetTypeDefinition(builder.TypeName);
|
||||
if (_typeHasLocalizationPart || typeDefinition.Parts.Any(ctpd => ctpd.PartDefinition.Name == "LocalizationPart")) {
|
||||
_typeHasLocalizationPart = true;
|
||||
var settings = new LocalizationCultureNeutralitySettings();
|
||||
if (updateModel.TryUpdateModel(settings, "LocalizationCultureNeutralitySettings", null, null)) {
|
||||
builder.WithSetting("LocalizationCultureNeutralitySettings.CultureNeutral", settings.CultureNeutral.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
yield return DefinitionTemplate(settings);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Orchard.Environment.Extensions;
|
||||
|
||||
namespace Orchard.Localization.Settings {
|
||||
[OrchardFeature("Orchard.Localization.CultureNeutralPartsAndFields")]
|
||||
public class LocalizationCultureNeutralitySettings {
|
||||
public bool CultureNeutral { get; set; } //this setting applies to both parts and fields, and can be controlled during type definition
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@model Orchard.Localization.Settings.LocalizationCultureNeutralitySettings
|
||||
|
||||
<fieldset>
|
||||
@Html.CheckBoxFor(m => m.CultureNeutral)
|
||||
<label for="@Html.FieldIdFor(m => m.CultureNeutral)" class="forcheckbox">@T("Culture Neutral")</label>
|
||||
<span class="hint">@T("Check to mark this part/field as Culture Neutral. The values of Culture Neutral components will be synchronized across elements of a localization set whenever one of them is published.")</span>
|
||||
</fieldset>
|
||||
@@ -365,6 +365,7 @@ namespace Orchard.Taxonomies.Controllers {
|
||||
IsInternal = taxonomy.IsInternal,
|
||||
ContentItem = taxonomy.ContentItem,
|
||||
IsChecked = false,
|
||||
HasDraft = taxonomy.ContentItem.HasDraft(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web.Mvc;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Localization.Services;
|
||||
using Orchard.Taxonomies.Drivers;
|
||||
using Orchard.Taxonomies.Fields;
|
||||
using Orchard.Taxonomies.Helpers;
|
||||
using Orchard.Taxonomies.Models;
|
||||
using Orchard.Taxonomies.Services;
|
||||
using Orchard.Taxonomies.Settings;
|
||||
using Orchard.Taxonomies.ViewModels;
|
||||
|
||||
namespace Orchard.Taxonomies.Controllers {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class LocalizedTaxonomyController : Controller {
|
||||
|
||||
private readonly IContentDefinitionManager _contentDefinitionManager;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly ITaxonomyService _taxonomyService;
|
||||
private readonly ITaxonomyExtensionsService _taxonomyExtensionsService;
|
||||
|
||||
public LocalizedTaxonomyController(
|
||||
IContentDefinitionManager contentDefinitionManager,
|
||||
ILocalizationService localizationService,
|
||||
ITaxonomyService taxonomyService,
|
||||
ITaxonomyExtensionsService taxonomyExtensionsService) {
|
||||
_taxonomyService = taxonomyService;
|
||||
_taxonomyExtensionsService = taxonomyExtensionsService;
|
||||
_contentDefinitionManager = contentDefinitionManager;
|
||||
_localizationService = localizationService;
|
||||
}
|
||||
|
||||
public ActionResult GetTaxonomy(string contentTypeName, string taxonomyFieldName, int contentId, string culture) {
|
||||
var viewModel = new TaxonomyFieldViewModel();
|
||||
bool autocomplete = false;
|
||||
var contentDefinition = _contentDefinitionManager.GetTypeDefinition(contentTypeName);
|
||||
if (contentDefinition != null) {
|
||||
var taxonomyField = contentDefinition.Parts.SelectMany(p => p.PartDefinition.Fields).Where(x => x.FieldDefinition.Name == "TaxonomyField" && x.Name == taxonomyFieldName).FirstOrDefault();
|
||||
var contentTypePartDefinition = contentDefinition.Parts.Where(x => x.PartDefinition.Fields.Any(a => a.FieldDefinition.Name == "TaxonomyField" && a.Name == taxonomyFieldName)).FirstOrDefault();
|
||||
ViewData.TemplateInfo.HtmlFieldPrefix = contentTypePartDefinition.PartDefinition.Name + "." + taxonomyField.Name;
|
||||
if (taxonomyField != null) {
|
||||
var taxonomySettings = taxonomyField.Settings.GetModel<TaxonomyFieldSettings>();
|
||||
// Getting the translated taxonomy and its terms
|
||||
|
||||
var masterTaxonomy = _taxonomyExtensionsService.GetMasterItem(_taxonomyService.GetTaxonomyByName(taxonomySettings.Taxonomy));
|
||||
IContent taxonomy;
|
||||
var trytranslate = _localizationService.GetLocalizedContentItem(masterTaxonomy, culture);
|
||||
if (trytranslate == null) // case taxonomy not localized
|
||||
taxonomy = masterTaxonomy;
|
||||
else
|
||||
taxonomy = _localizationService.GetLocalizedContentItem(masterTaxonomy, culture).ContentItem;
|
||||
var terms = taxonomy != null // && !taxonomySettings.Autocomplete
|
||||
? _taxonomyService.GetTerms(taxonomy.Id).Where(t => !string.IsNullOrWhiteSpace(t.Name)).Select(t => t.CreateTermEntry()).Where(te => !te.HasDraft).ToList()
|
||||
: new List<TermEntry>(0);
|
||||
List<TermPart> appliedTerms = new List<TermPart>();
|
||||
if (contentId > 0) {
|
||||
appliedTerms = _taxonomyService.GetTermsForContentItem(contentId, taxonomyFieldName, VersionOptions.Published).Distinct(new TermPartComparer()).ToList();
|
||||
terms.ForEach(t => t.IsChecked = appliedTerms.Select(x => x.Id).Contains(t.Id));
|
||||
}
|
||||
viewModel = new TaxonomyFieldViewModel {
|
||||
DisplayName = taxonomyField.DisplayName,
|
||||
Name = taxonomyField.Name,
|
||||
Terms = terms,
|
||||
SelectedTerms = appliedTerms,
|
||||
Settings = taxonomySettings,
|
||||
SingleTermId = appliedTerms.Select(t => t.Id).FirstOrDefault(),
|
||||
TaxonomyId = taxonomy != null ? taxonomy.Id : 0,
|
||||
HasTerms = taxonomy != null && _taxonomyService.GetTermsCount(taxonomy.Id) > 0
|
||||
};
|
||||
if (taxonomySettings.Autocomplete)
|
||||
autocomplete = true;
|
||||
}
|
||||
}
|
||||
var templateName = autocomplete ? "../EditorTemplates/Fields/TaxonomyField.Autocomplete" : "../EditorTemplates/Fields/TaxonomyField";
|
||||
return View(templateName, viewModel);
|
||||
}
|
||||
private IEnumerable<TermPart> GetAppliedTerms(ContentPart part, TaxonomyField field = null, VersionOptions versionOptions = null) {
|
||||
string fieldName = field != null ? field.Name : string.Empty;
|
||||
return _taxonomyService.GetTermsForContentItem(part.ContentItem.Id, fieldName, versionOptions ?? VersionOptions.Published).Distinct(new TermPartComparer());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,20 +14,24 @@ using Orchard.Taxonomies.Helpers;
|
||||
using Orchard.UI.Navigation;
|
||||
using Orchard.Settings;
|
||||
using Orchard.DisplayManagement;
|
||||
using Orchard.Taxonomies.Events;
|
||||
|
||||
namespace Orchard.Taxonomies.Controllers {
|
||||
[ValidateInput(false), Admin]
|
||||
public class TermAdminController : Controller, IUpdateModel {
|
||||
private readonly ITaxonomyService _taxonomyService;
|
||||
private readonly ISiteService _siteService;
|
||||
private readonly ITermLocalizationEventHandler _termLocalizationEventHandler;
|
||||
|
||||
public TermAdminController(IOrchardServices services,
|
||||
ITaxonomyService taxonomyService,
|
||||
ISiteService siteService,
|
||||
IShapeFactory shapeFactory) {
|
||||
IShapeFactory shapeFactory,
|
||||
ITermLocalizationEventHandler termLocalizationEventHandler) {
|
||||
Services = services;
|
||||
_siteService = siteService;
|
||||
_taxonomyService = taxonomyService;
|
||||
_termLocalizationEventHandler = termLocalizationEventHandler;
|
||||
|
||||
T = NullLocalizer.Instance;
|
||||
Shape = shapeFactory;
|
||||
@@ -167,9 +171,14 @@ namespace Orchard.Taxonomies.Controllers {
|
||||
if (!Services.Authorizer.Authorize(Permissions.EditTerm, T("Not allowed to move terms")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
MoveTermsContext context = new MoveTermsContext();
|
||||
context.Terms = ResolveTermIds(termIds);
|
||||
context.ParentTerm = _taxonomyService.GetTerm(selectedTermId);
|
||||
_termLocalizationEventHandler.MovingTerms(context);
|
||||
|
||||
var taxonomy = _taxonomyService.GetTaxonomy(taxonomyId);
|
||||
var parentTerm = _taxonomyService.GetTerm(selectedTermId);
|
||||
var terms = ResolveTermIds(termIds);
|
||||
var terms = context.Terms;
|
||||
|
||||
foreach (var term in terms) {
|
||||
_taxonomyService.MoveTerm(taxonomy, term, parentTerm);
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Linq;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Drivers;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Taxonomies.Fields;
|
||||
using Orchard.Taxonomies.Settings;
|
||||
using Orchard.Taxonomies.ViewModels;
|
||||
|
||||
namespace Orchard.Taxonomies.Drivers {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class LocalizedTaxonomyFieldDriver : ContentFieldDriver<TaxonomyField> {
|
||||
private static string GetPrefix(ContentField field, ContentPart part) {
|
||||
return part.PartDefinition.Name + "." + field.Name;
|
||||
}
|
||||
private readonly IContentDefinitionManager _contentDefinitionManager;
|
||||
public LocalizedTaxonomyFieldDriver(IContentDefinitionManager contentDefinitionManager) {
|
||||
_contentDefinitionManager = contentDefinitionManager;
|
||||
}
|
||||
|
||||
protected override DriverResult Editor(ContentPart part, TaxonomyField field, dynamic shapeHelper) {
|
||||
return ContentShape("Fields_TaxonomyFieldList_Edit", GetDifferentiator(field, part), () => {
|
||||
var templateName = "Fields/TaxonomyFieldList";
|
||||
var taxonomySettings= new TaxonomyFieldSettings();
|
||||
var contentDefinition = _contentDefinitionManager.GetTypeDefinition(part.ContentItem.ContentType);
|
||||
if (contentDefinition != null) {
|
||||
var taxonomyField = contentDefinition.Parts.SelectMany(p => p.PartDefinition.Fields).Where(x => x.FieldDefinition.Name == "TaxonomyField" && x.Name == field.Name).FirstOrDefault();
|
||||
if (taxonomyField != null) {
|
||||
taxonomySettings = taxonomyField.Settings.GetModel<TaxonomyFieldSettings>();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var viewModel = new LocalizedTaxonomiesViewModel {
|
||||
ContentType = part.ContentItem.ContentType,
|
||||
FieldName = field.Name,
|
||||
Id = part.ContentItem.Id,
|
||||
Setting = taxonomySettings
|
||||
};
|
||||
return shapeHelper.EditorTemplate(TemplateName: templateName, Model: viewModel, Prefix: GetPrefix(field, part));
|
||||
});
|
||||
}
|
||||
|
||||
protected override DriverResult Editor(ContentPart part, TaxonomyField field, IUpdateModel updater, dynamic shapeHelper) {
|
||||
return Editor(part, field, shapeHelper);
|
||||
}
|
||||
|
||||
private static string GetDifferentiator(TaxonomyField field, ContentPart part) {
|
||||
return field.Name;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Drivers;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Taxonomies.Models;
|
||||
using Orchard.Taxonomies.Services;
|
||||
using Orchard.Taxonomies.ViewModels;
|
||||
|
||||
namespace Orchard.Taxonomies.Drivers {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class LocalizedTaxonomyPartDriver : ContentPartDriver<TaxonomyPart> {
|
||||
private readonly ITaxonomyExtensionsService _taxonomyExtensionsService;
|
||||
|
||||
public LocalizedTaxonomyPartDriver(ITaxonomyExtensionsService taxonomyExtensionsService) {
|
||||
_taxonomyExtensionsService = taxonomyExtensionsService;
|
||||
}
|
||||
|
||||
protected override string Prefix { get { return "LocalizedTaxonomy"; } }
|
||||
|
||||
protected override DriverResult Editor(TaxonomyPart part, dynamic shapeHelper) {
|
||||
AssociateTermTypeViewModel model = new AssociateTermTypeViewModel();
|
||||
model.TermTypes = _taxonomyExtensionsService.GetAllTermTypes();
|
||||
model.TermCreationAction = TermCreationOptions.CreateLocalized;
|
||||
model.SelectedTermTypeId = part.TermTypeName;
|
||||
model.ContentItem = part;
|
||||
|
||||
return ContentShape("Parts_TaxonomyTermSelector",
|
||||
() => shapeHelper.EditorTemplate(
|
||||
TemplateName: "Parts/TaxonomyTermSelector",
|
||||
Model: model,
|
||||
Prefix: Prefix));
|
||||
}
|
||||
|
||||
protected override DriverResult Editor(TaxonomyPart part, IUpdateModel updater, dynamic shapeHelper) {
|
||||
if (string.IsNullOrWhiteSpace(part.TermTypeName)) {
|
||||
AssociateTermTypeViewModel model = new AssociateTermTypeViewModel();
|
||||
if (updater.TryUpdateModel(model, Prefix, null, null)) {
|
||||
switch (model.TermCreationAction) {
|
||||
case TermCreationOptions.CreateLocalized:
|
||||
_taxonomyExtensionsService.CreateLocalizedTermContentType(part);
|
||||
break;
|
||||
case TermCreationOptions.UseExisting:
|
||||
part.TermTypeName = model.SelectedTermTypeId;
|
||||
break;
|
||||
default:
|
||||
part.TermTypeName = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Editor(part, shapeHelper);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Drivers;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Localization.Models;
|
||||
using Orchard.Localization.Records;
|
||||
using Orchard.Localization.Services;
|
||||
using Orchard.Taxonomies.Models;
|
||||
using Orchard.Taxonomies.Services;
|
||||
using Orchard.UI.Notify;
|
||||
using System.Linq;
|
||||
|
||||
namespace Orchard.Taxonomies.Drivers {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class LocalizedTermPartDriver : ContentPartDriver<TermPart> {
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly INotifier _notifier;
|
||||
private readonly ITaxonomyExtensionsService _taxonomyExtensionsService;
|
||||
|
||||
public LocalizedTermPartDriver(
|
||||
IContentManager contentManager,
|
||||
ILocalizationService localizationService,
|
||||
INotifier notifier,
|
||||
ITaxonomyExtensionsService taxonomyExtensionsService) {
|
||||
_contentManager = contentManager;
|
||||
_localizationService = localizationService;
|
||||
_notifier = notifier;
|
||||
_taxonomyExtensionsService = taxonomyExtensionsService;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
public Localizer T { get; set; }
|
||||
protected override string Prefix { get { return "LocalizedTerm"; } }
|
||||
|
||||
protected override DriverResult Editor(TermPart termPart, IUpdateModel updater, dynamic shapeHelper) {
|
||||
if (updater.TryUpdateModel(termPart, Prefix, null, null)) {
|
||||
//If the term is localized and has a parent term I check if the term culture is the same of its parent culture
|
||||
if (termPart.Has<LocalizationPart>()) {
|
||||
if (IsParentLocalized(termPart, updater)) {
|
||||
//Retrieving the parent taxonomy and the parent term
|
||||
ContentItem parentTerm = _taxonomyExtensionsService.GetParentTerm(termPart);
|
||||
ContentItem parentTaxonomy = _taxonomyExtensionsService.GetParentTaxonomy(termPart);
|
||||
|
||||
if (termPart.As<LocalizationPart>().Culture != null) {
|
||||
CultureRecord termCulture = termPart.As<LocalizationPart>().Culture;
|
||||
CultureRecord taxonomyCulture = null;
|
||||
|
||||
//If a parent term exists I retrieve its localized version.
|
||||
ContentItem localizedParentTerm = parentTerm;
|
||||
if (parentTerm != null) {
|
||||
if (parentTerm.Has<LocalizationPart>()) {
|
||||
CultureRecord parentTermCulture = parentTerm.As<LocalizationPart>().Culture;
|
||||
|
||||
if (parentTermCulture != null && termCulture.Id != parentTermCulture.Id) {
|
||||
IContent masterParentTerm = _taxonomyExtensionsService.GetMasterItem(parentTerm);
|
||||
var localizedParent = _localizationService.GetLocalizedContentItem(masterParentTerm, termCulture.Culture);
|
||||
if (localizedParent != null)
|
||||
localizedParentTerm = localizedParent.As<ContentItem>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Retrieving the localized version of the Taxonomy
|
||||
ContentItem localizedParentTaxonomy = parentTaxonomy;
|
||||
if (parentTaxonomy.Has<LocalizationPart>()) {
|
||||
if (parentTaxonomy.As<LocalizationPart>().Culture != null) {
|
||||
taxonomyCulture = parentTaxonomy.As<LocalizationPart>().Culture;
|
||||
if (termCulture.Id != taxonomyCulture.Id) {
|
||||
IContent masterTaxonomy = _taxonomyExtensionsService.GetMasterItem(parentTaxonomy);
|
||||
var localizedTaxonomy = _localizationService.GetLocalizedContentItem(masterTaxonomy, termCulture.Culture);
|
||||
if (localizedTaxonomy != null)
|
||||
localizedParentTaxonomy = localizedTaxonomy.As<ContentItem>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Assigning to the term the corresponding localized container
|
||||
if ((localizedParentTerm == null && localizedParentTaxonomy != parentTaxonomy) || (localizedParentTerm != null && localizedParentTerm != parentTerm && localizedParentTerm.Id != termPart.Id)) {
|
||||
termPart.Container = localizedParentTerm == null ? localizedParentTaxonomy.As<TaxonomyPart>().ContentItem : localizedParentTerm.As<TermPart>().ContentItem;
|
||||
termPart.Path = localizedParentTerm != null ? localizedParentTerm.As<TermPart>().FullPath + "/" : "/";
|
||||
if (localizedParentTerm == null)
|
||||
termPart.TaxonomyId = localizedParentTaxonomy.Id;
|
||||
else
|
||||
termPart.TaxonomyId = localizedParentTerm.As<TermPart>().TaxonomyId;
|
||||
|
||||
if (localizedParentTaxonomy != parentTaxonomy)
|
||||
_notifier.Add(NotifyType.Information, T("The parent taxonomy has been changed to its localized version associated to the culture {0}.", localizedParentTaxonomy.As<LocalizationPart>().Culture.Culture));
|
||||
|
||||
if (localizedParentTerm != null && localizedParentTerm != parentTerm)
|
||||
_notifier.Add(NotifyType.Information, T("The parent term has been changed to its localized version associated to the culture {0}.", localizedParentTerm.As<LocalizationPart>().Culture.Culture));
|
||||
}
|
||||
else if (termCulture != taxonomyCulture && taxonomyCulture != null && _localizationService.GetLocalizations(parentTaxonomy).Count() > 0) {
|
||||
//I can associate to a taxonomy a term of a different culture only if the taxonomy is unlocalized or has no translations
|
||||
updater.AddModelError("WrongTaxonomyLocalization", T("A localization of the taxonomy in the specified language does not exist. Please create it first."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsParentLocalized(TermPart termPart, IUpdateModel updater) {
|
||||
bool isLocalized = true;
|
||||
|
||||
//Checking if the term has a parent term
|
||||
ContentItem parentTerm = null;
|
||||
var container = _contentManager.Get(termPart.Container.Id);
|
||||
if (container.ContentType != "Taxonomy")
|
||||
parentTerm = container;
|
||||
|
||||
if (parentTerm != null) {
|
||||
if (parentTerm.Has<LocalizationPart>()) {
|
||||
var termCulture = termPart.As<LocalizationPart>().Culture;
|
||||
var parentTermCulture = parentTerm.As<LocalizationPart>().Culture;
|
||||
|
||||
if (termCulture != null) {
|
||||
//If the parent is not localized it must be localized first
|
||||
if (parentTermCulture == null) {
|
||||
isLocalized = false;
|
||||
updater.AddModelError("MissingParentTermLocalization", T("The parent term is not localized. Please localize it first."));
|
||||
}
|
||||
else {
|
||||
//If the two cultures are different, I check if the parent has a translation with the same culture of the new term
|
||||
if (termCulture != parentTermCulture) {
|
||||
//If it doesn't exists the term cannot be saved
|
||||
if (_localizationService.GetLocalizedContentItem(parentTerm, termCulture.Culture) == null) {
|
||||
isLocalized = false;
|
||||
updater.AddModelError("WrongParentTermLocalization", T("A localization of the parent term in the specified language does not exist. Please create it first."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isLocalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,16 @@ using Orchard.UI.Notify;
|
||||
namespace Orchard.Taxonomies.Drivers {
|
||||
public class TaxonomyFieldDriver : ContentFieldDriver<TaxonomyField> {
|
||||
private readonly ITaxonomyService _taxonomyService;
|
||||
private readonly ITaxonomySource _taxonomySource;
|
||||
public IOrchardServices Services { get; set; }
|
||||
|
||||
public TaxonomyFieldDriver(
|
||||
IOrchardServices services,
|
||||
ITaxonomyService taxonomyService,
|
||||
IRepository<TermContentItem> repository) {
|
||||
IRepository<TermContentItem> repository,
|
||||
ITaxonomySource taxonomySource) {
|
||||
_taxonomyService = taxonomyService;
|
||||
_taxonomySource = taxonomySource;
|
||||
Services = services;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
@@ -63,7 +66,7 @@ namespace Orchard.Taxonomies.Drivers {
|
||||
protected override DriverResult Editor(ContentPart part, TaxonomyField field, IUpdateModel updater, dynamic shapeHelper) {
|
||||
// Initializing viewmodel using the terms that are already selected to prevent loosing them when updating an editor group this field isn't displayed in.
|
||||
var appliedTerms = GetAppliedTerms(part, field, VersionOptions.Latest).ToList();
|
||||
var viewModel = new TaxonomyFieldViewModel { Terms = appliedTerms.Select(t => t.CreateTermEntry()).ToList() };
|
||||
var viewModel = new TaxonomyFieldViewModel { Terms = appliedTerms.Select(t => t.CreateTermEntry()).Where(te => !te.HasDraft).ToList() };
|
||||
foreach (var item in viewModel.Terms) item.IsChecked = true;
|
||||
|
||||
if (updater.TryUpdateModel(viewModel, GetPrefix(field, part), null, null)) {
|
||||
@@ -87,9 +90,9 @@ namespace Orchard.Taxonomies.Drivers {
|
||||
return ContentShape("Fields_TaxonomyField_Edit", GetDifferentiator(field, part), () => {
|
||||
var settings = field.PartFieldDefinition.Settings.GetModel<TaxonomyFieldSettings>();
|
||||
var appliedTerms = GetAppliedTerms(part, field, VersionOptions.Latest).ToDictionary(t => t.Id, t => t);
|
||||
var taxonomy = _taxonomyService.GetTaxonomyByName(settings.Taxonomy);
|
||||
var taxonomy = _taxonomySource.GetTaxonomy(settings.Taxonomy, part.ContentItem);
|
||||
var terms = taxonomy != null && !settings.Autocomplete
|
||||
? _taxonomyService.GetTerms(taxonomy.Id).Where(t => !string.IsNullOrWhiteSpace(t.Name)).Select(t => t.CreateTermEntry()).ToList()
|
||||
? _taxonomyService.GetTerms(taxonomy.Id).Where(t => !string.IsNullOrWhiteSpace(t.Name)).Select(t => t.CreateTermEntry()).Where(te => !te.HasDraft).ToList()
|
||||
: new List<TermEntry>(0);
|
||||
|
||||
// Ensure the modified taxonomy items are not lost if a model validation error occurs
|
||||
|
||||
@@ -72,5 +72,9 @@ namespace Orchard.Taxonomies.Drivers {
|
||||
|
||||
part.TermTypeName = context.Attribute(part.PartDefinition.Name, "TermTypeName");
|
||||
}
|
||||
|
||||
protected override void Cloning(TaxonomyPart originalPart, TaxonomyPart clonePart, CloneContentContext context) {
|
||||
clonePart.TermTypeName = originalPart.TermTypeName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,12 +93,7 @@ namespace Orchard.Taxonomies.Drivers {
|
||||
}
|
||||
|
||||
protected override DriverResult Editor(TermPart termPart, IUpdateModel updater, dynamic shapeHelper) {
|
||||
if (updater.TryUpdateModel(termPart, Prefix, null, null)) {
|
||||
var existing = _taxonomyService.GetTermByName(termPart.TaxonomyId, termPart.Name);
|
||||
if (existing != null && existing.Record != termPart.Record && existing.Container.ContentItem.Record == termPart.Container.ContentItem.Record) {
|
||||
updater.AddModelError("Name", T("The term {0} already exists at this level", termPart.Name));
|
||||
}
|
||||
}
|
||||
//Moved to TermPartHandler to work also with Taxonomy Localization feature
|
||||
|
||||
return Editor(termPart, shapeHelper);
|
||||
}
|
||||
@@ -150,5 +145,13 @@ namespace Orchard.Taxonomies.Drivers {
|
||||
part.Path += pathContentItem.Id + "/";
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Cloning(TermPart originalPart, TermPart clonePart, CloneContentContext context) {
|
||||
clonePart.Count = originalPart.Count;
|
||||
clonePart.Selectable = originalPart.Selectable;
|
||||
clonePart.Weight = originalPart.Weight;
|
||||
clonePart.TaxonomyId = originalPart.TaxonomyId;
|
||||
clonePart.Path = originalPart.Path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Linq;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Localization.Models;
|
||||
using Orchard.Taxonomies.Events;
|
||||
using Orchard.Taxonomies.Models;
|
||||
using Orchard.UI.Notify;
|
||||
|
||||
namespace Orchard.Taxonomies.EventHandlers {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class TermMovingEventHandler : ITermLocalizationEventHandler {
|
||||
private readonly INotifier _notifier;
|
||||
public TermMovingEventHandler(INotifier notifier) {
|
||||
_notifier = notifier;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
public Localizer T { get; set; }
|
||||
public void MovingTerms(MoveTermsContext context) {
|
||||
bool termsRemoved = false;
|
||||
if (context.ParentTerm != null) {
|
||||
if (context.ParentTerm.Has<LocalizationPart>()) {
|
||||
var parentTermCulture = context.ParentTerm.As<LocalizationPart>().Culture;
|
||||
for (int i = context.Terms.Count() - 1; i >= 0; i--) {
|
||||
if (context.Terms[i].Has<LocalizationPart>()) {
|
||||
var termCulture = context.Terms[i].As<LocalizationPart>().Culture;
|
||||
if (termCulture != null && termCulture != parentTermCulture) {
|
||||
context.Terms.RemoveAt(i);
|
||||
termsRemoved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (termsRemoved)
|
||||
_notifier.Warning(T("Some terms were not moved because their culture was different from the culture of the selected parent."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Orchard.Events;
|
||||
using Orchard.Taxonomies.Models;
|
||||
|
||||
namespace Orchard.Taxonomies.Events {
|
||||
public interface ITermLocalizationEventHandler : IEventHandler {
|
||||
void MovingTerms(MoveTermsContext context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Core.Title.Models;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Localization.Models;
|
||||
using Orchard.Localization.Services;
|
||||
using Orchard.Taxonomies.Drivers;
|
||||
using Orchard.Taxonomies.Models;
|
||||
using Orchard.Taxonomies.Services;
|
||||
using Orchard.Taxonomies.Settings;
|
||||
using Orchard.UI.Notify;
|
||||
|
||||
namespace Orchard.Taxonomies.Handlers {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class LocalizedTaxonomyFieldHandler : ContentHandler {
|
||||
|
||||
private readonly INotifier _notifier;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly ITaxonomyService _taxonomyService;
|
||||
private readonly ITaxonomyExtensionsService _taxonomyExtensionsService;
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly ICultureManager _cultureManager;
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
||||
public LocalizedTaxonomyFieldHandler(
|
||||
ILocalizationService localizationService,
|
||||
INotifier notifier,
|
||||
ITaxonomyService taxonomyService,
|
||||
ITaxonomyExtensionsService taxonomyExtensionsService,
|
||||
IContentManager contentManager,
|
||||
ICultureManager cultureManager
|
||||
) {
|
||||
_notifier = notifier;
|
||||
_localizationService = localizationService;
|
||||
_taxonomyService = taxonomyService;
|
||||
_taxonomyExtensionsService = taxonomyExtensionsService;
|
||||
_contentManager = contentManager;
|
||||
_cultureManager = cultureManager;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
private IEnumerable<LocalizationPart> GetEditorLocalizations(LocalizationPart part) {
|
||||
return _localizationService.GetLocalizations(part.ContentItem, VersionOptions.Latest)
|
||||
.Where(c => c.Culture != null)
|
||||
.ToList();
|
||||
}
|
||||
private List<string> RetrieveMissingCultures(LocalizationPart part, bool excludePartCulture) {
|
||||
var editorLocalizations = GetEditorLocalizations(part);
|
||||
var cultures = _cultureManager
|
||||
.ListCultures()
|
||||
.Where(s => editorLocalizations.All(l => l.Culture.Culture != s))
|
||||
.ToList();
|
||||
if (excludePartCulture) {
|
||||
cultures.Remove(part.Culture.Culture);
|
||||
}
|
||||
return cultures;
|
||||
}
|
||||
|
||||
protected override void BuildEditorShape(BuildEditorContext context) {
|
||||
// case new translation of contentitem
|
||||
var localizationPart = context.ContentItem.As<LocalizationPart>();
|
||||
if (localizationPart == null || localizationPart.Culture != null || context.ContentItem.As<LocalizationPart>().MasterContentItem == null || context.ContentItem.As<LocalizationPart>().MasterContentItem.Id == 0) {
|
||||
return;
|
||||
}
|
||||
var partFieldDefinitions = context.ContentItem.Parts.SelectMany(p => p.PartDefinition.Fields).Where(x => x.FieldDefinition.Name == "TaxonomyField");
|
||||
if (partFieldDefinitions == null)
|
||||
return; // contentitem without taxonomy
|
||||
base.BuildEditorShape(context);
|
||||
var missingCultures = localizationPart.HasTranslationGroup ?
|
||||
RetrieveMissingCultures(localizationPart.MasterContentItem.As<LocalizationPart>(), true) :
|
||||
RetrieveMissingCultures(localizationPart, localizationPart.Culture != null);
|
||||
foreach (var partFieldDefinition in partFieldDefinitions) {
|
||||
if (partFieldDefinition.Settings.GetModel<TaxonomyFieldLocalizationSettings>().TryToLocalize) {
|
||||
var originalTermParts = _taxonomyService.GetTermsForContentItem(context.ContentItem.As<LocalizationPart>().MasterContentItem.Id, partFieldDefinition.Name, VersionOptions.Latest).Distinct(new TermPartComparer()).ToList();
|
||||
var newTermParts = new List<TermPart>();
|
||||
foreach (var originalTermPart in originalTermParts) {
|
||||
var masterTermPart = _taxonomyExtensionsService.GetMasterItem(originalTermPart.ContentItem);
|
||||
if (masterTermPart != null) {
|
||||
foreach (var missingCulture in missingCultures) {
|
||||
var newTerm = _localizationService.GetLocalizedContentItem(masterTermPart, missingCulture);
|
||||
if (newTerm != null)
|
||||
newTermParts.Add(newTerm.ContentItem.As<TermPart>());
|
||||
else
|
||||
_notifier.Add(NotifyType.Warning, T("Term {0} can't be localized on {1}, term has been removed on this language", originalTermPart.ContentItem.As<TitlePart>().Title, missingCulture));
|
||||
}
|
||||
}
|
||||
else
|
||||
_notifier.Add(NotifyType.Warning, T("Term {0} can't be localized, term has been removed", originalTermPart.ContentItem.As<TitlePart>().Title));
|
||||
}
|
||||
_taxonomyService.UpdateTerms(context.ContentItem, newTermParts, partFieldDefinition.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateEditorShape(UpdateEditorContext context) {
|
||||
// case contentitem without localization and taxonomyfield localized
|
||||
if (context.ContentItem.As<LocalizationPart>() != null) {
|
||||
return;
|
||||
}
|
||||
var partFieldDefinitions = context.ContentItem.Parts.SelectMany(p => p.PartDefinition.Fields).Where(x => x.FieldDefinition.Name == "TaxonomyField");
|
||||
if (partFieldDefinitions == null) {
|
||||
return;
|
||||
}
|
||||
base.UpdateEditorShape(context);
|
||||
var allCultures = _cultureManager.ListCultures();
|
||||
if (allCultures.Count() > 1) {
|
||||
foreach (var partFieldDefinition in partFieldDefinitions) {
|
||||
if (partFieldDefinition.Settings.GetModel<TaxonomyFieldLocalizationSettings>().TryToLocalize) {
|
||||
var taxonomyUsed = _taxonomyService.GetTaxonomyByName(partFieldDefinition.Settings.GetModel<TaxonomyFieldSettings>().Taxonomy);
|
||||
var originalTermParts = _taxonomyService.GetTermsForContentItem(context.ContentItem.Id, partFieldDefinition.Name, VersionOptions.Latest).Distinct(new TermPartComparer()).ToList();
|
||||
var newTermParts = new List<TermPart>();
|
||||
foreach (var originalTermPart in originalTermParts) {
|
||||
newTermParts.Add(originalTermPart);
|
||||
var masterTermPart = _taxonomyExtensionsService.GetMasterItem(originalTermPart.ContentItem);
|
||||
if (masterTermPart != null) {
|
||||
foreach (var missingCulture in allCultures) {
|
||||
var newTerm = _localizationService.GetLocalizedContentItem(masterTermPart, missingCulture);
|
||||
if (newTerm != null)
|
||||
newTermParts.Add(newTerm.As<TermPart>());
|
||||
else
|
||||
_notifier.Add(NotifyType.Warning, T("Term {0} can't be localized on {1}, term has been removed on this language", originalTermPart.ContentItem.As<TitlePart>().Title, missingCulture));
|
||||
}
|
||||
}
|
||||
else
|
||||
_notifier.Add(NotifyType.Warning, T("Term {0} can't be localized", originalTermPart.ContentItem.As<TitlePart>().Title));
|
||||
}
|
||||
_taxonomyService.UpdateTerms(context.ContentItem, newTermParts.Distinct(new TermPartComparer()), partFieldDefinition.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Localization.Models;
|
||||
using Orchard.Localization.Services;
|
||||
using Orchard.Taxonomies.Models;
|
||||
using Orchard.Taxonomies.Services;
|
||||
using Orchard.UI.Notify;
|
||||
|
||||
namespace Orchard.Taxonomies.Handlers {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class LocalizedTaxonomyPartHandler : ContentHandler {
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly INotifier _notifier;
|
||||
private readonly ITaxonomyService _taxonomyService;
|
||||
private readonly ITaxonomyExtensionsService _taxonomyExtensionsService;
|
||||
|
||||
public LocalizedTaxonomyPartHandler(
|
||||
ILocalizationService localizationService,
|
||||
INotifier notifier,
|
||||
ITaxonomyService taxonomyService,
|
||||
ITaxonomyExtensionsService taxonomyExtensionsService) {
|
||||
_localizationService = localizationService;
|
||||
_notifier = notifier;
|
||||
_taxonomyService = taxonomyService;
|
||||
_taxonomyExtensionsService = taxonomyExtensionsService;
|
||||
|
||||
T = NullLocalizer.Instance;
|
||||
|
||||
OnPublishing<TaxonomyPart>((context, part) => ImportLocalizedTerms(context, part));
|
||||
}
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
||||
private void ImportLocalizedTerms(PublishContentContext context, TaxonomyPart part) {
|
||||
// When saving a Taxonomy translation I automatically move to the taxonomy any term in the corresponding language associated to the other translations
|
||||
bool termsMoved = false;
|
||||
|
||||
if (context.ContentItem.Has<LocalizationPart>()) {
|
||||
var taxonomyCulture = context.ContentItem.As<LocalizationPart>().Culture;
|
||||
if (taxonomyCulture != null) {
|
||||
var taxonomyIds = _localizationService.GetLocalizations(context.ContentItem).Select(x => x.Id);
|
||||
|
||||
foreach (var taxonomyId in taxonomyIds) {
|
||||
var parentTaxonomyCulture = _taxonomyService.GetTaxonomy(taxonomyId).As<LocalizationPart>().Culture;
|
||||
var termIds = _taxonomyService.GetTerms(taxonomyId).Select(x => x.Id);
|
||||
|
||||
foreach (var termId in termIds) {
|
||||
var term = _taxonomyService.GetTerm(termId);
|
||||
var parentTerm = _taxonomyExtensionsService.GetParentTerm(term);
|
||||
if (term.Has<LocalizationPart>()) {
|
||||
var termCulture = term.As<LocalizationPart>().Culture;
|
||||
|
||||
if (termCulture != null && termCulture != parentTaxonomyCulture && termCulture == taxonomyCulture) {
|
||||
term.TaxonomyId = context.ContentItem.Id;
|
||||
term.Path = parentTerm != null ? parentTerm.As<TermPart>().FullPath + "/" : "/";
|
||||
if (parentTerm == null)
|
||||
term.Container = context.ContentItem;
|
||||
|
||||
_taxonomyExtensionsService.RegenerateAutoroute(term.ContentItem);
|
||||
|
||||
termsMoved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (termsMoved)
|
||||
_notifier.Add(NotifyType.Information, T("Terms in the chosen language have been automatically moved from the other translations."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ namespace Orchard.Taxonomies.Handlers {
|
||||
Filters.Add(StorageFilter.For(repository));
|
||||
OnPublished<TaxonomyPart>((context, part) => {
|
||||
|
||||
if (part.TermTypeName == null) {
|
||||
if (String.IsNullOrWhiteSpace(part.TermTypeName)) {
|
||||
// is it a new taxonomy ?
|
||||
taxonomyService.CreateTermContentType(part);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,18 @@ using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Data;
|
||||
using System.Web.Routing;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.UI.Notify;
|
||||
using Orchard.Taxonomies.Services;
|
||||
using Orchard.Localization;
|
||||
|
||||
namespace Orchard.Taxonomies.Handlers {
|
||||
public class TermPartHandler : ContentHandler {
|
||||
public TermPartHandler(IRepository<TermPartRecord> repository) {
|
||||
private readonly ITaxonomyService _taxonomyService;
|
||||
public Localizer T { get; set; }
|
||||
|
||||
public TermPartHandler(IRepository<TermPartRecord> repository, INotifier notifier, ITaxonomyService taxonomyService) {
|
||||
_taxonomyService = taxonomyService;
|
||||
T = NullLocalizer.Instance;
|
||||
Filters.Add(StorageFilter.For(repository));
|
||||
OnInitializing<TermPart>((context, part) => part.Selectable = true);
|
||||
}
|
||||
@@ -24,5 +32,17 @@ namespace Orchard.Taxonomies.Handlers {
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
protected override void UpdateEditorShape(UpdateEditorContext context) {
|
||||
var part = context.ContentItem.As<TermPart>();
|
||||
if (part == null) {
|
||||
return;
|
||||
}
|
||||
base.UpdateEditorShape(context);
|
||||
var existing = _taxonomyService.GetTermByName(part.TaxonomyId, part.Name);
|
||||
if (existing != null && existing.Record != part.Record && existing.Container.ContentItem.Record == part.Container.ContentItem.Record) {
|
||||
context.Updater.AddModelError("Name", T("The term {0} already exists at this level", part.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using Orchard.Taxonomies.Models;
|
||||
using System;
|
||||
using Orchard.Taxonomies.ViewModels;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Taxonomies.Models;
|
||||
using Orchard.Taxonomies.ViewModels;
|
||||
|
||||
namespace Orchard.Taxonomies.Helpers {
|
||||
public static class TermExtensions {
|
||||
@@ -22,7 +23,8 @@ namespace Orchard.Taxonomies.Helpers {
|
||||
Path = term.Path,
|
||||
Weight = term.Weight,
|
||||
IsChecked = false,
|
||||
ContentItem = term.ContentItem
|
||||
ContentItem = term.ContentItem,
|
||||
HasDraft = term.ContentItem.HasDraft()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.Data.Migration;
|
||||
using Orchard.Environment.Extensions;
|
||||
|
||||
namespace Orchard.Taxonomies {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class LocalizedTaxonomyMigration : DataMigrationImpl {
|
||||
|
||||
public int Create() {
|
||||
ContentDefinitionManager.AlterTypeDefinition("Taxonomy", cfg => cfg
|
||||
.WithPart("LocalizationPart")
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Orchard.Taxonomies.Models {
|
||||
public class MoveTermsContext {
|
||||
public IList<TermPart> Terms { get; set; }
|
||||
public TermPart ParentTerm { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -11,3 +11,8 @@ Features:
|
||||
Description: Categorize a content item.
|
||||
Category: Content
|
||||
Dependencies: Orchard.Autoroute, Title, Contents, Orchard.Tokens
|
||||
Orchard.Taxonomies.LocalizationExtensions
|
||||
Name: Taxonomies Localization Extensions
|
||||
Description: Adds localization support to taxonomies
|
||||
Category: Content
|
||||
Dependencies: Orchard.Taxonomies, Orchard.Localization
|
||||
|
||||
@@ -109,6 +109,26 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="AdminMenu.cs" />
|
||||
<Compile Include="Controllers\AdminController.cs" />
|
||||
<Compile Include="Controllers\LocalizedTaxonomyController.cs" />
|
||||
<Compile Include="Drivers\LocalizedTaxonomyFieldDriver.cs" />
|
||||
<Compile Include="Drivers\LocalizedTaxonomyPartDriver.cs" />
|
||||
<Compile Include="Drivers\LocalizedTermPartDriver.cs" />
|
||||
<Compile Include="EventHandlers\TermMovingEventHandler.cs" />
|
||||
<Compile Include="Events\ITermLocalizationEventHandler.cs" />
|
||||
<Compile Include="Handlers\LocalizedTaxonomyFieldHandler.cs" />
|
||||
<Compile Include="Handlers\LocalizedTaxonomyPartHandler.cs" />
|
||||
<Compile Include="LocalizedTaxonomyMigration.cs" />
|
||||
<Compile Include="Models\MoveTermsContext.cs" />
|
||||
<Compile Include="Services\ITaxonomyExtensionsService.cs" />
|
||||
<Compile Include="Services\ITaxonomySource.cs" />
|
||||
<Compile Include="Services\LocalizedTaxonomySource.cs" />
|
||||
<Compile Include="Services\TaxonomyExtensionsService.cs" />
|
||||
<Compile Include="Services\TaxonomyServiceDraftable.cs" />
|
||||
<Compile Include="Services\TaxonomySource.cs" />
|
||||
<Compile Include="Settings\TaxonomyFieldLocalizationEditorEvents.cs" />
|
||||
<Compile Include="Settings\TaxonomyFieldLocalizationSettings.cs" />
|
||||
<Compile Include="ViewModels\AssociateTermTypeViewModel.cs" />
|
||||
<Compile Include="ViewModels\LocalizedTaxonomiesViewModel.cs" />
|
||||
<Compile Include="ViewModels\Tag.cs" />
|
||||
<Compile Include="Controllers\TagsController.cs" />
|
||||
<Compile Include="Controllers\TermAdminController.cs" />
|
||||
@@ -227,6 +247,10 @@
|
||||
<Project>{66fccd76-2761-47e3-8d11-b45d0001ddaa}</Project>
|
||||
<Name>Orchard.Autoroute</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.Localization\Orchard.Localization.csproj">
|
||||
<Project>{fbc8b571-ed50-49d8-8d9d-64ab7454a0d6}</Project>
|
||||
<Name>Orchard.Localization</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.Tokens\Orchard.Tokens.csproj">
|
||||
<Project>{6f759635-13d7-4e94-bcc9-80445d63f117}</Project>
|
||||
<Name>Orchard.Tokens</Name>
|
||||
@@ -257,6 +281,15 @@
|
||||
<ItemGroup>
|
||||
<Content Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\DefinitionTemplates\TaxonomyFieldLocalizationSettings.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\EditorTemplates\Fields\TaxonomyFieldList.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\EditorTemplates\Parts\TaxonomyTermSelector.cshtml" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Placement>
|
||||
<!-- Location of field edition -->
|
||||
<Place Fields_TaxonomyField_Edit="Content:9"/>
|
||||
<Place Fields_TaxonomyFieldList_Edit="Content:9"/>
|
||||
|
||||
<!-- Display terms in front-end summaries -->
|
||||
<Match DisplayType="Summary">
|
||||
@@ -20,6 +21,9 @@
|
||||
<!-- Display in admin -->
|
||||
<Place Parts_Taxonomies_Term_Fields="Content:2"/>
|
||||
|
||||
<!-- Display term selector in admin -->
|
||||
<Place Parts_TaxonomyTermSelector="Content:1"/>
|
||||
|
||||
<!-- New Shapes -->
|
||||
<Place Parts_TaxonomyPart="Content:5" />
|
||||
<Place Parts_TermPart="Content:5" />
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.MetaData.Models;
|
||||
using Orchard.Taxonomies.Models;
|
||||
|
||||
namespace Orchard.Taxonomies.Services {
|
||||
|
||||
public interface ITaxonomyExtensionsService : IDependency {
|
||||
|
||||
/// <summary>
|
||||
/// Returns all the <see cref="ContentTypeDefinition" /> data for content types containing a Term Part.
|
||||
/// </summary>
|
||||
/// <returns>A list of distinct <see cref="ContentTypeDefinition"/> of terms.</returns>
|
||||
IEnumerable<ContentTypeDefinition> GetAllTermTypes();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Content Type for the terms of a <see cref="TaxonomyPart"/> and attaches a LocalizationPart to it.
|
||||
/// </summary>
|
||||
/// <param name="taxonomy">The taxonomy to create a term content type for.</param>
|
||||
void CreateLocalizedTermContentType(TaxonomyPart taxonomy);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the parent Taxonomy of the specified ContentItem, if it exists
|
||||
/// </summary>
|
||||
/// <param name="part">The <see cref="TermPart"/> for which to search the parent Taxonomy.</param>
|
||||
/// <returns>The parent Taxonomy as a <see cref="ContentItem"/> if it exists, otherwise null.</returns>
|
||||
ContentItem GetParentTaxonomy(TermPart part);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the parent Term of the specified ContentItem, if it exists
|
||||
/// </summary>
|
||||
/// <param name="part">The <see cref="TermPart"/> for which to search the parent Term.</param>
|
||||
/// <returns>The parent Term as a <see cref="ContentItem"/> if it exists, otherwise null.</returns>
|
||||
ContentItem GetParentTerm(TermPart part);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the master item of the specified Content Item, if it exists.
|
||||
/// </summary>
|
||||
/// <param name="item">The item for which to search the master.</param>
|
||||
/// <returns>The master item if it exists, otherwise null.</returns>
|
||||
IContent GetMasterItem(IContent item);
|
||||
|
||||
/// <summary>
|
||||
/// Regenerates the autoroute for the specified <see cref="ContentItem"/>
|
||||
/// </summary>
|
||||
/// <param name="item">The item for which to regenerate the autoroute.</param>
|
||||
void RegenerateAutoroute(ContentItem item);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Taxonomies.Fields;
|
||||
using Orchard.Taxonomies.Models;
|
||||
|
||||
namespace Orchard.Taxonomies.Services {
|
||||
public interface ITaxonomySource : IDependency {
|
||||
TaxonomyPart GetTaxonomy(string name, ContentItem item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Core.Title.Models;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Localization.Models;
|
||||
using Orchard.Localization.Services;
|
||||
using Orchard.Taxonomies.Models;
|
||||
|
||||
|
||||
namespace Orchard.Taxonomies.Services {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class LocalizedTaxonomySource : ITaxonomySource {
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IContentManager _contentManager;
|
||||
public LocalizedTaxonomySource(
|
||||
ILocalizationService localizationService,
|
||||
IContentManager contentManager) {
|
||||
_localizationService = localizationService;
|
||||
_contentManager = contentManager;
|
||||
}
|
||||
|
||||
public TaxonomyPart GetTaxonomy(string name, ContentItem currentcontent) {
|
||||
if (String.IsNullOrWhiteSpace(name)) {
|
||||
throw new ArgumentNullException("name");
|
||||
}
|
||||
string culture = _localizationService.GetContentCulture(currentcontent);
|
||||
var taxonomyPart = _contentManager.Query<TaxonomyPart, TaxonomyPartRecord>()
|
||||
.Join<TitlePartRecord>()
|
||||
.Where(r => r.Title == name)
|
||||
.List()
|
||||
.FirstOrDefault();
|
||||
if (String.IsNullOrWhiteSpace(culture) || _localizationService.GetContentCulture(taxonomyPart.ContentItem) == culture)
|
||||
return taxonomyPart;
|
||||
else {
|
||||
// correction for property MasterContentItem=null for contentitem master
|
||||
var masterCorrection = taxonomyPart.ContentItem.As<LocalizationPart>().MasterContentItem;
|
||||
if (masterCorrection == null)
|
||||
masterCorrection = taxonomyPart;
|
||||
var localizedLocalizationPart = _localizationService.GetLocalizedContentItem(masterCorrection.ContentItem, culture);
|
||||
if (localizedLocalizationPart == null)
|
||||
return taxonomyPart;
|
||||
else
|
||||
return localizedLocalizationPart.ContentItem.As<TaxonomyPart>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Orchard.Autoroute.Models;
|
||||
using Orchard.Autoroute.Services;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.ContentManagement.MetaData.Models;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Localization.Models;
|
||||
using Orchard.Localization.Services;
|
||||
using Orchard.Taxonomies.Models;
|
||||
|
||||
namespace Orchard.Taxonomies.Services {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class TaxonomyExtensionsService : ITaxonomyExtensionsService {
|
||||
private readonly IAutorouteService _autorouteService;
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly IContentDefinitionManager _contentDefinitionManager;
|
||||
private readonly ITaxonomyService _taxonomyService;
|
||||
|
||||
public TaxonomyExtensionsService(
|
||||
IAutorouteService autorouteService,
|
||||
IContentManager contentManager,
|
||||
IContentDefinitionManager contentDefinitionManager,
|
||||
ITaxonomyService taxonomyService,
|
||||
ILocalizationService localizationService) {
|
||||
_autorouteService = autorouteService;
|
||||
_contentManager = contentManager;
|
||||
_contentDefinitionManager = contentDefinitionManager;
|
||||
_taxonomyService = taxonomyService;
|
||||
}
|
||||
|
||||
public IEnumerable<ContentTypeDefinition> GetAllTermTypes() {
|
||||
return _contentManager.GetContentTypeDefinitions().Where(t => t.Parts.Any(p => p.PartDefinition.Name.Equals(typeof(TermPart).Name)));
|
||||
}
|
||||
|
||||
public void CreateLocalizedTermContentType(TaxonomyPart taxonomy) {
|
||||
_taxonomyService.CreateTermContentType(taxonomy);
|
||||
_contentDefinitionManager.AlterTypeDefinition(taxonomy.TermTypeName,
|
||||
cfg => cfg
|
||||
.WithPart("LocalizationPart")
|
||||
);
|
||||
}
|
||||
|
||||
public ContentItem GetParentTaxonomy(TermPart part) {
|
||||
var container = _contentManager.Get(part.Container.Id);
|
||||
ContentItem parentTaxonomy = container;
|
||||
while (parentTaxonomy != null && parentTaxonomy.ContentType != "Taxonomy")
|
||||
parentTaxonomy = _contentManager.Get(parentTaxonomy.As<TermPart>().Container.Id);
|
||||
|
||||
return parentTaxonomy;
|
||||
}
|
||||
|
||||
public ContentItem GetParentTerm(TermPart part) {
|
||||
var container = _contentManager.Get(part.Container.Id);
|
||||
if (container.ContentType != "Taxonomy")
|
||||
return container;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public IContent GetMasterItem(IContent item) {
|
||||
if (item == null)
|
||||
return null;
|
||||
|
||||
var itemLocalization = item.As<LocalizationPart>();
|
||||
if (itemLocalization == null)
|
||||
return item;
|
||||
else {
|
||||
IContent masterParentTerm = itemLocalization.MasterContentItem;
|
||||
if (masterParentTerm == null)
|
||||
masterParentTerm = item;
|
||||
|
||||
return masterParentTerm;
|
||||
}
|
||||
}
|
||||
|
||||
public void RegenerateAutoroute(ContentItem item) {
|
||||
if (item.Has<AutoroutePart>()) {
|
||||
_autorouteService.RemoveAliases(item.As<AutoroutePart>());
|
||||
item.As<AutoroutePart>().DisplayAlias = _autorouteService.GenerateAlias(item.As<AutoroutePart>());
|
||||
_autorouteService.PublishAlias(item.As<AutoroutePart>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,7 @@ namespace Orchard.Taxonomies.Services {
|
||||
DeleteTerm(term);
|
||||
}
|
||||
|
||||
if (_contentManager.Query<TaxonomyPart, TaxonomyPartRecord>().Where(x => x.Id != taxonomy.Id && x.TermTypeName == taxonomy.TermTypeName).Count() == 0)
|
||||
_contentDefinitionManager.DeleteTypeDefinition(taxonomy.TermTypeName);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Orchard.Autoroute.Models;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.Core.Title.Models;
|
||||
using Orchard.Data;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.Descriptor;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Environment.State;
|
||||
using Orchard.Security;
|
||||
using Orchard.Taxonomies.Models;
|
||||
using Orchard.UI.Notify;
|
||||
|
||||
namespace Orchard.Taxonomies.Services {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class TaxonomyServiceDraftable : TaxonomyService, ITaxonomyService {
|
||||
private readonly IRepository<TermContentItem> _termContentItemRepository;
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly INotifier _notifier;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly IContentDefinitionManager _contentDefinitionManager;
|
||||
private readonly IOrchardServices _services;
|
||||
private readonly IProcessingEngine _processingEngine;
|
||||
private readonly ShellSettings _shellSettings;
|
||||
private readonly IShellDescriptorManager _shellDescriptorManager;
|
||||
|
||||
public TaxonomyServiceDraftable(
|
||||
IRepository<TermContentItem> termContentItemRepository,
|
||||
IContentManager contentManager,
|
||||
INotifier notifier,
|
||||
IContentDefinitionManager contentDefinitionManager,
|
||||
IAuthorizationService authorizationService,
|
||||
IOrchardServices services,
|
||||
IProcessingEngine processingEngine,
|
||||
ShellSettings shellSettings,
|
||||
IShellDescriptorManager shellDescriptorManager) : base(
|
||||
termContentItemRepository,
|
||||
contentManager,
|
||||
notifier,
|
||||
contentDefinitionManager,
|
||||
authorizationService,
|
||||
services,
|
||||
processingEngine,
|
||||
shellSettings,
|
||||
shellDescriptorManager) {
|
||||
_termContentItemRepository = termContentItemRepository;
|
||||
_contentManager = contentManager;
|
||||
_notifier = notifier;
|
||||
_authorizationService = authorizationService;
|
||||
_contentDefinitionManager = contentDefinitionManager;
|
||||
_services = services;
|
||||
_processingEngine = processingEngine;
|
||||
_shellSettings = shellSettings;
|
||||
_shellDescriptorManager = shellDescriptorManager;
|
||||
}
|
||||
|
||||
new public IEnumerable<TaxonomyPart> GetTaxonomies() {
|
||||
return _contentManager.Query<TaxonomyPart, TaxonomyPartRecord>().ForVersion(VersionOptions.Latest).List();
|
||||
}
|
||||
|
||||
new public TaxonomyPart GetTaxonomy(int id) {
|
||||
return _contentManager.Get(id, VersionOptions.Latest).As<TaxonomyPart>();
|
||||
}
|
||||
|
||||
new public TaxonomyPart GetTaxonomyByName(string name) {
|
||||
|
||||
if (String.IsNullOrWhiteSpace(name)) {
|
||||
throw new ArgumentNullException("name");
|
||||
}
|
||||
|
||||
// include the record in the query to optimize the query plan
|
||||
return _contentManager.Query<TaxonomyPart, TaxonomyPartRecord>().ForVersion(VersionOptions.Latest)
|
||||
.Join<TitlePartRecord>()
|
||||
.Where(r => r.Title == name)
|
||||
.List()
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
new public TaxonomyPart GetTaxonomyBySlug(string slug) {
|
||||
if (String.IsNullOrWhiteSpace(slug)) {
|
||||
throw new ArgumentNullException("slug");
|
||||
}
|
||||
|
||||
return _contentManager
|
||||
.Query<TaxonomyPart, TaxonomyPartRecord>().ForVersion(VersionOptions.Latest)
|
||||
.Join<TitlePartRecord>()
|
||||
.Join<AutoroutePartRecord>()
|
||||
.Where(r => r.DisplayAlias == slug)
|
||||
.List()
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
new public IEnumerable<TermPart> GetTerms(int taxonomyId) {
|
||||
var result = _contentManager.Query<TermPart, TermPartRecord>().ForVersion(VersionOptions.Latest)
|
||||
.Where(x => x.TaxonomyId == taxonomyId)
|
||||
.List();
|
||||
|
||||
return TermPart.Sort(result);
|
||||
}
|
||||
|
||||
new public TermPart GetTermByPath(string path) {
|
||||
return _contentManager.Query<TermPart, TermPartRecord>().ForVersion(VersionOptions.Latest)
|
||||
.Join<AutoroutePartRecord>()
|
||||
.Where(rr => rr.DisplayAlias == path)
|
||||
.List()
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
new public IEnumerable<TermPart> GetAllTerms() {
|
||||
var result = _contentManager
|
||||
.Query<TermPart, TermPartRecord>().ForVersion(VersionOptions.Latest)
|
||||
.List();
|
||||
return TermPart.Sort(result);
|
||||
}
|
||||
|
||||
new public TermPart GetTerm(int id) {
|
||||
return _contentManager
|
||||
.Query<TermPart, TermPartRecord>().ForVersion(VersionOptions.Latest)
|
||||
.Where(x => x.Id == id).List().FirstOrDefault();
|
||||
}
|
||||
|
||||
new public TermPart GetTermByName(int taxonomyId, string name) {
|
||||
return _contentManager
|
||||
.Query<TermPart, TermPartRecord>().ForVersion(VersionOptions.Latest)
|
||||
.Where(t => t.TaxonomyId == taxonomyId)
|
||||
.Join<TitlePartRecord>()
|
||||
.Where(r => r.Title == name)
|
||||
.List()
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
new public IContentQuery<TaxonomyPart, TaxonomyPartRecord> GetTaxonomiesQuery() {
|
||||
return _contentManager.Query<TaxonomyPart, TaxonomyPartRecord>().ForVersion(VersionOptions.Latest);
|
||||
}
|
||||
|
||||
new public IContentQuery<TermPart, TermPartRecord> GetTermsQuery(int taxonomyId) {
|
||||
return _contentManager.Query<TermPart, TermPartRecord>().ForVersion(VersionOptions.Latest).Where(x => x.TaxonomyId == taxonomyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Taxonomies.Models;
|
||||
|
||||
namespace Orchard.Taxonomies.Services {
|
||||
public class TaxonomySource : ITaxonomySource {
|
||||
private readonly ITaxonomyService _taxonomyService;
|
||||
public TaxonomySource(ITaxonomyService taxonomyService) {
|
||||
_taxonomyService = taxonomyService;
|
||||
}
|
||||
|
||||
public TaxonomyPart GetTaxonomy(string name, ContentItem item) {
|
||||
return _taxonomyService.GetTaxonomyByName(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.ContentManagement.MetaData.Builders;
|
||||
@@ -21,7 +22,7 @@ namespace Orchard.Taxonomies.Settings {
|
||||
public override IEnumerable<TemplateViewModel> PartFieldEditor(ContentPartFieldDefinition definition) {
|
||||
if (definition.FieldDefinition.Name == "TaxonomyField") {
|
||||
var model = definition.Settings.GetModel<TaxonomyFieldSettings>();
|
||||
model.Taxonomies = _taxonomyService.GetTaxonomies();
|
||||
model.Taxonomies = _taxonomyService.GetTaxonomies().Where(tax => !tax.ContentItem.HasDraft());
|
||||
yield return DefinitionTemplate(model);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.ContentManagement.MetaData.Builders;
|
||||
using Orchard.ContentManagement.MetaData.Models;
|
||||
using Orchard.ContentManagement.ViewModels;
|
||||
using Orchard.Environment.Extensions;
|
||||
|
||||
namespace Orchard.Taxonomies.Settings {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class TaxonomyFieldLocalizationEditorEvents : ContentDefinitionEditorEventsBase {
|
||||
|
||||
public override IEnumerable<TemplateViewModel> PartFieldEditor(ContentPartFieldDefinition definition) {
|
||||
if (definition.FieldDefinition.Name == "TaxonomyField") {
|
||||
var model = definition.Settings.GetModel<TaxonomyFieldLocalizationSettings>();
|
||||
yield return DefinitionTemplate(model);
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<TemplateViewModel> PartFieldEditorUpdate(ContentPartFieldDefinitionBuilder builder, IUpdateModel updateModel) {
|
||||
if (builder.FieldType != "TaxonomyField") {
|
||||
yield break;
|
||||
}
|
||||
|
||||
var model = new TaxonomyFieldLocalizationSettings();
|
||||
if (updateModel.TryUpdateModel(model, "TaxonomyFieldLocalizationSettings", null, null)) {
|
||||
builder.WithSetting("TaxonomyFieldLocalizationSettings.TryToLocalize", model.TryToLocalize.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
yield return DefinitionTemplate(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Orchard.Environment.Extensions;
|
||||
|
||||
namespace Orchard.Taxonomies.Settings {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class TaxonomyFieldLocalizationSettings {
|
||||
public bool TryToLocalize { get; set; }
|
||||
public TaxonomyFieldLocalizationSettings() {
|
||||
TryToLocalize = true; // default value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.MetaData.Models;
|
||||
using Orchard.Environment.Extensions;
|
||||
|
||||
namespace Orchard.Taxonomies.ViewModels {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class AssociateTermTypeViewModel {
|
||||
public IEnumerable<ContentTypeDefinition> TermTypes { get; set; }
|
||||
public TermCreationOptions TermCreationAction { get; set; }
|
||||
public string SelectedTermTypeId { get; set; }
|
||||
public IContent ContentItem { get; set; }
|
||||
}
|
||||
|
||||
public enum TermCreationOptions {
|
||||
CreateLocalized,
|
||||
CreateCultureNeutral,
|
||||
UseExisting
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Taxonomies.Settings;
|
||||
|
||||
namespace Orchard.Taxonomies.ViewModels {
|
||||
[OrchardFeature("Orchard.Taxonomies.LocalizationExtensions")]
|
||||
public class LocalizedTaxonomiesViewModel {
|
||||
public string ContentType { get; set; }
|
||||
public string FieldName { get; set; }
|
||||
public int Id { get; set; }
|
||||
public TaxonomyFieldSettings Setting { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ namespace Orchard.Taxonomies.ViewModels {
|
||||
public string Name { get; set; }
|
||||
public ContentItem ContentItem { get; set; }
|
||||
public bool IsChecked { get; set; }
|
||||
public bool HasDraft { get; set; }
|
||||
}
|
||||
|
||||
public enum TaxonomiesAdminIndexBulkAction {
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Orchard.Taxonomies.ViewModels {
|
||||
public int Weight { get; set; }
|
||||
public bool IsChecked { get; set; }
|
||||
public ContentItem ContentItem { get; set; }
|
||||
public bool HasDraft { get; set; }
|
||||
}
|
||||
|
||||
public enum TermsAdminIndexBulkAction {
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
else {
|
||||
@Html.ItemDisplayText(taxonomyEntry.ContentItem)
|
||||
}
|
||||
@if (taxonomyEntry.HasDraft) {
|
||||
<text>@T(" (Draft)")</text>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (!taxonomyEntry.IsInternal) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
@model Orchard.Taxonomies.Settings.TaxonomyFieldLocalizationSettings
|
||||
<fieldset>
|
||||
<label>@T("Localization")</label>
|
||||
@Html.CheckBoxFor(m => m.TryToLocalize)
|
||||
<label for="@Html.FieldIdFor(m => m.TryToLocalize)" class="forcheckbox">@T("Try to translate the selected terms")</label>
|
||||
<span class="hint">@T("Check to attempt to replace the selected terms with their translation in the main content culture when saving. This only applies if the main content has a LocalizationPart. If term as not translation on content culture then term will be removed.")</span>
|
||||
</fieldset>
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
}
|
||||
<fieldset class="taxonomy-wrapper" data-name-prefix="@Html.FieldNameFor(m => m)" data-id-prefix="@Html.FieldIdFor(m => m)">
|
||||
<label @if(Model.Settings.Required) { <text>class="required"</text> }>@Model.DisplayName</label>
|
||||
<label @if (Model.Settings.Required) { <text> class="required" </text> }>@Model.DisplayName</label>
|
||||
@if (Model.Settings.Autocomplete) {
|
||||
<div class="terms-editor text text-medium" data-autocomplete-url="@Url.HttpRouteUrl("", new { area = "Orchard.Taxonomies", controller = "Tags" })" data-taxonomy-id="@Model.TaxonomyId" data-leaves-only="@Model.Settings.LeavesOnly" data-selected-terms="@selectedTerms" data-allow-new-terms="@Model.Settings.AllowCustomTerms.ToString().ToLower()" data-singlechoice="@Model.Settings.SingleChoice.ToString().ToLower()">
|
||||
<ul></ul>
|
||||
@@ -54,10 +54,10 @@
|
||||
@{
|
||||
var disabled = IsTermDisabled(entry);
|
||||
if (Model.Settings.SingleChoice) {
|
||||
<input @if (disabled){ <text>disabled="disabled"</text> } type="radio" value="@entry.Id" @if (entry.Id == Model.SingleTermId){ <text>checked="checked"</text> } name="@Html.FieldNameFor(m => m.SingleTermId)" id="@Html.FieldIdFor(m => m.Terms[ti].IsChecked)" data-term="@entry.Name" data-term-identity="@entry.Name.ToLower()" />
|
||||
<input @if (disabled) { <text> disabled="disabled" </text> } type="radio" value="@entry.Id" @if (entry.Id == Model.SingleTermId) { <text> checked="checked" </text> } name="@Html.FieldNameFor(m => m.SingleTermId)" id="@Html.FieldIdFor(m => m.Terms[ti].IsChecked)" data-term="@entry.Name" data-term-identity="@entry.Name.ToLower()" />
|
||||
}
|
||||
else {
|
||||
<input @if (disabled){ <text>disabled="disabled"</text> } type="checkbox" value="true" checked="checked" name="@Html.FieldNameFor(m => m.Terms[ti].IsChecked)" id="@Html.FieldIdFor(m => m.Terms[ti].IsChecked)" data-term="@entry.Name" data-term-identity="@entry.Name.ToLower()" />
|
||||
<input @if (disabled) { <text> disabled="disabled" </text> } type="checkbox" value="true" checked="checked" name="@Html.FieldNameFor(m => m.Terms[ti].IsChecked)" id="@Html.FieldIdFor(m => m.Terms[ti].IsChecked)" data-term="@entry.Name" data-term-identity="@entry.Name.ToLower()" />
|
||||
}
|
||||
}
|
||||
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.Terms[ti].IsChecked)">@entry.Name</label>
|
||||
@@ -75,4 +75,4 @@
|
||||
</div>
|
||||
}
|
||||
@Html.HiddenFor(m => m.TaxonomyId)
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
@using Orchard.Utility.Extensions;
|
||||
@model Orchard.Taxonomies.ViewModels.LocalizedTaxonomiesViewModel
|
||||
|
||||
@{
|
||||
Script.Require("jQuery");
|
||||
var Taxonomyprefix = ((ViewData)).TemplateInfo.HtmlFieldPrefix.Replace('.', '_');
|
||||
using (Script.Foot()) {
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
$(document).ready(function () {
|
||||
var pageurl = '@Url.Action("GetTaxonomy", "LocalizedTaxonomy", new { area = "Orchard.Taxonomies" })';
|
||||
function filterTaxonomyCulture(culture) {
|
||||
$.ajax({
|
||||
url: pageurl,
|
||||
data: { contentTypeName: '@Model.ContentType', taxonomyFieldName: '@Model.FieldName', contentId: @Model.Id, culture: culture },
|
||||
success: function (html) {
|
||||
$(".taxonomy-wrapper[data-id-prefix='@Taxonomyprefix']").replaceWith(html);
|
||||
$(".taxonomy-wrapper[data-id-prefix='@Taxonomyprefix'] legend").expandoControl(function (controller) { return controller.nextAll(".expando"); }, { collapse: true, remember: false });
|
||||
@if (Model.Setting.Autocomplete) {
|
||||
<text>
|
||||
var createTermCheckbox = function($wrapper, tag, checked) {
|
||||
var $ul = $("ul.terms", $wrapper);
|
||||
var singleChoice = $(".terms-editor", $wrapper).data("singlechoice");
|
||||
var namePrefix = $wrapper.data("name-prefix");
|
||||
var idPrefix = $wrapper.data("id-prefix");
|
||||
var nextIndex = $("li", $ul).length;
|
||||
var id = isNaN(tag.value) ? -nextIndex : tag.value;
|
||||
var checkboxId = idPrefix + "_Terms_" + nextIndex + "__IsChecked";
|
||||
var checkboxName = namePrefix + ".Terms[" + nextIndex + "].IsChecked";
|
||||
var radioName = namePrefix + ".SingleTermId";
|
||||
var checkboxHtml = "<input type=\"checkbox\" value=\"" + (checked ? "true\" checked=\"checked\"" : "false") + " data-term=\"" + tag.label + "\" data-term-identity=\"" + tag.label.toLowerCase() + "\" id=\"" + checkboxId + "\" name=\"" + checkboxName + "\" />";
|
||||
var radioHtml = "<input type=\"radio\" value=\"" + id + (checked ? "\" checked=\"checked\"" : "\"") + " data-term=\"" + tag.label + "\" data-term-identity=\"" + tag.label.toLowerCase() + "\" id=\"" + checkboxId + "\" name=\"" + radioName + "\" />";
|
||||
var inputHtml = singleChoice ? radioHtml : checkboxHtml;
|
||||
var $li = $("<li>" +
|
||||
inputHtml +
|
||||
"<input type=\"hidden\" value=\"" + id + "\" id=\"" + idPrefix + "_Terms_" + nextIndex + "__Id" + "\" name=\"" + namePrefix + ".Terms[" + nextIndex + "].Id" + "\" />" +
|
||||
"<input type=\"hidden\" value=\"" + tag.label + "\" id=\"" + idPrefix + "_Terms_" + nextIndex + "__Name" + "\" name=\"" + namePrefix + ".Terms[" + nextIndex + "].Name" + "\" />" +
|
||||
"<label class=\"forcheckbox\" for=\"" + checkboxId + "\">" + tag.label + "</label>" +
|
||||
"</li>").hide();
|
||||
|
||||
if (checked && singleChoice) {
|
||||
$("input[type='radio']", $ul).removeAttr("checked");
|
||||
$("input[type='radio'][name$='IsChecked']", $ul).val("false");
|
||||
}
|
||||
|
||||
$ul.append($li);
|
||||
$li.fadeIn();
|
||||
};
|
||||
|
||||
/* Event handlers
|
||||
**********************************************************************/
|
||||
var onTagsChanged = function(tagLabelOrValue, action, tag) {
|
||||
|
||||
if (tagLabelOrValue == null)
|
||||
return;
|
||||
|
||||
var $input = this.appendTo;
|
||||
var $wrapper = $input.parents("fieldset:first");
|
||||
var $tagIt = $("ul.tagit", $wrapper);
|
||||
var singleChoice = $(".terms-editor", $wrapper).data("singlechoice");
|
||||
var $terms = $("ul.terms", $wrapper);
|
||||
var initialTags = $(".terms-editor", $wrapper).data("selected-terms");
|
||||
|
||||
if (singleChoice && action == "added") {
|
||||
$tagIt.tagit("fill", tag);
|
||||
}
|
||||
|
||||
$terms.empty();
|
||||
|
||||
var tags = $tagIt.tagit("tags");
|
||||
$(tags).each(function(index, tag) {
|
||||
createTermCheckbox($wrapper, tag, true);
|
||||
});
|
||||
|
||||
// Add any tags that are no longer selected but were initially on page load.
|
||||
// These are required to be posted back so they can be removed.
|
||||
var removedTags = $.grep(initialTags, function(initialTag) { return $.grep(tags, function(tag) { return tag.value === initialTag.value }).length === 0 });
|
||||
$(removedTags).each(function(index, tag) {
|
||||
createTermCheckbox($wrapper, tag, false);
|
||||
});
|
||||
|
||||
$(".no-terms", $wrapper).hide();
|
||||
};
|
||||
|
||||
var renderAutocompleteItem = function(ul, item) {
|
||||
|
||||
var label = item.label;
|
||||
|
||||
for (var i = 0; i < item.levels; i++) {
|
||||
label = "<span class=\"gap\"> </span>" + label;
|
||||
}
|
||||
|
||||
var li = item.disabled ? "<li class=\"disabled\"></li>" : "<li></li>";
|
||||
|
||||
return $(li)
|
||||
.data("item.autocomplete", item)
|
||||
.append($("<a></a>").html(label))
|
||||
.appendTo(ul);
|
||||
};
|
||||
|
||||
/* Initialization
|
||||
**********************************************************************/
|
||||
$(".terms-editor").each(function() {
|
||||
var selectedTerms = $(this).data("selected-terms");
|
||||
|
||||
var autocompleteUrl = $(this).data("autocomplete-url");
|
||||
|
||||
var $tagit = $("> ul", this).tagit({
|
||||
tagSource: function(request, response) {
|
||||
var termsEditor = $(this.element).parents(".terms-editor");
|
||||
$.getJSON(autocompleteUrl, { taxonomyId: termsEditor.data("taxonomy-id"), leavesOnly: termsEditor.data("leaves-only"), query: request.term }, function(data, status, xhr) {
|
||||
response(data);
|
||||
});
|
||||
},
|
||||
initialTags: selectedTerms,
|
||||
triggerKeys: ['enter', 'tab'], // default is ['enter', 'space', 'comma', 'tab'] but we remove comma and space to allow them in the terms
|
||||
allowNewTags: $(this).data("allow-new-terms"),
|
||||
tagsChanged: onTagsChanged,
|
||||
caseSensitive: false,
|
||||
minLength: 0,
|
||||
sortable: true
|
||||
}).data("ui-tagit");
|
||||
|
||||
$tagit.input.autocomplete().data("ui-autocomplete")._renderItem = renderAutocompleteItem;
|
||||
$tagit.input.autocomplete().on("autocompletefocus", function(event, ui) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
});
|
||||
</text>
|
||||
}
|
||||
|
||||
///////////////////////////
|
||||
}
|
||||
}).fail(function () {
|
||||
alert("@T("Loading taxonomy fail, Retry")");
|
||||
});
|
||||
}
|
||||
$("#Localization_SelectedCulture").on('change', function () {
|
||||
var optionSelected = $("option:selected", this);
|
||||
filterTaxonomyCulture($("#Localization_SelectedCulture").val());
|
||||
});
|
||||
$("#Localization_SelectedCulture").trigger("change");
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
@model Orchard.Taxonomies.ViewModels.AssociateTermTypeViewModel
|
||||
@using Orchard.Taxonomies.ViewModels;
|
||||
|
||||
<fieldset class="term-selector">
|
||||
@if (Model.ContentItem.ContentItem.Id == 0) {
|
||||
<legend>@T("Term type to use")</legend>
|
||||
<div>
|
||||
@Html.RadioButtonFor(m => m.TermCreationAction, TermCreationOptions.CreateLocalized.ToString(), new { id = "localizedTerm" })
|
||||
<label for="localizedTerm" class="forcheckbox">@T("Create a new localized Term type")</label>
|
||||
</div>
|
||||
<div>
|
||||
@Html.RadioButtonFor(m => m.TermCreationAction, TermCreationOptions.CreateCultureNeutral.ToString(), new { id = "cultureNeutralTerm" })
|
||||
<label for="cultureNeutralTerm" class="forcheckbox">@T("Create a new culture neutral Term type")</label>
|
||||
</div>
|
||||
if (Model.TermTypes.Count() > 0) {
|
||||
<div>
|
||||
@Html.RadioButtonFor(m => m.TermCreationAction, TermCreationOptions.UseExisting.ToString(), new { id = "existingTerm" })
|
||||
<label for="existingTerm" class="forcheckbox">@T("Use an existing Term type")</label>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (Model.TermTypes.Count() > 0) {
|
||||
<div class="options">
|
||||
<span data-controllerid="existingTerm">
|
||||
<label>@T("Term type")</label>
|
||||
@Html.DropDownListFor(m => m.SelectedTermTypeId, new SelectList(Model.TermTypes.OrderBy(o => o.DisplayName), "Name", "DisplayName"), Model.ContentItem.ContentItem.Id != 0 ? new { @disabled = "disabled" } as object : new { } as object)
|
||||
<span class="hint">@T("The content type of the terms used by this Taxonomy")</span>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</fieldset>
|
||||
|
||||
@using (Script.Foot()) {
|
||||
<script type="text/javascript">
|
||||
(function ($) {
|
||||
$(function () {
|
||||
$('input[name="@Html.FieldNameFor(m => m.TermCreationAction)"]:checked').click();
|
||||
});
|
||||
})(jQuery);
|
||||
</script>
|
||||
}
|
||||
@@ -44,6 +44,9 @@
|
||||
@* Tabs for levels *@ @for (var i = 1; i <= termEntry.GetLevels(); i++) { <span class="gap"> </span> }
|
||||
<input type="checkbox" value="true" name="@Html.NameOf(m => m.Terms[ti].IsChecked)" />
|
||||
@Html.ItemDisplayLink(termEntry.Name, termEntry.ContentItem)
|
||||
@if (termEntry.HasDraft) {
|
||||
<text>@T(" (Draft)")</text>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (Authorizer.Authorize(Permissions.EditTerm)) {
|
||||
|
||||
@@ -85,11 +85,11 @@ namespace Orchard.ContentManagement.Drivers {
|
||||
}
|
||||
|
||||
void IContentFieldDriver.Cloning(CloneContentContext context) {
|
||||
ProcessClone(context.ContentItem, context.CloneContentItem, (part, originalField, cloneField) => Cloning(part, originalField, cloneField, context), context.Logger);
|
||||
ProcessClone(context.ContentItem, context.CloneContentItem, (part, originalField, cloneField) => Cloning(part, originalField, cloneField, context), context);
|
||||
}
|
||||
|
||||
void IContentFieldDriver.Cloned(CloneContentContext context) {
|
||||
ProcessClone(context.ContentItem, context.CloneContentItem, (part, originalField, cloneField) => Cloned(part, originalField, cloneField, context), context.Logger);
|
||||
ProcessClone(context.ContentItem, context.CloneContentItem, (part, originalField, cloneField) => Cloned(part, originalField, cloneField, context), context);
|
||||
}
|
||||
|
||||
void IContentFieldDriver.Describe(DescribeMembersContext context) {
|
||||
@@ -101,10 +101,20 @@ namespace Orchard.ContentManagement.Drivers {
|
||||
occurences.Invoke(pf => effort(pf.part, pf.field), logger);
|
||||
}
|
||||
|
||||
void ProcessClone(ContentItem originalItem, ContentItem cloneItem, Action<ContentPart, TField, TField> effort, ILogger logger) {
|
||||
var occurences = originalItem.Parts.SelectMany(part => part.Fields.OfType<TField>().Select(field => new { part, field }))
|
||||
.Join(cloneItem.Parts.SelectMany(part => part.Fields.OfType<TField>()), original => original.field.Name, cloneField => cloneField.Name, (original, cloneField) => new { original, cloneField } );
|
||||
occurences.Invoke(pf => effort(pf.original.part, pf.original.field, pf.cloneField), logger);
|
||||
void ProcessClone(ContentItem originalItem, ContentItem cloneItem, Action<ContentPart, TField, TField> effort, CloneContentContext context) {
|
||||
var occurences = originalItem
|
||||
.Parts
|
||||
.SelectMany(part => part.Fields.OfType<TField>().Select(field => new { part, field }))
|
||||
.Join(cloneItem
|
||||
.Parts
|
||||
.SelectMany(part =>
|
||||
part
|
||||
.Fields
|
||||
.OfType<TField>()
|
||||
.Where(fi =>
|
||||
string.IsNullOrWhiteSpace(context.FieldName) || context.FieldName == fi.Name)),
|
||||
original => original.field.Name, cloneField => cloneField.Name, (original, cloneField) => new { original, cloneField } );
|
||||
occurences.Invoke(pf => effort(pf.original.part, pf.original.field, pf.cloneField), context.Logger);
|
||||
}
|
||||
|
||||
DriverResult Process(ContentItem item, Func<ContentPart, TField, DriverResult> effort, ILogger logger) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
namespace Orchard.ContentManagement.Handlers {
|
||||
public class CloneContentContext : ContentContextBase {
|
||||
public ContentItem CloneContentItem { get; set; }
|
||||
public string FieldName { get; set; } //in case we are cloning fields, this is the name of the field. The actual name, not the name of the type.
|
||||
public CloneContentContext(ContentItem contentItem, ContentItem cloneContentItem)
|
||||
:base(contentItem) {
|
||||
CloneContentItem = cloneContentItem;
|
||||
|
||||
@@ -141,10 +141,10 @@
|
||||
<PropertyValue name="EnableExpandPrecedence">false</PropertyValue>
|
||||
<PropertyValue name="EnableExpandScopes">false</PropertyValue>
|
||||
<PropertyValue name="EnableExtractFunction">false</PropertyValue>
|
||||
<PropertyValue name="EnableSQLiteStoreEngine">true</PropertyValue>
|
||||
<PropertyValue name="EnableSQLiteStoreEngine">false</PropertyValue>
|
||||
<PropertyValue name="EnableSingleFileISense">true</PropertyValue>
|
||||
<PropertyValue name="EnableSingleFileISenseSquiggles">false</PropertyValue>
|
||||
<PropertyValue name="EnumerateCommentTasks">true</PropertyValue>
|
||||
<PropertyValue name="EnumerateCommentTasks">false</PropertyValue>
|
||||
<PropertyValue name="GroupBrackets">true</PropertyValue>
|
||||
<PropertyValue name="HideExperimentalAd">true</PropertyValue>
|
||||
<PropertyValue name="HighlightMatchingTokens">true</PropertyValue>
|
||||
|
||||
Reference in New Issue
Block a user