Merge branch 'leekelleher-U4-337' into 7.3.0

Conflicts:
	src/Umbraco.Core/Umbraco.Core.csproj
This commit is contained in:
Shannon
2015-01-06 12:24:29 +11:00
18 changed files with 427 additions and 3 deletions

View File

@@ -293,7 +293,7 @@ namespace Umbraco.Core
}
/// <summary>
/// Defines the alias identifiers for Umbraco relation types.
/// Defines the alias identifiers for built-in Umbraco relation types.
/// </summary>
public static class RelationTypes
{
@@ -306,6 +306,16 @@ namespace Umbraco.Core
/// ContentType alias for default relation type "Relate Document On Copy".
/// </summary>
public const string RelateDocumentOnCopyAlias = "relateDocumentOnCopy";
/// <summary>
/// ContentType name for default relation type "Relate Parent Document On Delete".
/// </summary>
public const string RelateParentDocumentOnDeleteName = "Relate Parent Document On Delete";
/// <summary>
/// ContentType alias for default relation type "Relate Parent Document On Delete".
/// </summary>
public const string RelateParentDocumentOnDeleteAlias = "relateParentDocumentOnDelete";
}
}
}

View File

@@ -10,7 +10,7 @@ namespace Umbraco.Core.Models.Rdbms
internal class RelationTypeDto
{
[Column("id")]
[PrimaryKeyColumn(IdentitySeed = 2)]
[PrimaryKeyColumn(IdentitySeed = 3)]
public int Id { get; set; }
[Column("dual")]

View File

@@ -268,7 +268,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
private void CreateUmbracoRelationTypeData()
{
_database.Insert("umbracoRelationType", "id", false, new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"), Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName });
_database.Insert("umbracoRelationType", "id", false, new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName });
_database.Insert("umbracoRelationType", "id", false, new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName });
}
private void CreateCmsTaskTypeData()

View File

@@ -0,0 +1,26 @@
using System;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero
{
[Migration("7.3.0", 0, GlobalSettings.UmbracoMigrationName)]
public class AddRelationTypeForDocumentOnDelete : MigrationBase
{
public override void Up()
{
Execute.Code(AddRelationType);
}
public static string AddRelationType(Database database)
{
database.Insert("umbracoRelationType", "id", false, new RelationTypeDto { Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName });
return string.Empty;
}
public override void Down()
{
}
}
}

View File

@@ -393,6 +393,7 @@
<Compile Include="Persistence\Mappers\TemplateMapper.cs" />
<Compile Include="Persistence\Migrations\DataLossException.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\RemoveTemplateMasterColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddRelationTypeForDocumentOnDelete.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenTwoZero\AddMissingForeignKeyForContentType.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenTwoZero\AlterDataTypePreValueTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenOneZero\AssignMissingPrimaryForMySqlKeys.cs" />

View File

@@ -20,6 +20,7 @@ Umbraco.Sys.ServerVariables = {
"entityApiBaseUrl": "/umbraco/UmbracoApi/Entity/",
"dashboardApiBaseUrl": "/umbraco/UmbracoApi/Dashboard/",
"updateCheckApiBaseUrl": "/umbraco/Api/UpdateCheck/",
"relationApiBaseUrl": "/umbraco/UmbracoApi/Relation/",
"rteApiBaseUrl": "/umbraco/UmbracoApi/RichTextPreValue/"
},
umbracoSettings: {

View File

@@ -0,0 +1,65 @@
/**
* @ngdoc service
* @name umbraco.resources.relationResource
* @description Handles loading of relation data
**/
function relationResource($q, $http, umbRequestHelper) {
return {
/**
* @ngdoc method
* @name umbraco.resources.relationResource#getByChildId
* @methodOf umbraco.resources.relationResource
*
* @description
* Retrieves the relation data for a given child ID
*
* @param {int} id of the child item
* @param {string} alias of the relation type
* @returns {Promise} resourcePromise object containing the relations array.
*
*/
getByChildId: function (id, alias) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"relationApiBaseUrl",
"GetByChildId",
[{ childId: id, relationTypeAlias: alias }])),
"Failed to get relation by child ID " + id + " and type of " + alias);
},
/**
* @ngdoc method
* @name umbraco.resources.relationResource#deleteById
* @methodOf umbraco.resources.relationResource
*
* @description
* Deletes a relation item with a given id
*
* ##usage
* <pre>
* relationResource.deleteById(1234)
* .then(function() {
* alert('its gone!');
* });
* </pre>
*
* @param {Int} id id of relation item to delete
* @returns {Promise} resourcePromise object.
*
*/
deleteById: function (id) {
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"relationApiBaseUrl",
"DeleteById",
[{ id: id }])),
'Failed to delete item ' + id);
}
};
}
angular.module('umbraco.resources').factory('relationResource', relationResource);

View File

@@ -0,0 +1,57 @@
angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController",
function ($scope, relationResource, contentResource, navigationService, appState, treeService) {
var dialogOptions = $scope.dialogOptions;
var node = dialogOptions.currentNode;
relationResource.getByChildId(node.id, "relateParentDocumentOnDelete").then(function (data) {
$scope.relation = data[0];
if ($scope.relation.ParentId == -1) {
$scope.target = { id: -1, name: "Root" };
} else {
contentResource.getById($scope.relation.ParentId).then(function (data) {
$scope.target = data;
}, function (err) {
$scope.success = false;
$scope.error = err;
});
}
}, function (err) {
$scope.success = false;
$scope.error = err;
});
$scope.restore = function () {
// this code was copied from `content.move.controller.js`
contentResource.move({ parentId: $scope.target.id, id: node.id })
.then(function (path) {
$scope.error = false;
$scope.success = true;
//first we need to remove the node that launched the dialog
treeService.removeNode($scope.currentNode);
//get the currently edited node (if any)
var activeNode = appState.getTreeState("selectedNode");
//we need to do a double sync here: first sync to the moved content - but don't activate the node,
//then sync to the currenlty edited content (note: this might not be the content that was moved!!)
navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }).then(function (args) {
if (activeNode) {
var activeNodePath = treeService.getPath(activeNode).join();
//sync to this node now - depending on what was copied this might already be synced but might not be
navigationService.syncTree({ tree: "content", path: activeNodePath, forceReload: false, activate: true });
}
});
}, function (err) {
$scope.success = false;
$scope.error = err;
});
};
});

View File

@@ -0,0 +1,26 @@
<div ng-controller="Umbraco.Editors.Content.RestoreController">
<div class="umb-dialog-body">
<div class="umb-pane">
<p class="abstract" ng-hide="success">
Restore <strong>{{currentNode.name}}</strong> to under <strong>{{target.name}}</strong>?
</p>
<div class="alert alert-error" ng-show="error">
<h4>{{error.errorMsg}}</h4>
<p>{{error.data.Message}}</p>
</div>
<div class="alert alert-success" ng-show="success">
<p><strong>{{currentNode.name}}</strong> was moved underneath <strong>{{target.name}}</strong></p>
<button class="btn btn-primary" ng-click="nav.hideDialog()">OK</button>
</div>
</div>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar" ng-hide="success">
<a class="btn btn-link" ng-click="nav.hideDialog()">Cancel</a>
<button class="btn btn-primary" ng-click="restore()">Restore</button>
</div>
</div>

View File

@@ -27,6 +27,7 @@
<key alias="unpublish">Unpublish</key>
<key alias="refreshNode">Reload nodes</key>
<key alias="republish">Republish entire site</key>
<key alias="restore" version="7.3.0">Restore</key>
<key alias="rights">Permissions</key>
<key alias="rollback">Rollback</key>
<key alias="sendtopublish">Send To Publish</key>

View File

@@ -27,6 +27,7 @@
<key alias="unpublish">Unpublish</key>
<key alias="refreshNode">Reload nodes</key>
<key alias="republish">Republish entire site</key>
<key alias="restore" version="7.3.0">Restore</key>
<key alias="rights">Permissions</key>
<key alias="rollback">Rollback</key>
<key alias="sendtopublish">Send To Publish</key>

View File

@@ -212,6 +212,10 @@ namespace Umbraco.Web.Editors
"packageInstallApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<PackageInstallController>(
controller => controller.Fetch(string.Empty))
},
{
"relationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<RelationController>(
controller => controller.GetById(0))
},
{
"rteApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<RichTextPreValueController>(
controller => controller.GetConfiguration())

View File

@@ -0,0 +1,67 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi.Filters;
using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.Editors
{
[PluginController("UmbracoApi")]
[UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)]
public class RelationController : ContentControllerBase
{
public RelationController()
: this(UmbracoContext.Current)
{
}
public RelationController(UmbracoContext umbracoContext)
: base(umbracoContext)
{
}
public IRelation GetById(int id)
{
return Services.RelationService.GetById(id);
}
[EnsureUserPermissionForContent("childId")]
public IEnumerable<IRelation> GetByChildId(int childId, string relationTypeAlias = "")
{
var relations = Services.RelationService.GetByChildId(childId);
if (relations == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
if (!string.IsNullOrWhiteSpace(relationTypeAlias))
{
return relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias));
}
return relations;
}
[HttpDelete]
[HttpPost]
public HttpResponseMessage DeleteById(int id)
{
var foundRelation = GetObjectFromRequest(() => Services.RelationService.GetById(id));
if (foundRelation == null)
{
return HandleContentNotFound(id, false);
}
Services.RelationService.Delete(foundRelation);
return Request.CreateResponse(HttpStatusCode.OK);
}
}
}

View File

@@ -0,0 +1,65 @@
using System;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Auditing;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
namespace Umbraco.Web.Strategies
{
public sealed class RelateOnTrashHandler : ApplicationEventHandler
{
protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
ContentService.Moved += ContentService_Moved;
ContentService.Trashed += ContentService_Trashed;
}
private void ContentService_Moved(IContentService sender, MoveEventArgs<IContent> e)
{
foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinContent.ToInvariantString())))
{
var relationService = ApplicationContext.Current.Services.RelationService;
var relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias;
var relations = relationService.GetByChildId(item.Entity.Id);
foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias)))
{
relationService.Delete(relation);
}
}
}
private void ContentService_Trashed(IContentService sender, MoveEventArgs<IContent> e)
{
var relationService = ApplicationContext.Current.Services.RelationService;
var relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias;
var relationType = relationService.GetRelationTypeByAlias(relationTypeAlias);
// check that the relation-type exists, if not, then recreate it
if (relationType == null)
{
var documentObjectType = new Guid(Constants.ObjectTypes.Document);
var relationTypeName = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName;
relationType = new RelationType(documentObjectType, documentObjectType, relationTypeAlias, relationTypeName);
relationService.Save(relationType);
}
foreach (var item in e.MoveInfoCollection)
{
var originalPath = item.OriginalPath.ToDelimitedList();
var originalParentId = originalPath.Count > 2
? int.Parse(originalPath[originalPath.Count - 2])
: Constants.System.Root;
// Add a relation for the item being deleted, so that we can know the original parent for if we need to restore later
var relation = new Relation(originalParentId, item.Entity.Id, relationType);
relationService.Save(relation);
Audit.Add(AuditTypes.Delete, string.Format("Trashed content with Id: '{0}' related to original parent content with Id: '{1}'", item.Entity.Id, originalParentId), item.Entity.WriterId, item.Entity.Id);
}
}
}
}

View File

@@ -177,6 +177,7 @@ namespace Umbraco.Web.Trees
if (item.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Contains(RecycleBinId.ToInvariantString()))
{
nodeMenu.DefaultMenuAlias = null;
nodeMenu.Items.Insert(2, new MenuItem(ActionRestore.Instance, ui.Text("actions", ActionRestore.Instance.Alias)));
}
else
{

View File

@@ -310,6 +310,7 @@
<Compile Include="Editors\EntityControllerConfigurationAttribute.cs" />
<Compile Include="Editors\ImagesController.cs" />
<Compile Include="Editors\PackageInstallController.cs" />
<Compile Include="Editors\RelationController.cs" />
<Compile Include="Editors\CanvasDesignerController.cs" />
<Compile Include="Editors\UserController.cs" />
<Compile Include="GridTemplateExtensions.cs" />
@@ -495,6 +496,7 @@
<Compile Include="Mvc\UmbracoVirtualNodeRouteHandler.cs" />
<Compile Include="Routing\CustomRouteUrlProvider.cs" />
<Compile Include="Routing\UrlProviderExtensions.cs" />
<Compile Include="Strategies\RelateOnTrashHandler.cs" />
<Compile Include="Strategies\Migrations\ClearCsrfCookiesAfterUpgrade.cs" />
<Compile Include="Strategies\Migrations\ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs" />
<Compile Include="Strategies\Migrations\EnsureListViewDataTypeIsCreated.cs" />

View File

@@ -0,0 +1,95 @@
using System;
using umbraco.interfaces;
using umbraco.BasePages;
namespace umbraco.BusinessLogic.Actions
{
/// <summary>
/// This action is invoked when the content item is to be restored from the recycle bin
/// </summary>
public class ActionRestore : IAction
{
//create singleton
#pragma warning disable 612,618
private static readonly ActionRestore m_instance = new ActionRestore();
#pragma warning restore 612,618
/// <summary>
/// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons.
/// All Umbraco assemblies should use the singleton instantiation (this.Instance)
/// When this applicatio is refactored, this constuctor should be made private.
/// </summary>
[Obsolete("Use the singleton instantiation instead of a constructor")]
public ActionRestore() { }
public static ActionRestore Instance
{
get { return m_instance; }
}
#region IAction Members
public char Letter
{
get
{
return 'V';
}
}
public string JsFunctionName
{
get
{
return null;
}
}
public string JsSource
{
get
{
return null;
}
}
public string Alias
{
get
{
return "restore";
}
}
public string Icon
{
get
{
return "undo";
}
}
public bool ShowInNotifier
{
get
{
return true;
}
}
public bool CanBePermissionAssigned
{
get
{
return true;
}
}
#endregion
}
}

View File

@@ -211,6 +211,7 @@
<Compile Include="Actions\ActionUnPublish.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Actions\ActionRestore.cs" />
<Compile Include="Actions\LegacyActionMenuItemAttribute.cs" />
<Compile Include="businesslogic\CMSPreviewNode.cs" />
<Compile Include="businesslogic\datatype\AbstractDataEditorControl.cs" />