Compare commits

...

52 Commits

Author SHA1 Message Date
Stephan 0074f57970 Version 7.6-alpha063 2017-02-14 11:23:20 +01:00
Stephan 9b4a6f2761 Merge origin/dev-v7.6 into deploy-219 2017-02-14 11:22:03 +01:00
Stephan f889b5206c Scope - handle publishing events 2017-02-14 11:11:40 +01:00
Stephan 507d7dd4f0 Scope - forgot to complete some scopes 2017-02-14 11:11:40 +01:00
Stephan 55c5b41693 Scope - report completion 2017-02-14 11:11:39 +01:00
Stephan b58d8677b3 Scope - bugfixing 2017-02-14 11:11:39 +01:00
Shannon f30d7fc68c Fixes nuspec to be net45 as a min 2017-02-14 14:29:23 +11:00
Warren Buckley d504d8f54e Update Core Deploy EntityTypes for Forms (Workflow is part of main form artifact) and we dont deploy records. Changing to PreValue & DataSources 2017-02-13 15:54:39 +00:00
Claus b519d68e1a fixing PartialViewMacrosTree and ScriptTree in trees.Release.config. 2017-02-13 13:34:35 +01:00
Claus deb8d652c5 fixing PartialViewsTreeController reference in trees.Release.config. 2017-02-13 12:44:16 +01:00
Stephan 12dd01dd29 Scope - refactor call context 2017-02-13 09:05:33 +01:00
Stephan c64e3a8908 Scope - refactor event dispatching 2017-02-13 09:05:32 +01:00
Stephan 74e8390bda Scope - enable http vs call context switching 2017-02-11 09:56:15 +01:00
Stephan 4f9bbdf7f7 Scope - Fix BulkInsertRecords, ensure a db connection is open 2017-02-11 09:56:12 +01:00
Stephan 67fc8b6215 Scope - fix media files deletion 2017-02-11 09:55:32 +01:00
Sebastiaan Janssen 3840ac2fed Merge pull request #1743 from umbraco/temp-U4-9484
U4-9484 Implement correct tree menu logic for partialviews, partialviewmacros and scripts
2017-02-10 17:04:41 +01:00
Sebastiaan Janssen 7c64f1aa0d Merge pull request #1745 from umbraco/temp-U4-7385
Fixes: U4-7385 Improve UI for listview - Make it clearer that users can search children
2017-02-10 16:43:35 +01:00
Stephan 82c18955b0 Version 7.6-alpha060 2017-02-10 14:38:50 +01:00
Mads Rasmussen 0dc0e68174 reset pagination when searching 2017-02-10 10:36:58 +01:00
Emil Wangaa aaa9c8f194 Fixes delete of folders in partialviews,partialviewmacros and scripts 2017-02-10 09:24:14 +01:00
Stephan 308a61895e deploy-219 - implement filesystem CanAddPhysical, AddFile from physical path 2017-02-09 15:58:44 +01:00
Mads Rasmussen 1901fbb6ed add markup for member picker and content picker 2017-02-09 10:42:15 +01:00
Mads Rasmussen 1de52482e3 fix back arrow alignment 2017-02-09 09:23:30 +01:00
Mads Rasmussen 49c2ae3673 cleanup 2017-02-08 23:37:48 +01:00
Mads Rasmussen 5f22c81994 fixes: U4-9467 Mini list view: don’t animate list view if it is a start node 2017-02-08 23:37:33 +01:00
Mads Rasmussen f13427f5fb fixes to breadcrumb 2017-02-08 22:49:16 +01:00
Mads Rasmussen 2785eaeda5 only show unpublished state for content 2017-02-08 20:22:54 +01:00
Emil Wangaa a31cfb86f8 Moved reload to be last menu option and added a separator for delete option to keep consistency 2017-02-08 15:57:53 +01:00
Emil Wangaa e4802b592f Centralized the tree menu logic for partialviews, partialviewmacros and scripts to the FileSystemTreeController and implemented logic to match what we do for Document Types 2017-02-08 15:43:31 +01:00
Mads Rasmussen 548f9fd01f open mini list view before children are loaded 2017-02-08 15:02:03 +01:00
Mads Rasmussen 843796c9a4 refactor move logic from tree controller into to mini-list-view component 2017-02-08 14:58:54 +01:00
Mads Rasmussen c060856782 fix breadcrumb for pickers with a startNode 2017-02-08 11:09:09 +01:00
Mads Rasmussen 857063ebd8 fixes: U4-9465 Mini list view: don’t show expand arrow on list views in trees not supporting mini list view 2017-02-08 09:53:52 +01:00
Mads Rasmussen 2ba5627e2c fixes: U4-9471 Mini list view: show breadcrumb instead of back button 2017-02-07 15:57:53 +01:00
Mads Rasmussen 2669c68df0 fixes: U4-9468 Mini list view: add empty state for no children + no search result 2017-02-07 11:55:45 +01:00
Mads Rasmussen 912a6a52cc fixes: U4-9472 Mini list view: show published/unpublished state of node 2017-02-07 10:10:37 +01:00
Mads Rasmussen b4450ca7f2 add null check for startNode 2017-02-02 11:44:13 +01:00
Mads Rasmussen 7004765acf remove unused code 2017-02-02 11:40:53 +01:00
Mads Rasmussen 77a91bc017 fix entity look up 2017-02-02 11:28:56 +01:00
Mads Rasmussen 14b96da588 combine redundant resource code into one function 2017-02-02 11:19:56 +01:00
Mads Rasmussen 99324035e5 clean up animation classes 2017-02-02 10:19:54 +01:00
Mads Rasmussen 8b9a5000be move back button styles to class 2017-02-02 10:09:31 +01:00
Mads Rasmussen c82bddb7dd remove fake list view search node 2017-02-02 09:51:13 +01:00
Mads Rasmussen f2fd45576b speed up animations 2017-02-02 09:35:01 +01:00
Mads Rasmussen d5a687cf3d start on code clean up - make ump-mini-list-view directive + move inline styles to classes 2017-02-01 23:12:49 +01:00
Mads Rasmussen 26acecb720 start on animation and nested list views 2017-02-01 00:28:40 +01:00
Mads Rasmussen 7c42cd26c2 clear minilistview data when leaving 2017-01-31 10:26:03 +01:00
Mads Rasmussen 13758418ac add better load indicators 2017-01-31 10:14:17 +01:00
Mads Rasmussen 77b04d74d4 load mini list view for pickers with a start node 2017-01-31 10:00:44 +01:00
Mads Rasmussen bf797bccc7 open list view on expand 2017-01-30 22:35:19 +01:00
Mads Rasmussen 4336f5201d show expand arrow for list views 2017-01-30 22:34:04 +01:00
Mads Rasmussen ed019a90e2 start on mini list view for pickers 2017-01-30 14:25:10 +01:00
71 changed files with 1865 additions and 866 deletions
+31 -31
View File
@@ -44,37 +44,37 @@
</dependencies>
</metadata>
<files>
<file src="..\_BuildOutput\WebApp\bin\businesslogic.dll" target="lib\net462\businesslogic.dll" />
<file src="..\_BuildOutput\WebApp\bin\businesslogic.xml" target="lib\net462\businesslogic.xml" />
<file src="..\_BuildOutput\WebApp\bin\cms.dll" target="lib\net462\cms.dll" />
<file src="..\_BuildOutput\WebApp\bin\cms.xml" target="lib\net462\cms.xml" />
<file src="..\_BuildOutput\WebApp\bin\controls.dll" target="lib\net462\controls.dll" />
<file src="..\_BuildOutput\WebApp\bin\controls.xml" target="lib\net462\controls.xml" />
<file src="..\_BuildOutput\WebApp\bin\interfaces.dll" target="lib\net462\interfaces.dll" />
<file src="..\_BuildOutput\WebApp\bin\interfaces.xml" target="lib\net462\interfaces.xml" />
<file src="..\_BuildOutput\WebApp\bin\log4net.dll" target="lib\net462\log4net.dll" />
<file src="..\_BuildOutput\WebApp\bin\Microsoft.ApplicationBlocks.Data.dll" target="lib\net462\Microsoft.ApplicationBlocks.Data.dll" />
<file src="..\_BuildOutput\WebApp\bin\SQLCE4Umbraco.dll" target="lib\net462\SQLCE4Umbraco.dll" />
<file src="..\_BuildOutput\WebApp\bin\SQLCE4Umbraco.xml" target="lib\net462\SQLCE4Umbraco.xml" />
<file src="..\_BuildOutput\WebApp\bin\System.Data.SqlServerCe.dll" target="lib\net462\System.Data.SqlServerCe.dll" />
<file src="..\_BuildOutput\WebApp\bin\System.Data.SqlServerCe.Entity.dll" target="lib\net462\System.Data.SqlServerCe.Entity.dll" />
<file src="..\_BuildOutput\WebApp\bin\TidyNet.dll" target="lib\net462\TidyNet.dll" />
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Core.dll" target="lib\net462\Umbraco.Core.dll" />
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Core.xml" target="lib\net462\Umbraco.Core.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.DataLayer.dll" target="lib\net462\umbraco.DataLayer.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.DataLayer.xml" target="lib\net462\umbraco.DataLayer.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.dll" target="lib\net462\umbraco.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.xml" target="lib\net462\umbraco.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.editorControls.dll" target="lib\net462\umbraco.editorControls.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.editorControls.xml" target="lib\net462\umbraco.editorControls.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.MacroEngines.dll" target="lib\net462\umbraco.MacroEngines.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.MacroEngines.xml" target="lib\net462\umbraco.MacroEngines.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.providers.dll" target="lib\net462\umbraco.providers.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.providers.xml" target="lib\net462\umbraco.providers.xml" />
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Web.UI.dll" target="lib\net462\Umbraco.Web.UI.dll" />
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Web.UI.xml" target="lib\net462\Umbraco.Web.UI.xml" />
<file src="..\_BuildOutput\WebApp\bin\UmbracoExamine.dll" target="lib\net462\UmbracoExamine.dll" />
<file src="..\_BuildOutput\WebApp\bin\UmbracoExamine.xml" target="lib\net462\UmbracoExamine.xml" />
<file src="..\_BuildOutput\WebApp\bin\businesslogic.dll" target="lib\net45\businesslogic.dll" />
<file src="..\_BuildOutput\WebApp\bin\businesslogic.xml" target="lib\net45\businesslogic.xml" />
<file src="..\_BuildOutput\WebApp\bin\cms.dll" target="lib\net45\cms.dll" />
<file src="..\_BuildOutput\WebApp\bin\cms.xml" target="lib\net45\cms.xml" />
<file src="..\_BuildOutput\WebApp\bin\controls.dll" target="lib\net45\controls.dll" />
<file src="..\_BuildOutput\WebApp\bin\controls.xml" target="lib\net45\controls.xml" />
<file src="..\_BuildOutput\WebApp\bin\interfaces.dll" target="lib\net45\interfaces.dll" />
<file src="..\_BuildOutput\WebApp\bin\interfaces.xml" target="lib\net45\interfaces.xml" />
<file src="..\_BuildOutput\WebApp\bin\log4net.dll" target="lib\net45\log4net.dll" />
<file src="..\_BuildOutput\WebApp\bin\Microsoft.ApplicationBlocks.Data.dll" target="lib\net45\Microsoft.ApplicationBlocks.Data.dll" />
<file src="..\_BuildOutput\WebApp\bin\SQLCE4Umbraco.dll" target="lib\net45\SQLCE4Umbraco.dll" />
<file src="..\_BuildOutput\WebApp\bin\SQLCE4Umbraco.xml" target="lib\net45\SQLCE4Umbraco.xml" />
<file src="..\_BuildOutput\WebApp\bin\System.Data.SqlServerCe.dll" target="lib\net45\System.Data.SqlServerCe.dll" />
<file src="..\_BuildOutput\WebApp\bin\System.Data.SqlServerCe.Entity.dll" target="lib\net45\System.Data.SqlServerCe.Entity.dll" />
<file src="..\_BuildOutput\WebApp\bin\TidyNet.dll" target="lib\net45\TidyNet.dll" />
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Core.dll" target="lib\net45\Umbraco.Core.dll" />
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Core.xml" target="lib\net45\Umbraco.Core.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.DataLayer.dll" target="lib\net45\umbraco.DataLayer.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.DataLayer.xml" target="lib\net45\umbraco.DataLayer.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.dll" target="lib\net45\umbraco.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.xml" target="lib\net45\umbraco.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.editorControls.dll" target="lib\net45\umbraco.editorControls.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.editorControls.xml" target="lib\net45\umbraco.editorControls.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.MacroEngines.dll" target="lib\net45\umbraco.MacroEngines.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.MacroEngines.xml" target="lib\net45\umbraco.MacroEngines.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.providers.dll" target="lib\net45\umbraco.providers.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.providers.xml" target="lib\net45\umbraco.providers.xml" />
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Web.UI.dll" target="lib\net45\Umbraco.Web.UI.dll" />
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Web.UI.xml" target="lib\net45\Umbraco.Web.UI.xml" />
<file src="..\_BuildOutput\WebApp\bin\UmbracoExamine.dll" target="lib\net45\UmbracoExamine.dll" />
<file src="..\_BuildOutput\WebApp\bin\UmbracoExamine.xml" target="lib\net45\UmbracoExamine.xml" />
<file src="tools\install.core.ps1" target="tools\install.ps1" />
<!-- Added to be able to produce a symbols package -->
+1 -1
View File
@@ -1,3 +1,3 @@
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
7.6.0
alpha061
alpha063
+1 -1
View File
@@ -12,4 +12,4 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.6.0")]
[assembly: AssemblyInformationalVersion("7.6.0-alpha061")]
[assembly: AssemblyInformationalVersion("7.6.0-alpha063")]
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration
/// Gets the version comment (like beta or RC).
/// </summary>
/// <value>The version comment.</value>
public static string CurrentComment { get { return "alpha061"; } }
public static string CurrentComment { get { return "alpha063"; } }
// Get the version of the umbraco.dll by looking at a class in that dll
// Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx
+10 -10
View File
@@ -40,8 +40,8 @@ namespace Umbraco.Core
// forms
public const string FormsForm = "forms-form";
public const string FormsWorkflow = "forms-workflow";
public const string FormsRecord = "forms-record";
public const string FormsPreValue = "forms-prevalue";
public const string FormsDataSource = "forms-datasource";
// string entity types
@@ -89,10 +89,10 @@ namespace Umbraco.Core
return RelationType;
case UmbracoObjectTypes.FormsForm:
return FormsForm;
case UmbracoObjectTypes.FormsWorkflow:
return FormsWorkflow;
case UmbracoObjectTypes.FormsRecord:
return FormsRecord;
case UmbracoObjectTypes.FormsPreValue:
return FormsPreValue;
case UmbracoObjectTypes.FormsDataSource:
return FormsDataSource;
}
throw new NotSupportedException(string.Format("UmbracoObjectType \"{0}\" does not have a matching EntityType.", umbracoObjectType));
}
@@ -131,10 +131,10 @@ namespace Umbraco.Core
return UmbracoObjectTypes.RelationType;
case FormsForm:
return UmbracoObjectTypes.FormsForm;
case FormsWorkflow:
return UmbracoObjectTypes.FormsWorkflow;
case FormsRecord:
return UmbracoObjectTypes.FormsRecord;
case FormsPreValue:
return UmbracoObjectTypes.FormsPreValue;
case FormsDataSource:
return UmbracoObjectTypes.FormsDataSource;
}
throw new NotSupportedException(
string.Format("EntityType \"{0}\" does not have a matching UmbracoObjectType.", entityType));
+8 -8
View File
@@ -174,24 +174,24 @@ namespace Umbraco.Core
public static readonly Guid FormsFormGuid = new Guid(FormsForm);
/// <summary>
/// Guid for a Forms Workflow.
/// Guid for a Forms PreValue Source.
/// </summary>
public const string FormsWorkflow = "42D7BF9B-A362-4FEE-B45A-674D5C064B70";
public const string FormsPreValue = "42D7BF9B-A362-4FEE-B45A-674D5C064B70";
/// <summary>
/// Guid for a Forms Workflow.
/// Guid for a Forms PreValue Source.
/// </summary>
public static readonly Guid FormsWorkflowGuid = new Guid(FormsWorkflow);
public static readonly Guid FormsPreValueGuid = new Guid(FormsPreValue);
/// <summary>
/// Guid for a Forms Record.
/// Guid for a Forms DataSource.
/// </summary>
public const string FormsRecord = "CFED6CE4-9359-443E-9977-9956FEB1D867";
public const string FormsDataSource = "CFED6CE4-9359-443E-9977-9956FEB1D867";
/// <summary>
/// Guid for a Forms Record.
/// Guid for a Forms DataSource.
/// </summary>
public static readonly Guid FormsRecordGuid = new Guid(FormsRecord);
public static readonly Guid FormsDataSourceGuid = new Guid(FormsDataSource);
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Umbraco.Core.Events
{
public class DeleteEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<DeleteEventArgs<TEntity>>
public class DeleteEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<DeleteEventArgs<TEntity>>, IDeletingMediaFilesEventArgs
{
/// <summary>
/// Constructor accepting multiple entities that are used in the delete operation
@@ -1,29 +0,0 @@
namespace Umbraco.Core.Events
{
public enum EventsDispatchMode
{
// in 7.5 we'd do:
//
// using (var uow = ...)
// { ... }
// Done.RaiseEvent(...);
//
// and so the event would trigger only *after* the transaction has completed,
// so actually PassThrough is more aggressive than what we had in 7.5 and should
// not be used. now in 7.6 we do:
//
// using (var uow = ...)
// {
// ...
// uow.Events.Dispatch(Done, ...);
// }
//
// so the event can be collected, so the default "kinda compatible" more has to be
// the Scope mode, and Passive is for Deploy only
Unspecified = 0,
PassThrough, // both Doing and Done trigger immediately
Scope, // Doing triggers immediately, Done queued and triggered when & if the scope completes
Passive // Doing never triggers, Done queued and needs to be handled by custom code
}
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Umbraco.Core.Events
{
internal interface IDeletingMediaFilesEventArgs
{
List<string> MediaFilesToDelete { get; }
}
}
@@ -5,7 +5,7 @@ using Umbraco.Core.Models;
namespace Umbraco.Core.Events
{
public class RecycleBinEventArgs : CancellableEventArgs, IEquatable<RecycleBinEventArgs>
public class RecycleBinEventArgs : CancellableEventArgs, IEquatable<RecycleBinEventArgs>, IDeletingMediaFilesEventArgs
{
public RecycleBinEventArgs(Guid nodeObjectType, Dictionary<int, IEnumerable<Property>> allPropertyData, bool emptiedSuccessfully)
: base(false)
@@ -97,6 +97,8 @@ namespace Umbraco.Core.Events
/// </remarks>
public List<string> Files { get; private set; }
public List<string> MediaFilesToDelete { get { return Files; } }
/// <summary>
/// Gets the list of all property data associated with a content id
/// </summary>
+31 -109
View File
@@ -1,126 +1,48 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Umbraco.Core.IO;
namespace Umbraco.Core.Events
{
{
/// <summary>
/// This event manager is created for each scope and is aware of if it is nested in an outer scope
/// </summary>
/// <remarks>
/// The outer scope is the only scope that can raise events, the inner scope's will defer to the outer scope
/// </remarks>
internal class ScopeEventDispatcher : IEventDispatcher
internal class ScopeEventDispatcher : ScopeEventDispatcherBase
{
private readonly EventsDispatchMode _mode;
private List<IEventDefinition> _events;
public ScopeEventDispatcher()
: base(true)
{ }
public ScopeEventDispatcher(EventsDispatchMode mode)
{
_mode = mode;
}
private List<IEventDefinition> Events { get { return _events ?? (_events = new List<IEventDefinition>()); } }
private bool PassThroughCancelable { get { return _mode == EventsDispatchMode.PassThrough || _mode == EventsDispatchMode.Scope; } }
private bool PassThrough { get { return _mode == EventsDispatchMode.PassThrough; } }
private bool RaiseEvents { get { return _mode == EventsDispatchMode.Scope; } }
public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null)
{
if (eventHandler == null) return args.Cancel;
if (PassThroughCancelable == false) return args.Cancel;
eventHandler(sender, args);
return args.Cancel;
}
public bool DispatchCancelable<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string eventName = null)
where TArgs : CancellableEventArgs
{
if (eventHandler == null) return args.Cancel;
if (PassThroughCancelable == false) return args.Cancel;
eventHandler(sender, args);
return args.Cancel;
}
public bool DispatchCancelable<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string eventName = null)
where TArgs : CancellableEventArgs
{
if (eventHandler == null) return args.Cancel;
if (PassThroughCancelable == false) return args.Cancel;
eventHandler(sender, args);
return args.Cancel;
}
public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null)
{
if (eventHandler == null) return;
if (PassThrough)
eventHandler(sender, args);
else
Events.Add(new EventDefinition(eventHandler, sender, args, eventName));
}
public void Dispatch<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string eventName = null)
{
if (eventHandler == null) return;
if (PassThrough)
eventHandler(sender, args);
else
Events.Add(new EventDefinition<TArgs>(eventHandler, sender, args, eventName));
}
public void Dispatch<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string eventName = null)
{
if (eventHandler == null) return;
if (PassThrough)
eventHandler(sender, args);
else
Events.Add(new EventDefinition<TSender, TArgs>(eventHandler, sender, args, eventName));
}
public IEnumerable<IEventDefinition> GetEvents(EventDefinitionFilter filter)
{
if (_events == null)
return Enumerable.Empty<IEventDefinition>();
switch (filter)
{
case EventDefinitionFilter.All:
return _events;
case EventDefinitionFilter.FirstIn:
var l1 = new OrderedHashSet<IEventDefinition>();
foreach (var e in _events)
{
l1.Add(e);
}
return l1;
case EventDefinitionFilter.LastIn:
var l2 = new OrderedHashSet<IEventDefinition>(keepOldest:false);
foreach (var e in _events)
{
l2.Add(e);
}
return l2;
default:
throw new ArgumentOutOfRangeException("filter", filter, null);
}
}
public void ScopeExit(bool completed)
protected override void ScopeExitCompleted()
{
// fixme - we'd need to de-duplicate events somehow, etc - and the deduplication should be last in wins
if (_events == null) return;
foreach (var e in GetEvents(EventDefinitionFilter.All))
{
e.RaiseEvent();
if (RaiseEvents && completed)
foreach (var e in _events)
e.RaiseEvent();
_events.Clear();
// fixme - not sure I like doing it here - but then where? how?
var delete = e.Args as IDeletingMediaFilesEventArgs;
if (delete != null && delete.MediaFilesToDelete.Count > 0)
MediaFileSystem.DeleteMediaFiles(delete.MediaFilesToDelete);
}
}
private MediaFileSystem _mediaFileSystem;
private MediaFileSystem MediaFileSystem
{
get
{
if (_mediaFileSystem != null) return _mediaFileSystem;
// fixme - insane! reading config goes cross AppDomain and serializes context?
using (new SafeCallContext())
{
return _mediaFileSystem = FileSystemProviderManager.Current.MediaFileSystem;
}
}
}
}
}
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.Events
{
public abstract class ScopeEventDispatcherBase : IEventDispatcher
{
private List<IEventDefinition> _events;
private readonly bool _raiseCancelable;
protected ScopeEventDispatcherBase(bool raiseCancelable)
{
_raiseCancelable = raiseCancelable;
}
private List<IEventDefinition> Events { get { return _events ?? (_events = new List<IEventDefinition>()); } }
public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null)
{
if (eventHandler == null) return args.Cancel;
if (_raiseCancelable == false) return args.Cancel;
eventHandler(sender, args);
return args.Cancel;
}
public bool DispatchCancelable<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string eventName = null)
where TArgs : CancellableEventArgs
{
if (eventHandler == null) return args.Cancel;
if (_raiseCancelable == false) return args.Cancel;
eventHandler(sender, args);
return args.Cancel;
}
public bool DispatchCancelable<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string eventName = null)
where TArgs : CancellableEventArgs
{
if (eventHandler == null) return args.Cancel;
if (_raiseCancelable == false) return args.Cancel;
eventHandler(sender, args);
return args.Cancel;
}
public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null)
{
if (eventHandler == null) return;
Events.Add(new EventDefinition(eventHandler, sender, args, eventName));
}
public void Dispatch<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string eventName = null)
{
if (eventHandler == null) return;
Events.Add(new EventDefinition<TArgs>(eventHandler, sender, args, eventName));
}
public void Dispatch<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string eventName = null)
{
if (eventHandler == null) return;
Events.Add(new EventDefinition<TSender, TArgs>(eventHandler, sender, args, eventName));
}
public IEnumerable<IEventDefinition> GetEvents(EventDefinitionFilter filter)
{
if (_events == null)
return Enumerable.Empty<IEventDefinition>();
switch (filter)
{
case EventDefinitionFilter.All:
return _events;
case EventDefinitionFilter.FirstIn:
var l1 = new OrderedHashSet<IEventDefinition>();
foreach (var e in _events)
{
l1.Add(e);
}
return l1;
case EventDefinitionFilter.LastIn:
var l2 = new OrderedHashSet<IEventDefinition>(keepOldest: false);
foreach (var e in _events)
{
l2.Add(e);
}
return l2;
default:
throw new ArgumentOutOfRangeException("filter", filter, null);
}
}
public void ScopeExit(bool completed)
{
if (_events == null) return;
if (completed)
ScopeExitCompleted();
_events.Clear();
}
protected abstract void ScopeExitCompleted();
}
}
+19
View File
@@ -109,5 +109,24 @@ namespace Umbraco.Core.IO
var wrapped2 = Wrapped as IFileSystem2;
return wrapped2 == null ? Wrapped.GetSize(path) : wrapped2.GetSize(path);
}
// explicitely implementing - not breaking
bool IFileSystem2.CanAddPhysical
{
get
{
var wrapped2 = Wrapped as IFileSystem2;
return wrapped2 != null && wrapped2.CanAddPhysical;
}
}
// explicitely implementing - not breaking
void IFileSystem2.AddFile(string path, string physicalPath, bool overrideIfExists, bool copy)
{
var wrapped2 = Wrapped as IFileSystem2;
if (wrapped2 == null)
throw new NotSupportedException();
wrapped2.AddFile(path, physicalPath, overrideIfExists, copy);
}
}
}
+4
View File
@@ -44,6 +44,10 @@ namespace Umbraco.Core.IO
{
long GetSize(string path);
bool CanAddPhysical { get; }
void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false);
// TODO: implement these
//
//void CreateDirectory(string path);
+62 -1
View File
@@ -7,6 +7,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
@@ -24,6 +25,7 @@ namespace Umbraco.Core.IO
{
private readonly IContentSection _contentConfig;
private readonly UploadAutoFillProperties _uploadAutoFillProperties;
private readonly ILogger _logger;
private readonly object _folderCounterLock = new object();
private long _folderCounter;
@@ -42,6 +44,7 @@ namespace Umbraco.Core.IO
public MediaFileSystem(IFileSystem wrapped, IContentSection contentConfig, ILogger logger)
: base(wrapped)
{
_logger = logger;
_contentConfig = contentConfig;
_uploadAutoFillProperties = new UploadAutoFillProperties(this, logger, contentConfig);
}
@@ -99,7 +102,7 @@ namespace Umbraco.Core.IO
}
else
{
// new scheme: path is "<cuid>-<puid>/<filename>" OR "<cuid>-<puid>-<filename>"
// new scheme: path is "<xuid>/<filename>" where xuid is a combination of cuid and puid
// default media filesystem maps to "~/media/<filepath>"
// assumes that cuid and puid keys can be trusted - and that a single property type
// for a single content cannot store two different files with the same name
@@ -435,6 +438,64 @@ namespace Umbraco.Core.IO
}
}
public void DeleteMediaFiles(IEnumerable<string> files)
{
files = files.Distinct();
Parallel.ForEach(files, file =>
{
try
{
if (file.IsNullOrWhiteSpace()) return;
if (FileExists(file) == false) return;
DeleteFile(file, true);
if (UseTheNewMediaPathScheme == false)
{
// old scheme: filepath is "<int>/<filename>" OR "<int>-<filename>"
// remove the directory if any
var dir = Path.GetDirectoryName(file);
if (string.IsNullOrWhiteSpace(dir) == false)
DeleteDirectory(dir, true);
}
else
{
// new scheme: path is "<xuid>/<filename>" where xuid is a combination of cuid and puid
// remove the directory
var dir = Path.GetDirectoryName(file);
DeleteDirectory(dir, true);
}
// I don't even understand...
/*
var relativeFilePath = GetRelativePath(file); // fixme - should be relative already
if (FileExists(relativeFilePath) == false) return;
var parentDirectory = Path.GetDirectoryName(relativeFilePath);
// don't want to delete the media folder if not using directories.
if (_contentSection.UploadAllowDirectories && parentDirectory != GetRelativePath("/"))
{
//issue U4-771: if there is a parent directory the recursive parameter should be true
DeleteDirectory(parentDirectory, string.IsNullOrEmpty(parentDirectory) == false);
}
else
{
DeleteFile(file, true);
}
*/
}
catch (Exception e)
{
_logger.Error<MediaFileSystem>("Failed to delete attached file \"" + file + "\".", e);
}
});
}
#endregion
#region GenerateThumbnails
+23
View File
@@ -364,6 +364,29 @@ namespace Umbraco.Core.IO
return file.Exists ? file.Length : -1;
}
public bool CanAddPhysical { get { return true; } }
public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false)
{
var fullPath = GetFullPath(path);
if (File.Exists(fullPath))
{
if (overrideIfExists == false)
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
File.Delete(fullPath);
}
var directory = Path.GetDirectoryName(fullPath);
if (directory == null) throw new InvalidOperationException("Could not get directory.");
Directory.CreateDirectory(directory); // ensure it exists
if (copy)
File.Copy(physicalPath, fullPath);
else
File.Move(physicalPath, fullPath);
}
#region Helper Methods
protected virtual void EnsureDirectory(string path)
+41 -2
View File
@@ -33,8 +33,16 @@ namespace Umbraco.Core.IO
{
try
{
using (var stream = _sfs.OpenFile(kvp.Key))
_fs.AddFile(kvp.Key, stream, true);
var fs2 = _fs as IFileSystem2;
if (fs2 != null && fs2.CanAddPhysical)
{
fs2.AddFile(kvp.Key, _sfs.GetFullPath(kvp.Key)); // overwrite, move
}
else
{
using (var stream = _sfs.OpenFile(kvp.Key))
_fs.AddFile(kvp.Key, stream, true);
}
}
catch (Exception e)
{
@@ -282,5 +290,36 @@ namespace Umbraco.Core.IO
if (sf.IsDelete || sf.IsDir) throw new InvalidOperationException("Invalid path.");
return _sfs.GetSize(path);
}
public bool CanAddPhysical { get { return true; } }
public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false)
{
ShadowNode sf;
var normPath = NormPath(path);
if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false))
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
var parts = normPath.Split('/');
for (var i = 0; i < parts.Length - 1; i++)
{
var dirPath = string.Join("/", parts.Take(i + 1));
ShadowNode sd;
if (Nodes.TryGetValue(dirPath, out sd))
{
if (sd.IsFile) throw new InvalidOperationException("Invalid path.");
if (sd.IsDelete) Nodes[dirPath] = new ShadowNode(false, true);
}
else
{
if (_fs.DirectoryExists(dirPath)) continue;
if (_fs.FileExists(dirPath)) throw new InvalidOperationException("Invalid path.");
Nodes[dirPath] = new ShadowNode(false, true);
}
}
_sfs.AddFile(path, physicalPath, overrideIfExists, copy);
Nodes[normPath] = new ShadowNode(false, false);
}
}
}
+17
View File
@@ -164,5 +164,22 @@ namespace Umbraco.Core.IO
var filesystem2 = filesystem as IFileSystem2;
return filesystem2 == null ? filesystem.GetSize(path) : filesystem2.GetSize(path);
}
public bool CanAddPhysical
{
get
{
var fileSystem2 = FileSystem as IFileSystem2;
return fileSystem2 != null && fileSystem2.CanAddPhysical;
}
}
public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false)
{
var fileSystem2 = FileSystem as IFileSystem2;
if (fileSystem2 == null)
throw new NotSupportedException();
fileSystem2.AddFile(path, physicalPath, overrideIfExists, copy);
}
}
}
@@ -153,17 +153,17 @@ namespace Umbraco.Core.Models
FormsForm,
/// <summary>
/// Forms Workflow
/// Forms PreValue
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.FormsWorkflow)]
[FriendlyName("Workflow")]
FormsWorkflow,
[UmbracoObjectType(Constants.ObjectTypes.FormsPreValue)]
[FriendlyName("PreValue")]
FormsPreValue,
/// <summary>
/// Forms Record
/// Forms DataSource
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.FormsRecord)]
[FriendlyName("Record")]
FormsRecord
[UmbracoObjectType(Constants.ObjectTypes.FormsDataSource)]
[FriendlyName("DataSource")]
FormsDataSource
}
}
+5
View File
@@ -647,5 +647,10 @@ namespace Umbraco.Core
return "[GetPropertyValueException]";
}
}
internal static Guid AsGuid(this object value)
{
return value is Guid ? (Guid) value : Guid.Empty;
}
}
}
@@ -105,8 +105,8 @@ namespace Umbraco.Core.Persistence
// try to update
var rowCount = updateCommand.IsNullOrWhiteSpace()
? db.Update(poco)
: db.Update<T>(updateCommand, updateArgs);
? db.Update(poco)
: db.Update<T>(updateCommand, updateArgs);
if (rowCount > 0)
return RecordPersistenceType.Update;
@@ -162,7 +162,7 @@ namespace Umbraco.Core.Persistence
[Obsolete("Use the DatabaseSchemaHelper instead")]
public static void CreateTable<T>(this Database db)
where T : new()
where T : new()
{
var creator = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider);
creator.CreateTable<T>();
@@ -170,7 +170,7 @@ namespace Umbraco.Core.Persistence
[Obsolete("Use the DatabaseSchemaHelper instead")]
public static void CreateTable<T>(this Database db, bool overwrite)
where T : new()
where T : new()
{
var creator = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider);
creator.CreateTable<T>(overwrite);
@@ -231,6 +231,24 @@ namespace Umbraco.Core.Persistence
ISqlSyntaxProvider syntaxProvider,
bool useNativeSqlPlatformBulkInsert = true,
bool commitTrans = false)
{
db.OpenSharedConnection();
try
{
return BulkInsertRecordsTry(db, collection, tr, syntaxProvider, useNativeSqlPlatformBulkInsert, commitTrans);
}
finally
{
db.CloseSharedConnection();
}
}
public static int BulkInsertRecordsTry<T>(this Database db,
IEnumerable<T> collection,
Transaction tr,
ISqlSyntaxProvider syntaxProvider,
bool useNativeSqlPlatformBulkInsert = true,
bool commitTrans = false)
{
if (commitTrans && tr == null)
throw new ArgumentNullException("tr", "The transaction cannot be null if commitTrans is true.");
@@ -241,15 +259,15 @@ namespace Umbraco.Core.Persistence
return 0;
}
var pd = Database.PocoData.ForType(typeof(T));
if (pd == null) throw new InvalidOperationException("Could not find PocoData for " + typeof(T));
var pd = Database.PocoData.ForType(typeof (T));
if (pd == null) throw new InvalidOperationException("Could not find PocoData for " + typeof (T));
try
{
int processed = 0;
var usedNativeSqlPlatformInserts = useNativeSqlPlatformBulkInsert
&& NativeSqlPlatformBulkInsertRecords(db, syntaxProvider, pd, collection, out processed);
&& NativeSqlPlatformBulkInsertRecords(db, syntaxProvider, pd, collection, out processed);
if (usedNativeSqlPlatformInserts == false)
{
@@ -283,19 +301,13 @@ namespace Umbraco.Core.Persistence
}
if (commitTrans)
{
if (tr == null) throw new ArgumentNullException("The transaction cannot be null if commitTrans is true");
tr.Complete();
}
return processed;
}
catch
{
if (commitTrans)
{
if (tr == null) throw new ArgumentNullException("The transaction cannot be null if commitTrans is true");
tr.Dispose();
}
throw;
}
@@ -337,13 +349,16 @@ namespace Umbraco.Core.Persistence
IEnumerable<T> collection,
out string[] sql)
{
if (db == null) throw new ArgumentNullException("db");
if (db.Connection == null) throw new ArgumentException("db.Connection is null.");
var tableName = db.EscapeTableName(pd.TableInfo.TableName);
//get all columns to include and format for sql
var cols = string.Join(", ",
pd.Columns
.Where(c => IncludeColumn(pd, c))
.Select(c => tableName + "." + db.EscapeSqlIdentifier(c.Key)).ToArray());
.Where(c => IncludeColumn(pd, c))
.Select(c => tableName + "." + db.EscapeSqlIdentifier(c.Key)).ToArray());
var itemArray = collection.ToArray();
@@ -357,9 +372,9 @@ namespace Umbraco.Core.Persistence
// 4168 / 262 = 15.908... = there will be 16 trans in total
//all items will be included if we have disabled db parameters
var itemsPerTrans = Math.Floor(2000.00 / paramsPerItem);
var itemsPerTrans = Math.Floor(2000.00/paramsPerItem);
//there will only be one transaction if we have disabled db parameters
var numTrans = Math.Ceiling(itemArray.Length / itemsPerTrans);
var numTrans = Math.Ceiling(itemArray.Length/itemsPerTrans);
var sqlQueries = new List<string>();
var commands = new List<IDbCommand>();
@@ -367,8 +382,8 @@ namespace Umbraco.Core.Persistence
for (var tIndex = 0; tIndex < numTrans; tIndex++)
{
var itemsForTrans = itemArray
.Skip(tIndex * (int)itemsPerTrans)
.Take((int)itemsPerTrans);
.Skip(tIndex*(int) itemsPerTrans)
.Take((int) itemsPerTrans);
var cmd = db.CreateCommand(db.Connection, string.Empty);
var pocoValues = new List<string>();
@@ -418,7 +433,6 @@ namespace Umbraco.Core.Persistence
/// <param name="processed">The number of records inserted</param>
private static bool NativeSqlPlatformBulkInsertRecords<T>(Database db, ISqlSyntaxProvider syntaxProvider, Database.PocoData pd, IEnumerable<T> collection, out int processed)
{
var dbConnection = db.Connection;
//unwrap the profiled connection if there is one
@@ -447,7 +461,6 @@ namespace Umbraco.Core.Persistence
//could not use the SQL server's specific bulk insert operations
processed = 0;
return false;
}
/// <summary>
@@ -1,7 +1,10 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Persistence.Repositories
{
// cannot kill in v7 because it is public, kill in v8
[Obsolete("Use MediaFileSystem.DeleteMediaFiles instead.", false)]
public interface IDeleteMediaFilesRepository
{
/// <summary>
@@ -9,6 +12,7 @@ namespace Umbraco.Core.Persistence.Repositories
/// </summary>
/// <param name="files"></param>
/// <returns></returns>
[Obsolete("Use MediaFileSystem.DeleteMediaFiles instead.", false)]
bool DeleteMediaFiles(IEnumerable<string> files);
}
}
-13
View File
@@ -1,13 +0,0 @@
using System;
namespace Umbraco.Core.Scoping
{
[Flags]
public enum ActionTime
{
None = 0,
BeforeCommit = 1,
BeforeEvents = 2,
BeforeDispose = 4
}
}
@@ -0,0 +1,9 @@
using System;
namespace Umbraco.Core.Scoping
{
public interface IInstanceIdentifiable
{
Guid InstanceId { get; }
}
}
+4 -6
View File
@@ -8,7 +8,7 @@ namespace Umbraco.Core.Scoping
/// <summary>
/// Represents a scope.
/// </summary>
public interface IScope : IDisposable
public interface IScope : IDisposable, IInstanceIdentifiable
{
/// <summary>
/// Gets the scope database.
@@ -38,10 +38,8 @@ namespace Umbraco.Core.Scoping
/// <summary>
/// Completes the scope.
/// </summary>
void Complete();
#if DEBUG_SCOPES
Guid InstanceId { get; }
#endif
/// <returns>A value indicating whether the scope has been successfully completed.</returns>
/// <remarks>Can return false if any child scope has not completed.</remarks>
bool Complete();
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ namespace Umbraco.Core.Scoping
internal interface IScopeInternal : IScope
{
IScopeInternal ParentScope { get; }
EventsDispatchMode DispatchMode { get; }
bool CallContext { get; }
IsolationLevel IsolationLevel { get; }
UmbracoDatabase DatabaseOrNull { get; }
EventMessages MessagesOrNull { get; }
+10 -5
View File
@@ -1,4 +1,5 @@
using System.Data;
using System;
using System.Data;
using Umbraco.Core.Events;
#if DEBUG_SCOPES
using System.Collections.Generic;
@@ -23,8 +24,9 @@ namespace Umbraco.Core.Scoping
IScope CreateScope(
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
bool? scopeFileSystems = null);
IEventDispatcher eventDispatcher = null,
bool? scopeFileSystems = null,
bool callContext = false);
/// <summary>
/// Creates a detached scope.
@@ -37,17 +39,18 @@ namespace Umbraco.Core.Scoping
IScope CreateDetachedScope(
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
IEventDispatcher eventDispatcher = null,
bool? scopeFileSystems = null);
/// <summary>
/// Attaches a scope.
/// </summary>
/// <param name="scope">The scope to attach.</param>
/// <param name="callContext">A value indicating whether to force usage of call context.</param>
/// <remarks>
/// <para>Only a scope created by <see cref="CreateDetachedScope"/> can be attached.</para>
/// </remarks>
void AttachScope(IScope scope);
void AttachScope(IScope scope, bool callContext = false);
/// <summary>
/// Detaches a scope.
@@ -64,7 +67,9 @@ namespace Umbraco.Core.Scoping
ScopeContext Context { get; }
#if DEBUG_SCOPES
Dictionary<Guid, object> CallContextObjects { get; }
IEnumerable<ScopeInfo> ScopeInfos { get; }
ScopeInfo GetScopeInfo(IScope scope);
#endif
}
}
+5 -6
View File
@@ -24,10 +24,11 @@ namespace Umbraco.Core.Scoping
#endif
}
#if DEBUG_SCOPES
private readonly Guid _instanceId = Guid.NewGuid();
public Guid InstanceId { get { return _instanceId; } }
#endif
/// <inheritdoc />
public bool CallContext { get { return false; } }
/// <inheritdoc />
public RepositoryCacheMode RepositoryCacheMode
@@ -75,7 +76,7 @@ namespace Umbraco.Core.Scoping
}
/// <inheritdoc />
public void Complete()
public bool Complete()
{
throw new NotSupportedException();
}
@@ -100,15 +101,13 @@ namespace Umbraco.Core.Scoping
if (_database != null)
_database.Dispose();
_scopeProvider.AmbientScope = null;
_scopeProvider.AmbientContext = null;
_scopeProvider.SetAmbient(null);
_disposed = true;
GC.SuppressFinalize(this);
}
public IScopeInternal ParentScope { get { return null; } }
public EventsDispatchMode DispatchMode { get {return EventsDispatchMode.Unspecified; } }
public IsolationLevel IsolationLevel { get {return IsolationLevel.Unspecified; } }
public bool ScopedFileSystems { get { return false; } }
public void ChildCompleted(bool? completed) { }
+56 -37
View File
@@ -16,9 +16,9 @@ namespace Umbraco.Core.Scoping
private readonly ScopeProvider _scopeProvider;
private readonly IsolationLevel _isolationLevel;
private readonly RepositoryCacheMode _repositoryCacheMode;
private readonly EventsDispatchMode _dispatchMode;
private readonly bool? _scopeFileSystem;
private readonly ScopeContext _scopeContext;
private bool _callContext;
private bool _disposed;
private bool? _completed;
@@ -35,15 +35,17 @@ namespace Umbraco.Core.Scoping
Scope parent, ScopeContext scopeContext, bool detachable,
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
bool? scopeFileSystems = null)
IEventDispatcher eventDispatcher = null,
bool? scopeFileSystems = null,
bool callContext = false)
{
_scopeProvider = scopeProvider;
_scopeContext = scopeContext;
_isolationLevel = isolationLevel;
_repositoryCacheMode = repositoryCacheMode;
_dispatchMode = dispatchMode;
_eventDispatcher = eventDispatcher;
_scopeFileSystem = scopeFileSystems;
_callContext = callContext;
Detachable = detachable;
#if DEBUG_SCOPES
@@ -74,9 +76,9 @@ namespace Umbraco.Core.Scoping
if (repositoryCacheMode != RepositoryCacheMode.Unspecified && parent.RepositoryCacheMode != repositoryCacheMode)
throw new ArgumentException("Cannot be different from parent.", "repositoryCacheMode");
// cannot specify a different mode!
if (_dispatchMode != EventsDispatchMode.Unspecified && parent._dispatchMode != dispatchMode)
throw new ArgumentException("Cannot be different from parent.", "dispatchMode");
// cannot specify a dispatcher!
if (_eventDispatcher != null)
throw new ArgumentException("Cannot be specified on nested scope.", "eventDispatcher");
// cannot specify a different fs scope!
if (scopeFileSystems != null && parent._scopeFileSystem != scopeFileSystems)
@@ -97,30 +99,31 @@ namespace Umbraco.Core.Scoping
ScopeContext scopeContext,
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
bool? scopeFileSystems = null)
: this(scopeProvider, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems)
{
}
IEventDispatcher eventDispatcher = null,
bool? scopeFileSystems = null,
bool callContext = false)
: this(scopeProvider, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext)
{ }
// initializes a new scope in a nested scopes chain, with its parent
public Scope(ScopeProvider scopeProvider, Scope parent,
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
bool? scopeFileSystems = null)
: this(scopeProvider, parent, null, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems)
{
}
IEventDispatcher eventDispatcher = null,
bool? scopeFileSystems = null,
bool callContext = false)
: this(scopeProvider, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext)
{ }
// initializes a new scope, replacing a NoScope instance
public Scope(ScopeProvider scopeProvider, NoScope noScope,
ScopeContext scopeContext,
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
bool? scopeFileSystems = null)
: this(scopeProvider, null, scopeContext, false, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems)
IEventDispatcher eventDispatcher = null,
bool? scopeFileSystems = null,
bool callContext = false)
: this(scopeProvider, null, scopeContext, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext)
{
// steal everything from NoScope
_database = noScope.DatabaseOrNull;
@@ -130,10 +133,20 @@ namespace Umbraco.Core.Scoping
throw new Exception("NoScope instance is not free.");
}
#if DEBUG_SCOPES
private readonly Guid _instanceId = Guid.NewGuid();
public Guid InstanceId { get { return _instanceId; } }
#endif
// a value indicating whether to force call-context
public bool CallContext
{
get
{
if (_callContext) return true;
if (ParentScope != null) return ParentScope.CallContext;
return false;
}
set { _callContext = value; }
}
public bool ScopedFileSystems
{
@@ -144,16 +157,6 @@ namespace Umbraco.Core.Scoping
}
}
public EventsDispatchMode DispatchMode
{
get
{
if (_dispatchMode != EventsDispatchMode.Unspecified) return _dispatchMode;
if (ParentScope != null) return ParentScope.DispatchMode;
return EventsDispatchMode.Scope;
}
}
/// <inheritdoc />
public RepositoryCacheMode RepositoryCacheMode
{
@@ -184,6 +187,8 @@ namespace Umbraco.Core.Scoping
// the parent scope (in a nested scopes chain)
public IScopeInternal ParentScope { get; set; }
public bool Attached { get; set; }
// the original scope (when attaching a detachable scope)
public IScopeInternal OrigScope { get; set; }
@@ -290,15 +295,16 @@ namespace Umbraco.Core.Scoping
{
EnsureNotDisposed();
if (ParentScope != null) return ParentScope.Events;
return _eventDispatcher ?? (_eventDispatcher = new ScopeEventDispatcher(DispatchMode));
return _eventDispatcher ?? (_eventDispatcher = new ScopeEventDispatcher());
}
}
/// <inheritdoc />
public void Complete()
public bool Complete()
{
if (_completed.HasValue == false)
_completed = true;
return _completed.Value;
}
public void Reset()
@@ -324,7 +330,18 @@ namespace Umbraco.Core.Scoping
EnsureNotDisposed();
if (this != _scopeProvider.AmbientScope)
{
#if DEBUG_SCOPES
var ambient = _scopeProvider.AmbientScope;
Logging.LogHelper.Debug<Scope>("Dispose error (" + (ambient == null ? "no" : "other") + " ambient)");
if (ambient == null)
throw new InvalidOperationException("Not the ambient scope (no ambient scope).");
var infos = _scopeProvider.GetScopeInfo(ambient);
throw new InvalidOperationException("Not the ambient scope (see current ambient ctor stack trace).\r\n" + infos.CtorStack);
#else
throw new InvalidOperationException("Not the ambient scope.");
#endif
}
#if DEBUG_SCOPES
_scopeProvider.Disposed(this);
@@ -418,7 +435,7 @@ namespace Umbraco.Core.Scoping
}
finally
{
_scopeProvider.AmbientContext = null;
_scopeProvider.SetAmbient(null);
}
}
}, () =>
@@ -426,8 +443,10 @@ namespace Umbraco.Core.Scoping
if (Detachable)
{
// get out of the way, restore original
_scopeProvider.AmbientScope = OrigScope;
_scopeProvider.AmbientContext = OrigContext;
_scopeProvider.SetAmbient(OrigScope, OrigContext);
Attached = false;
OrigScope = null;
OrigContext = null;
}
});
}
+4 -1
View File
@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Umbraco.Core.Scoping
{
public class ScopeContext
public class ScopeContext : IInstanceIdentifiable
{
private Dictionary<string, IEnlistedObject> _enlisted;
@@ -27,6 +27,9 @@ namespace Umbraco.Core.Scoping
throw new AggregateException("Exceptions were thrown by listed actions.", exceptions);
}
private readonly Guid _instanceId = Guid.NewGuid();
public Guid InstanceId { get { return _instanceId; } }
private IDictionary<string, IEnlistedObject> Enlisted
{
get
+291 -110
View File
@@ -1,11 +1,14 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Web;
using Umbraco.Core.Events;
using Umbraco.Core.Persistence;
#if DEBUG_SCOPES
using System.Linq;
#endif
namespace Umbraco.Core.Scoping
{
@@ -24,17 +27,17 @@ namespace Umbraco.Core.Scoping
SafeCallContext.Register(
() =>
{
var scope = StaticAmbientScope;
var context = StaticAmbientContext;
StaticAmbientScope = null;
StaticAmbientContext = null;
var scope = GetCallContextObject<IScopeInternal>(ScopeItemKey);
var context = GetCallContextObject<ScopeContext>(ContextItemKey);
SetCallContextObject(ScopeItemKey, null);
SetCallContextObject(ContextItemKey, null);
return Tuple.Create(scope, context);
},
o =>
{
// cannot re-attached over leaked scope/context
// except of course over NoScope (which leaks)
var ambientScope = StaticAmbientScope;
var ambientScope = GetCallContextObject<IScope>(ScopeItemKey);
if (ambientScope != null)
{
var ambientNoScope = ambientScope as NoScope;
@@ -44,59 +47,210 @@ namespace Umbraco.Core.Scoping
// this should rollback any pending transaction
ambientNoScope.Dispose();
}
if (StaticAmbientContext != null) throw new Exception("Found leaked context when restoring call context.");
if (GetCallContextObject<ScopeContext>(ContextItemKey) != null)
throw new Exception("Found leaked context when restoring call context.");
var t = (Tuple<IScopeInternal, ScopeContext>)o;
StaticAmbientScope = t.Item1;
StaticAmbientContext = t.Item2;
var t = (Tuple<IScopeInternal, ScopeContext>) o;
SetCallContextObject(ScopeItemKey, t.Item1);
SetCallContextObject(ContextItemKey, t.Item2);
});
}
public IDatabaseFactory2 DatabaseFactory { get; private set; }
#region Context
// objects that go into the logical call context better be serializable else they'll eventually
// cause issues whenever some cross-AppDomain code executes - could be due to ReSharper running
// tests, any other things (see https://msdn.microsoft.com/en-us/library/dn458353(v=vs.110).aspx),
// but we don't want to make all of our objects serializable since they are *not* meant to be
// used in cross-AppDomain scenario anyways.
// in addition, whatever goes into the logical call context is serialized back and forth any
// time cross-AppDomain code executes, so if we put an "object" there, we'll can *another*
// "object" instance - and so we cannot use a random object as a key.
// so what we do is: we register a guid in the call context, and we keep a table mapping those
// guids to the actual objects. the guid serializes back and forth without causing any issue,
// and we can retrieve the actual objects from the table.
// only issue: how are we supposed to clear the table? we can't, really. objects should take
// care of de-registering themselves from context.
// everything we use does, except the NoScope scope, which just stays there
//
// during tests, NoScope can to into call context... nothing much we can do about it
private static readonly object StaticCallContextObjectsLock = new object();
private static readonly Dictionary<Guid, object> StaticCallContextObjects
= new Dictionary<Guid, object>();
#if DEBUG_SCOPES
public Dictionary<Guid, object> CallContextObjects
{
get
{
lock (StaticCallContextObjectsLock)
{
// capture in a dictionary
return StaticCallContextObjects.ToDictionary(x => x.Key, x => x.Value);
}
}
}
#endif
private static T GetCallContextObject<T>(string key)
where T : class
{
var objectKey = CallContext.LogicalGetData(key).AsGuid();
lock (StaticCallContextObjectsLock)
{
object callContextObject;
if (StaticCallContextObjects.TryGetValue(objectKey, out callContextObject))
{
#if DEBUG_SCOPES
//Logging.LogHelper.Debug<ScopeProvider>("GotObject " + objectKey.ToString("N").Substring(0, 8));
#endif
return (T) callContextObject;
}
#if DEBUG_SCOPES
//Logging.LogHelper.Debug<ScopeProvider>("MissedObject " + objectKey.ToString("N").Substring(0, 8));
#endif
return null;
}
}
private static void SetCallContextObject(string key, IInstanceIdentifiable value)
{
#if DEBUG_SCOPES
// manage the 'context' that contains the scope (null, "http" or "call")
// only for scopes of course!
if (key == ScopeItemKey)
{
// first, null-register the existing value
var ambientKey = CallContext.LogicalGetData(ScopeItemKey).AsGuid();
object o = null;
lock (StaticCallContextObjectsLock)
{
if (ambientKey != default(Guid))
StaticCallContextObjects.TryGetValue(ambientKey, out o);
}
var ambientScope = o as IScope;
if (ambientScope != null) RegisterContext(ambientScope, null);
// then register the new value
var scope = value as IScope;
if (scope != null) RegisterContext(scope, "call");
}
#endif
if (value == null)
{
var objectKey = CallContext.LogicalGetData(key).AsGuid();
CallContext.FreeNamedDataSlot(key);
if (objectKey == default (Guid)) return;
lock (StaticCallContextObjectsLock)
{
#if DEBUG_SCOPES
//Logging.LogHelper.Debug<ScopeProvider>("RemoveObject " + objectKey.ToString("N").Substring(0, 8));
#endif
StaticCallContextObjects.Remove(objectKey);
}
}
else
{
// note - we are *not* detecting an already-existing value
// because our code in this class *always* sets to null before
// setting to a real value
var objectKey = value.InstanceId;
lock (StaticCallContextObjectsLock)
{
#if DEBUG_SCOPES
//Logging.LogHelper.Debug<ScopeProvider>("AddObject " + objectKey.ToString("N").Substring(0, 8));
#endif
StaticCallContextObjects.Add(objectKey, value);
}
CallContext.LogicalSetData(key, objectKey);
}
}
internal static Func<IDictionary> HttpContextItemsGetter { get; set; }
private static IDictionary HttpContextItems
{
get
{
return HttpContextItemsGetter == null
? (HttpContext.Current == null ? null : HttpContext.Current.Items)
: HttpContextItemsGetter();
}
}
public static T GetHttpContextObject<T>(string key, bool required = true)
where T : class
{
var httpContextItems = HttpContextItems;
if (httpContextItems != null)
return (T)httpContextItems[key];
if (required)
throw new Exception("HttpContext.Current is null.");
return null;
}
private static bool SetHttpContextObject(string key, object value, bool required = true)
{
var httpContextItems = HttpContextItems;
if (httpContextItems == null)
{
if (required)
throw new Exception("HttpContext.Current is null.");
return false;
}
#if DEBUG_SCOPES
// manage the 'context' that contains the scope (null, "http" or "call")
// only for scopes of course!
if (key == ScopeItemKey)
{
// first, null-register the existing value
var ambientScope = (IScope)httpContextItems[ScopeItemKey];
if (ambientScope != null) RegisterContext(ambientScope, null);
// then register the new value
var scope = value as IScope;
if (scope != null) RegisterContext(scope, "http");
}
#endif
if (value == null)
httpContextItems.Remove(key);
else
httpContextItems[key] = value;
return true;
}
#endregion
#region Ambient Context
internal const string ContextItemKey = "Umbraco.Core.Scoping.ScopeContext";
private static ScopeContext CallContextContextValue
internal static ScopeContext AmbientContextInternal
{
get { return (ScopeContext)CallContext.LogicalGetData(ContextItemKey); }
set
get
{
if (value == null) CallContext.FreeNamedDataSlot(ContextItemKey);
else CallContext.LogicalSetData(ContextItemKey, value);
// try http context, fallback onto call context
var value = GetHttpContextObject<ScopeContext>(ContextItemKey, false);
return value ?? GetCallContextObject<ScopeContext>(ContextItemKey);
}
}
private static ScopeContext HttpContextContextValue
{
get { return (ScopeContext)HttpContext.Current.Items[ContextItemKey]; }
set
{
if (value == null)
HttpContext.Current.Items.Remove(ContextItemKey);
else
HttpContext.Current.Items[ContextItemKey] = value;
}
}
// clear both
SetHttpContextObject(ContextItemKey, null, false);
SetCallContextObject(ContextItemKey, null);
if (value == null) return;
private static ScopeContext StaticAmbientContext
{
get { return HttpContext.Current == null ? CallContextContextValue : HttpContextContextValue; }
set
{
if (HttpContext.Current == null)
CallContextContextValue = value;
else
HttpContextContextValue = value;
// set http/call context
if (SetHttpContextObject(ContextItemKey, value, false) == false)
SetCallContextObject(ContextItemKey, value);
}
}
/// <inheritdoc />
public ScopeContext AmbientContext
{
get { return StaticAmbientContext; }
set { StaticAmbientContext = value; }
get { return AmbientContextInternal; }
}
#endregion
@@ -109,86 +263,84 @@ namespace Umbraco.Core.Scoping
// only 1 instance which can be disposed and disposed again
private static readonly ScopeReference StaticScopeReference = new ScopeReference(new ScopeProvider(null));
private static IScopeInternal CallContextValue
internal static IScopeInternal AmbientScopeInternal
{
get { return (IScopeInternal) CallContext.LogicalGetData(ScopeItemKey); }
set
get
{
#if DEBUG_SCOPES
// manage the 'context' that contains the scope (null, "http" or "lcc")
var ambientScope = (IScope) CallContext.LogicalGetData(ScopeItemKey);
if (ambientScope != null) RegisterContext(ambientScope, null);
if (value != null) RegisterContext(value, "lcc");
#endif
if (value == null) CallContext.FreeNamedDataSlot(ScopeItemKey);
else CallContext.LogicalSetData(ScopeItemKey, value);
// try http context, fallback onto call context
var value = GetHttpContextObject<IScopeInternal>(ScopeItemKey, false);
return value ?? GetCallContextObject<IScopeInternal>(ScopeItemKey);
}
}
private static IScopeInternal HttpContextValue
{
get { return (IScopeInternal) HttpContext.Current.Items[ScopeItemKey]; }
set
{
#if DEBUG_SCOPES
// manage the 'context' that contains the scope (null, "http" or "lcc")
var ambientScope = (IScope) HttpContext.Current.Items[ScopeItemKey];
if (ambientScope != null) RegisterContext(ambientScope, null);
if (value != null) RegisterContext(value, "http");
#endif
if (value == null)
{
HttpContext.Current.Items.Remove(ScopeItemKey);
HttpContext.Current.Items.Remove(ScopeRefItemKey);
}
else
{
HttpContext.Current.Items[ScopeItemKey] = value;
if (HttpContext.Current.Items[ScopeRefItemKey] == null)
HttpContext.Current.Items[ScopeRefItemKey] = StaticScopeReference;
}
}
}
// clear both
SetHttpContextObject(ScopeItemKey, null, false);
SetHttpContextObject(ScopeRefItemKey, null, false);
SetCallContextObject(ScopeItemKey, null);
if (value == null) return;
private static IScopeInternal StaticAmbientScope
{
get { return HttpContext.Current == null ? CallContextValue : HttpContextValue; }
set
{
if (HttpContext.Current == null)
CallContextValue = value;
// set http/call context
if (value.CallContext == false && SetHttpContextObject(ScopeItemKey, value, false))
SetHttpContextObject(ScopeRefItemKey, StaticScopeReference);
else
HttpContextValue = value;
SetCallContextObject(ScopeItemKey, value);
}
}
/// <inheritdoc />
public IScopeInternal AmbientScope
{
get { return StaticAmbientScope; }
set { StaticAmbientScope = value; }
get { return AmbientScopeInternal; }
internal set { AmbientScopeInternal = value; }
}
/// <inheritdoc />
public IScopeInternal GetAmbientOrNoScope()
{
return AmbientScope ?? (AmbientScope = new NoScope(this));
return AmbientScope ?? (AmbientScope = new NoScope(this));
}
#endregion
public void SetAmbient(IScopeInternal scope, ScopeContext context = null)
{
// clear all
SetHttpContextObject(ScopeItemKey, null, false);
SetHttpContextObject(ScopeRefItemKey, null, false);
SetCallContextObject(ScopeItemKey, null);
SetHttpContextObject(ContextItemKey, null, false);
SetCallContextObject(ContextItemKey, null);
if (scope == null)
{
if (context != null)
throw new ArgumentException("Must be null if scope is null.", "context");
return;
}
if (scope.CallContext == false && SetHttpContextObject(ScopeItemKey, scope, false))
{
SetHttpContextObject(ScopeRefItemKey, StaticScopeReference);
SetHttpContextObject(ContextItemKey, context);
}
else
{
SetCallContextObject(ScopeItemKey, scope);
SetCallContextObject(ContextItemKey, context);
}
}
/// <inheritdoc />
public IScope CreateDetachedScope(
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
IEventDispatcher eventDispatcher = null,
bool? scopeFileSystems = null)
{
return new Scope(this, true, null, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems);
return new Scope(this, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems);
}
/// <inheritdoc />
public void AttachScope(IScope other)
public void AttachScope(IScope other, bool callContext = false)
{
var otherScope = other as Scope;
if (otherScope == null)
@@ -197,10 +349,15 @@ namespace Umbraco.Core.Scoping
if (otherScope.Detachable == false)
throw new ArgumentException("Not a detachable scope.");
if (otherScope.Attached)
throw new InvalidOperationException("Already attached.");
otherScope.Attached = true;
otherScope.OrigScope = AmbientScope;
otherScope.OrigContext = AmbientContext;
AmbientScope = otherScope;
AmbientContext = otherScope.Context;
otherScope.CallContext = callContext;
SetAmbient(otherScope, otherScope.Context);
}
/// <inheritdoc />
@@ -221,10 +378,10 @@ namespace Umbraco.Core.Scoping
if (scope.Detachable == false)
throw new InvalidOperationException("Ambient scope is not detachable.");
AmbientScope = scope.OrigScope;
AmbientContext = scope.OrigContext;
SetAmbient(scope.OrigScope, scope.OrigContext);
scope.OrigScope = null;
scope.OrigContext = null;
scope.Attached = false;
return scope;
}
@@ -232,16 +389,19 @@ namespace Umbraco.Core.Scoping
public IScope CreateScope(
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
EventsDispatchMode dispatchMode = EventsDispatchMode.Unspecified,
bool? scopeFileSystems = null)
IEventDispatcher eventDispatcher = null,
bool? scopeFileSystems = null,
bool callContext = false)
{
var ambient = AmbientScope;
if (ambient == null)
{
var context = AmbientContext == null ? new ScopeContext() : null;
var scope = new Scope(this, false, context, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems);
if (AmbientContext == null) AmbientContext = context; // assign only if scope creation did not throw!
return AmbientScope = scope;
var ambientContext = AmbientContext;
var newContext = ambientContext == null ? new ScopeContext() : null;
var scope = new Scope(this, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext);
// assign only if scope creation did not throw!
SetAmbient(scope, newContext ?? ambientContext);
return scope;
}
// replace noScope with a real one
@@ -255,16 +415,20 @@ namespace Umbraco.Core.Scoping
var database = noScope.DatabaseOrNull;
if (database != null && database.InTransaction)
throw new Exception("NoScope is in a transaction.");
var context = AmbientContext == null ? new ScopeContext() : null;
var scope = new Scope(this, noScope, context, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems);
if (AmbientContext == null) AmbientContext = context; // assign only if scope creation did not throw!
return AmbientScope = scope;
var ambientContext = AmbientContext;
var newContext = ambientContext == null ? new ScopeContext() : null;
var scope = new Scope(this, noScope, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext);
// assign only if scope creation did not throw!
SetAmbient(scope, newContext ?? ambientContext);
return scope;
}
var ambientScope = ambient as Scope;
if (ambientScope == null) throw new Exception("Ambient scope is not a Scope instance.");
return AmbientScope = new Scope(this, ambientScope, isolationLevel, repositoryCacheMode, dispatchMode, scopeFileSystems);
var nested = new Scope(this, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext);
SetAmbient(nested, AmbientContext);
return nested;
}
/// <inheritdoc />
@@ -309,7 +473,7 @@ namespace Umbraco.Core.Scoping
// all scope instances that are currently beeing tracked
private static readonly object StaticScopeInfosLock = new object();
private static readonly List<ScopeInfo> StaticScopeInfos = new List<ScopeInfo>();
private static readonly Dictionary<IScope, ScopeInfo> StaticScopeInfos = new Dictionary<IScope, ScopeInfo>();
public IEnumerable<ScopeInfo> ScopeInfos
{
@@ -317,37 +481,53 @@ namespace Umbraco.Core.Scoping
{
lock (StaticScopeInfosLock)
{
return StaticScopeInfos.ToArray(); // capture in an array
return StaticScopeInfos.Values.ToArray(); // capture in an array
}
}
}
public ScopeInfo GetScopeInfo(IScope scope)
{
lock (StaticScopeInfosLock)
{
ScopeInfo scopeInfo;
return StaticScopeInfos.TryGetValue(scope, out scopeInfo) ? scopeInfo : null;
}
}
//private static void Log(string message, UmbracoDatabase database)
//{
// LogHelper.Debug<ScopeProvider>(message + " (" + (database == null ? "" : database.InstanceSid) + ").");
//}
// register a scope and capture its ctor stacktrace
public void RegisterScope(IScope scope)
{
lock (StaticScopeInfosLock)
{
if (StaticScopeInfos.Any(x => x.Scope == scope)) throw new Exception("oops: already registered.");
StaticScopeInfos.Add(new ScopeInfo(scope, Environment.StackTrace));
if (StaticScopeInfos.ContainsKey(scope)) throw new Exception("oops: already registered.");
//Logging.LogHelper.Debug<ScopeProvider>("Register " + scope.InstanceId.ToString("N").Substring(0, 8));
StaticScopeInfos[scope] = new ScopeInfo(scope, Environment.StackTrace);
}
}
// 'context' that contains the scope (null, "http" or "lcc")
// register that a scope is in a 'context'
// 'context' that contains the scope (null, "http" or "call")
public static void RegisterContext(IScope scope, string context)
{
lock (StaticScopeInfosLock)
{
var info = StaticScopeInfos.FirstOrDefault(x => x.Scope == scope);
ScopeInfo info;
if (StaticScopeInfos.TryGetValue(scope, out info) == false) info = null;
if (info == null)
{
if (context == null) return;
throw new Exception("oops: unregistered scope.");
}
//Logging.LogHelper.Debug<ScopeProvider>("Register context " + (context ?? "null") + " for " + scope.InstanceId.ToString("N").Substring(0, 8));
if (context == null) info.NullStack = Environment.StackTrace;
//if (context == null)
// Logging.LogHelper.Debug<ScopeProvider>("STACK\r\n" + info.NullStack);
info.Context = context;
}
}
@@ -356,11 +536,12 @@ namespace Umbraco.Core.Scoping
{
lock (StaticScopeInfosLock)
{
var info = StaticScopeInfos.FirstOrDefault(x => x.Scope == scope);
if (info != null)
if (StaticScopeInfos.ContainsKey(scope))
{
// enable this by default
StaticScopeInfos.Remove(info);
//Console.WriteLine("unregister " + scope.InstanceId.ToString("N").Substring(0, 8));
StaticScopeInfos.Remove(scope);
//Logging.LogHelper.Debug<ScopeProvider>("Remove " + scope.InstanceId.ToString("N").Substring(0, 8));
// instead, enable this to keep *all* scopes
// beware, there can be a lot of scopes!
+7 -14
View File
@@ -179,7 +179,7 @@ namespace Umbraco.Core.Services
uow.Events.Dispatch(Created, this, new NewEventArgs<IContent>(content, false, contentTypeAlias, parentId));
//Created.RaiseEvent(new NewEventArgs<IContent>(content, false, contentTypeAlias, parentId), this, uow.Events);
// fixme
// fixme
var auditRepo = RepositoryFactory.CreateAuditRepository(uow);
auditRepo.AddOrUpdate(new AuditItem(content.Id, string.Format("Content '{0}' was created", name), AuditType.New, content.CreatorId));
uow.Commit();
@@ -1051,9 +1051,9 @@ namespace Umbraco.Core.Services
UnPublish(content, userId);
}
content.WriterId = userId;
content.ChangeTrashedState(true);
content.ChangeTrashedState(true);
repository.AddOrUpdate(content);
//Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId
foreach (var descendant in descendants)
{
@@ -1063,14 +1063,14 @@ namespace Umbraco.Core.Services
descendant.ChangeTrashedState(true, descendant.ParentId);
repository.AddOrUpdate(descendant);
moveInfo.Add(new MoveEventInfo<IContent>(descendant, descendant.Path, descendant.ParentId));
moveInfo.Add(new MoveEventInfo<IContent>(descendant, descendant.Path, descendant.ParentId));
}
uow.Commit();
uow.Events.Dispatch(Trashed, this, new MoveEventArgs<IContent>(false, evtMsgs, moveInfo.ToArray()), "Trashed");
}
Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id);
return OperationStatus.Success(evtMsgs);
@@ -1285,9 +1285,6 @@ namespace Umbraco.Core.Services
var args = new DeleteEventArgs<IContent>(content, false, evtMsgs);
uow.Events.Dispatch(Deleted, this, args, "Deleted"); // fixme why the event name?!
//remove any flagged media files
repository.DeleteMediaFiles(args.MediaFilesToDelete);
}
Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id);
@@ -1335,7 +1332,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Deletes all content of the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin.
/// </summary>
/// </summary>
/// <param name="contentTypeIds">Id of the <see cref="IContentType"/></param>
/// <param name="userId">Optional Id of the user issueing the delete operation</param>
public void DeleteContentOfTypes(IEnumerable<int> contentTypeIds, int userId = 0)
@@ -1344,7 +1341,7 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateContentRepository(uow);
//track the 'root' items of the collection of nodes discovered to delete, we need to use
//these items to lookup descendants that are not of this doc type so they can be transfered
//to the recycle bin
@@ -1559,9 +1556,6 @@ namespace Umbraco.Core.Services
success = repository.EmptyRecycleBin();
if (success)
repository.DeleteMediaFiles(files);
uow.Events.Dispatch(EmptiedRecycleBin, this, new RecycleBinEventArgs(nodeObjectType, entities, files, success));
uow.Commit();
}
@@ -1610,7 +1604,6 @@ namespace Umbraco.Core.Services
// fixme mess!
using (var scope = UowProvider.ScopeProvider.CreateScope())
{
using (var uow = UowProvider.GetUnitOfWork())
{
if (uow.Events.DispatchCancelable(Copying, this, new CopyEventArgs<IContent>(content, copy, parentId)))
@@ -756,6 +756,7 @@ namespace Umbraco.Core.Services
using (var scope = UowProvider.ScopeProvider.CreateScope()) // fixme what a mess
{
scope.Events.Dispatch(SavedContentType, this, new SaveEventArgs<IContentType>(contentType, false));
scope.Complete();
}
Audit(AuditType.Save, "Save ContentType performed by user", userId, contentType.Id);
}
@@ -800,8 +801,9 @@ namespace Umbraco.Core.Services
using (var scope = UowProvider.ScopeProvider.CreateScope()) // fixme what a mess
{
scope.Events.Dispatch(SavedContentType, this, new SaveEventArgs<IContentType>(asArray, false));
scope.Complete();
}
Audit(AuditType.Save, "Save ContentTypes performed by user", userId, -1);
Audit(AuditType.Save, "Save ContentTypes performed by user", userId, -1);
}
/// <summary>
@@ -1211,6 +1213,7 @@ namespace Umbraco.Core.Services
using (var scope = UowProvider.ScopeProvider.CreateScope()) // fixme what a mess
{
scope.Events.Dispatch(SavedMediaType, this, new SaveEventArgs<IMediaType>(mediaType, false));
scope.Complete();
}
Audit(AuditType.Save, "Save MediaType performed by user", userId, mediaType.Id);
}
@@ -1255,6 +1258,7 @@ namespace Umbraco.Core.Services
using (var scope = UowProvider.ScopeProvider.CreateScope()) // fixme what a mess
{
scope.Events.Dispatch(SavedMediaType, this, new SaveEventArgs<IMediaType>(asArray, false));
scope.Complete();
}
Audit(AuditType.Save, "Save MediaTypes performed by user", userId, -1);
}
@@ -831,9 +831,6 @@ namespace Umbraco.Core.Services
var args = new DeleteEventArgs<IMedia>(media, false, evtMsgs);
uow.Events.Dispatch(Deleted, this, args);
//remove any flagged media files
repository.DeleteMediaFiles(args.MediaFilesToDelete);
}
Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id);
@@ -958,9 +955,6 @@ namespace Umbraco.Core.Services
success = repository.EmptyRecycleBin();
// FIXME shouldn't we commit here?!
uow.Events.Dispatch(EmptiedRecycleBin, this, new RecycleBinEventArgs(nodeObjectType, entities, files, success));
if (success)
repository.DeleteMediaFiles(files);
}
}
Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, -21);
+9 -12
View File
@@ -45,7 +45,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Gets the default MemberType alias
/// </summary>
/// <remarks>By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll
/// <remarks>By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll
/// return the first type that is not an admin, otherwise if there's only one we will return that one.</remarks>
/// <returns>Alias of the default MemberType</returns>
public string GetDefaultMemberType()
@@ -86,7 +86,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method
/// </summary>
/// <remarks>This method exists so that Umbraco developers can use one entry point to create/update
/// <remarks>This method exists so that Umbraco developers can use one entry point to create/update
/// Members if they choose to. </remarks>
/// <param name="member">The Member to save the password for</param>
/// <param name="password">The password to encrypt and save</param>
@@ -776,7 +776,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Creates and persists a Member
/// </summary>
/// <remarks>Using this method will persist the Member object before its returned
/// <remarks>Using this method will persist the Member object before its returned
/// meaning that it will have an Id available (unlike the CreateMember method)</remarks>
/// <param name="username">Username of the Member to create</param>
/// <param name="email">Email of the Member to create</param>
@@ -792,7 +792,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Creates and persists a Member
/// </summary>
/// <remarks>Using this method will persist the Member object before its returned
/// <remarks>Using this method will persist the Member object before its returned
/// meaning that it will have an Id available (unlike the CreateMember method)</remarks>
/// <param name="username">Username of the Member to create</param>
/// <param name="email">Email of the Member to create</param>
@@ -806,7 +806,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Creates and persists a Member
/// </summary>
/// <remarks>Using this method will persist the Member object before its returned
/// <remarks>Using this method will persist the Member object before its returned
/// meaning that it will have an Id available (unlike the CreateMember method)</remarks>
/// <param name="username">Username of the Member to create</param>
/// <param name="email">Email of the Member to create</param>
@@ -836,7 +836,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Creates and persists a Member
/// </summary>
/// <remarks>Using this method will persist the Member object before its returned
/// <remarks>Using this method will persist the Member object before its returned
/// meaning that it will have an Id available (unlike the CreateMember method)</remarks>
/// <param name="username">Username of the Member to create</param>
/// <param name="email">Email of the Member to create</param>
@@ -921,7 +921,7 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IMember"/></returns>
public IMember GetByUsername(string username)
{
//TODO: Somewhere in here, whether at this level or the repository level, we need to add
//TODO: Somewhere in here, whether at this level or the repository level, we need to add
// a caching mechanism since this method is used by all the membership providers and could be
// called quite a bit when dealing with members.
@@ -953,9 +953,6 @@ namespace Umbraco.Core.Services
var args = new DeleteEventArgs<IMember>(member, false);
uow.Events.Dispatch(Deleted, this, args);
//remove any flagged media files
repository.DeleteMediaFiles(args.MediaFilesToDelete);
}
}
@@ -963,7 +960,7 @@ namespace Umbraco.Core.Services
/// Saves an <see cref="IMember"/>
/// </summary>
/// <param name="entity"><see cref="IMember"/> to Save</param>
/// <param name="raiseEvents">Optional parameter to raise events.
/// <param name="raiseEvents">Optional parameter to raise events.
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
public void Save(IMember entity, bool raiseEvents = true)
{
@@ -1004,7 +1001,7 @@ namespace Umbraco.Core.Services
/// Saves a list of <see cref="IMember"/> objects
/// </summary>
/// <param name="entities"><see cref="IEnumerable{IMember}"/> to save</param>
/// <param name="raiseEvents">Optional parameter to raise events.
/// <param name="raiseEvents">Optional parameter to raise events.
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
public void Save(IEnumerable<IMember> entities, bool raiseEvents = true)
{
@@ -17,7 +17,7 @@ namespace Umbraco.Core.Services
private readonly IMemberService _memberService;
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
public MemberTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberService memberService)
: base(provider, repositoryFactory, logger, eventMessagesFactory)
@@ -110,7 +110,7 @@ namespace Umbraco.Core.Services
public void Save(IEnumerable<IMemberType> memberTypes, int userId = 0)
{
var asArray = memberTypes.ToArray();
using (new WriteLock(Locker))
{
using (var uow = UowProvider.GetUnitOfWork())
@@ -137,7 +137,7 @@ namespace Umbraco.Core.Services
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IMemberType>(asArray, false));
}
}
}
public void Delete(IMemberType memberType, int userId = 0)
@@ -167,7 +167,7 @@ namespace Umbraco.Core.Services
public void Delete(IEnumerable<IMemberType> memberTypes, int userId = 0)
{
var asArray = memberTypes.ToArray();
using (new WriteLock(Locker))
{
using (var scope = UowProvider.ScopeProvider.CreateScope())
+3 -2
View File
@@ -313,6 +313,8 @@
<Compile Include="Deploy\Direction.cs" />
<Compile Include="Deploy\IFileStore.cs" />
<Compile Include="Events\EventDefinitionFilter.cs" />
<Compile Include="Events\IDeletingMediaFilesEventArgs.cs" />
<Compile Include="Events\ScopeEventDispatcherBase.cs" />
<Compile Include="OrderedHashSet.cs" />
<Compile Include="Events\UninstallPackageEventArgs.cs" />
<Compile Include="Models\GridValue.cs" />
@@ -362,7 +364,6 @@
<Compile Include="Events\MoveEventInfo.cs" />
<Compile Include="Events\PublishEventArgs.cs" />
<Compile Include="Events\RecycleBinEventArgs.cs" />
<Compile Include="Events\EventsDispatchMode.cs" />
<Compile Include="Events\TransientMessagesFactory.cs" />
<Compile Include="Events\TypedEventHandler.cs" />
<Compile Include="Events\MoveEventArgs.cs" />
@@ -562,7 +563,7 @@
<Compile Include="Publishing\UnPublishedStatusType.cs" />
<Compile Include="Publishing\UnPublishStatus.cs" />
<Compile Include="SafeCallContext.cs" />
<Compile Include="Scoping\ActionTime.cs" />
<Compile Include="Scoping\IInstanceIdentifiable.cs" />
<Compile Include="Scoping\IScope.cs" />
<Compile Include="Scoping\IScopeInternal.cs" />
<Compile Include="Scoping\IScopeProvider.cs" />
@@ -364,7 +364,8 @@ namespace Umbraco.Tests.IO
ss.Complete();
Assert.IsTrue(File.Exists(path + "/ShadowSystem/path/to/some/dir/f1.txt")); // *not* cleaning
// yes we are cleaning now
//Assert.IsTrue(File.Exists(path + "/ShadowSystem/path/to/some/dir/f1.txt")); // *not* cleaning
Assert.IsTrue(File.Exists(path + "/ShadowTests/path/to/some/dir/f1.txt"));
Assert.IsFalse(File.Exists(path + "/ShadowTests/sub/sub/f2.txt"));
}
@@ -558,7 +559,7 @@ namespace Umbraco.Tests.IO
Assert.AreEqual(1, ae.InnerExceptions.Count);
e = ae.InnerExceptions[0];
Assert.IsNotNull(e.InnerException);
Assert.IsInstanceOf<UnauthorizedAccessException>(e.InnerException);
Assert.IsInstanceOf<Exception>(e.InnerException);
}
// still, the rest of the changes has been applied ok
+9 -5
View File
@@ -2,6 +2,7 @@
using System.Data;
using System.Runtime.Remoting.Messaging;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Persistence;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
@@ -38,11 +39,14 @@ namespace Umbraco.Tests.Scoping
_database.BeginTransaction(); // opens and maintains a connection
// the test is leaking a scope with a non-null database
var contextScope = CallContext.LogicalGetData(ScopeProvider.ScopeItemKey);
Assert.IsNotNull(contextScope);
Assert.IsInstanceOf<NoScope>(CallContext.LogicalGetData(ScopeProvider.ScopeItemKey));
Assert.IsNotNull(((NoScope) contextScope).DatabaseOrNull);
Assert.AreSame(_database, ((NoScope)contextScope).DatabaseOrNull);
var contextGuid = CallContext.LogicalGetData(ScopeProvider.ScopeItemKey).AsGuid();
Assert.AreNotEqual(Guid.Empty, contextGuid);
// only if Core.DEBUG_SCOPES are defined
//var contextScope = DatabaseContext.ScopeProvider.CallContextObjects[contextGuid] as NoScope;
//Assert.IsNotNull(contextScope);
//Assert.IsNotNull(contextScope.DatabaseOrNull);
//Assert.AreSame(_database, contextScope.DatabaseOrNull);
// save the connection
_connection = _database.Connection;
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
@@ -8,7 +9,6 @@ using Umbraco.Core.Scoping;
namespace Umbraco.Tests.Scoping
{
[TestFixture]
public class ScopeEventDispatcherTests
{
@@ -21,11 +21,15 @@ namespace Umbraco.Tests.Scoping
DoThing3 = null;
}
[TestCase(EventsDispatchMode.PassThrough, true, true)]
[TestCase(EventsDispatchMode.PassThrough, true, false)]
[TestCase(EventsDispatchMode.PassThrough, false, true)]
[TestCase(EventsDispatchMode.PassThrough, false, false)]
public void PassThroughCancelable(EventsDispatchMode mode, bool cancel, bool complete)
[TestCase(false, true, true)]
[TestCase(false, true, false)]
[TestCase(false, false, true)]
[TestCase(false, false, false)]
[TestCase(true, true, true)]
[TestCase(true, true, false)]
[TestCase(true, false, true)]
[TestCase(true, false, false)]
public void EventsHandling(bool passive, bool cancel, bool complete)
{
var counter1 = 0;
var counter2 = 0;
@@ -34,7 +38,7 @@ namespace Umbraco.Tests.Scoping
DoThing2 += (sender, args) => { counter2++; };
var scopeProvider = new ScopeProvider(Mock.Of<IDatabaseFactory2>());
using (var scope = scopeProvider.CreateScope(dispatchMode: mode))
using (var scope = scopeProvider.CreateScope(eventDispatcher: passive ? new PassiveEventDispatcher() : null))
{
var cancelled = scope.Events.DispatchCancelable(DoThing1, this, new SaveEventArgs<string>("test"));
if (cancelled == false)
@@ -43,22 +47,15 @@ namespace Umbraco.Tests.Scoping
scope.Complete();
}
var expected1 = mode == EventsDispatchMode.Passive ? 0 : 1;
var expected1 = passive ? 0 : 1;
Assert.AreEqual(expected1, counter1);
var expected2 = -1;
switch (mode)
{
case EventsDispatchMode.PassThrough:
expected2 = cancel ? 0 : 1;
break;
case EventsDispatchMode.Scope:
expected2 = cancel ? 0 : (complete ? 1 : 0);
break;
case EventsDispatchMode.Passive:
expected2 = 0;
break;
}
int expected2;
if (passive)
expected2 = 0;
else
expected2 = cancel ? 0 : (complete ? 1 : 0);
Assert.AreEqual(expected2, counter2);
}
@@ -70,7 +67,7 @@ namespace Umbraco.Tests.Scoping
DoThing3 += OnDoThingFail;
var scopeProvider = new ScopeProvider(Mock.Of<IDatabaseFactory2>());
using (var scope = scopeProvider.CreateScope(dispatchMode: EventsDispatchMode.Passive))
using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher()))
{
scope.Events.Dispatch(DoThing1, this, new SaveEventArgs<string>("test"));
scope.Events.Dispatch(DoThing2, this, new SaveEventArgs<int>(0));
@@ -94,45 +91,14 @@ namespace Umbraco.Tests.Scoping
[TestCase(true)]
[TestCase(false)]
public void EventsDispatchmode_PassThrough(bool complete)
{
var counter = 0;
DoThing1 += (sender, args) => { counter++; };
DoThing2 += (sender, args) => { counter++; };
DoThing3 += (sender, args) => { counter++; };
var scopeProvider = new ScopeProvider(Mock.Of<IDatabaseFactory2>());
using (var scope = scopeProvider.CreateScope(dispatchMode: EventsDispatchMode.PassThrough))
{
scope.Events.Dispatch(DoThing1, this, new SaveEventArgs<string>("test"));
scope.Events.Dispatch(DoThing2, this, new SaveEventArgs<int>(0));
scope.Events.Dispatch(DoThing3, this, new SaveEventArgs<decimal>(0));
// events have not been queued
Assert.IsEmpty(scope.Events.GetEvents(EventDefinitionFilter.All));
// events have been raised
Assert.AreEqual(3, counter);
if (complete)
scope.Complete();
}
// nothing has changed
Assert.AreEqual(3, counter);
}
[TestCase(true)]
[TestCase(false)]
public void EventsDispatchMode_Passive(bool complete)
public void EventsDispatching_Passive(bool complete)
{
DoThing1 += OnDoThingFail;
DoThing2 += OnDoThingFail;
DoThing3 += OnDoThingFail;
var scopeProvider = new ScopeProvider(Mock.Of<IDatabaseFactory2>());
using (var scope = scopeProvider.CreateScope(dispatchMode: EventsDispatchMode.Passive))
using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher()))
{
scope.Events.Dispatch(DoThing1, this, new SaveEventArgs<string>("test"));
scope.Events.Dispatch(DoThing2, this, new SaveEventArgs<int>(0));
@@ -150,7 +116,7 @@ namespace Umbraco.Tests.Scoping
[TestCase(true)]
[TestCase(false)]
public void EventsDispatchMode_Scope(bool complete)
public void EventsDispatching_Scope(bool complete)
{
var counter = 0;
IScope ambientScope = null;
@@ -170,7 +136,7 @@ namespace Umbraco.Tests.Scoping
};
Guid guid;
using (var scope = scopeProvider.CreateScope(dispatchMode: EventsDispatchMode.Scope))
using (var scope = scopeProvider.CreateScope())
{
Assert.IsNotNull(scopeProvider.AmbientContext);
guid = scopeProvider.Context.Enlist("value", Guid.NewGuid, (c, o) => { });
@@ -181,6 +147,7 @@ namespace Umbraco.Tests.Scoping
// events have been queued
Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count());
Assert.AreEqual(0, counter);
if (complete)
scope.Complete();
@@ -215,5 +182,17 @@ namespace Umbraco.Tests.Scoping
public static event EventHandler<SaveEventArgs<int>> DoThing2;
public static event TypedEventHandler<ScopeEventDispatcherTests, SaveEventArgs<decimal>> DoThing3;
}
public class PassiveEventDispatcher : ScopeEventDispatcherBase
{
public PassiveEventDispatcher()
: base(false)
{ }
protected override void ScopeExitCompleted()
{
// do nothing
}
}
}
}
+123 -8
View File
@@ -1,10 +1,12 @@
using System;
using System.Collections;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Persistence;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Core.Events;
namespace Umbraco.Tests.Scoping
{
@@ -97,6 +99,54 @@ namespace Umbraco.Tests.Scoping
Assert.IsNull(scopeProvider.AmbientScope);
}
[Test]
public void NestedMigrateScope()
{
var scopeProvider = DatabaseContext.ScopeProvider;
Assert.IsNull(scopeProvider.AmbientScope);
var httpContextItems = new Hashtable();
ScopeProvider.HttpContextItemsGetter = () => httpContextItems;
try
{
using (var scope = scopeProvider.CreateScope())
{
Assert.IsInstanceOf<Scope>(scope);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
Assert.AreSame(scope, httpContextItems[ScopeProvider.ScopeItemKey]);
// only if Core.DEBUG_SCOPES are defined
//Assert.IsEmpty(scopeProvider.CallContextObjects);
using (var nested = scopeProvider.CreateScope(callContext: true))
{
Assert.IsInstanceOf<Scope>(nested);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(nested, scopeProvider.AmbientScope);
Assert.AreSame(scope, ((Scope) nested).ParentScope);
// it's moved over to call context
Assert.IsNull(httpContextItems[ScopeProvider.ScopeItemKey]);
var callContextKey = CallContext.LogicalGetData(ScopeProvider.ScopeItemKey).AsGuid();
Assert.AreNotEqual(Guid.Empty, callContextKey);
// only if Core.DEBUG_SCOPES are defined
//var ccnested = scopeProvider.CallContextObjects[callContextKey];
//Assert.AreSame(nested, ccnested);
}
// it's naturally back in http context
Assert.AreSame(scope, httpContextItems[ScopeProvider.ScopeItemKey]);
}
Assert.IsNull(scopeProvider.AmbientScope);
}
finally
{
ScopeProvider.HttpContextItemsGetter = null;
}
}
[Test]
public void NestedCreateScopeContext()
{
@@ -478,17 +528,82 @@ namespace Umbraco.Tests.Scoping
}
[Test]
public void CallContextScope()
public void CallContextScope1()
{
var scopeProvider = DatabaseContext.ScopeProvider;
var scope = scopeProvider.CreateScope();
Assert.IsNotNull(scopeProvider.AmbientScope);
using (new SafeCallContext())
using (var scope = scopeProvider.CreateScope())
{
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.IsNotNull(scopeProvider.AmbientContext);
using (new SafeCallContext())
{
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
using (var newScope = scopeProvider.CreateScope())
{
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientScope.ParentScope);
Assert.IsNotNull(scopeProvider.AmbientContext);
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
}
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
}
[Test]
public void CallContextScope2()
{
var scopeProvider = DatabaseContext.ScopeProvider;
Assert.IsNull(scopeProvider.AmbientScope);
var httpContextItems = new Hashtable();
ScopeProvider.HttpContextItemsGetter = () => httpContextItems;
try
{
using (var scope = scopeProvider.CreateScope())
{
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.IsNotNull(scopeProvider.AmbientContext);
using (new SafeCallContext())
{
// pretend it's another thread
ScopeProvider.HttpContextItemsGetter = null;
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
using (var newScope = scopeProvider.CreateScope())
{
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientScope.ParentScope);
Assert.IsNotNull(scopeProvider.AmbientContext);
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
// back to original thread
ScopeProvider.HttpContextItemsGetter = () => httpContextItems;
}
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
}
finally
{
ScopeProvider.HttpContextItemsGetter = null;
}
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
}
[Test]
+2 -6
View File
@@ -213,12 +213,8 @@ namespace Umbraco.Tests.Scoping
var scopeProvider = ApplicationContext.Current.ScopeProvider as IScopeProviderInternal;
Assert.IsNotNull(scopeProvider);
if (complete)
// because some event handlers trigger xml refresh with directly uses the DB
Assert.IsNotNull(scopeProvider.AmbientScope);
else
// because nothing happened
Assert.IsNull(scopeProvider.AmbientScope);
// ambient scope may be null, or maybe not, depending on whether the code that
// was called did proper scoped work, or some direct (NoScope) use of the database
Assert.IsNull(scopeProvider.AmbientContext);
// limited number of clones!
@@ -72,11 +72,12 @@ namespace Umbraco.Tests.TestHelpers
GetDbProviderName(),
Logger);
// fixme - bah - this is needed to reset static properties? Stephen to update this note
// ensure we start tests in a clean state ie without any scope in context
// anything that used a true 'Scope' would have removed it, but there could
// be a rogue 'NoScope' there - and we want to make sure it is gone
var scopeProvider = new ScopeProvider(null);
if (scopeProvider.AmbientScope != null)
scopeProvider.AmbientScope.Dispose();
scopeProvider.AmbientScope = null;
scopeProvider.AmbientScope.Dispose(); // removes scope from context
base.Initialize();
@@ -40,27 +40,50 @@ Use this directive to generate a list of breadcrumbs.
@param {array} ancestors Array of ancestors
@param {string} entityType The content entity type (member, media, content).
@param {callback} Callback when an ancestor is clicked. It will override the default link behaviour.
**/
(function() {
'use strict';
(function () {
'use strict';
function BreadcrumbsDirective() {
function BreadcrumbsDirective() {
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/editor/umb-breadcrumbs.html',
scope: {
ancestors: "=",
entityType: "@"
}
};
function link(scope, el, attr, ctrl) {
return directive;
scope.allowOnOpen = false;
}
scope.open = function(ancestor) {
if(scope.onOpen && scope.allowOnOpen) {
scope.onOpen({'ancestor': ancestor});
}
};
angular.module('umbraco.directives').directive('umbBreadcrumbs', BreadcrumbsDirective);
function onInit() {
if ("onOpen" in attr) {
scope.allowOnOpen = true;
}
}
onInit();
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/editor/umb-breadcrumbs.html',
scope: {
ancestors: "=",
entityType: "@",
onOpen: "&"
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbBreadcrumbs', BreadcrumbsDirective);
})();
@@ -21,7 +21,8 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat
customtreeparams: '@',
eventhandler: '=',
enablecheckboxes: '@',
enablelistviewsearch: '@'
enablelistviewsearch: '@',
enablelistviewexpand: '@'
},
compile: function(element, attrs) {
@@ -35,7 +36,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat
'<a class="umb-options" ng-hide="tree.root.isContainer || !tree.root.menuUrl" ng-click="options(tree.root, $event)" ng-swipe-right="options(tree.root, $event)"><i></i><i></i><i></i></a>' +
'</div>';
template += '<ul>' +
'<umb-tree-item ng-repeat="child in tree.root.children" eventhandler="eventhandler" node="child" current-node="currentNode" tree="this" section="{{section}}" ng-animate="animation()"></umb-tree-item>' +
'<umb-tree-item ng-repeat="child in tree.root.children" enablelistviewexpand="{{enablelistviewexpand}}" eventhandler="eventhandler" node="child" current-node="currentNode" tree="this" section="{{section}}" ng-animate="animation()"></umb-tree-item>' +
'</ul>' +
'</li>' +
'</ul>';
@@ -27,6 +27,7 @@ angular.module("umbraco.directives")
section: '@',
eventhandler: '=',
currentNode: '=',
enablelistviewexpand: '@',
node: '=',
tree: '='
},
@@ -38,7 +39,7 @@ angular.module("umbraco.directives")
'<div ng-class="getNodeCssClass(node)" ng-swipe-right="options(node, $event)" >' +
//NOTE: This ins element is used to display the search icon if the node is a container/listview and the tree is currently in dialog
//'<ins ng-if="tree.enablelistviewsearch && node.metaData.isContainer" class="umb-tree-node-search icon-search" ng-click="searchNode(node, $event)" alt="searchAltText"></ins>' +
'<ins ng-class="{\'icon-navigation-right\': !node.expanded, \'icon-navigation-down\': node.expanded}" ng-click="load(node)">&nbsp;</ins>' +
'<ins ng-class="{\'icon-navigation-right\': !node.expanded || node.metaData.isContainer, \'icon-navigation-down\': node.expanded && !node.metaData.isContainer}" ng-click="load(node)">&nbsp;</ins>' +
'<i class="icon umb-tree-icon sprTree" ng-click="select(node, $event)"></i>' +
'<a href="#/{{node.routePath}}" ng-click="select(node, $event)"></a>' +
//NOTE: These are the 'option' elipses
@@ -74,11 +75,12 @@ angular.module("umbraco.directives")
//toggle visibility of last 'ins' depending on children
//visibility still ensure the space is "reserved", so both nodes with and without children are aligned.
if (!node.hasChildren) {
element.find("ins").last().css("visibility", "hidden");
if (node.hasChildren || node.metaData.isContainer && scope.enablelistviewexpand === "true") {
element.find("ins").last().css("visibility", "visible");
}
else {
element.find("ins").last().css("visibility", "visible");
element.find("ins").last().css("visibility", "hidden");
}
var icon = element.find("i:first");
@@ -192,7 +194,7 @@ angular.module("umbraco.directives")
emits treeNodeCollapsing event if already expanded and treeNodeExpanding if collapsed
*/
scope.load = function (node) {
if (node.expanded) {
if (node.expanded && !node.metaData.isContainer) {
deleteAnimations = false;
emitEvent("treeNodeCollapsing", { tree: scope.tree, node: node, element: element });
node.expanded = false;
@@ -227,7 +229,7 @@ angular.module("umbraco.directives")
setupNodeDom(scope.node, scope.tree);
var template = '<ul ng-class="{collapsed: !node.expanded}"><umb-tree-item ng-repeat="child in node.children" eventhandler="eventhandler" tree="tree" current-node="currentNode" node="child" section="{{section}}" ng-animate="animation()"></umb-tree-item></ul>';
var template = '<ul ng-class="{collapsed: !node.expanded}"><umb-tree-item ng-repeat="child in node.children" enablelistviewexpand="{{enablelistviewexpand}}" eventhandler="eventhandler" tree="tree" current-node="currentNode" node="child" section="{{section}}" ng-animate="animation()"></umb-tree-item></ul>';
var newElement = angular.element(template);
$compile(newElement)(scope);
element.append(newElement);
@@ -0,0 +1,209 @@
(function () {
'use strict';
function MiniListViewDirective(contentResource, memberResource, mediaResource) {
function link(scope, el, attr, ctrl) {
scope.search = "";
scope.miniListViews = [];
scope.breadcrumb = [];
var miniListViewsHistory = [];
var goingForward = true;
var skipAnimation = true;
function onInit() {
open(scope.node);
}
function open(node) {
goingForward = true;
var miniListView = {
node: node,
loading: true,
pagination: {
pageSize: 10,
pageNumber: 1,
filter: '',
orderDirection: "Ascending",
orderBy: "SortOrder",
orderBySystemField: true
}
};
// clear and push mini list view in dom so we only render 1 view
scope.miniListViews = [];
scope.miniListViews.push(miniListView);
// store in history so we quickly can navigate back
miniListViewsHistory.push(miniListView);
// get children
getChildrenForMiniListView(miniListView);
makeBreadcrumb();
}
function getChildrenForMiniListView(miniListView) {
// start loading animation list view
miniListView.loading = true;
// setup the correct resource depending on section
var resource = "";
if (scope.entityType === "Member") {
resource = memberResource.getPagedResults;
} else if (scope.entityType === "Media") {
resource = mediaResource.getChildren;
} else {
resource = contentResource.getChildren;
}
resource(miniListView.node.id, miniListView.pagination)
.then(function (data) {
// update children
miniListView.children = data.items;
// update pagination
miniListView.pagination.totalItems = data.totalItems;
miniListView.pagination.totalPages = data.totalPages;
// stop load indicator
miniListView.loading = false;
});
}
scope.openNode = function(event, node) {
open(node);
event.stopPropagation();
};
scope.selectNode = function(node) {
if(scope.onSelect) {
scope.onSelect({'node': node});
}
};
/* Pagination */
scope.goToPage = function(pageNumber, miniListView) {
// set new page number
miniListView.pagination.pageNumber = pageNumber;
// get children
getChildrenForMiniListView(miniListView);
};
/* Breadcrumb */
scope.clickBreadcrumb = function(ancestor) {
var found = false;
goingForward = false;
angular.forEach(miniListViewsHistory, function(historyItem, index){
// We need to make sure we can compare the two id's.
// Some id's are integers and others are strings.
// Members have string ids like "all-members".
if(historyItem.node.id.toString() === ancestor.id.toString()) {
// load the list view from history
scope.miniListViews = [];
scope.miniListViews.push(historyItem);
// clean up history - remove all children after
miniListViewsHistory.splice(index + 1, miniListViewsHistory.length);
found = true;
}
});
if(!found) {
// if we can't find the view in the history - close the list view
scope.exitMiniListView();
}
// update the breadcrumb
makeBreadcrumb();
};
scope.showBackButton = function() {
// don't show the back button if the start node is a list view
if(scope.node.metaData && scope.node.metaData.IsContainer || scope.node.isContainer) {
return false;
} else {
return true;
}
};
scope.exitMiniListView = function() {
miniListViewsHistory = [];
scope.miniListViews = [];
if(scope.onClose) {
scope.onClose();
}
};
function makeBreadcrumb() {
scope.breadcrumb = [];
angular.forEach(miniListViewsHistory, function(historyItem){
scope.breadcrumb.push(historyItem.node);
});
}
/* Search */
scope.searchMiniListView = function(search, miniListView) {
// set search value
miniListView.pagination.filter = search;
// reset pagination
miniListView.pagination.pageNumber = 1;
// start loading animation list view
miniListView.loading = true;
searchMiniListView(miniListView);
};
var searchMiniListView = _.debounce(function (miniListView) {
scope.$apply(function () {
getChildrenForMiniListView(miniListView);
});
}, 500);
/* Animation */
scope.getMiniListViewAnimation = function() {
// disable the first "slide-in-animation"" if the start node is a list view
if(scope.node.metaData && scope.node.metaData.IsContainer && skipAnimation || scope.node.isContainer && skipAnimation) {
skipAnimation = false;
return;
}
if(goingForward) {
return 'umb-mini-list-view--forward';
} else {
return 'umb-mini-list-view--backwards';
}
};
onInit();
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/umb-mini-list-view.html',
scope: {
node: "=",
entityType: "@",
startNodeId: "=",
onSelect: "&",
onClose: "&"
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbMiniListView', MiniListViewDirective);
})();
@@ -134,25 +134,40 @@ Use this directive to generate a pagination.
}
scope.next = function() {
if (scope.onNext && scope.pageNumber < scope.totalPages) {
scope.pageNumber++;
scope.onNext(scope.pageNumber);
}
scope.next = function () {
if (scope.pageNumber < scope.totalPages) {
scope.pageNumber++;
if (scope.onNext) {
scope.onNext(scope.pageNumber);
}
if (scope.onChange) {
scope.onChange({ "pageNumber": scope.pageNumber });
}
}
};
scope.prev = function(pageNumber) {
if (scope.onPrev && scope.pageNumber > 1) {
scope.pageNumber--;
scope.onPrev(scope.pageNumber);
}
scope.prev = function (pageNumber) {
if (scope.pageNumber > 1) {
scope.pageNumber--;
if (scope.onPrev) {
scope.onPrev(scope.pageNumber);
}
if (scope.onChange) {
scope.onChange({ "pageNumber": scope.pageNumber });
}
}
};
scope.goToPage = function(pageNumber) {
if(scope.onGoToPage) {
scope.pageNumber = pageNumber + 1;
scope.onGoToPage(scope.pageNumber);
}
scope.goToPage = function (pageNumber) {
scope.pageNumber = pageNumber + 1;
if (scope.onGoToPage) {
scope.onGoToPage(scope.pageNumber);
}
if (scope.onChange) {
if (scope.onChange) {
scope.onChange({ "pageNumber": scope.pageNumber });
}
}
};
var unbindPageNumberWatcher = scope.$watch('pageNumber', function(newValue, oldValue){
@@ -176,7 +191,8 @@ Use this directive to generate a pagination.
totalPages: "=",
onNext: "=",
onPrev: "=",
onGoToPage: "="
onGoToPage: "=",
onChange: "&"
},
link: link
};
@@ -118,6 +118,8 @@
@import "components/umb-avatar.less";
@import "components/umb-progress-bar.less";
@import "components/umb-querybuilder.less";
@import "components/umb-pagination.less";
@import "components/umb-mini-list-view.less";
@import "components/buttons/umb-button.less";
@import "components/buttons/umb-button-group.less";
@@ -0,0 +1,97 @@
.umb-mini-list-view__title {
display: flex;
align-items: center;
}
.umb-mini-list-view__title-text {
font-size: 16px;
font-weight: bold;
}
.umb-mini-list-view__title-icon {
font-size: 20px;
margin-right: 5px;
}
.umb-mini-list-view__back {
font-size: 11px;
margin-right: 5px;
color: @gray;
display: flex;
align-items: center;
}
.umb-mini-list-view__back-icon {
margin-right: 4px;
height: 11px;
line-height: 11px;
}
.umb-mini-list-view__back-text {
text-decoration: underline;
margin-right: 5px;
}
.umb-mini-list-view__back:hover {
opacity: 1;
text-decoration: none;
color: @black;
}
/* Animations */
/* Forward */
.umb-mini-list-view--forward-enter,
.umb-mini-list-view--forward-leave
{
transition: 120ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
position: relative;
display: block;
}
.umb-mini-list-view--forward-enter {
left: 100%;
}
.umb-mini-list-view--forward-enter.umb-mini-list-view--forward-enter-active {
left: 0;
opacity: 1;
}
.umb-mini-list-view--forward-leave {
left: 0;
}
.umb-mini-list-view--forward-leave.umb-mini-list-view--forward-leave-active{
left: -100%;
opacity: 0;
}
/* Backwards */
.umb-mini-list-view--backwards-enter,
.umb-mini-list-view--backwards-leave
{
transition: 120ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
position: relative;
display: block;
}
.umb-mini-list-view--backwards-enter {
right: 100%;
}
.umb-mini-list-view--backwards-enter.umb-mini-list-view--backwards-enter-active {
right: 0;
opacity: 1;
}
.umb-mini-list-view--backwards-leave {
left: 0;
}
.umb-mini-list-view--backwards-leave.umb-mini-list-view--backwards-leave-active{
right: -100%;
opacity: 0;
}
@@ -0,0 +1,3 @@
.umb-pagination ul {
box-shadow: none;
}
@@ -10,7 +10,7 @@
flex-wrap: nowrap;
justify-content: space-between;
min-width: 640px;
min-width: auto;
}
.umb-table.umb-table-inactive {
@@ -103,11 +103,17 @@ input.umb-table__input {
// Table Body Styles
.umb-table-body {
position: relative;
}
.umb-table-body .umb-table-row {
color: fade(@gray, 75%);
border-top: 1px solid @grayLight;
cursor: pointer;
font-size: 13px;
position: relative;
min-height: 32px;
&:hover {
background-color: fade(@grayLighter, 90%);
@@ -198,30 +204,33 @@ input.umb-table__input {
}
}
// Table Row Styles
.umb-table-row {
display: flex;
flex-flow: row nowrap;
align-items: center;
user-select: none;
}
.umb-table-body .umb-table-row--empty {
flex: 1 1 auto;
display: flex;
justify-content: center;
padding: 5px 0;
cursor: auto;
user-select: auto;
}
.umb-table-body .umb-table-row--empty:hover {
background-color: transparent;
cursor: auto;
}
.umb-table-row.-selected,
.umb-table-row.-selected:hover {
background-color: fade(@blueDark, 4%);
}
// Table Cell Styles
.umb-table-cell {
display: flex;
@@ -250,9 +259,44 @@ input.umb-table__input {
padding: 15px 0;
}
.umb-table-cell--auto-width {
flex: 0 0 auto !important;
}
.umb-table-cell--faded {
opacity: 0.4;
}
// Increases the space for the name cell
.umb-table__name {
flex: 1 1 25%;
max-width: 25%;
}
.umb-table__loading-overlay {
position: absolute;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.7);
z-index: 1;
}
.umb-table__row-expand {
font-size: 12px;
text-decoration: none;
color: @black;
}
.umb-table--condensed {
.umb-table-cell:first-of-type:not(.not-fixed) {
padding-top: 10px;
padding-bottom: 10px;
}
.umb-table-body__icon {
font-size: 20px;
}
}
@@ -36,6 +36,7 @@
.red{color: @red;}
.blue{color: @blue;}
.black{color: @black;}
//icon colors for tree icons
@@ -351,4 +352,4 @@
// SORTABLE
// --------------------------------------------------
@sortableHelperBg: rgba(4, 156, 219, 0.5);
@sortablePlaceholderBg : @blue;
@sortablePlaceholderBg : @blue;
@@ -1,34 +1,48 @@
<div ng-controller="Umbraco.Overlays.TreePickerController" ng-init="init('content')">
<div class="umb-control-group">
<umb-tree-search-box
hide-search-callback="hideSearch"
search-callback="onSearchResults"
search-from-id="{{searchInfo.searchFromId}}"
search-from-name="{{searchInfo.searchFromName}}"
show-search="{{searchInfo.showSearch}}"
section="content">
</umb-tree-search-box>
</div>
<div ng-hide="miniListView">
<umb-tree-search-results
ng-if="searchInfo.showSearch"
results="searchInfo.results"
select-result-callback="selectResult">
</umb-tree-search-results>
<div class="umb-control-group">
<umb-tree-search-box
hide-search-callback="hideSearch"
search-callback="onSearchResults"
search-from-id="{{searchInfo.searchFromId}}"
search-from-name="{{searchInfo.searchFromName}}"
show-search="{{searchInfo.showSearch}}"
section="content">
</umb-tree-search-box>
</div>
<div ng-hide="searchInfo.showSearch" ng-animate="'tree-fade-out'">
<umb-tree
section="content"
treealias="content"
hideheader="{{hideHeader}}"
hideoptions="true"
isdialog="true"
customtreeparams="{{customTreeParams}}"
eventhandler="dialogTreeEventHandler"
enablelistviewsearch="true"
enablecheckboxes="{{multiPicker}}">
</umb-tree>
</div>
<umb-tree-search-results
ng-if="searchInfo.showSearch"
results="searchInfo.results"
select-result-callback="selectResult">
</umb-tree-search-results>
</div>
<div ng-hide="searchInfo.showSearch" ng-animate="'tree-fade-out'">
<umb-tree
section="content"
treealias="content"
hideheader="{{hideHeader}}"
hideoptions="true"
isdialog="true"
customtreeparams="{{customTreeParams}}"
eventhandler="dialogTreeEventHandler"
enablelistviewsearch="true"
enablelistviewexpand="true"
enablecheckboxes="{{multiPicker}}">
</umb-tree>
</div>
</div>
<umb-mini-list-view
ng-if="miniListView"
node="miniListView"
entity-type="{{entityType}}"
start-node-id="model.startNodeId"
on-select="selectListViewNode(node)"
on-close="closeMiniListView()">
</umb-mini-list-view>
</div>
@@ -1,34 +1,47 @@
<div ng-controller="Umbraco.Overlays.TreePickerController" ng-init="init('member')">
<div class="umb-control-group">
<umb-tree-search-box
hide-search-callback="hideSearch"
search-callback="onSearchResults"
search-from-id="{{searchInfo.searchFromId}}"
search-from-name="{{searchInfo.searchFromName}}"
show-search="{{searchInfo.showSearch}}"
section="member">
</umb-tree-search-box>
</div>
<div ng-hide="miniListView">
<umb-tree-search-results
ng-if="searchInfo.showSearch"
results="searchInfo.results"
select-result-callback="selectResult">
</umb-tree-search-results>
<div class="umb-control-group">
<umb-tree-search-box
hide-search-callback="hideSearch"
search-callback="onSearchResults"
search-from-id="{{searchInfo.searchFromId}}"
search-from-name="{{searchInfo.searchFromName}}"
show-search="{{searchInfo.showSearch}}"
section="member">
</umb-tree-search-box>
</div>
<div ng-hide="searchInfo.showSearch" ng-animate="'tree-fade-out'">
<umb-tree
section="member"
treealias="member"
hideheader="{{hideHeader}}"
hideoptions="true"
isdialog="true"
customtreeparams="{{customTreeParams}}"
eventhandler="dialogTreeEventHandler"
enablelistviewsearch="true"
enablecheckboxes="{{multiPicker}}">
</umb-tree>
</div>
<umb-tree-search-results
ng-if="searchInfo.showSearch"
results="searchInfo.results"
select-result-callback="selectResult">
</umb-tree-search-results>
<div ng-hide="searchInfo.showSearch" ng-animate="'tree-fade-out'">
<umb-tree
section="member"
treealias="member"
hideheader="{{hideHeader}}"
hideoptions="true"
isdialog="true"
customtreeparams="{{customTreeParams}}"
eventhandler="dialogTreeEventHandler"
enablelistviewsearch="true"
enablelistviewexpand="true"
enablecheckboxes="{{multiPicker}}">
</umb-tree>
</div>
</div>
<umb-mini-list-view
ng-if="miniListView"
node="miniListView"
entity-type="{{entityType}}"
start-node-id="model.startNodeId"
on-select="selectListViewNode(node)"
on-close="closeMiniListView()">
</umb-mini-list-view>
</div>
@@ -1,6 +1,6 @@
//used for the media picker dialog
angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) {
function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) {
var tree = null;
var dialogOptions = $scope.model;
@@ -22,27 +22,22 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
$scope.init = function(contentType) {
if(contentType === "content") {
entityType = "Document";
$scope.entityType = "Document";
if(!$scope.model.title) {
$scope.model.title = localizationService.localize("defaultdialogs_selectContent");
}
} else if(contentType === "member") {
entityType = "Member";
$scope.entityType = "Member";
if(!$scope.model.title) {
$scope.model.title = localizationService.localize("defaultdialogs_selectMember");
}
} else if(contentType === "media") {
entityType = "Media";
$scope.entityType = "Media";
if(!$scope.model.title) {
$scope.model.title = localizationService.localize("defaultdialogs_selectMedia");
}
}
}
// Search is only working for content, media and member section so we will remove it from everything else
if($scope.section === "content" || $scope.section === "media" || $scope.section === "member" ) {
$scope.enableSearh = true;
}
//create the custom query string param for this tree
$scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : "";
@@ -54,7 +49,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
});
// Allow the entity type to be passed in but defaults to Document for backwards compatibility.
var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document";
$scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document";
//min / max values
@@ -66,10 +61,24 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
}
if (dialogOptions.section === "member") {
entityType = "Member";
$scope.entityType = "Member";
}
else if (dialogOptions.section === "media") {
entityType = "Media";
$scope.entityType = "Media";
}
// Search and listviews is only working for content, media and member section so we will remove it from everything else
if ($scope.section === "content" || $scope.section === "media" || $scope.section === "member") {
$scope.enableSearh = true;
//if a alternative startnode is used, we need to check if it is a container
if (dialogOptions.startNodeId && dialogOptions.startNodeId !== -1) {
entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function (node) {
if (node.metaData.IsContainer) {
openMiniListView(node);
}
});
}
}
//Configures filtering
@@ -101,54 +110,17 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
}
function nodeExpandedHandler(ev, args) {
// open mini list view for list views
if (args.node.metaData.isContainer) {
openMiniListView(args.node);
}
if (angular.isArray(args.children)) {
//iterate children
_.each(args.children, function (child) {
//check if any of the items are list views, if so we need to add some custom
// children: A node to activate the search, any nodes that have already been
// selected in the search
if (child.metaData.isContainer) {
child.hasChildren = true;
child.children = [
{
level: child.level + 1,
hasChildren: false,
parent: function () {
return child;
},
name: searchText,
metaData: {
listViewNode: child,
},
cssClass: "icon-search",
cssClasses: ["not-published"]
}
];
//add base transition classes to this node
child.cssClasses.push("tree-node-slide-up");
var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) {
return i.parentId == child.id;
});
_.each(listViewResults, function(item) {
child.children.unshift({
id: item.id,
name: item.name,
cssClass: "icon umb-tree-icon sprTree " + item.icon,
level: child.level + 1,
metaData: {
isSearchResult: true
},
hasChildren: false,
parent: function () {
return child;
}
});
});
}
//now we need to look in the already selected search results and
// toggle the check boxes for those ones that are listed
var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
@@ -174,18 +146,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
args.event.preventDefault();
args.event.stopPropagation();
if (args.node.metaData.listViewNode) {
//check if list view 'search' node was selected
$scope.searchInfo.showSearch = true;
$scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
$scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
//add transition classes
var listViewNode = args.node.parent();
listViewNode.cssClasses.push('tree-node-slide-up-hide-active');
}
else if (args.node.metaData.isSearchResult) {
if (args.node.metaData.isSearchResult) {
//check if the item selected was a search result from a list view
//unselect
@@ -232,7 +193,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
multiSelectItem(entity);
} else {
//otherwise we have to get it from the server
entityResource.getById(id, entityType).then(function (ent) {
entityResource.getById(id, $scope.entityType).then(function (ent) {
multiSelectItem(ent);
});
}
@@ -257,7 +218,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
multiSelectItem(entity);
} else {
//otherwise we have to get it from the server
entityResource.getById(id, entityType).then(function (ent) {
entityResource.getById(id, $scope.entityType).then(function (ent) {
multiSelectItem(ent);
});
}
@@ -274,7 +235,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
$scope.model.submit($scope.model);
} else {
//otherwise we have to get it from the server
entityResource.getById(id, entityType).then(function (ent) {
entityResource.getById(id, $scope.entityType).then(function (ent) {
$scope.model.selection.push(ent);
$scope.model.submit($scope.model);
});
@@ -355,7 +316,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
}
$scope.multiSubmit = function (result) {
entityResource.getByIds(result, entityType).then(function (ents) {
entityResource.getByIds(result, $scope.entityType).then(function (ents) {
$scope.submit(ents);
});
};
@@ -504,4 +465,19 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
$scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
$scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
});
$scope.selectListViewNode = function(node) {
select(node.name, node.id);
//toggle checked state
node.selected = node.selected === true ? false : true;
};
$scope.closeMiniListView = function() {
$scope.miniListView = undefined;
};
function openMiniListView(node) {
$scope.miniListView = node;
}
});
@@ -1,35 +1,49 @@
<div ng-controller="Umbraco.Overlays.TreePickerController">
<div class="umb-control-group">
<umb-tree-search-box
ng-if="enableSearh"
hide-search-callback="hideSearch"
search-callback="onSearchResults"
search-from-id="{{searchInfo.searchFromId}}"
search-from-name="{{searchInfo.searchFromName}}"
show-search="{{searchInfo.showSearch}}"
section="{{section}}">
</umb-tree-search-box>
</div>
<div ng-hide="miniListView">
<umb-tree-search-results
ng-if="searchInfo.showSearch"
results="searchInfo.results"
select-result-callback="selectResult">
</umb-tree-search-results>
<div class="umb-control-group">
<umb-tree-search-box
ng-if="enableSearh"
hide-search-callback="hideSearch"
search-callback="onSearchResults"
search-from-id="{{searchInfo.searchFromId}}"
search-from-name="{{searchInfo.searchFromName}}"
show-search="{{searchInfo.showSearch}}"
section="{{section}}">
</umb-tree-search-box>
</div>
<div ng-hide="searchInfo.showSearch" ng-animate="'tree-fade-out'">
<umb-tree
section="{{section}}"
treealias="{{treeAlias}}"
hideheader="{{hideHeader}}"
hideoptions="true"
isdialog="true"
customtreeparams="{{customTreeParams}}"
eventhandler="dialogTreeEventHandler"
enablelistviewsearch="true"
enablecheckboxes="{{multiPicker}}">
</umb-tree>
</div>
<umb-tree-search-results
ng-if="searchInfo.showSearch"
results="searchInfo.results"
select-result-callback="selectResult">
</umb-tree-search-results>
</div>
<div ng-hide="searchInfo.showSearch" ng-animate="'tree-fade-out'">
<umb-tree
section="{{section}}"
treealias="{{treeAlias}}"
hideheader="{{hideHeader}}"
hideoptions="true"
isdialog="true"
customtreeparams="{{customTreeParams}}"
eventhandler="dialogTreeEventHandler"
enablelistviewsearch="true"
enablelistviewexpand="true"
enablecheckboxes="{{multiPicker}}">
</umb-tree>
</div>
</div>
<umb-mini-list-view
ng-if="miniListView"
node="miniListView"
entity-type="{{entityType}}"
start-node-id="model.startNodeId"
on-select="selectListViewNode(node)"
on-close="closeMiniListView()">
</umb-mini-list-view>
</div>
@@ -1,10 +1,15 @@
<ul class="umb-breadcrumbs">
<li class="umb-breadcrumbs__ancestor" ng-repeat="ancestor in ancestors">
<li class="umb-breadcrumbs__ancestor" ng-repeat="ancestor in ancestors">
<!-- go to node on click -->
<a ng-if="!$last && !allowOnOpen" href="#/{{entityType}}/{{entityType}}/edit/{{ancestor.id}}" class="umb-breadcrumbs__ancestor-link" title="{{ancestor.name}}">{{ancestor.name}}</a>
<a ng-if="!$last" href="#/{{entityType}}/{{entityType}}/edit/{{ancestor.id}}" class="umb-breadcrumbs__ancestor-link" title="{{ancestor.name}}">{{ancestor.name}}</a>
<span ng-if="!$last" class="umb-breadcrumbs__seperator">&#47;</span>
<!-- use callback to handle click -->
<a ng-if="!$last && allowOnOpen" href="#" ng-click="open(ancestor)" class="umb-breadcrumbs__ancestor-link" title="{{ancestor.name}}" prevent-default>{{ancestor.name}}</a>
<span class="umb-breadcrumbs__ancestor-text" ng-if="$last" title="{{ancestor.name}}">{{ancestor.name}}</span>
<span ng-if="!$last" class="umb-breadcrumbs__seperator">&#47;</span>
</li>
</ul>
<span class="umb-breadcrumbs__ancestor-text" ng-if="$last" title="{{ancestor.name}}">{{ancestor.name}}</span>
</li>
</ul>
@@ -0,0 +1,101 @@
<div>
<div ng-repeat="miniListView in miniListViews" ng-animate="getMiniListViewAnimation()">
<div class="umb-mini-list-view__title">
<i class="umb-mini-list-view__title-icon {{ miniListView.node.icon }}"></i>
<h4 class="umb-mini-list-view__title-text">{{ miniListView.node.name }}</h4>
</div>
<div class="flex" style="margin-bottom: 10px;">
<a ng-if="showBackButton()" href="" class="umb-mini-list-view__back" ng-click="exitMiniListView()">
<i class="icon-arrow-left umb-mini-list-view__back-icon"></i>
<span class="umb-mini-list-view__back-text">Back</span> /
</a>
<umb-breadcrumbs
ng-if="breadcrumb && breadcrumb.length > 0"
ancestors="breadcrumb"
entity-type="content"
on-open="clickBreadcrumb(ancestor)">
</umb-breadcrumbs>
</div>
<div class="umb-table umb-table--condensed">
<!-- Head -->
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell" style="display: none;"></div>
<div class="umb-table-cell" style="padding-top: 8px; padding-bottom: 8px;">
<form class="form-search -no-margin-bottom" style="width: 100%; margin-right: 0;" novalidate>
<div class="inner-addon left-addon">
<i class="icon icon-search" style="font-size: 14px;"></i>
<input
style="width: 100%;"
class="form-control search-input"
type="text"
localize="placeholder"
placeholder="@general_typeToSearch"
ng-model="search"
ng-change="searchMiniListView(search, miniListView)"
prevent-enter-submit
no-dirty-check>
</div>
</form>
</div>
</div>
</div>
<!-- Body -->
<div class="umb-table-body">
<!-- Load indicator when the list has items -->
<div class="umb-table__loading-overlay" ng-if="miniListView.loading && miniListView.children.length > 0">
<umb-load-indicator></umb-load-indicator>
</div>
<!-- Items -->
<div class="umb-table-row"
ng-repeat="child in miniListView.children"
ng-click="selectNode(child)"
ng-class="{'-selected':child.selected}">
<div class="umb-table-cell umb-table-cell--auto-width" ng-class="{'umb-table-cell--faded':!child.published && entityType === 'Document'}">
<div class="flex items-center">
<ins class="icon-navigation-right umb-table__row-expand" ng-click="openNode($event, child)">&nbsp;</ins>
<i class="umb-table-body__icon umb-table-body__fileicon {{child.icon}}"></i>
<i class="umb-table-body__icon umb-table-body__checkicon icon-check"></i>
</div>
</div>
<div class="umb-table-cell black" ng-class="{'umb-table-cell--faded':!child.published && entityType === 'Document'}">{{ child.name }}</div>
</div>
<!-- Load indicator when the list doesn't have items -->
<div ng-if="!miniListView.loading && !miniListView.children" class="umb-table-row umb-table-row--empty">
<span ng-if="search === ''"><localize key="general_noItemsInList"></localize></span>
<span ng-if="search !== ''"><localize key="general_searchNoResult"></localize></span>
</div>
<!-- Load indicator when the list doesn't have items -->
<div ng-if="miniListView.loading && !miniListView.children" class="umb-table-row umb-table-row--empty">
<umb-load-indicator></umb-load-indicator>
</div>
</div>
</div>
<div class="flex justify-center">
<umb-pagination
ng-if="miniListView.pagination.totalPages > 0 && !miniListView.loading"
page-number="miniListView.pagination.pageNumber"
total-pages="miniListView.pagination.totalPages"
on-change="goToPage(pageNumber, miniListView)">
</umb-pagination>
</div>
</div>
</div>
@@ -1,4 +1,4 @@
<div class="pagination" ng-show="pagination.length > 1">
<div class="umb-pagination pagination" ng-show="pagination.length > 1">
<ul>
<li ng-class="{disabled:pageNumber <= 1}">
@@ -11,10 +11,10 @@
<!--Settings-->
<add initialize="true" sortOrder="0" alias="documentTypes" application="settings" title="Document Types" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.ContentTypeTreeController, umbraco" />
<add application="settings" alias="templates" title="Templates" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.TemplatesTreeController, umbraco" initialize="true" sortOrder="1" />
<add application="settings" alias="partialViews" title="Partial Views" silent="false" initialize="true" iconClosed="icon-folder" iconOpen="icon-folder" type="Umbraco.Web.Trees.PartialViewsTree, umbraco" sortOrder="2" />
<add application="settings" alias="partialViews" title="Partial Views" silent="false" initialize="true" iconClosed="icon-folder" iconOpen="icon-folder" type="Umbraco.Web.Trees.PartialViewsTreeController, umbraco" sortOrder="2" />
<add application="settings" alias="stylesheets" title="Stylesheets" type="umbraco.loadStylesheets, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="3" />
<add application="settings" alias="stylesheetProperty" title="Stylesheet Property" type="umbraco.loadStylesheetProperty, umbraco" iconClosed="" iconOpen="" initialize="false" sortOrder="0" />
<add application="settings" alias="scripts" title="Scripts" type="umbraco.loadScripts, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4" />
<add application="settings" alias="scripts" title="Scripts" type="Umbraco.Web.Trees.ScriptTreeController, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4" />
<add application="settings" alias="languages" title="Languages" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.LanguageTreeController, umbraco" sortOrder="5" />
<add application="settings" alias="dictionary" title="Dictionary" type="umbraco.loadDictionary, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="6" />
<add initialize="true" sortOrder="7" alias="mediaTypes" application="settings" title="Media Types" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MediaTypeTreeController, umbraco" />
@@ -25,7 +25,7 @@
<add application="developer" alias="macros" title="Macros" type="umbraco.loadMacros, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="2" />
<add application="developer" alias="relationTypes" title="Relation Types" type="umbraco.loadRelationTypes, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4" />
<add application="developer" alias="xslt" title="XSLT Files" type="umbraco.loadXslt, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="5" />
<add application="developer" alias="partialViewMacros" type="Umbraco.Web.Trees.PartialViewMacrosTree, umbraco" silent="false" initialize="true" sortOrder="6" title="Partial View Macro Files" iconClosed="icon-folder" iconOpen="icon-folder" />
<add application="developer" alias="partialViewMacros" type="Umbraco.Web.Trees.PartialViewMacrosTreeController, umbraco" silent="false" initialize="true" sortOrder="6" title="Partial View Macro Files" iconClosed="icon-folder" iconOpen="icon-folder" />
<!--Users-->
<add application="users" alias="users" title="Users" type="umbraco.loadUsers, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="0" />
@@ -496,6 +496,8 @@
<key alias="retry">Retry</key>
<key alias="rights">Permissions</key>
<key alias="search">Search</key>
<key alias="searchNoResult">Sorry, we can not find what you are looking for</key>
<key alias="noItemsInList">No items have been added</key>
<key alias="server">Server</key>
<key alias="show">Show</key>
<key alias="showPageOnSend">Show page on Send</key>
@@ -243,6 +243,20 @@ namespace Umbraco.Web.Cache
#region Publishing
// IPublishingStrategy (obsolete) events are proxied into ContentService, which works fine when
// events are actually raised, but not when they are handled by HandleEvents, so we have to have
// these proxy methods that are *not* registered against any event *but* used by HandleEvents.
static void PublishingStrategy_UnPublished(IPublishingStrategy sender, PublishEventArgs<IContent> e)
{
ContentService_UnPublished(sender, e);
}
static void PublishingStrategy_Published(IPublishingStrategy sender, PublishEventArgs<IContent> e)
{
ContentService_Published(sender, e);
}
static void ContentService_UnPublished(IPublishingStrategy sender, PublishEventArgs<IContent> e)
{
if (e.PublishedEntities.Any())
+28 -3
View File
@@ -1,5 +1,6 @@
using AutoMapper;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -266,29 +267,46 @@ namespace Umbraco.Web.Editors
{
if (string.IsNullOrWhiteSpace(type) == false && string.IsNullOrWhiteSpace(virtualPath) == false)
{
virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath);
switch (type)
{
case Core.Constants.Trees.PartialViews:
if (IsDirectory(virtualPath, SystemDirectories.PartialViews))
{
Services.FileService.DeletePartialViewFolder(virtualPath);
return Request.CreateResponse(HttpStatusCode.OK);
}
if (Services.FileService.DeletePartialView(virtualPath, Security.CurrentUser.Id))
{
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View found with the specified path");
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View or folder found with the specified path");
case Core.Constants.Trees.PartialViewMacros:
if (IsDirectory(virtualPath, SystemDirectories.MacroPartials))
{
Services.FileService.DeletePartialViewMacroFolder(virtualPath);
return Request.CreateResponse(HttpStatusCode.OK);
}
if (Services.FileService.DeletePartialViewMacro(virtualPath, Security.CurrentUser.Id))
{
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View Macro found with the specified path");
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View Macro or folder found with the specified path");
case Core.Constants.Trees.Scripts:
if (IsDirectory(virtualPath, SystemDirectories.Scripts))
{
Services.FileService.DeleteScriptFolder(virtualPath);
return Request.CreateResponse(HttpStatusCode.OK);
}
if (Services.FileService.GetScriptByName(virtualPath) != null)
{
Services.FileService.DeleteScript(virtualPath, Security.CurrentUser.Id);
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Script found with the specified path");
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Script or folder found with the specified path");
default:
return Request.CreateResponse(HttpStatusCode.NotFound);
@@ -442,5 +460,12 @@ namespace Umbraco.Web.Editors
return value;
}
private bool IsDirectory(string virtualPath, string systemDirectory)
{
var path = IOHelper.MapPath(systemDirectory + "/" + virtualPath);
var dirInfo = new DirectoryInfo(path);
return dirInfo.Attributes == FileAttributes.Directory;
}
}
}
+2 -1
View File
@@ -79,10 +79,11 @@ namespace Umbraco.Web.Scheduling
// running on a background task, and Log.CleanLogs uses the old SqlHelper,
// better wrap in a scope and ensure it's all cleaned up and nothing leaks
using (ApplicationContext.Current.ScopeProvider.CreateScope())
using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope())
using (DisposableTimer.DebugDuration<LogScrubber>("Log scrubbing executing", "Log scrubbing complete"))
{
Log.CleanLogs(GetLogScrubbingMaximumAge(_settings));
scope.Complete();
}
return true; // repeat
@@ -85,10 +85,11 @@ namespace Umbraco.Web.Scheduling
// running on a background task, requires its own (safe) scope
// (GetAuthenticationHeaderValue uses UserService to load the current user, hence requires a database)
// (might not need a scope but we don't know really)
using (ApplicationContext.Current.ScopeProvider.CreateScope())
using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope())
{
//pass custom the authorization header
request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext);
scope.Complete();
}
var result = await wc.SendAsync(request, token);
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http.Formatting;
using umbraco.BusinessLogic.Actions;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
using Umbraco.Web.Models.Trees;
namespace Umbraco.Web.Trees
@@ -27,7 +27,7 @@ namespace Umbraco.Web.Trees
/// <param name="xNode"></param>
protected virtual void OnRenderFolderNode(ref TreeNode treeNode) { }
protected override Models.Trees.TreeNodeCollection GetTreeNodes(string id, System.Net.Http.Formatting.FormDataCollection queryStrings)
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
{
string orgPath = "";
string path = "";
@@ -90,6 +90,64 @@ namespace Umbraco.Web.Trees
}
return nodes;
}
}
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
{
var menu = new MenuItemCollection();
//if root node no need to visit the filesystem so lets just create the menu and return it
if (id == Constants.System.Root.ToInvariantString())
{
//set the default to create
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
//create action
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
//refresh action
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
return menu;
}
string path;
if (string.IsNullOrEmpty(id) == false)
{
var orgPath = System.Web.HttpUtility.UrlDecode(id);
path = IOHelper.MapPath(FilePath + "/" + orgPath);
}
else
{
path = IOHelper.MapPath(FilePath);
}
var dirInfo = new DirectoryInfo(path);
//check if it's a directory
if (dirInfo.Attributes == FileAttributes.Directory)
{
//set the default to create
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
//create action
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
var hasChildren = dirInfo.GetFiles().Length > 0 || dirInfo.GetDirectories().Length > 0;
//We can only delete folders if it doesn't have any children (folders or files)
if (hasChildren == false)
{
//delete action
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)), true);
}
//refresh action
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
}
//if it's not a directory then we only allow to delete the item
else
{
//delete action
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)));
}
return menu;
}
}
}
@@ -1,16 +1,13 @@
using Umbraco.Core;
using Umbraco.Core.IO;
using umbraco.BusinessLogic.Actions;
using Umbraco.Web.Models.Trees;
using System.Net.Http.Formatting;
using Umbraco.Core.Services;
namespace Umbraco.Web.Trees
{
/// <summary>
/// Tree for displaying partial view macros in the developer app
/// </summary>
[Tree(Constants.Applications.Developer, "partialViewMacros", "Partial View Macro Files", sortOrder: 6)]
/// <summary>
/// Tree for displaying partial view macros in the developer app
/// </summary>
[Tree(Constants.Applications.Developer, "partialViewMacros", "Partial View Macro Files", sortOrder: 6)]
public class PartialViewMacrosTreeController : FileSystemTreeController
{
protected override string FilePath
@@ -28,37 +25,6 @@ namespace Umbraco.Web.Trees
get { return "icon-article"; }
}
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
{
var menu = new MenuItemCollection();
if (id == Constants.System.Root.ToInvariantString())
{
//set the default to create
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
//create action
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
//refresh action
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
return menu;
}
if (id.EndsWith(FileSearchPattern.TrimStart("*")) == false)
{
//set the default to create
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
//create action
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
//refresh action
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
}
// TODO: Wire up new delete dialog
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)));
return menu;
}
protected override void OnRenderFileNode(ref TreeNode treeNode)
{
base.OnRenderFileNode(ref treeNode);
@@ -1,9 +1,6 @@
using Umbraco.Core;
using Umbraco.Core.IO;
using umbraco.BusinessLogic.Actions;
using Umbraco.Web.Models.Trees;
using System.Net.Http.Formatting;
using Umbraco.Core.Services;
namespace Umbraco.Web.Trees
{
@@ -27,37 +24,6 @@ namespace Umbraco.Web.Trees
get { return "icon-article"; }
}
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
{
var menu = new MenuItemCollection();
if (id == Constants.System.Root.ToInvariantString())
{
//set the default to create
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
//create action
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
//refresh action
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
return menu;
}
if (id.EndsWith(FileSearchPattern.TrimStart("*")) == false)
{
//set the default to create
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
//create action
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
//refresh action
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
}
// TODO: Wire up new delete dialog
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)));
return menu;
}
protected override void OnRenderFileNode(ref TreeNode treeNode)
{
base.OnRenderFileNode(ref treeNode);
+1 -36
View File
@@ -1,10 +1,6 @@
using System.Linq;
using Umbraco.Core;
using Umbraco.Core;
using Umbraco.Core.IO;
using umbraco.BusinessLogic.Actions;
using Umbraco.Web.Models.Trees;
using System.Net.Http.Formatting;
using Umbraco.Core.Services;
namespace Umbraco.Web.Trees
{
@@ -25,37 +21,6 @@ namespace Umbraco.Web.Trees
get { return "icon-script"; }
}
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
{
var menu = new MenuItemCollection();
if (id == Constants.System.Root.ToInvariantString())
{
//set the default to create
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
//create action
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
//refresh action
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
return menu;
}
if (id.EndsWith(FileSearchPattern.TrimStart("*")) == false)
{
//set the default to create
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
//create action
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
//refresh action
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
}
// TODO: Wire up new delete dialog
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)));
return menu;
}
protected override void OnRenderFolderNode(ref TreeNode treeNode)
{
//TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now.
@@ -57,7 +57,7 @@ namespace UmbracoExamine.DataServices
[Obsolete("This should no longer be used, latest content will be indexed by using the IContentService directly")]
public XDocument GetLatestContentByXPath(string xpath)
{
using (ApplicationContext.Current.ScopeProvider.CreateScope())
using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope())
{
var xmlContent = XDocument.Parse("<content></content>");
var rootContent = _applicationContext.Services.ContentService.GetRootContent();
@@ -67,6 +67,7 @@ namespace UmbracoExamine.DataServices
xmlContent.Root.Add(c.ToDeepXml(_applicationContext.Services.PackagingService));
}
var result = ((IEnumerable)xmlContent.XPathEvaluate(xpath)).Cast<XElement>();
scope.Complete();
return result.ToXDocument();
}
}
@@ -79,9 +80,11 @@ namespace UmbracoExamine.DataServices
/// <returns></returns>
public bool IsProtected(int nodeId, string path)
{
using (ApplicationContext.Current.ScopeProvider.CreateScope())
using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope())
{
return _applicationContext.Services.PublicAccessService.IsProtected(path.EnsureEndsWith("," + nodeId));
var ret = _applicationContext.Services.PublicAccessService.IsProtected(path.EnsureEndsWith("," + nodeId));
scope.Complete();
return ret;
}
}
@@ -92,11 +95,12 @@ namespace UmbracoExamine.DataServices
public IEnumerable<string> GetAllUserPropertyNames()
{
using (ApplicationContext.Current.ScopeProvider.CreateScope())
using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope())
{
try
{
var result = _applicationContext.DatabaseContext.Database.Fetch<string>("select distinct alias from cmsPropertyType order by alias");
scope.Complete();
return result;
}
catch (Exception ex)