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:
Matteo Piovanelli
2017-04-03 19:33:14 +02:00
committed by Sébastien Ros
parent a9066c0ade
commit 4cb0b3b5c4
57 changed files with 1774 additions and 85 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
? _contentManager.Query<LocalizationPart>(localized.ContentItem.ContentType)
: _contentManager.Query<LocalizationPart>(versionOptions, localized.ContentItem.ContentType);
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;

View File

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

View File

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

View File

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

View File

@@ -365,6 +365,7 @@ namespace Orchard.Taxonomies.Controllers {
IsInternal = taxonomy.IsInternal,
ContentItem = taxonomy.ContentItem,
IsChecked = false,
HasDraft = taxonomy.ContentItem.HasDraft(),
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
using Orchard.Events;
using Orchard.Taxonomies.Models;
namespace Orchard.Taxonomies.Events {
public interface ITermLocalizationEventHandler : IEventHandler {
void MovingTerms(MoveTermsContext context);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -121,7 +121,8 @@ namespace Orchard.Taxonomies.Services {
DeleteTerm(term);
}
_contentDefinitionManager.DeleteTypeDefinition(taxonomy.TermTypeName);
if (_contentManager.Query<TaxonomyPart, TaxonomyPartRecord>().Where(x => x.Id != taxonomy.Id && x.TermTypeName == taxonomy.TermTypeName).Count() == 0)
_contentDefinitionManager.DeleteTypeDefinition(taxonomy.TermTypeName);
}
public string GenerateTermTypeName(string taxonomyName) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -50,6 +50,9 @@
else {
@Html.ItemDisplayText(taxonomyEntry.ContentItem)
}
@if (taxonomyEntry.HasDraft) {
<text>@T(" (Draft)")</text>
}
</td>
<td>
@if (!taxonomyEntry.IsInternal) {

View File

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

View File

@@ -31,48 +31,48 @@
}
<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>
@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>
@if (Model.Settings.SingleChoice) {
<div class="hint">@T("Enter a single term. Hit <i>tab</i> or <i>enter</i> to apply the term.") @if (!Model.Settings.AllowCustomTerms) { <text>@T("This taxonomy does not allow you to create new terms.") </text> }</div>
}
else {
<div class="hint">@T("Enter multiple terms. Hit <i>tab</i>, <i>enter</i> or <i>,</i> to add multiple terms.") @if (!Model.Settings.AllowCustomTerms) { <text>@T("This taxonomy does not allow you to create new terms.") </text> }</div>
}
</div>
}
@if (!String.IsNullOrWhiteSpace(Model.Settings.Hint)) {
<span class="hint">@Model.Settings.Hint</span>
}
<div class="hidden-taxonomy-state">
<ul class="terms">
@foreach (var entry in checkedTerms) {
<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>
@if (Model.Settings.SingleChoice) {
<div class="hint">@T("Enter a single term. Hit <i>tab</i> or <i>enter</i> to apply the term.") @if (!Model.Settings.AllowCustomTerms) { <text>@T("This taxonomy does not allow you to create new terms.") </text> }</div>
}
else {
<div class="hint">@T("Enter multiple terms. Hit <i>tab</i>, <i>enter</i> or <i>,</i> to add multiple terms.") @if (!Model.Settings.AllowCustomTerms) { <text>@T("This taxonomy does not allow you to create new terms.") </text> }</div>
}
</div>
}
@if (!String.IsNullOrWhiteSpace(Model.Settings.Hint)) {
<span class="hint">@Model.Settings.Hint</span>
}
<div class="hidden-taxonomy-state">
<ul class="terms">
@foreach (var entry in checkedTerms) {
var ti = termIndex;
<li>
@{
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()" />
<li>
@{
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()" />
}
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()" />
}
}
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()" />
}
}
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.Terms[ti].IsChecked)">@entry.Name</label>
<input type="hidden" name="@Html.FieldNameFor(m => m.Terms[ti].Id)" id="@Html.FieldIdFor(m => m.Terms[ti].Id)" value="@entry.Id" />
</li>
termIndex++;
}
</ul>
</div>
@if (!Model.HasTerms && AuthorizedFor(Orchard.Taxonomies.Permissions.CreateTerm)) {
<div class="no-terms">
@T("There are no terms defined for {0} yet.", Model.DisplayName)
<a href="@Url.Action("Index", "TermAdmin", new { taxonomyId = Model.TaxonomyId, area = "Orchard.Taxonomies" })">@T("Create some terms")</a>
</div>
}
@Html.HiddenFor(m => m.TaxonomyId)
</fieldset>
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.Terms[ti].IsChecked)">@entry.Name</label>
<input type="hidden" name="@Html.FieldNameFor(m => m.Terms[ti].Id)" id="@Html.FieldIdFor(m => m.Terms[ti].Id)" value="@entry.Id" />
</li>
termIndex++;
}
</ul>
</div>
@if (!Model.HasTerms && AuthorizedFor(Orchard.Taxonomies.Permissions.CreateTerm)) {
<div class="no-terms">
@T("There are no terms defined for {0} yet.", Model.DisplayName)
<a href="@Url.Action("Index", "TermAdmin", new { taxonomyId = Model.TaxonomyId, area = "Orchard.Taxonomies" })">@T("Create some terms")</a>
</div>
}
@Html.HiddenFor(m => m.TaxonomyId)
</fieldset>

View File

@@ -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\">&nbsp;</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>
}
}

View File

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

View File

@@ -44,6 +44,9 @@
@* Tabs for levels *@ @for (var i = 1; i <= termEntry.GetLevels(); i++) { <span class="gap">&nbsp;</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)) {

View File

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

View File

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

View File

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