Skip to content

Commit

Permalink
Content cloning and translation
Browse files Browse the repository at this point in the history
  • Loading branch information
TFleury authored and sebastienros committed May 27, 2016
1 parent 147b4fd commit 87a3948
Show file tree
Hide file tree
Showing 34 changed files with 259 additions and 172 deletions.
5 changes: 5 additions & 0 deletions src/Orchard.Web/Core/Common/Drivers/BodyPartDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Orchard.Services;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.ContentManagement.Handlers;

namespace Orchard.Core.Common.Drivers {
public class BodyPartDriver : ContentPartDriver<BodyPart> {
Expand Down Expand Up @@ -75,6 +76,10 @@ protected override void Exporting(BodyPart part, ContentManagement.Handlers.Expo
context.Element(part.PartDefinition.Name).SetAttributeValue("Text", part.Text);
}

protected override void Cloning(BodyPart originalPart, BodyPart clonePart, CloneContentContext context) {

This comment has been minimized.

Copy link
@MatteoPiovanelli

MatteoPiovanelli Nov 7, 2016

Contributor

Why has the cloning behaviour been implemented in the driver rather than the handler? The handler seems to me to be more extensible.

This comment has been minimized.

Copy link
@TFleury

TFleury Nov 11, 2016

Author Contributor

Before Cloning feature, duplication was maid with Importing / Exporting which were in the driver.
So I placed Cloning logic in the driver too...

clonePart.Text = originalPart.Text;
}

private static BodyEditorViewModel BuildEditorViewModel(BodyPart part,RequestContext requestContext) {
return new BodyEditorViewModel {
BodyPart = part,
Expand Down
4 changes: 4 additions & 0 deletions src/Orchard.Web/Core/Common/Drivers/CommonPartDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,9 @@ protected override void Exporting(CommonPart part, ExportContentContext context)
.SetAttributeValue("ModifiedUtc", XmlConvert.ToString(part.ModifiedUtc.Value, XmlDateTimeSerializationMode.Utc));
}
}

protected override void Cloning(CommonPart originalPart, CommonPart clonePart, CloneContentContext context) {
clonePart.Container = originalPart.Container;
}
}
}
4 changes: 4 additions & 0 deletions src/Orchard.Web/Core/Common/Drivers/TextFieldDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ protected override void Exporting(ContentPart part, TextField field, ExportConte
context.Element(field.FieldDefinition.Name + "." + field.Name).SetAttributeValue("Text", field.Value);
}

protected override void Cloning(ContentPart part, TextField originalField, TextField cloneField, CloneContentContext context) {
cloneField.Value = originalField.Value;
}

protected override void Describe(DescribeMembersContext context) {
context
.Member(null, typeof(string), T("Value"), T("The text associated with the field."))
Expand Down
24 changes: 11 additions & 13 deletions src/Orchard.Web/Core/Contents/Controllers/AdminController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -383,26 +383,24 @@ private ActionResult EditPOST(int id, string returnUrl, Action<ContentItem> cond
}

[HttpPost]
public ActionResult Clone(int id, string returnUrl) {
var contentItem = _contentManager.GetLatest(id);
public ActionResult Clone(int id) {
var originalContentItem = _contentManager.GetLatest(id);

if (contentItem == null)
return HttpNotFound();
if (!Services.Authorizer.Authorize(Permissions.ViewContent, originalContentItem, T("Couldn't open original content")))
return new HttpUnauthorizedResult();

// pass a dummy content to the authorization check to check for "own" variations
var dummyContent = _contentManager.New(originalContentItem.ContentType);

if (!Services.Authorizer.Authorize(Permissions.EditContent, contentItem, T("Couldn't clone content")))
if (!Services.Authorizer.Authorize(Permissions.EditContent, dummyContent, T("Couldn't create clone content")))
return new HttpUnauthorizedResult();

try {
Services.ContentManager.Clone(contentItem);
}
catch (InvalidOperationException) {
Services.Notifier.Warning(T("Could not clone the content item."));
return this.RedirectLocal(returnUrl, () => RedirectToAction("List"));
}
var cloneContentItem = _contentManager.Clone(originalContentItem);

Services.Notifier.Success(T("Successfully cloned. The clone was saved as a draft."));

return this.RedirectLocal(returnUrl, () => RedirectToAction("List"));
var adminRouteValues = _contentManager.GetItemMetadata(cloneContentItem).AdminRouteValues;
return RedirectToRoute(adminRouteValues);
}

[HttpPost]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
@using Orchard.ContentManagement
@using Orchard.Core.Contents
@using Orchard.Utility.Extensions
@{
ContentPart contentPart = Model.ContentPart;
}
@if (Authorizer.Authorize(Permissions.EditContent, contentPart)) {
<a href="@Url.Action("Clone", "Admin", new { Id = Model.ContentItem.Id, ReturnUrl = Request.ToUrlString(), Area = "Contents" })" itemprop="UnsafeUrl">@T("Clone")</a>
<a href="@Url.Action("Clone", "Admin", new { Id = Model.ContentItem.Id, Area = "Contents" })" itemprop="UnsafeUrl">@T("Clone")</a>
@T(" | ")
}
4 changes: 4 additions & 0 deletions src/Orchard.Web/Core/Title/Drivers/TitlePartDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,9 @@ protected override void Importing(TitlePart part, ImportContentContext context)
protected override void Exporting(TitlePart part, ExportContentContext context) {
context.Element(part.PartDefinition.Name).SetAttributeValue("Title", part.Title);
}

protected override void Cloning(TitlePart originalPart, TitlePart clonePart, CloneContentContext context) {
clonePart.Title = originalPart.Title;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using System.Collections.Generic;
using Orchard.ContentManagement.Handlers;

namespace Orchard.Comments.Drivers {
public class CommentsPartDriver : ContentPartDriver<CommentsPart> {
Expand Down Expand Up @@ -110,7 +111,7 @@ protected override DriverResult Editor(CommentsPart part, IUpdateModel updater,
return Editor(part, shapeHelper);
}

protected override void Importing(CommentsPart part, ContentManagement.Handlers.ImportContentContext context) {
protected override void Importing(CommentsPart part, ImportContentContext context) {
// Don't do anything if the tag is not specified.
if (context.Data.Element(part.PartDefinition.Name) == null) {
return;
Expand All @@ -129,10 +130,16 @@ protected override void Importing(CommentsPart part, ContentManagement.Handlers.
);
}

protected override void Exporting(CommentsPart part, ContentManagement.Handlers.ExportContentContext context) {
protected override void Exporting(CommentsPart part, ExportContentContext context) {
context.Element(part.PartDefinition.Name).SetAttributeValue("CommentsShown", part.CommentsShown);
context.Element(part.PartDefinition.Name).SetAttributeValue("CommentsActive", part.CommentsActive);
context.Element(part.PartDefinition.Name).SetAttributeValue("ThreadedComments", part.ThreadedComments);
}

protected override void Cloning(CommentsPart originalPart, CommentsPart clonePart, CloneContentContext context) {
clonePart.CommentsShown = originalPart.CommentsShown;
clonePart.CommentsActive = originalPart.CommentsActive;
// ThreadedComments will be overrided with settings default
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Orchard.ContentPicker.ViewModels;
using Orchard.Localization;
using Orchard.Utility.Extensions;
using Orchard.ContentPicker.Fields;

namespace Orchard.ContentPicker.Drivers {
public class ContentPickerFieldDriver : ContentFieldDriver<Fields.ContentPickerField> {
Expand Down Expand Up @@ -98,6 +99,10 @@ protected override void Exporting(ContentPart part, Fields.ContentPickerField fi
}
}

protected override void Cloning(ContentPart part, ContentPickerField originalField, ContentPickerField cloneField, CloneContentContext context) {
cloneField.Ids = originalField.Ids;
}

protected override void Describe(DescribeMembersContext context) {
context
.Member(null, typeof(string), T("Ids"), T("A formatted list of the ids, e.g., {1},{42}"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ protected override void Exporting(ContentPart part, BooleanField field, ExportCo
context.Element(field.FieldDefinition.Name + "." + field.Name).SetAttributeValue("Value", field.Value);
}

protected override void Cloning(ContentPart part, BooleanField originalField, BooleanField cloneField, CloneContentContext context) {
cloneField.Value = originalField.Value;
}

protected override void Describe(DescribeMembersContext context) {
context
.Member(null, typeof(Boolean), T("Value"), T("The boolean value of the field."))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ protected override void Exporting(ContentPart part, DateTimeField field, ExportC
context.Element(GetPrefix(field, part)).SetAttributeValue("Value", XmlConvert.ToString(value, XmlDateTimeSerializationMode.Utc));
}

protected override void Cloning(ContentPart part, DateTimeField originalField, DateTimeField cloneField, CloneContentContext context) {
cloneField.DateTime = originalField.DateTime;
}

protected override void Describe(DescribeMembersContext context) {
context
.Member(null, typeof(DateTime), T("Value"), T("The date and time value of the field."))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ protected override void Exporting(ContentPart part, EnumerationField field, Expo
context.Element(field.FieldDefinition.Name + "." + field.Name).SetAttributeValue("Value", field.Value);
}

protected override void Cloning(ContentPart part, EnumerationField originalField, EnumerationField cloneField, CloneContentContext context) {
cloneField.Value = originalField.Value;
}

protected override void Describe(DescribeMembersContext context) {
context
.Member(null, typeof(string), T("Value"), T("The selected values of the field."))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ protected override void Exporting(ContentPart part, InputField field, ExportCont
context.Element(field.FieldDefinition.Name + "." + field.Name).SetAttributeValue("Value", field.Value);
}

protected override void Cloning(ContentPart part, InputField originalField, InputField cloneField, CloneContentContext context) {
cloneField.Value = originalField.Value;
}

protected override void Describe(DescribeMembersContext context) {
context
.Member(null, typeof(string), T("Value"), T("The value of the field."))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ protected override void Exporting(ContentPart part, LinkField field, ExportConte
}
}

protected override void Cloning(ContentPart part, LinkField originalField, LinkField cloneField, CloneContentContext context) {
cloneField.Text = originalField.Text;
cloneField.Value = originalField.Value;
cloneField.Target = originalField.Target;
}

protected override void Describe(DescribeMembersContext context) {
context
.Member("Text", typeof(string), T("Text"), T("The text of the link."))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ protected override void Exporting(ContentPart part, NumericField field, ExportCo
context.Element(field.FieldDefinition.Name + "." + field.Name).SetAttributeValue("Value", field.Value.Value.ToString(CultureInfo.InvariantCulture));
}

protected override void Cloning(ContentPart part, NumericField originalField, NumericField cloneField, CloneContentContext context) {
cloneField.Value = originalField.Value;
}

protected override void Describe(DescribeMembersContext context) {
context
.Member(null, typeof(decimal), T("Value"), T("The value of the field."))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,5 +213,10 @@ private string ReadFormattedLayoutData(string formattedLayoutData)
var layoutDataString = JsonConvert.SerializeObject(layoutData, Formatting.None);
return layoutDataString;
}

protected override void Cloning(LayoutPart originalPart, LayoutPart clonePart, CloneContentContext context) {
clonePart.LayoutData = originalPart.LayoutData;
clonePart.TemplateId = originalPart.TemplateId;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
using System;
using System.Web.Mvc;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Contents.Settings;
using Orchard.ContentManagement;
using Orchard.Core.Contents;
using Orchard.DisplayManagement;
using Orchard.Localization.Models;
using Orchard.Localization.Services;
using Orchard.Localization.ViewModels;
using Orchard.Mvc;
using Orchard.UI.Notify;
using System;
using System.Web.Mvc;

namespace Orchard.Localization.Controllers {
namespace Orchard.Localization.Controllers
{
[ValidateInput(false)]
public class AdminController : Controller, IUpdateModel {
public class AdminController : Controller {
private readonly IContentManager _contentManager;
private readonly ILocalizationService _localizationService;
private readonly ICultureManager _cultureManager;

public AdminController(
IOrchardServices orchardServices,
IContentManager contentManager,
ILocalizationService localizationService,
ICultureManager cultureManager,
IShapeFactory shapeFactory) {
_contentManager = contentManager;
_localizationService = localizationService;
_cultureManager = cultureManager;
T = NullLocalizer.Instance;
Services = orchardServices;
Shape = shapeFactory;
Expand All @@ -32,16 +33,20 @@ public AdminController(
public Localizer T { get; set; }
public IOrchardServices Services { get; set; }

[HttpPost]
public ActionResult Translate(int id, string to) {
var masterContentItem = _contentManager.Get(id, VersionOptions.Latest);
if (masterContentItem == null)
return HttpNotFound();

if (!Services.Authorizer.Authorize(Permissions.ViewContent, masterContentItem, T("Couldn't open original content")))
return new HttpUnauthorizedResult();

var masterLocalizationPart = masterContentItem.As<LocalizationPart>();
if (masterLocalizationPart == null)
return HttpNotFound();

// Check is current item stll exists, and redirect.
// Check if current item still exists, and redirect.
var existingTranslation = _localizationService.GetLocalizedContentItem(masterContentItem, to);
if (existingTranslation != null) {
var existingTranslationMetadata = _contentManager.GetItemMetadata(existingTranslation);
Expand All @@ -50,76 +55,24 @@ public ActionResult Translate(int id, string to) {
existingTranslationMetadata.EditorRouteValues);
}

var contentItemTranslation = _contentManager.New<LocalizationPart>(masterContentItem.ContentType);
contentItemTranslation.MasterContentItem = masterContentItem;

var content = _contentManager.BuildEditor(contentItemTranslation);

return View(content);
}

[HttpPost, ActionName("Translate")]
[FormValueRequired("submit.Save")]
public ActionResult TranslatePOST(int id) {
return TranslatePOST(id, contentItem => {
if (!contentItem.Has<IPublishingControlAspect>() && !contentItem.TypeDefinition.Settings.GetModel<ContentTypeSettings>().Draftable)
Services.ContentManager.Publish(contentItem);
});
}

[HttpPost, ActionName("Translate")]
[FormValueRequired("submit.Publish")]
public ActionResult TranslateAndPublishPOST(int id) {
return TranslatePOST(id, contentItem => Services.ContentManager.Publish(contentItem));
}

public ActionResult TranslatePOST(int id, Action<ContentItem> conditionallyPublish) {
var masterContentItem = _contentManager.Get(id, VersionOptions.Latest);
if (masterContentItem == null)
return HttpNotFound();

var masterLocalizationPart = masterContentItem.As<LocalizationPart>();
if (masterLocalizationPart == null)
return HttpNotFound();

var model = new EditLocalizationViewModel();
TryUpdateModel(model, "Localization");

var existingTranslation = _localizationService.GetLocalizedContentItem(masterContentItem, model.SelectedCulture);
if (existingTranslation != null) {
var existingTranslationMetadata = _contentManager.GetItemMetadata(existingTranslation);
return RedirectToAction(
Convert.ToString(existingTranslationMetadata.EditorRouteValues["action"]),
existingTranslationMetadata.EditorRouteValues);
}

var contentItemTranslation = _contentManager
.Create<LocalizationPart>(masterContentItem.ContentType, VersionOptions.Draft, part => {
part.MasterContentItem = masterContentItem;
});
// pass a dummy content to the authorization check to check for "own" variations
var dummyContent = _contentManager.New(masterContentItem.ContentType);

var content = _contentManager.UpdateEditor(contentItemTranslation, this);
var contentItemTranslation = _contentManager.Clone(masterContentItem);

This comment has been minimized.

Copy link
@MatteoPiovanelli

MatteoPiovanelli Nov 7, 2016

Contributor

We are cloning the masterContentItem here. However, in a translation scenario, that may not be the desired scenario. Suppose my master is in en-US, and I have a translation in it-IT. I may wish to create the fr-FR version from the it-IT one.

This comment has been minimized.

Copy link
@TFleury

TFleury Nov 11, 2016

Author Contributor

You are right, but it was the previous behavior. It should be discussed.


if (!ModelState.IsValid) {
Services.TransactionManager.Cancel();
if (!Services.Authorizer.Authorize(Permissions.EditContent, contentItemTranslation, T("Couldn't create translated content")))
return new HttpUnauthorizedResult();

This comment has been minimized.

Copy link
@MatteoPiovanelli

MatteoPiovanelli Nov 7, 2016

Contributor

Shouldn't the EditContent permission be verified using the dummyContent before calling _contentManager.Clone()?

This comment has been minimized.

Copy link
@TFleury

TFleury Nov 11, 2016

Author Contributor

It's what is done in the Contents AdminController's Create method :
We create a ContentItem (which is not persisted to the DB) and then check EditContent permission.

This comment has been minimized.

Copy link
@MatteoPiovanelli

MatteoPiovanelli Nov 11, 2016

Contributor

Yes. My comment is more about the actual cloning being called before che check for the permissions. Doing new -> dummy and then verifying on that is perfectly fine for me. I don't understand doing the other thing.

return View(content);
var localizationPart = contentItemTranslation.As<LocalizationPart>();
if(localizationPart != null) {
localizationPart.MasterContentItem = masterContentItem;
localizationPart.Culture = string.IsNullOrWhiteSpace(to) ? null : _cultureManager.GetCultureByName(to);
}

conditionallyPublish(contentItemTranslation.ContentItem);

Services.Notifier.Success(T("Created content item translation."));

var metadata = _contentManager.GetItemMetadata(contentItemTranslation);
return RedirectToAction(Convert.ToString(metadata.EditorRouteValues["action"]), metadata.EditorRouteValues);
}

bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) {
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
}
Services.Notifier.Success(T("Successfully cloned. The translated content was saved as a draft."));

void IUpdateModel.AddModelError(string key, LocalizedString errorMessage) {
ModelState.AddModelError(key, errorMessage.ToString());
var adminRouteValues = _contentManager.GetItemMetadata(contentItemTranslation).AdminRouteValues;
return RedirectToRoute(adminRouteValues);
}
}
}
Loading

0 comments on commit 87a3948

Please sign in to comment.