Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
5 changes: 5 additions & 0 deletions src/Orchard.Web/Modules/Orchard.Blogs/Module.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 6 additions & 0 deletions src/Orchard.Web/Modules/Orchard.Blogs/Orchard.Blogs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,21 @@ public ActionResult Translate(int id, string to) {
// 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
7 changes: 6 additions & 1 deletion src/Orchard.Web/Modules/Orchard.Localization/Module.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand All @@ -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" />
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public LocalizationService(IContentManager contentManager, ICultureManager cultu

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) {
Expand Down Expand Up @@ -56,18 +56,24 @@ void ILocalizationService.SetContentCulture(IContent content, string culture) {

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MatteoPiovanelli-Laser @sebastienros

I just found this during merging 1.10.x to dev and I have to admit that I looked at this PR and didn't notice this, but this is really bad. We can't put a reference like this in a module that provides low-level functionality (it's not a dependency, so it doesn't break anything, but still).

What's really strange is that two years later, @HermesSbicego-Laser committed a change that removes this, then two commits later I merged 1.10.x into dev, which readded that change. I don't know how the difftool would think that that should happen, but re-adding removed code by accident (without noticing) is even worse. This is not the first time it happens (and merging from 1.10.x to dev caused a lot of different problems in other cases too) - suggestions are welcome. I'll try to sort out the correct logic for LocalizationService...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: I made a mistake, Hermes didn't remove this (I was looking at the wrong diff). Nevertheless, this TermPart-related logic has to be removed somehow without breaking Taxonomy Localization.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the TermPart condition in this commit: a5a24d0, so there's no type filter in the query. This way it doesn't Taxonomies localization and I don't think filtering on content type matters anyway.

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;

Expand Down
Loading