Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content cloning and translation #6630

Merged
merged 21 commits into from
May 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
96d9bdb
Add Clone support to Parts and Fields drivers
TFleury Mar 22, 2016
e8b38e0
ContentManager : Use content cloning instead of import/export
TFleury Mar 22, 2016
92dbb4f
Update Content AdminController for displaying cloned content editor
TFleury Mar 22, 2016
8edac78
Orchard.Core : Implement Cloning
TFleury Mar 22, 2016
e9b7d30
Taxonomies : Implement cloning
TFleury Mar 22, 2016
5e5beed
Tags : Implement cloning
TFleury Mar 22, 2016
9a24f3e
Projections : Implement cloning
TFleury Mar 22, 2016
c83762b
MediaLibrary : Implement cloning on MediaLibraryPickerField
TFleury Mar 22, 2016
716558d
Layouts : Implement cloning on LayoutPart
TFleury Mar 22, 2016
899f6ec
Fields : Implement cloning
TFleury Mar 22, 2016
7fb4cca
ContentPicker : Implement cloning
TFleury Mar 22, 2016
264ce2b
Comments : Implement cloning
TFleury Mar 22, 2016
90c92df
Localization : Implement cloning on LocalizationPart
TFleury Mar 22, 2016
d1a4cdb
Localization : Update Translate Action to edit a clone of MasterConte…
TFleury Mar 22, 2016
54394d8
Move BooleanField's default value setting to a ContentHandler
TFleury Apr 12, 2016
301369e
Revert "Move BooleanField's default value setting to a ContentHandler"
TFleury Apr 27, 2016
4a53901
DefaultContentManager's Clone method creates clone content as draft.
TFleury Apr 27, 2016
bc98b91
Taxonomies : Rewrite Cloning
TFleury Apr 27, 2016
32cdbf9
Localization : Rewrite Translate
TFleury Apr 27, 2016
0bf1dbc
cleanup! Localization : Rewrite Translate
TFleury Apr 27, 2016
09299f6
Partial revert of "Fields : Implement cloning" on BooleanField
TFleury Apr 27, 2016
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
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) {
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 @@ -89,6 +89,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 @@ -70,6 +70,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 @@ -186,6 +186,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 @@ -80,6 +80,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 @@ -67,6 +67,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 @@ -82,6 +82,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 @@ -114,6 +114,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);

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

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