Compare commits
200 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d3ad61835a | |||
| ad882ab5be | |||
| 774f333c70 | |||
| 22ff1bcaa0 | |||
| c3be9004f6 | |||
| 11fa015b64 | |||
| 4c9be2cc49 | |||
| 1aad4e50d5 | |||
| 8c8b6984c2 | |||
| f52a84937b | |||
| 7e9f55f517 | |||
| 3e8fb11abd | |||
| 5181988a41 | |||
| 5169310258 | |||
| f987640462 | |||
| 55e97dad74 | |||
| 8c70578764 | |||
| 5cb2dd0036 | |||
| a9713dc6fc | |||
| b32e7d4ccc | |||
| 8d4958e395 | |||
| d2f3e162f1 | |||
| 9883757704 | |||
| 7404e20a19 | |||
| 0310782d7d | |||
| 744ff0cf33 | |||
| afd0018dd6 | |||
| c36aec624b | |||
| c81e14758d | |||
| 1ba86f361a | |||
| 9b33c94437 | |||
| 2a30fd238e | |||
| 3cb4128c0f | |||
| 79958e0fe0 | |||
| f238f9fcea | |||
| 7c545410cc | |||
| e5a21625ea | |||
| dcf5fff3f0 | |||
| 1dc88658e8 | |||
| efd3d6937a | |||
| e17f75a18a | |||
| 6d46c2204d | |||
| 27b9385df5 | |||
| 88c20c7c5f | |||
| bd9adffaa5 | |||
| ea6d81f7ac | |||
| 0ce059d5bd | |||
| 773ef1d168 | |||
| 1f7de81cd2 | |||
| f56bd731b7 | |||
| bee0642417 | |||
| 43abb08e59 | |||
| 17db5709f1 | |||
| 2fc6d9b7df | |||
| e4561851a4 | |||
| c6daf2e111 | |||
| 883366f741 | |||
| cac331adbc | |||
| 18318ecabf | |||
| 6b9ef6df7a | |||
| fcaf78ef29 | |||
| 833b4c552c | |||
| c678b7f6d9 | |||
| e320840a29 | |||
| b664fc5e94 | |||
| b87346bee5 | |||
| 6029306fe9 | |||
| f979792290 | |||
| 5f82fc6e20 | |||
| b1a0ca98ea | |||
| f4f99c2922 | |||
| 6f55b84c65 | |||
| 43f369f96f | |||
| c1b3da869d | |||
| e39158d5e5 | |||
| aed476de0a | |||
| ddd9c8ef7d | |||
| 9e2c7d16a6 | |||
| 44db4e7a48 | |||
| 680d97c630 | |||
| 89347b359b | |||
| 8053cea92d | |||
| f44b158c15 | |||
| 1ff66d0c3a | |||
| ae07f34215 | |||
| aba2c2a79e | |||
| 9f62dfc087 | |||
| a21912dc41 | |||
| 733b4a770f | |||
| 2e097b7e46 | |||
| e1b6997ef7 | |||
| b6b7b9f433 | |||
| 8181f88cb0 | |||
| 3a4865ef7d | |||
| 37f6dbfa6b | |||
| 131fea3ba4 | |||
| b0661218a0 | |||
| 6f1bd3fd71 | |||
| fb99b101d8 | |||
| 91e658c41c | |||
| 0ce7efc978 | |||
| 3b8a2d7d65 | |||
| 61b9ab5c7b | |||
| cfda04b6ed | |||
| 11bdfbba19 | |||
| 9694831638 | |||
| a20844b55b | |||
| 158e40d7f6 | |||
| 6939cc757e | |||
| 2baec8a129 | |||
| 93a1fc1eba | |||
| fe67a09ccb | |||
| 1c4e1db054 | |||
| 5d694a73a3 | |||
| af369fa6c7 | |||
| 901036535d | |||
| 5d6cbee196 | |||
| 9e9ae09ec2 | |||
| e49d14d687 | |||
| 3c98d9d2e1 | |||
| e010ff2a8c | |||
| 8d14a3ef2c | |||
| 2f3d9c9509 | |||
| 6e8f3b6e13 | |||
| b9cbdbd9a7 | |||
| ebf35356a8 | |||
| 00cec3267c | |||
| 4bfb2d219b | |||
| f3ba1fa7ed | |||
| 7eca99e249 | |||
| 80c61110df | |||
| 837bb83d48 | |||
| 054ac78970 | |||
| 8e47f91ba5 | |||
| 31f6bd4c02 | |||
| 3e5744dbb7 | |||
| 5982a868ea | |||
| 69a42e26a9 | |||
| d9ed420ed4 | |||
| 2b3397120f | |||
| cb0d8320e6 | |||
| 53d9b65e64 | |||
| df0fe62fca | |||
| 1f41aa7d81 | |||
| dad77fc506 | |||
| 350f5c88c7 | |||
| 928c0dc535 | |||
| 483a2c7c02 | |||
| cc7a28db65 | |||
| 065e764957 | |||
| 000ba73887 | |||
| 084fde885a | |||
| 08b81d991b | |||
| 4937f87c0d | |||
| 50a5679b5c | |||
| 2c248791e1 | |||
| 6b6a7fe231 | |||
| 6fd197d1bf | |||
| 00aa59c9c6 | |||
| 06254ba462 | |||
| 684cb19a7c | |||
| 9c5da12653 | |||
| 83f229abc6 | |||
| 3405a1f600 | |||
| 8be02ff7b0 | |||
| c371f00315 | |||
| 0ccf798f14 | |||
| 6ca94074f3 | |||
| 8236eee7a6 | |||
| 8bdeca6374 | |||
| e37bbdfcf4 | |||
| dac882ce9f | |||
| dc67d82671 | |||
| c7d9683fc4 | |||
| ed7675bdcd | |||
| 3c7c6b4ad7 | |||
| 838d7975c9 | |||
| 5af9712d51 | |||
| 579c572dd8 | |||
| bbf4c18825 | |||
| e72758e04a | |||
| 9d373c194d | |||
| 856e123ec2 | |||
| 0b2af6b48f | |||
| 9260f943ff | |||
| dcdaa8d62d | |||
| 360665c082 | |||
| ff98b5f321 | |||
| 92378095a2 | |||
| daa0741802 | |||
| eaa8ca5fdc | |||
| 73b2d31298 | |||
| 8676a99b3a | |||
| 0b2cfe956d | |||
| e0e9823a03 | |||
| 3a98d63bf1 | |||
| ac65af772a | |||
| 64f477f90e | |||
| 813741bc3d | |||
| a4f83e0125 |
+2
-1
@@ -139,4 +139,5 @@ src/PrecompiledWeb/*
|
||||
|
||||
build.out/
|
||||
build.tmp/
|
||||
build/Modules/*/temp/
|
||||
build/Modules/*/temp/
|
||||
/src/.idea/*
|
||||
|
||||
@@ -32,13 +32,13 @@
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.8.0.0, 2.0.0)" />
|
||||
<dependency id="AutoMapper" version="[3.3.1, 4.0.0)" />
|
||||
<dependency id="Newtonsoft.Json" version="[10.0.2, 11.0.0)" />
|
||||
<dependency id="Examine" version="[0.1.85, 1.0.0)" />
|
||||
<dependency id="Examine" version="[0.1.89, 1.0.0)" />
|
||||
<dependency id="ImageProcessor" version="[2.5.6, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web" version="[4.8.7, 5.0.0)" />
|
||||
<dependency id="semver" version="[1.1.2, 3.0.0)" />
|
||||
<!-- Markdown can not be updated due to: https://github.com/hey-red/markdownsharp/issues/71#issuecomment-233585487 -->
|
||||
<dependency id="Markdown" version="[1.14.7, 2.0.0)" />
|
||||
<dependency id="System.Threading.Tasks.Dataflow" version="[4.7.0, 5.0.0)" />
|
||||
<dependency id="System.Threading.Tasks.Dataflow" version="[4.7.0, 5.0.0)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<dependencies>
|
||||
<dependency id="UmbracoCms.Core" version="[$version$]" />
|
||||
<dependency id="Newtonsoft.Json" version="[10.0.2, 11.0.0)" />
|
||||
<dependency id="Umbraco.ModelsBuilder" version="[3.0.7, 4.0.0)" />
|
||||
<dependency id="Umbraco.ModelsBuilder" version="[3.0.8, 4.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.2.1, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web.Config" version="[2.3.1, 3.0.0)" />
|
||||
</dependencies>
|
||||
@@ -32,6 +32,7 @@
|
||||
<file src="$BuildTmp$\WebApp\bin\amd64\**" target="UmbracoFiles\bin\amd64" />
|
||||
<file src="$BuildTmp$\WebApp\bin\x86\**" target="UmbracoFiles\bin\x86" />
|
||||
<file src="$BuildTmp$\WebApp\config\splashes\**" target="UmbracoFiles\Config\splashes" />
|
||||
<file src="$BuildTmp$\WebApp\config\BackOfficeTours\**" target="UmbracoFiles\Config\BackOfficeTours" />
|
||||
<file src="$BuildTmp$\WebApp\umbraco\**" target="UmbracoFiles\umbraco" />
|
||||
<file src="$BuildTmp$\WebApp\umbraco_client\**" target="UmbracoFiles\umbraco_client" />
|
||||
<file src="$BuildTmp$\WebApp\Media\Web.config" target="Content\Media\Web.config" />
|
||||
|
||||
@@ -79,14 +79,14 @@
|
||||
<add sortOrder="1" alias="dataTypes" application="developer"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes(sortOrder)" />
|
||||
|
||||
<add application="developer" alias="macros" title="Macros" type="umbraco.loadMacros, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="2"
|
||||
|
||||
<add initialize="true" sortOrder="2" alias="macros" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MacroTreeController, umbraco"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes()" />
|
||||
<add application="developer" alias="relationTypes" title="Relation Types" type="umbraco.loadRelationTypes, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes()" />
|
||||
<add application="developer" alias="xslt" title="XSLT Files" type="umbraco.loadXslt, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="5"
|
||||
<add initialize="true" sortOrder="5" alias="xslt" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.XsltTreeController, umbraco"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes()" />
|
||||
|
||||
|
||||
@@ -49,11 +49,9 @@
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.SqlServerCe.Entity, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
|
||||
+1
-1
@@ -12,4 +12,4 @@ using System.Resources;
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyFileVersion("7.8.0")]
|
||||
[assembly: AssemblyInformationalVersion("7.8.0-beta")]
|
||||
[assembly: AssemblyInformationalVersion("7.8.0-beta003")]
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Threading;
|
||||
using Semver;
|
||||
@@ -261,7 +262,7 @@ namespace Umbraco.Core
|
||||
/// - http://issues.umbraco.org/issue/U4-5728
|
||||
/// - http://issues.umbraco.org/issue/U4-5391
|
||||
/// </remarks>
|
||||
internal string UmbracoApplicationUrl
|
||||
public string UmbracoApplicationUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -269,9 +270,21 @@ namespace Umbraco.Core
|
||||
return _umbracoApplicationUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the url.
|
||||
/// </summary>
|
||||
public void ResetUmbracoApplicationUrl()
|
||||
{
|
||||
_umbracoApplicationUrl = null;
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
internal string _umbracoApplicationUrl;
|
||||
internal string _umbracoApplicationUrl;
|
||||
|
||||
internal List<string> _umbracoApplicationDomains = new List<string>();
|
||||
|
||||
internal string _umbracoApplicationDeploymentId;
|
||||
|
||||
private Lazy<bool> _configured;
|
||||
internal MainDom MainDom { get; private set; }
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a list of types.
|
||||
/// </summary>
|
||||
/// <remarks>Types in the list are, or derive from, or implement, the base type.</remarks>
|
||||
/// <typeparam name="TBase">The base type.</typeparam>
|
||||
internal class TypeList<TBase>
|
||||
{
|
||||
private readonly List<Type> _list = new List<Type>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a type to the list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to add.</typeparam>
|
||||
public void Add<T>()
|
||||
where T : TBase
|
||||
{
|
||||
_list.Add(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a type is in the list.
|
||||
/// </summary>
|
||||
public bool Contains(Type type)
|
||||
{
|
||||
return _list.Contains(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,11 @@
|
||||
/// </summary>
|
||||
public const string Media = "media";
|
||||
|
||||
/// <summary>
|
||||
/// alias for the macro tree.
|
||||
/// </summary>
|
||||
public const string Macros = "macros";
|
||||
|
||||
/// <summary>
|
||||
/// alias for the datatype tree.
|
||||
/// </summary>
|
||||
|
||||
@@ -76,260 +76,257 @@ namespace Umbraco.Core.Events
|
||||
{
|
||||
if (_events == null)
|
||||
return Enumerable.Empty<IEventDefinition>();
|
||||
|
||||
|
||||
IReadOnlyList<IEventDefinition> events;
|
||||
switch (filter)
|
||||
{
|
||||
case EventDefinitionFilter.All:
|
||||
return FilterSupersededAndUpdateToLatestEntity(_events);
|
||||
events = _events;
|
||||
break;
|
||||
case EventDefinitionFilter.FirstIn:
|
||||
var l1 = new OrderedHashSet<IEventDefinition>();
|
||||
foreach (var e in _events)
|
||||
{
|
||||
l1.Add(e);
|
||||
}
|
||||
return FilterSupersededAndUpdateToLatestEntity(l1);
|
||||
events = l1;
|
||||
break;
|
||||
case EventDefinitionFilter.LastIn:
|
||||
var l2 = new OrderedHashSet<IEventDefinition>(keepOldest: false);
|
||||
foreach (var e in _events)
|
||||
{
|
||||
l2.Add(e);
|
||||
}
|
||||
return FilterSupersededAndUpdateToLatestEntity(l2);
|
||||
events = l2;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("filter", filter, null);
|
||||
}
|
||||
|
||||
return FilterSupersededAndUpdateToLatestEntity(events);
|
||||
}
|
||||
|
||||
private class EventDefinitionTypeData
|
||||
|
||||
private class EventDefinitionInfos
|
||||
{
|
||||
public IEventDefinition EventDefinition { get; set; }
|
||||
public Type EventArgType { get; set; }
|
||||
public SupersedeEventAttribute[] SupersedeAttributes { get; set; }
|
||||
public Type[] SupersedeTypes { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will iterate over the events (latest first) and filter out any events or entities in event args that are included
|
||||
/// in more recent events that Supersede previous ones. For example, If an Entity has been Saved and then Deleted, we don't want
|
||||
/// to raise the Saved event (well actually we just don't want to include it in the args for that saved event)
|
||||
/// </summary>
|
||||
/// <param name="events"></param>
|
||||
/// <returns></returns>
|
||||
private static IEnumerable<IEventDefinition> FilterSupersededAndUpdateToLatestEntity(IReadOnlyList<IEventDefinition> events)
|
||||
|
||||
// fixme
|
||||
// this is way too convoluted, the superceede attribute is used only on DeleteEventargs to specify
|
||||
// that it superceeds save, publish, move and copy - BUT - publish event args is also used for
|
||||
// unpublishing and should NOT be superceeded - so really it should not be managed at event args
|
||||
// level but at event level
|
||||
//
|
||||
// what we want is:
|
||||
// if an entity is deleted, then all Saved, Moved, Copied, Published events prior to this should
|
||||
// not trigger for the entity - and even though, does it make any sense? making a copy of an entity
|
||||
// should ... trigger?
|
||||
//
|
||||
// not going to refactor it all - we probably want to *always* trigger event but tell people that
|
||||
// due to scopes, they should not expected eg a saved entity to still be around - however, now,
|
||||
// going to write a ugly condition to deal with U4-10764
|
||||
|
||||
// iterates over the events (latest first) and filter out any events or entities in event args that are included
|
||||
// in more recent events that Supersede previous ones. For example, If an Entity has been Saved and then Deleted, we don't want
|
||||
// to raise the Saved event (well actually we just don't want to include it in the args for that saved event)
|
||||
internal static IEnumerable<IEventDefinition> FilterSupersededAndUpdateToLatestEntity(IReadOnlyList<IEventDefinition> events)
|
||||
{
|
||||
//used to keep the 'latest' entity and associated event definition data
|
||||
var allEntities = new List<Tuple<IEntity, EventDefinitionTypeData>>();
|
||||
|
||||
//tracks all CancellableObjectEventArgs instances in the events which is the only type of args we can work with
|
||||
var cancelableArgs = new List<CancellableObjectEventArgs>();
|
||||
// keeps the 'latest' entity and associated event data
|
||||
var entities = new List<Tuple<IEntity, EventDefinitionInfos>>();
|
||||
|
||||
// collects the event definitions
|
||||
// collects the arguments in result, that require their entities to be updated
|
||||
var result = new List<IEventDefinition>();
|
||||
var resultArgs = new List<CancellableObjectEventArgs>();
|
||||
|
||||
//This will eagerly load all of the event arg types and their attributes so we don't have to continuously look this data up
|
||||
var allArgTypesWithAttributes = events.Select(x => x.Args.GetType())
|
||||
// eagerly fetch superceeded arg types for each arg type
|
||||
var argTypeSuperceeding = events.Select(x => x.Args.GetType())
|
||||
.Distinct()
|
||||
.ToDictionary(x => x, x => x.GetCustomAttributes<SupersedeEventAttribute>(false).ToArray());
|
||||
|
||||
//Iterate all events and collect the actual entities in them and relates them to their corresponding EventDefinitionTypeData
|
||||
//we'll process the list in reverse because events are added in the order they are raised and we want to filter out
|
||||
//any entities from event args that are not longer relevant
|
||||
//(i.e. if an item is Deleted after it's Saved, we won't include the item in the Saved args)
|
||||
.ToDictionary(x => x, x => x.GetCustomAttributes<SupersedeEventAttribute>(false).Select(y => y.SupersededEventArgsType).ToArray());
|
||||
|
||||
// iterate over all events and filter
|
||||
//
|
||||
// process the list in reverse, because events are added in the order they are raised and we want to keep
|
||||
// the latest (most recent) entities and filter out what is not relevant anymore (too old), eg if an entity
|
||||
// is Deleted after being Saved, we want to filter out the Saved event
|
||||
for (var index = events.Count - 1; index >= 0; index--)
|
||||
{
|
||||
var eventDefinition = events[index];
|
||||
var def = events[index];
|
||||
|
||||
var argType = eventDefinition.Args.GetType();
|
||||
var attributes = allArgTypesWithAttributes[eventDefinition.Args.GetType()];
|
||||
|
||||
var meta = new EventDefinitionTypeData
|
||||
var infos = new EventDefinitionInfos
|
||||
{
|
||||
EventDefinition = eventDefinition,
|
||||
EventArgType = argType,
|
||||
SupersedeAttributes = attributes
|
||||
EventDefinition = def,
|
||||
SupersedeTypes = argTypeSuperceeding[def.Args.GetType()]
|
||||
};
|
||||
|
||||
var args = eventDefinition.Args as CancellableObjectEventArgs;
|
||||
if (args != null)
|
||||
var args = def.Args as CancellableObjectEventArgs;
|
||||
if (args == null)
|
||||
{
|
||||
var list = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);
|
||||
|
||||
if (list == null)
|
||||
// not a cancellable event arg, include event definition in result
|
||||
result.Add(def);
|
||||
}
|
||||
else
|
||||
{
|
||||
// event object can either be a single object or an enumerable of objects
|
||||
// try to get as an enumerable, get null if it's not
|
||||
var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);
|
||||
if (eventObjects == null)
|
||||
{
|
||||
//extract the event object
|
||||
var obj = args.EventObject as IEntity;
|
||||
if (obj != null)
|
||||
// single object, cast as an IEntity
|
||||
// if cannot cast, cannot filter, nothing - just include event definition in result
|
||||
var eventEntity = args.EventObject as IEntity;
|
||||
if (eventEntity == null)
|
||||
{
|
||||
//Now check if this entity already exists in other event args that supersede this current event arg type
|
||||
if (IsFiltered(obj, meta, allEntities) == false)
|
||||
{
|
||||
//if it's not filtered we can adde these args to the response
|
||||
cancelableArgs.Add(args);
|
||||
result.Add(eventDefinition);
|
||||
//track the entity
|
||||
allEntities.Add(Tuple.Create(obj, meta));
|
||||
}
|
||||
result.Add(def);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
// look for this entity in superceding event args
|
||||
// found = must be removed (ie not added), else track
|
||||
if (IsSuperceeded(eventEntity, infos, entities) == false)
|
||||
{
|
||||
//Can't retrieve the entity so cant' filter or inspect, just add to the output
|
||||
result.Add(eventDefinition);
|
||||
// track
|
||||
entities.Add(Tuple.Create(eventEntity, infos));
|
||||
|
||||
// track result arguments
|
||||
// include event definition in result
|
||||
resultArgs.Add(args);
|
||||
result.Add(def);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// enumerable of objects
|
||||
var toRemove = new List<IEntity>();
|
||||
foreach (var entity in list)
|
||||
foreach (var eventObject in eventObjects)
|
||||
{
|
||||
//extract the event object
|
||||
var obj = entity as IEntity;
|
||||
if (obj != null)
|
||||
{
|
||||
//Now check if this entity already exists in other event args that supersede this current event arg type
|
||||
if (IsFiltered(obj, meta, allEntities))
|
||||
{
|
||||
//track it to be removed
|
||||
toRemove.Add(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
//track the entity, it's not filtered
|
||||
allEntities.Add(Tuple.Create(obj, meta));
|
||||
}
|
||||
}
|
||||
// extract the event object, cast as an IEntity
|
||||
// if cannot cast, cannot filter, nothing to do - just leave it in the list & continue
|
||||
var eventEntity = eventObject as IEntity;
|
||||
if (eventEntity == null)
|
||||
continue;
|
||||
|
||||
// look for this entity in superceding event args
|
||||
// found = must be removed, else track
|
||||
if (IsSuperceeded(eventEntity, infos, entities))
|
||||
toRemove.Add(eventEntity);
|
||||
else
|
||||
{
|
||||
//we don't need to do anything here, we can't cast to IEntity so we cannot filter, so it will just remain in the list
|
||||
}
|
||||
entities.Add(Tuple.Create(eventEntity, infos));
|
||||
}
|
||||
|
||||
//remove anything that has been filtered
|
||||
// remove superceded entities
|
||||
foreach (var entity in toRemove)
|
||||
{
|
||||
list.Remove(entity);
|
||||
}
|
||||
eventObjects.Remove(entity);
|
||||
|
||||
//track the event and include in the response if there's still entities remaining in the list
|
||||
if (list.Count > 0)
|
||||
// if there are still entities in the list, keep the event definition
|
||||
if (eventObjects.Count > 0)
|
||||
{
|
||||
if (toRemove.Count > 0)
|
||||
{
|
||||
//re-assign if the items have changed
|
||||
args.EventObject = list;
|
||||
// re-assign if changed
|
||||
args.EventObject = eventObjects;
|
||||
}
|
||||
cancelableArgs.Add(args);
|
||||
result.Add(eventDefinition);
|
||||
|
||||
// track result arguments
|
||||
// include event definition in result
|
||||
resultArgs.Add(args);
|
||||
result.Add(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//it's not a cancelable event arg so we just include it in the result
|
||||
result.Add(eventDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
//Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args
|
||||
UpdateToLatestEntities(allEntities, cancelableArgs);
|
||||
// go over all args in result, and update them with the latest instanceof each entity
|
||||
UpdateToLatestEntities(entities, resultArgs);
|
||||
|
||||
//we need to reverse the result since we've been adding by latest added events first!
|
||||
// reverse, since we processed the list in reverse
|
||||
result.Reverse();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void UpdateToLatestEntities(IEnumerable<Tuple<IEntity, EventDefinitionTypeData>> allEntities, IEnumerable<CancellableObjectEventArgs> cancelableArgs)
|
||||
// edits event args to use the latest instance of each entity
|
||||
private static void UpdateToLatestEntities(IEnumerable<Tuple<IEntity, EventDefinitionInfos>> entities, IEnumerable<CancellableObjectEventArgs> args)
|
||||
{
|
||||
//Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args
|
||||
|
||||
// get the latest entities
|
||||
// ordered hash set + keepOldest will keep the latest inserted entity (in case of duplicates)
|
||||
var latestEntities = new OrderedHashSet<IEntity>(keepOldest: true);
|
||||
foreach (var entity in allEntities.OrderByDescending(entity => entity.Item1.UpdateDate))
|
||||
{
|
||||
foreach (var entity in entities.OrderByDescending(entity => entity.Item1.UpdateDate))
|
||||
latestEntities.Add(entity.Item1);
|
||||
}
|
||||
|
||||
foreach (var args in cancelableArgs)
|
||||
foreach (var arg in args)
|
||||
{
|
||||
var list = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);
|
||||
if (list == null)
|
||||
// event object can either be a single object or an enumerable of objects
|
||||
// try to get as an enumerable, get null if it's not
|
||||
var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(arg.EventObject);
|
||||
if (eventObjects == null)
|
||||
{
|
||||
//try to find the args entity in the latest entity - based on the equality operators, this will
|
||||
//match by Id since that is the default equality checker for IEntity. If one is found, than it is
|
||||
//the most recent entity instance so update the args with that instance so we don't emit a stale instance.
|
||||
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, args.EventObject));
|
||||
// single object
|
||||
// look for a more recent entity for that object, and replace if any
|
||||
// works by "equalling" entities ie the more recent one "equals" this one (though different object)
|
||||
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, arg.EventObject));
|
||||
if (foundEntity != null)
|
||||
{
|
||||
args.EventObject = foundEntity;
|
||||
}
|
||||
arg.EventObject = foundEntity;
|
||||
}
|
||||
else
|
||||
{
|
||||
// enumerable of objects
|
||||
// same as above but for each object
|
||||
var updated = false;
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
for (var i = 0; i < eventObjects.Count; i++)
|
||||
{
|
||||
//try to find the args entity in the latest entity - based on the equality operators, this will
|
||||
//match by Id since that is the default equality checker for IEntity. If one is found, than it is
|
||||
//the most recent entity instance so update the args with that instance so we don't emit a stale instance.
|
||||
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, list[i]));
|
||||
if (foundEntity != null)
|
||||
{
|
||||
list[i] = foundEntity;
|
||||
updated = true;
|
||||
}
|
||||
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, eventObjects[i]));
|
||||
if (foundEntity == null) continue;
|
||||
eventObjects[i] = foundEntity;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (updated)
|
||||
{
|
||||
args.EventObject = list;
|
||||
}
|
||||
arg.EventObject = eventObjects;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will check against all of the processed entity/events (allEntities) to see if this entity already exists in
|
||||
/// event args that supersede the event args being passed in and if so returns true.
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="eventDef"></param>
|
||||
/// <param name="allEntities"></param>
|
||||
/// <returns></returns>
|
||||
private static bool IsFiltered(
|
||||
IEntity entity,
|
||||
EventDefinitionTypeData eventDef,
|
||||
List<Tuple<IEntity, EventDefinitionTypeData>> allEntities)
|
||||
// determines if a given entity, appearing in a given event definition, should be filtered out,
|
||||
// considering the entities that have already been visited - an entity is filtered out if it
|
||||
// appears in another even definition, which superceedes this event definition.
|
||||
private static bool IsSuperceeded(IEntity entity, EventDefinitionInfos infos, List<Tuple<IEntity, EventDefinitionInfos>> entities)
|
||||
{
|
||||
var argType = eventDef.EventDefinition.Args.GetType();
|
||||
//var argType = meta.EventArgsType;
|
||||
var argType = infos.EventDefinition.Args.GetType();
|
||||
|
||||
//check if the entity is found in any processed event data that could possible supersede this one
|
||||
var foundByEntity = allEntities
|
||||
.Where(x => x.Item2.SupersedeAttributes.Length > 0
|
||||
//if it's the same arg type than it cannot supersede
|
||||
&& x.Item2.EventArgType != argType
|
||||
&& Equals(x.Item1, entity))
|
||||
// look for other instances of the same entity, coming from an event args that supercedes other event args,
|
||||
// ie is marked with the attribute, and is not this event args (cannot supersede itself)
|
||||
var superceeding = entities
|
||||
.Where(x => x.Item2.SupersedeTypes.Length > 0 // has the attribute
|
||||
&& x.Item2.EventDefinition.Args.GetType() != argType // is not the same
|
||||
&& Equals(x.Item1, entity)) // same entity
|
||||
.ToArray();
|
||||
|
||||
//no args have been processed with this entity so it should not be filtered
|
||||
if (foundByEntity.Length == 0)
|
||||
// first time we see this entity = not filtered
|
||||
if (superceeding.Length == 0)
|
||||
return false;
|
||||
|
||||
// fixme see notes above
|
||||
// delete event args does NOT superceedes 'unpublished' event
|
||||
if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "UnPublished")
|
||||
return false;
|
||||
|
||||
// found occurences, need to determine if this event args is superceded
|
||||
if (argType.IsGenericType)
|
||||
{
|
||||
var supercededBy = foundByEntity
|
||||
.FirstOrDefault(x =>
|
||||
x.Item2.SupersedeAttributes.Any(y =>
|
||||
//if the attribute type is a generic type def then compare with the generic type def of the event arg
|
||||
(y.SupersededEventArgsType.IsGenericTypeDefinition && y.SupersededEventArgsType == argType.GetGenericTypeDefinition())
|
||||
//if the attribute type is not a generic type def then compare with the normal type of the event arg
|
||||
|| (y.SupersededEventArgsType.IsGenericTypeDefinition == false && y.SupersededEventArgsType == argType)));
|
||||
// generic, must compare type arguments
|
||||
var supercededBy = superceeding.FirstOrDefault(x =>
|
||||
x.Item2.SupersedeTypes.Any(y =>
|
||||
// superceeding a generic type which has the same generic type definition
|
||||
// fixme no matter the generic type parameters? could be different?
|
||||
y.IsGenericTypeDefinition && y == argType.GetGenericTypeDefinition()
|
||||
// or superceeding a non-generic type which is ... fixme how is this ever possible? argType *is* generic?
|
||||
|| y.IsGenericTypeDefinition == false && y == argType));
|
||||
return supercededBy != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var supercededBy = foundByEntity
|
||||
.FirstOrDefault(x =>
|
||||
x.Item2.SupersedeAttributes.Any(y =>
|
||||
//since the event arg type is not a generic type, then we just compare type 1:1
|
||||
y.SupersededEventArgsType == argType));
|
||||
// non-generic, can compare types 1:1
|
||||
var supercededBy = superceeding.FirstOrDefault(x =>
|
||||
x.Item2.SupersedeTypes.Any(y => y == argType));
|
||||
return supercededBy != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
@@ -107,7 +108,7 @@ namespace Umbraco.Core.IO
|
||||
|
||||
try
|
||||
{
|
||||
Directory.Delete(fullPath, recursive);
|
||||
WithRetry(() => Directory.Delete(fullPath, recursive));
|
||||
}
|
||||
catch (DirectoryNotFoundException ex)
|
||||
{
|
||||
@@ -225,8 +226,8 @@ namespace Umbraco.Core.IO
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(fullPath);
|
||||
{
|
||||
WithRetry(() => File.Delete(fullPath));
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
@@ -378,7 +379,7 @@ namespace Umbraco.Core.IO
|
||||
{
|
||||
if (overrideIfExists == false)
|
||||
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
|
||||
File.Delete(fullPath);
|
||||
WithRetry(() => File.Delete(fullPath));
|
||||
}
|
||||
|
||||
var directory = Path.GetDirectoryName(fullPath);
|
||||
@@ -386,9 +387,9 @@ namespace Umbraco.Core.IO
|
||||
Directory.CreateDirectory(directory); // ensure it exists
|
||||
|
||||
if (copy)
|
||||
File.Copy(physicalPath, fullPath);
|
||||
WithRetry(() => File.Copy(physicalPath, fullPath));
|
||||
else
|
||||
File.Move(physicalPath, fullPath);
|
||||
WithRetry(() => File.Move(physicalPath, fullPath));
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
@@ -417,6 +418,35 @@ namespace Umbraco.Core.IO
|
||||
return path;
|
||||
}
|
||||
|
||||
protected void WithRetry(Action action)
|
||||
{
|
||||
// 10 times 100ms is 1s
|
||||
const int count = 10;
|
||||
const int pausems = 100;
|
||||
|
||||
for (var i = 0;; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
break; // done
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// if it's not *exactly* IOException then it could be
|
||||
// some inherited exception such as FileNotFoundException,
|
||||
// and then we don't want to retry
|
||||
if (e.GetType() != typeof(IOException)) throw;
|
||||
|
||||
// if we have tried enough, throw, else swallow
|
||||
// the exception and retry after a pause
|
||||
if (i == count) throw;
|
||||
}
|
||||
|
||||
Thread.Sleep(pausems);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using log4net.Appender;
|
||||
using log4net.Util;
|
||||
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// This class will do the exact same thing as the RollingFileAppender that comes from log4net
|
||||
/// With the extension, that it is able to do automatic cleanup of the logfiles in the directory where logging happens
|
||||
///
|
||||
/// By specifying the properties MaxLogFileDays and BaseFilePattern, the files will automaticly get deleted when
|
||||
/// the logger is configured(typically when the app starts). To utilize this appender swap out the type of the rollingFile appender
|
||||
/// that ships with Umbraco, to be Umbraco.Core.Logging.RollingFileCleanupAppender, and add the maxLogFileDays and baseFilePattern elements
|
||||
/// to the configuration i.e.:
|
||||
///
|
||||
/// <example>
|
||||
/// <appender name="rollingFile" type="Log4netAwesomeness.CustomRollingFileAppender, Log4netAwesomeness">
|
||||
/// <file type="log4net.Util.PatternString" value="App_Data\Logs\UmbracoTraceLog.%property{log4net:HostName}.txt" />
|
||||
/// <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
|
||||
/// <appendToFile value="true" />
|
||||
/// <rollingStyle value="Date" />
|
||||
/// <maximumFileSize value="5MB" />
|
||||
/// <maxLogFileDays value="5"/>
|
||||
/// <basefilePattern value="UmbracoTraceLog.*.txt.*"/>
|
||||
/// <layout type="log4net.Layout.PatternLayout">
|
||||
/// <conversionPattern value=" %date [P%property{processId}/D%property{appDomainId}/T%thread] %-5level %logger - %message%newline" />
|
||||
/// </layout>
|
||||
/// <layout type="log4net.Layout.PatternLayout">
|
||||
/// <conversionPattern value=" %date [P%property{processId}/D%property{appDomainId}/T%thread] %-5level %logger - %message%newline" />
|
||||
/// </layout>
|
||||
/// <encoding value="utf-8" />
|
||||
/// </appender>
|
||||
/// </example>
|
||||
/// </summary>
|
||||
public class RollingFileCleanupAppender : RollingFileAppender
|
||||
{
|
||||
public int MaxLogFileDays { get; set; }
|
||||
public string BaseFilePattern { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This override will delete logs older than the specified amount of days
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="append"></param>
|
||||
protected override void OpenFile(string fileName, bool append)
|
||||
{
|
||||
bool cleanup = true;
|
||||
// Validate settings and input
|
||||
if (MaxLogFileDays <= 0)
|
||||
{
|
||||
LogLog.Warn(typeof(RollingFileCleanupAppender), "Parameter 'MaxLogFileDays' needs to be a positive integer, aborting cleanup");
|
||||
cleanup = false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(BaseFilePattern))
|
||||
{
|
||||
LogLog.Warn(typeof(RollingFileCleanupAppender), "Parameter 'BaseFilePattern' is empty, aborting cleanup");
|
||||
cleanup = false;
|
||||
}
|
||||
// grab the directory we are logging to, as this is were we will search for older logfiles
|
||||
var logFolder = Path.GetDirectoryName(fileName);
|
||||
if (Directory.Exists(logFolder) == false)
|
||||
{
|
||||
LogLog.Warn(typeof(RollingFileCleanupAppender), string.Format("Directory '{0}' for logfiles does not exist, aborting cleanup", logFolder));
|
||||
cleanup = false;
|
||||
}
|
||||
// If everything is validated, we can do the actual cleanup
|
||||
if (cleanup)
|
||||
{
|
||||
Cleanup(logFolder);
|
||||
}
|
||||
|
||||
base.OpenFile(fileName, append);
|
||||
}
|
||||
|
||||
private void Cleanup(string directoryPath)
|
||||
{
|
||||
// only take files that matches the pattern we are using i.e. UmbracoTraceLog.*.txt.*
|
||||
string[] logFiles = Directory.GetFiles(directoryPath, BaseFilePattern);
|
||||
LogLog.Debug(typeof(RollingFileCleanupAppender), string.Format("Found {0} files that matches the baseFilePattern: '{1}'", logFiles.Length, BaseFilePattern));
|
||||
|
||||
foreach (var logFile in logFiles)
|
||||
{
|
||||
DateTime lastAccessTime = System.IO.File.GetLastWriteTimeUtc(logFile);
|
||||
// take the value from the config file
|
||||
if (lastAccessTime < DateTime.Now.AddDays(-MaxLogFileDays))
|
||||
{
|
||||
LogLog.Debug(typeof(RollingFileCleanupAppender), string.Format("Deleting file {0} as its lastAccessTime is older than {1} days speficied by MaxLogFileDays", logFile, MaxLogFileDays));
|
||||
base.DeleteFile(logFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,11 +188,11 @@ namespace Umbraco.Core.Models
|
||||
Language,
|
||||
|
||||
/// <summary>
|
||||
/// Document
|
||||
/// Document Blueprint
|
||||
/// </summary>
|
||||
[UmbracoObjectType(Constants.ObjectTypes.DocumentBlueprint, typeof(IContent))]
|
||||
[FriendlyName("DocumentBlueprint")]
|
||||
[UmbracoUdiType(Constants.UdiEntityType.DocumentBluePrint)]
|
||||
[UmbracoUdiType(Constants.UdiEntityType.DocumentBlueprint)]
|
||||
DocumentBlueprint,
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -183,17 +183,17 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
|
||||
public IEnumerable<IMemberGroup> GetMemberGroupsForMember(string username)
|
||||
{
|
||||
var sql = new Sql()
|
||||
{
|
||||
var sql = new Sql()
|
||||
.Select("un.*")
|
||||
.From("umbracoNode AS un")
|
||||
.InnerJoin("cmsMember2MemberGroup")
|
||||
.On("un.id = cmsMember2MemberGroup.MemberGroup")
|
||||
.LeftJoin("(SELECT umbracoNode.id, cmsMember.LoginName FROM umbracoNode INNER JOIN cmsMember ON umbracoNode.id = cmsMember.nodeId) AS member")
|
||||
.On("member.id = cmsMember2MemberGroup.Member")
|
||||
.Where("un.nodeObjectType=@objectType", new {objectType = NodeObjectTypeId })
|
||||
.Where("member.LoginName=@loginName", new {loginName = username});
|
||||
|
||||
.On("cmsMember2MemberGroup.MemberGroup = un.id")
|
||||
.InnerJoin("cmsMember")
|
||||
.On("cmsMember.nodeId = cmsMember2MemberGroup.Member")
|
||||
.Where("un.nodeObjectType=@objectType", new { objectType = NodeObjectTypeId })
|
||||
.Where("cmsMember.LoginName=@loginName", new { loginName = username });
|
||||
|
||||
return Database.Fetch<NodeDto>(sql)
|
||||
.DistinctBy(dto => dto.NodeId)
|
||||
.Select(x => _modelFactory.BuildEntity(x));
|
||||
|
||||
@@ -192,11 +192,11 @@ ORDER BY colName";
|
||||
|
||||
return new Dictionary<UserState, int>
|
||||
{
|
||||
{UserState.All, result[0].num},
|
||||
{UserState.Active, result[1].num},
|
||||
{UserState.Disabled, result[2].num},
|
||||
{UserState.LockedOut, result[3].num},
|
||||
{UserState.Invited, result[4].num}
|
||||
{UserState.All, (int)result[0].num},
|
||||
{UserState.Active, (int)result[1].num},
|
||||
{UserState.Disabled, (int)result[2].num},
|
||||
{UserState.LockedOut, (int)result[3].num},
|
||||
{UserState.Invited, (int)result[4].num}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Security.Permissions;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Umbraco.Core")]
|
||||
@@ -12,8 +10,8 @@ using System.Security.Permissions;
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyProduct("Umbraco CMS")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
@@ -50,6 +48,8 @@ using System.Security.Permissions;
|
||||
[assembly: InternalsVisibleTo("Umbraco.Forms.Core.Providers")]
|
||||
[assembly: InternalsVisibleTo("Umbraco.Forms.Web")]
|
||||
|
||||
[assembly: InternalsVisibleTo("Umbraco.Headless")]
|
||||
|
||||
//allow this to be mocked in our unit tests
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
|
||||
|
||||
@@ -993,8 +993,7 @@ namespace Umbraco.Core.Services
|
||||
FROM umbracoNode
|
||||
JOIN cmsDocument ON umbracoNode.id=cmsDocument.nodeId AND cmsDocument.published=@0
|
||||
WHERE umbracoNode.trashed=@1 AND umbracoNode.id IN (@2)",
|
||||
true, false, ids);
|
||||
Console.WriteLine(sql.SQL);
|
||||
true, false, ids);
|
||||
var x = uow.Database.Fetch<int>(sql);
|
||||
return ids.Length == x.Count;
|
||||
}
|
||||
@@ -2009,6 +2008,96 @@ namespace Umbraco.Core.Services
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts a collection of <see cref="IContent"/> objects by updating the SortOrder according
|
||||
/// to the ordering of node Ids passed in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Using this method will ensure that the Published-state is maintained upon sorting
|
||||
/// so the cache is updated accordingly - as needed.
|
||||
/// </remarks>
|
||||
/// <param name="ids"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="raiseEvents"></param>
|
||||
/// <returns>True if sorting succeeded, otherwise False</returns>
|
||||
public bool Sort(int[] ids, int userId = 0, bool raiseEvents = true)
|
||||
{
|
||||
var shouldBePublished = new List<IContent>();
|
||||
var shouldBeSaved = new List<IContent>();
|
||||
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
var allContent = GetByIds(ids).ToDictionary(x => x.Id, x => x);
|
||||
var items = ids.Select(x => allContent[x]);
|
||||
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
var asArray = items.ToArray();
|
||||
var saveEventArgs = new SaveEventArgs<IContent>(asArray);
|
||||
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
|
||||
{
|
||||
uow.Commit();
|
||||
return false;
|
||||
}
|
||||
|
||||
var repository = RepositoryFactory.CreateContentRepository(uow);
|
||||
|
||||
var i = 0;
|
||||
foreach (var content in asArray)
|
||||
{
|
||||
//If the current sort order equals that of the content
|
||||
//we don't need to update it, so just increment the sort order
|
||||
//and continue.
|
||||
if (content.SortOrder == i)
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
content.SortOrder = i;
|
||||
content.WriterId = userId;
|
||||
i++;
|
||||
|
||||
if (content.Published)
|
||||
{
|
||||
//TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable!
|
||||
var published = _publishingStrategy.Publish(uow, content, userId).Success;
|
||||
shouldBePublished.Add(content);
|
||||
}
|
||||
else
|
||||
shouldBeSaved.Add(content);
|
||||
|
||||
repository.AddOrUpdate(content);
|
||||
//add or update a preview
|
||||
repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
|
||||
}
|
||||
|
||||
foreach (var content in shouldBePublished)
|
||||
{
|
||||
//Create and Save ContentXml DTO
|
||||
repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
|
||||
}
|
||||
|
||||
if (raiseEvents)
|
||||
{
|
||||
saveEventArgs.CanCancel = false;
|
||||
uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
|
||||
}
|
||||
|
||||
if (shouldBePublished.Any())
|
||||
{
|
||||
//TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable!
|
||||
_publishingStrategy.PublishingFinalized(uow, shouldBePublished, false);
|
||||
}
|
||||
|
||||
Audit(uow, AuditType.Sort, "Sorting content performed by user", userId, 0);
|
||||
uow.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public IEnumerable<IContent> GetBlueprintsForContentTypes(params int[] documentTypeIds)
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
@@ -2748,4 +2837,4 @@ namespace Umbraco.Core.Services
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@ namespace Umbraco.Core.Services
|
||||
repo.Delete(container);
|
||||
uow.Commit();
|
||||
deleteEventArgs.CanCancel = false;
|
||||
uow.Events.Dispatch(DeletedMediaTypeContainer, this, deleteEventArgs);
|
||||
uow.Events.Dispatch(DeletedMediaTypeContainer, this, deleteEventArgs, "DeletedMediaTypeContainer");
|
||||
|
||||
return OperationStatus.Success(evtMsgs);
|
||||
//TODO: Audit trail ?
|
||||
|
||||
@@ -642,7 +642,21 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="raiseEvents"></param>
|
||||
/// <returns>True if sorting succeeded, otherwise False</returns>
|
||||
bool Sort(IEnumerable<IContent> items, int userId = 0, bool raiseEvents = true);
|
||||
bool Sort(IEnumerable<IContent> items, int userId = 0, bool raiseEvents = true);
|
||||
|
||||
/// <summary>
|
||||
/// Sorts a collection of <see cref="IContent"/> objects by updating the SortOrder according
|
||||
/// to the ordering of node Ids passed in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Using this method will ensure that the Published-state is maintained upon sorting
|
||||
/// so the cache is updated accordingly - as needed.
|
||||
/// </remarks>
|
||||
/// <param name="ids"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="raiseEvents"></param>
|
||||
/// <returns>True if sorting succeeded, otherwise False</returns>
|
||||
bool Sort(int[] ids, int userId = 0, bool raiseEvents = true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of the current content as an <see cref="IContent"/> item.
|
||||
|
||||
@@ -986,7 +986,11 @@ namespace Umbraco.Core.Services
|
||||
/// <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)
|
||||
{
|
||||
{
|
||||
//trimming username and email to make sure we have no trailing space
|
||||
entity.Username = entity.Username.Trim();
|
||||
entity.Email = entity.Email.Trim();
|
||||
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IMember>(entity);
|
||||
|
||||
@@ -228,9 +228,9 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an <see cref="IUser"/>
|
||||
/// Disables an <see cref="IUser"/>
|
||||
/// </summary>
|
||||
/// <param name="membershipUser"><see cref="IUser"/> to Delete</param>
|
||||
/// <param name="membershipUser"><see cref="IUser"/> to disable</param>
|
||||
public void Delete(IUser membershipUser)
|
||||
{
|
||||
//disable
|
||||
@@ -753,7 +753,7 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="id">Id of the User to retrieve</param>
|
||||
/// <returns><see cref="IProfile"/></returns>
|
||||
public IProfile GetProfileById(int id)
|
||||
{
|
||||
{
|
||||
//This is called a TON. Go get the full user from cache which should already be IProfile
|
||||
var fullUser = GetUserById(id);
|
||||
var asProfile = fullUser as IProfile;
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Web;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.ObjectResolution;
|
||||
|
||||
namespace Umbraco.Core.Sync
|
||||
@@ -46,8 +47,20 @@ namespace Umbraco.Core.Sync
|
||||
// settings: for unit tests only
|
||||
internal static void EnsureApplicationUrl(ApplicationContext appContext, HttpRequestBase request = null, IUmbracoSettingsSection settings = null)
|
||||
{
|
||||
bool newApplicationUrl = false;
|
||||
if (request != null)
|
||||
{
|
||||
var applicationUrl = GetApplicationUrlFromRequest(request);
|
||||
newApplicationUrl = !appContext._umbracoApplicationDomains.Contains(applicationUrl);
|
||||
if (newApplicationUrl)
|
||||
{
|
||||
appContext._umbracoApplicationDomains.Add(applicationUrl);
|
||||
LogHelper.Info(typeof(ApplicationUrlHelper), $"New ApplicationUrl detected: {applicationUrl}");
|
||||
}
|
||||
}
|
||||
|
||||
// if initialized, return
|
||||
if (appContext._umbracoApplicationUrl != null) return;
|
||||
if (appContext._umbracoApplicationUrl != null && !newApplicationUrl) return;
|
||||
|
||||
var logger = appContext.ProfilingLogger.Logger;
|
||||
|
||||
@@ -132,6 +145,12 @@ namespace Umbraco.Core.Sync
|
||||
{
|
||||
var logger = appContext.ProfilingLogger.Logger;
|
||||
|
||||
appContext._umbracoApplicationUrl = GetApplicationUrlFromRequest(request);
|
||||
logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (UmbracoModule request)");
|
||||
}
|
||||
|
||||
private static string GetApplicationUrlFromRequest(HttpRequestBase request)
|
||||
{
|
||||
// if (HTTP and SSL not required) or (HTTPS and SSL required),
|
||||
// use ports from request
|
||||
// otherwise,
|
||||
@@ -147,8 +166,7 @@ namespace Umbraco.Core.Sync
|
||||
var ssl = useSsl ? "s" : ""; // force, whatever the first request
|
||||
var url = "http" + ssl + "://" + request.ServerVariables["SERVER_NAME"] + port + IOHelper.ResolveUrl(SystemDirectories.Umbraco);
|
||||
|
||||
appContext._umbracoApplicationUrl = url.TrimEnd('/');
|
||||
logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (UmbracoModule request)");
|
||||
return url.TrimEnd('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace Umbraco.Core
|
||||
|
||||
{ AnyGuid, UdiType.GuidUdi },
|
||||
{ Document, UdiType.GuidUdi },
|
||||
{ DocumentBluePrint, UdiType.GuidUdi },
|
||||
{ DocumentBlueprint, UdiType.GuidUdi },
|
||||
{ Media, UdiType.GuidUdi },
|
||||
{ Member, UdiType.GuidUdi },
|
||||
{ DictionaryItem, UdiType.GuidUdi },
|
||||
@@ -68,7 +68,7 @@ namespace Umbraco.Core
|
||||
|
||||
public const string Document = "document";
|
||||
|
||||
public const string DocumentBluePrint = "document-blueprint";
|
||||
public const string DocumentBlueprint = "document-blueprint";
|
||||
|
||||
public const string Media = "media";
|
||||
public const string Member = "member";
|
||||
@@ -80,6 +80,7 @@ namespace Umbraco.Core
|
||||
public const string DocumentType = "document-type";
|
||||
public const string DocumentTypeContainer = "document-type-container";
|
||||
|
||||
//TODO: What is this? This alias is only used for the blue print tree to render the blueprint's document type, it's not a real udi type
|
||||
public const string DocumentTypeBluePrints = "document-type-blueprints";
|
||||
public const string MediaType = "media-type";
|
||||
public const string MediaTypeContainer = "media-type-container";
|
||||
@@ -117,6 +118,8 @@ namespace Umbraco.Core
|
||||
{
|
||||
case UmbracoObjectTypes.Document:
|
||||
return Document;
|
||||
case UmbracoObjectTypes.DocumentBlueprint:
|
||||
return DocumentBlueprint;
|
||||
case UmbracoObjectTypes.Media:
|
||||
return Media;
|
||||
case UmbracoObjectTypes.Member:
|
||||
@@ -161,6 +164,8 @@ namespace Umbraco.Core
|
||||
{
|
||||
case Document:
|
||||
return UmbracoObjectTypes.Document;
|
||||
case DocumentBlueprint:
|
||||
return UmbracoObjectTypes.DocumentBlueprint;
|
||||
case Media:
|
||||
return UmbracoObjectTypes.Media;
|
||||
case Member:
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace Umbraco.Core
|
||||
public static GuidUdi GetUdi(this IContent entity)
|
||||
{
|
||||
if (entity == null) throw new ArgumentNullException("entity");
|
||||
return new GuidUdi(entity.IsBlueprint ? Constants.UdiEntityType.DocumentBluePrint : Constants.UdiEntityType.Document, entity.Key).EnsureClosed();
|
||||
return new GuidUdi(entity.IsBlueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -39,18 +39,15 @@
|
||||
<ItemGroup>
|
||||
<Reference Include="AutoMapper, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="AutoMapper.Net4, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="ClientDependency.Core, Version=1.9.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ClientDependency.1.9.2\lib\net45\ClientDependency.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="HtmlAgilityPack, Version=1.4.9.5, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\HtmlAgilityPack.1.4.9.5\lib\Net45\HtmlAgilityPack.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImageProcessor, Version=2.5.6.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ImageProcessor.2.5.6\lib\net45\ImageProcessor.dll</HintPath>
|
||||
@@ -63,11 +60,9 @@
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AspNet.Identity.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AspNet.Identity.Owin, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.AspNet.Identity.Owin.2.2.1\lib\net45\Microsoft.AspNet.Identity.Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin, Version=3.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Owin.3.1.0\lib\net45\Microsoft.Owin.dll</HintPath>
|
||||
@@ -83,17 +78,16 @@
|
||||
</Reference>
|
||||
<Reference Include="MiniProfiler, Version=2.1.0.0, Culture=neutral, PublicKeyToken=b44f9351044011a3, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="MySql.Data, Version=6.9.9.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MySql.Data.6.9.9\lib\net45\MySql.Data.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Semver, Version=1.1.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\semver.1.1.2\lib\net45\Semver.dll</HintPath>
|
||||
@@ -105,11 +99,9 @@
|
||||
<Reference Include="System.Data.Entity" />
|
||||
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.SqlServerCe.Entity, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.DirectoryServices.AccountManagement" />
|
||||
<Reference Include="System.Drawing" />
|
||||
@@ -186,6 +178,7 @@
|
||||
<Compile Include="CodeAnnotations\UmbracoProposedPublicAttribute.cs" />
|
||||
<Compile Include="Collections\DeepCloneableList.cs" />
|
||||
<Compile Include="Collections\ListCloneBehavior.cs" />
|
||||
<Compile Include="Collections\TypeList.cs" />
|
||||
<Compile Include="ConcurrentHashSet.cs" />
|
||||
<Compile Include="Configuration\BaseRest\IBaseRestSection.cs" />
|
||||
<Compile Include="Configuration\BaseRest\IExtension.cs" />
|
||||
@@ -368,6 +361,7 @@
|
||||
<Compile Include="HashGenerator.cs" />
|
||||
<Compile Include="IEmailSender.cs" />
|
||||
<Compile Include="IHttpContextAccessor.cs" />
|
||||
<Compile Include="Logging\RollingFileCleanupAppender.cs" />
|
||||
<Compile Include="Models\EntityBase\EntityPath.cs" />
|
||||
<Compile Include="Models\EntityBase\IDeletableEntity.cs" />
|
||||
<Compile Include="Models\IAuditItem.cs" />
|
||||
|
||||
@@ -40,34 +40,27 @@
|
||||
<ItemGroup>
|
||||
<Reference Include="BenchmarkDotNet, Version=0.9.9.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\BenchmarkDotNet.0.9.9\lib\net45\BenchmarkDotNet.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="BenchmarkDotNet.Core, Version=0.9.9.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\BenchmarkDotNet.Core.0.9.9\lib\net45\BenchmarkDotNet.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="BenchmarkDotNet.Diagnostics.Windows, Version=0.9.9.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\BenchmarkDotNet.Diagnostics.Windows.0.9.9\lib\net45\BenchmarkDotNet.Diagnostics.Windows.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="BenchmarkDotNet.Toolchains.Roslyn, Version=0.9.9.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.9.9\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CodeAnalysis, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CodeAnalysis.CSharp, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Diagnostics.Tracing.TraceEvent, Version=1.0.41.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Moq, Version=4.1.1309.919, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Moq.4.1.1309.0919\lib\net40\Moq.dll</HintPath>
|
||||
@@ -75,27 +68,22 @@
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Collections.Immutable, Version=1.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.SqlServerCe.Entity, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Reflection.Metadata, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Tasks.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
@@ -116,6 +104,7 @@
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="packages.config" />
|
||||
<None Include="_TraceEventProgrammersGuide.docx" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj">
|
||||
@@ -132,8 +121,8 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
|
||||
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" />
|
||||
<Content Include="TraceEvent.ReadMe.txt" />
|
||||
<Content Include="TraceEvent.ReleaseNotes.txt" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -3,10 +3,13 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using umbraco.cms.businesslogic;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
|
||||
@@ -99,7 +102,7 @@ namespace Umbraco.Tests.Scoping
|
||||
DoDeleteForContent += OnDoThingFail;
|
||||
DoForTestArgs += OnDoThingFail;
|
||||
DoForTestArgs2 += OnDoThingFail;
|
||||
|
||||
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
|
||||
var content1 = MockedContent.CreateBasicContent(contentType);
|
||||
@@ -114,10 +117,10 @@ namespace Umbraco.Tests.Scoping
|
||||
var scopeProvider = new ScopeProvider(Mock.Of<IDatabaseFactory2>());
|
||||
using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher()))
|
||||
{
|
||||
|
||||
|
||||
//content1 will be filtered from the args
|
||||
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(new[]{ content1 , content3}));
|
||||
scope.Events.Dispatch(DoDeleteForContent, this, new DeleteEventArgs<IContent>(content1));
|
||||
scope.Events.Dispatch(DoDeleteForContent, this, new DeleteEventArgs<IContent>(content1), "DoDeleteForContent");
|
||||
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content2));
|
||||
//this entire event will be filtered
|
||||
scope.Events.Dispatch(DoForTestArgs, this, new TestEventArgs(content1));
|
||||
@@ -141,15 +144,36 @@ namespace Umbraco.Tests.Scoping
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SupersededEvents2()
|
||||
{
|
||||
var contentService = Mock.Of<IContentService>();
|
||||
var content = Mock.Of<IContent>();
|
||||
var l1 = new List<IEventDefinition>
|
||||
{
|
||||
new EventDefinition<IContentService, PublishEventArgs<IContent>>(Test_UnPublished, contentService, new PublishEventArgs<IContent>(new [] { content }), "UnPublished"),
|
||||
new EventDefinition<IContentService, DeleteEventArgs<IContent>>(Test_Deleted, contentService, new DeleteEventArgs<IContent>(new [] { content }), "Deleted")
|
||||
};
|
||||
|
||||
var l2 = new OrderedHashSet<IEventDefinition>(keepOldest: false);
|
||||
foreach (var e in l1)
|
||||
l2.Add(e);
|
||||
|
||||
var l3 = ScopeEventDispatcherBase.FilterSupersededAndUpdateToLatestEntity(l2);
|
||||
|
||||
// see U4-10764
|
||||
Assert.AreEqual(2, l3.Count());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will test that when we track events that before we Get the events we normalize all of the
|
||||
/// event entities to be the latest one (most current) found amongst the event so that there is
|
||||
/// event entities to be the latest one (most current) found amongst the event so that there is
|
||||
/// no 'stale' entities in any of the args
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LatestEntities()
|
||||
{
|
||||
DoSaveForContent += OnDoThingFail;
|
||||
DoSaveForContent += OnDoThingFail;
|
||||
|
||||
var now = DateTime.Now;
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
@@ -165,7 +189,7 @@ namespace Umbraco.Tests.Scoping
|
||||
|
||||
var scopeProvider = new ScopeProvider(Mock.Of<IDatabaseFactory2>());
|
||||
using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher()))
|
||||
{
|
||||
{
|
||||
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content1));
|
||||
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content2));
|
||||
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content3));
|
||||
@@ -173,7 +197,7 @@ namespace Umbraco.Tests.Scoping
|
||||
// events have been queued
|
||||
var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray();
|
||||
Assert.AreEqual(3, events.Length);
|
||||
|
||||
|
||||
foreach (var t in events)
|
||||
{
|
||||
var args = (SaveEventArgs<IContent>)t.Args;
|
||||
@@ -212,7 +236,7 @@ namespace Umbraco.Tests.Scoping
|
||||
|
||||
// events have been queued
|
||||
var events = scope.Events.GetEvents(EventDefinitionFilter.FirstIn).ToArray();
|
||||
Assert.AreEqual(1, events.Length);
|
||||
Assert.AreEqual(1, events.Length);
|
||||
Assert.AreEqual(content1, ((SaveEventArgs<IContent>) events[0].Args).SavedEntities.First());
|
||||
Assert.IsTrue(object.ReferenceEquals(content1, ((SaveEventArgs<IContent>)events[0].Args).SavedEntities.First()));
|
||||
Assert.AreEqual(content1.UpdateDate, ((SaveEventArgs<IContent>) events[0].Args).SavedEntities.First().UpdateDate);
|
||||
@@ -350,6 +374,9 @@ namespace Umbraco.Tests.Scoping
|
||||
|
||||
public static event TypedEventHandler<ScopeEventDispatcherTests, SaveEventArgs<decimal>> DoThing3;
|
||||
|
||||
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Test_UnPublished;
|
||||
public static event TypedEventHandler<IContentService, DeleteEventArgs<IContent>> Test_Deleted;
|
||||
|
||||
public class TestEventArgs : CancellableObjectEventArgs
|
||||
{
|
||||
public TestEventArgs(object eventObject) : base(eventObject)
|
||||
|
||||
@@ -62,8 +62,8 @@
|
||||
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Examine, Version=0.1.85.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Examine.0.1.85\lib\net45\Examine.dll</HintPath>
|
||||
<Reference Include="Examine, Version=0.1.89.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Examine.0.1.89\lib\net45\Examine.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
|
||||
@@ -17,6 +17,7 @@ using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.ControllerTesting;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Features;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using IUser = Umbraco.Core.Models.Membership.IUser;
|
||||
|
||||
@@ -26,6 +27,11 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
[TestFixture]
|
||||
public class UsersControllerTests : BaseDatabaseFactoryTest
|
||||
{
|
||||
protected override void FreezeResolution()
|
||||
{
|
||||
FeaturesResolver.Current = new FeaturesResolver(new UmbracoFeatures());
|
||||
base.FreezeResolution();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async void Save_User()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<packages>
|
||||
<package id="AutoMapper" version="3.3.1" targetFramework="net45" />
|
||||
<package id="Castle.Core" version="4.0.0" targetFramework="net45" />
|
||||
<package id="Examine" version="0.1.85" targetFramework="net45" />
|
||||
<package id="Examine" version="0.1.89" targetFramework="net45" />
|
||||
<package id="log4net" version="2.0.8" targetFramework="net45" />
|
||||
<package id="Log4Net.Async" version="2.0.4" targetFramework="net45" />
|
||||
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net45" />
|
||||
|
||||
@@ -34,60 +34,47 @@
|
||||
"font-awesome": "~4.2",
|
||||
"angular-moment": "^1.0.1"
|
||||
},
|
||||
|
||||
"install": {
|
||||
|
||||
"path": "lib-bower",
|
||||
|
||||
"ignore": [
|
||||
"font-awesome",
|
||||
"angular",
|
||||
"bootstrap",
|
||||
"codemirror"
|
||||
],
|
||||
|
||||
"sources": {
|
||||
"moment": "bower_components/moment/min/moment-with-locales.js",
|
||||
|
||||
"underscore": [
|
||||
"bower_components/underscore/underscore-min.js",
|
||||
"bower_components/underscore/underscore-min.map"
|
||||
],
|
||||
|
||||
"jquery": [
|
||||
"bower_components/jquery/dist/jquery.min.js",
|
||||
"bower_components/jquery/dist/jquery.min.map"
|
||||
],
|
||||
|
||||
"angular-dynamic-locale": [
|
||||
"bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js",
|
||||
"bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js.map"
|
||||
],
|
||||
|
||||
"angular-local-storage": [
|
||||
"bower_components/angular-local-storage/dist/angular-local-storage.min.js",
|
||||
"bower_components/angular-local-storage/dist/angular-local-storage.min.js.map"
|
||||
],
|
||||
|
||||
"angular-moment": [
|
||||
"bower_components/angular-moment/angular-moment.min.js"
|
||||
],
|
||||
|
||||
"tinymce": [
|
||||
"bower_components/tinymce/tinymce.min.js"
|
||||
],
|
||||
|
||||
"typeahead.js": "bower_components/typeahead.js/dist/typeahead.bundle.min.js",
|
||||
|
||||
"rgrove-lazyload":"bower_components/rgrove-lazyload/lazyload.js",
|
||||
|
||||
"ng-file-upload":"bower_components/ng-file-upload/ng-file-upload.min.js",
|
||||
|
||||
"jquery-ui":"bower_components/jquery-ui/jquery-ui.min.js",
|
||||
|
||||
"jquery-migrate":"bower_components/jquery-migrate/jquery-migrate.min.js",
|
||||
|
||||
"rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js",
|
||||
"ng-file-upload": "bower_components/ng-file-upload/ng-file-upload.min.js",
|
||||
"jquery-ui": "bower_components/jquery-ui/jquery-ui.min.js",
|
||||
"jquery-migrate": "bower_components/jquery-migrate/jquery-migrate.min.js",
|
||||
"clipboard": "bower_components/clipboard/dist/clipboard.min.js"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"moment": ">=2.19.3 <3.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,15 +31,17 @@
|
||||
display: block;
|
||||
padding: 4px;
|
||||
line-height: @baseLineHeight;
|
||||
border: 1px solid #ddd;
|
||||
border: 1px solid @gray-8;
|
||||
.border-radius(@baseBorderRadius);
|
||||
.box-shadow(0 1px 3px rgba(0,0,0,.055));
|
||||
.transition(all .2s ease-in-out);
|
||||
}
|
||||
// Add a hover/focus state for linked versions only
|
||||
a.thumbnail:hover,
|
||||
a.thumbnail:focus {
|
||||
border-color: @linkColor;
|
||||
// Add a hover/focus state for linked versions only.
|
||||
a.thumbnail:hover,
|
||||
a.thumbnail:focus,
|
||||
a div.thumbnail:hover,
|
||||
a div.thumbnail:focus {
|
||||
border-color: @turquoise;
|
||||
.box-shadow(0 1px 4px rgba(0,105,214,.25));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"cssnano": "^3.7.6",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-connect": "^5.0.0",
|
||||
"gulp-connect": "5.0.0",
|
||||
"gulp-less": "^3.1.0",
|
||||
"gulp-ngdocs": "^0.3.0",
|
||||
"gulp-open": "^2.0.0",
|
||||
|
||||
+2
-1
@@ -135,11 +135,12 @@
|
||||
}
|
||||
|
||||
var scrollParent = element.scrollParent();
|
||||
var scrollToCenterOfContainer = element[0].offsetTop - (scrollParent[0].clientHeight / 2 ) + (element[0].clientHeight / 2);
|
||||
|
||||
// Detect if scroll is needed
|
||||
if (element[0].offsetTop > scrollParent[0].clientHeight) {
|
||||
scrollParent.animate({
|
||||
scrollTop: element[0].offsetTop
|
||||
scrollTop: scrollToCenterOfContainer
|
||||
}, function () {
|
||||
// Animation complete.
|
||||
setPopoverPosition(element);
|
||||
|
||||
+111
-30
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function ContentEditController($rootScope, $scope, $routeParams, $q, $timeout, $window, $location, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http, eventsService) {
|
||||
function ContentEditController($rootScope, $scope, $routeParams, $q, $timeout, $window, $location, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http, eventsService, relationResource) {
|
||||
|
||||
var evts = [];
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
$scope.page.menu.currentSection = appState.getSectionState("currentSection");
|
||||
$scope.page.listViewPath = null;
|
||||
$scope.page.isNew = $scope.isNew ? true : false;
|
||||
$scope.page.buttonGroupState = "init";
|
||||
$scope.page.buttonGroupState = "init";
|
||||
$scope.allowOpen = true;
|
||||
|
||||
|
||||
@@ -49,6 +49,40 @@
|
||||
|
||||
}
|
||||
|
||||
function getNode() {
|
||||
|
||||
$scope.page.loading = true;
|
||||
|
||||
//we are editing so get the content item from the server
|
||||
$scope.getMethod()($scope.contentId)
|
||||
.then(function (data) {
|
||||
|
||||
$scope.content = data;
|
||||
|
||||
if (data.isChildOfListView && data.trashed === false) {
|
||||
$scope.page.listViewPath = ($routeParams.page) ?
|
||||
"/content/content/edit/" + data.parentId + "?page=" + $routeParams.page :
|
||||
"/content/content/edit/" + data.parentId;
|
||||
}
|
||||
|
||||
init($scope.content);
|
||||
|
||||
//in one particular special case, after we've created a new item we redirect back to the edit
|
||||
// route but there might be server validation errors in the collection which we need to display
|
||||
// after the redirect, so we will bind all subscriptions which will show the server validation errors
|
||||
// if there are any and then clear them so the collection no longer persists them.
|
||||
serverValidationManager.executeAndClearAllSubscriptions();
|
||||
|
||||
syncTreeNode($scope.content, data.path, true);
|
||||
|
||||
resetLastListPageNumber($scope.content);
|
||||
|
||||
$scope.page.loading = false;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function createButtons(content) {
|
||||
var buttons = contentEditingHelper.configureContentEditorButtons({
|
||||
create: $scope.page.isNew,
|
||||
@@ -152,35 +186,8 @@
|
||||
}
|
||||
else {
|
||||
|
||||
$scope.page.loading = true;
|
||||
getNode();
|
||||
|
||||
//we are editing so get the content item from the server
|
||||
$scope.getMethod()($scope.contentId)
|
||||
.then(function (data) {
|
||||
|
||||
$scope.content = data;
|
||||
|
||||
if (data.isChildOfListView && data.trashed === false) {
|
||||
$scope.page.listViewPath = ($routeParams.page) ?
|
||||
"/content/content/edit/" + data.parentId + "?page=" + $routeParams.page :
|
||||
"/content/content/edit/" + data.parentId;
|
||||
}
|
||||
|
||||
init($scope.content);
|
||||
|
||||
//in one particular special case, after we've created a new item we redirect back to the edit
|
||||
// route but there might be server validation errors in the collection which we need to display
|
||||
// after the redirect, so we will bind all subscriptions which will show the server validation errors
|
||||
// if there are any and then clear them so the collection no longer persists them.
|
||||
serverValidationManager.executeAndClearAllSubscriptions();
|
||||
|
||||
syncTreeNode($scope.content, data.path, true);
|
||||
|
||||
resetLastListPageNumber($scope.content);
|
||||
|
||||
$scope.page.loading = false;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -252,6 +259,80 @@
|
||||
|
||||
};
|
||||
|
||||
$scope.restore = function (content) {
|
||||
|
||||
$scope.page.buttonRestore = "busy";
|
||||
|
||||
relationResource.getByChildId(content.id, "relateParentDocumentOnDelete").then(function (data) {
|
||||
|
||||
var relation = null;
|
||||
var target = null;
|
||||
var error = { headline: "Cannot automatically restore this item", content: "Use the Move menu item to move it manually"};
|
||||
|
||||
if (data.length == 0) {
|
||||
notificationsService.error(error.headline, "There is no 'restore' relation found for this node. Use the Move menu item to move it manually.");
|
||||
$scope.page.buttonRestore = "error";
|
||||
return;
|
||||
}
|
||||
|
||||
relation = data[0];
|
||||
|
||||
if (relation.parentId == -1) {
|
||||
target = { id: -1, name: "Root" };
|
||||
moveNode(content, target);
|
||||
} else {
|
||||
contentResource.getById(relation.parentId).then(function (data) {
|
||||
target = data;
|
||||
|
||||
// make sure the target item isn't in the recycle bin
|
||||
if(target.path.indexOf("-20") !== -1) {
|
||||
notificationsService.error(error.headline, "The item you want to restore it under (" + target.name + ") is in the recycle bin. Use the Move menu item to move the item manually.");
|
||||
$scope.page.buttonRestore = "error";
|
||||
return;
|
||||
}
|
||||
|
||||
moveNode(content, target);
|
||||
|
||||
}, function (err) {
|
||||
$scope.page.buttonRestore = "error";
|
||||
notificationsService.error(error.headline, error.content);
|
||||
});
|
||||
}
|
||||
|
||||
}, function (err) {
|
||||
$scope.page.buttonRestore = "error";
|
||||
notificationsService.error(error.headline, error.content);
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
|
||||
function moveNode(node, target) {
|
||||
|
||||
contentResource.move({ "parentId": target.id, "id": node.id })
|
||||
.then(function (path) {
|
||||
|
||||
// remove the node that we're working on
|
||||
if($scope.page.menu.currentNode) {
|
||||
treeService.removeNode($scope.page.menu.currentNode);
|
||||
}
|
||||
|
||||
// sync the destination node
|
||||
navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false });
|
||||
|
||||
$scope.page.buttonRestore = "success";
|
||||
notificationsService.success("Successfully restored " + node.name + " to " + target.name);
|
||||
|
||||
// reload the node
|
||||
getNode();
|
||||
|
||||
}, function (err) {
|
||||
$scope.page.buttonRestore = "error";
|
||||
notificationsService.error("Cannot automatically restore this item", err);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
//ensure to unregister from all events!
|
||||
$scope.$on('$destroy', function () {
|
||||
for (var e in evts) {
|
||||
|
||||
+115
-7
@@ -1,9 +1,13 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function ContentNodeInfoDirective($timeout, $location, logResource, eventsService) {
|
||||
function ContentNodeInfoDirective($timeout, $location, logResource, eventsService, userService, localizationService) {
|
||||
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
|
||||
var evts = [];
|
||||
var isInfoTab = false;
|
||||
scope.publishStatus = {};
|
||||
|
||||
function onInit() {
|
||||
|
||||
@@ -32,12 +36,11 @@
|
||||
// get document type details
|
||||
scope.documentType = scope.node.documentType;
|
||||
|
||||
// load audit trail when on the info tab
|
||||
eventsService.on("app.tabChange", function (event, args) {
|
||||
if (args.id === -1) {
|
||||
loadAuditTrail();
|
||||
}
|
||||
});
|
||||
// make sure dates are formatted to the user's locale
|
||||
formatDatesToLocal();
|
||||
|
||||
setNodePublishStatus(scope.node);
|
||||
|
||||
}
|
||||
|
||||
scope.auditTrailPageChange = function (pageNumber) {
|
||||
@@ -79,6 +82,14 @@
|
||||
|
||||
logResource.getPagedEntityLog(scope.auditTrailOptions)
|
||||
.then(function (data) {
|
||||
|
||||
// get current backoffice user and format dates
|
||||
userService.getCurrentUser().then(function (currentUser) {
|
||||
angular.forEach(data.items, function(item) {
|
||||
item.timestampFormatted = getLocalDate(item.timestamp, currentUser.locale, 'LLL');
|
||||
});
|
||||
});
|
||||
|
||||
scope.auditTrail = data.items;
|
||||
scope.auditTrailOptions.pageNumber = data.pageNumber;
|
||||
scope.auditTrailOptions.pageSize = data.pageSize;
|
||||
@@ -99,6 +110,7 @@
|
||||
item.logTypeColor = "success";
|
||||
break;
|
||||
case "UnPublish":
|
||||
case "Delete":
|
||||
item.logTypeColor = "danger";
|
||||
break;
|
||||
default:
|
||||
@@ -107,11 +119,42 @@
|
||||
});
|
||||
}
|
||||
|
||||
function setNodePublishStatus(node) {
|
||||
|
||||
// deleted node
|
||||
if(node.trashed === true) {
|
||||
scope.publishStatus.label = localizationService.localize("general_deleted");
|
||||
scope.publishStatus.color = "danger";
|
||||
}
|
||||
|
||||
// unpublished node
|
||||
if(node.published === false && node.trashed === false) {
|
||||
scope.publishStatus.label = localizationService.localize("content_unpublished");
|
||||
scope.publishStatus.color = "gray";
|
||||
}
|
||||
|
||||
// published node
|
||||
if(node.hasPublishedVersion === true && node.publishDate && node.published === true) {
|
||||
scope.publishStatus.label = localizationService.localize("content_published");
|
||||
scope.publishStatus.color = "success";
|
||||
}
|
||||
|
||||
// published node with pending changes
|
||||
if(node.hasPublishedVersion === true && node.publishDate && node.published === false) {
|
||||
scope.publishStatus.label = localizationService.localize("content_publishedPendingChanges");
|
||||
scope.publishStatus.color = "success"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setPublishDate(date) {
|
||||
|
||||
// update publish value
|
||||
scope.node.releaseDate = date;
|
||||
|
||||
// make sure dates are formatted to the user's locale
|
||||
formatDatesToLocal();
|
||||
|
||||
// emit event
|
||||
var args = { node: scope.node, date: date };
|
||||
eventsService.emit("editors.content.changePublishDate", args);
|
||||
@@ -134,6 +177,9 @@
|
||||
// update publish value
|
||||
scope.node.removeDate = date;
|
||||
|
||||
// make sure dates are formatted to the user's locale
|
||||
formatDatesToLocal();
|
||||
|
||||
// emit event
|
||||
var args = { node: scope.node, date: date };
|
||||
eventsService.emit("editors.content.changeUnpublishDate", args);
|
||||
@@ -150,6 +196,68 @@
|
||||
eventsService.emit("editors.content.changeUnpublishDate", args);
|
||||
|
||||
}
|
||||
|
||||
function ucfirst(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
function getLocalDate(date, culture, format) {
|
||||
if (date) {
|
||||
var dateVal;
|
||||
var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
|
||||
var localOffset = new Date().getTimezoneOffset();
|
||||
var serverTimeNeedsOffsetting = -serverOffset !== localOffset;
|
||||
if (serverTimeNeedsOffsetting) {
|
||||
dateVal = dateHelper.convertToLocalMomentTime(date, serverOffset);
|
||||
} else {
|
||||
dateVal = moment(date, 'YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
return dateVal.locale(culture).format(format);
|
||||
}
|
||||
}
|
||||
|
||||
function formatDatesToLocal() {
|
||||
// get current backoffice user and format dates
|
||||
userService.getCurrentUser().then(function (currentUser) {
|
||||
scope.node.createDateFormatted = getLocalDate(scope.node.createDate, currentUser.locale, 'LLL');
|
||||
scope.node.releaseDateMonth = scope.node.releaseDate ? ucfirst(getLocalDate(scope.node.releaseDate, currentUser.locale, 'MMMM')) : null;
|
||||
scope.node.releaseDateDay = scope.node.releaseDate ? ucfirst(getLocalDate(scope.node.releaseDate, currentUser.locale, 'dddd')) : null;
|
||||
scope.node.removeDateMonth = scope.node.removeDate ? ucfirst(getLocalDate(scope.node.removeDate, currentUser.locale, 'MMMM')) : null;
|
||||
scope.node.removeDateDay = scope.node.removeDate ? ucfirst(getLocalDate(scope.node.removeDate, currentUser.locale, 'dddd')) : null;
|
||||
});
|
||||
}
|
||||
|
||||
// load audit trail when on the info tab
|
||||
evts.push(eventsService.on("app.tabChange", function (event, args) {
|
||||
$timeout(function(){
|
||||
if (args.id === -1) {
|
||||
isInfoTab = true;
|
||||
loadAuditTrail();
|
||||
} else {
|
||||
isInfoTab = false;
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
// watch for content updates - reload content when node is saved, published etc.
|
||||
scope.$watch('node.updateDate', function(newValue, oldValue){
|
||||
|
||||
if(!newValue) { return; }
|
||||
if(newValue === oldValue) { return; }
|
||||
|
||||
if(isInfoTab) {
|
||||
loadAuditTrail();
|
||||
formatDatesToLocal();
|
||||
setNodePublishStatus(scope.node);
|
||||
}
|
||||
});
|
||||
|
||||
//ensure to unregister from all events!
|
||||
scope.$on('$destroy', function () {
|
||||
for (var e in evts) {
|
||||
eventsService.unsubscribe(evts[e]);
|
||||
}
|
||||
});
|
||||
|
||||
onInit();
|
||||
|
||||
|
||||
@@ -366,6 +366,9 @@ angular.module("umbraco.directives")
|
||||
// element might still be there even after the modal has been hidden.
|
||||
scope.$on('$destroy', function () {
|
||||
unsubscribe();
|
||||
if (tinyMceEditor !== undefined && tinyMceEditor != null) {
|
||||
tinyMceEditor.destroy()
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@
|
||||
|
||||
@param {string} icon (<code>binding</code>): The node icon.
|
||||
@param {string} name (<code>binding</code>): The node name.
|
||||
@param {boolean} published (<code>binding</code>): The node pusblished state.
|
||||
@param {boolean} published (<code>binding</code>): The node published state.
|
||||
@param {string} description (<code>binding</code>): A short description.
|
||||
@param {boolean} sortable (<code>binding</code>): Will add a move cursor on the node preview. Can used in combination with ui-sortable.
|
||||
@param {boolean} allowRemove (<code>binding</code>): Show/Hide the remove button.
|
||||
|
||||
+12
-1
@@ -1,4 +1,15 @@
|
||||
(function () {
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbPasswordToggle
|
||||
@restrict E
|
||||
@scope
|
||||
|
||||
@description
|
||||
<strong>Added in Umbraco v. 7.7.4:</strong> Use this directive to render a password toggle.
|
||||
|
||||
**/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// comes from https://codepen.io/jakob-e/pen/eNBQaP
|
||||
|
||||
@@ -1,3 +1,114 @@
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbTable
|
||||
@restrict E
|
||||
@scope
|
||||
|
||||
@description
|
||||
<strong>Added in Umbraco v. 7.4:</strong> Use this directive to render a data table.
|
||||
|
||||
<h3>Markup example</h3>
|
||||
<pre>
|
||||
<div ng-controller="My.TableController as vm">
|
||||
|
||||
<umb-table
|
||||
ng-if="items"
|
||||
items="vm.items"
|
||||
item-properties="vm.options.includeProperties"
|
||||
allow-select-all="vm.allowSelectAll"
|
||||
on-select="vm.selectItem"
|
||||
on-click="vm.clickItem"
|
||||
on-select-all="vm.selectAll"
|
||||
on-selected-all="vm.isSelectedAll"
|
||||
on-sorting-direction="vm.isSortDirection"
|
||||
on-sort="vm.sort">
|
||||
</umb-table>
|
||||
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
<h3>Controller example</h3>
|
||||
<pre>
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function Controller() {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.items = [
|
||||
{
|
||||
"icon": "icon-document",
|
||||
"name": "My node 1",
|
||||
"published": true,
|
||||
"description": "A short description of my node",
|
||||
"author": "Author 1"
|
||||
},
|
||||
{
|
||||
"icon": "icon-document",
|
||||
"name": "My node 2",
|
||||
"published": true,
|
||||
"description": "A short description of my node",
|
||||
"author": "Author 2"
|
||||
}
|
||||
];
|
||||
|
||||
vm.options = {
|
||||
includeProperties: [
|
||||
{ alias: "description", header: "Description" },
|
||||
{ alias: "author", header: "Author" }
|
||||
]
|
||||
};
|
||||
|
||||
vm.selectItem = selectItem;
|
||||
vm.clickItem = clickItem;
|
||||
vm.selectAll = selectAll;
|
||||
vm.isSelectedAll = isSelectedAll;
|
||||
vm.isSortDirection = isSortDirection;
|
||||
vm.sort = sort;
|
||||
|
||||
function selectAll($event) {
|
||||
alert("select all");
|
||||
}
|
||||
|
||||
function isSelectedAll() {
|
||||
|
||||
}
|
||||
|
||||
function clickItem(item) {
|
||||
alert("click node");
|
||||
}
|
||||
|
||||
function selectItem(selectedItem, $index, $event) {
|
||||
alert("select node");
|
||||
}
|
||||
|
||||
function isSortDirection(col, direction) {
|
||||
|
||||
}
|
||||
|
||||
function sort(field, allow, isSystem) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("My.TableController", Controller);
|
||||
|
||||
})();
|
||||
</pre>
|
||||
|
||||
@param {string} icon (<code>binding</code>): The node icon.
|
||||
@param {string} name (<code>binding</code>): The node name.
|
||||
@param {string} published (<code>binding</code>): The node published state.
|
||||
@param {function} onSelect (<code>expression</code>): Callback function when the row is selected.
|
||||
@param {function} onClick (<code>expression</code>): Callback function when the "Name" column link is clicked.
|
||||
@param {function} onSelectAll (<code>expression</code>): Callback function when selecting all items.
|
||||
@param {function} onSelectedAll (<code>expression</code>): Callback function when all items are selected.
|
||||
@param {function} onSortingDirection (<code>expression</code>): Callback function when sorting direction is changed.
|
||||
@param {function} onSort (<code>expression</code>): Callback function when sorting items.
|
||||
**/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
|
||||
@@ -16,52 +16,16 @@ function eventsService($q, $rootScope) {
|
||||
|
||||
return {
|
||||
|
||||
/** raise an event with a given name, returns an array of promises for each listener */
|
||||
emit: function (name, args) {
|
||||
/** raise an event with a given name */
|
||||
emit: function (name, args) {
|
||||
|
||||
//there are no listeners
|
||||
if (!$rootScope.$$listeners[name]) {
|
||||
return;
|
||||
//return [];
|
||||
}
|
||||
|
||||
//send the event
|
||||
$rootScope.$emit(name, args);
|
||||
|
||||
|
||||
//PP: I've commented out the below, since we currently dont
|
||||
// expose the eventsService as a documented api
|
||||
// and think we need to figure out our usecases for this
|
||||
// since the below modifies the return value of the then on() method
|
||||
/*
|
||||
//setup a deferred promise for each listener
|
||||
var deferred = [];
|
||||
for (var i = 0; i < $rootScope.$$listeners[name].length; i++) {
|
||||
deferred.push($q.defer());
|
||||
}*/
|
||||
|
||||
//create a new event args object to pass to the
|
||||
// $emit containing methods that will allow listeners
|
||||
// to return data in an async if required
|
||||
/*
|
||||
var eventArgs = {
|
||||
args: args,
|
||||
reject: function (a) {
|
||||
deferred.pop().reject(a);
|
||||
},
|
||||
resolve: function (a) {
|
||||
deferred.pop().resolve(a);
|
||||
}
|
||||
};*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
//return an array of promises
|
||||
var promises = _.map(deferred, function(p) {
|
||||
return p.promise;
|
||||
});
|
||||
return promises;*/
|
||||
},
|
||||
|
||||
/** subscribe to a method, or use scope.$on = same thing */
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
function registerAllTours() {
|
||||
return tourResource.getTours().then(function(tourFiles) {
|
||||
angular.forEach(tourFiles, function (tourFile) {
|
||||
angular.forEach(tourFile, function(newTour) {
|
||||
angular.forEach(tourFile.tours, function(newTour) {
|
||||
validateTour(newTour);
|
||||
validateTourRegistration(newTour);
|
||||
tours.push(newTour);
|
||||
|
||||
@@ -242,8 +242,8 @@
|
||||
var propGroups = _.find(genericTab.properties, function (item) {
|
||||
return item.alias === "_umb_membergroup";
|
||||
});
|
||||
saveModel.email = propEmail.value;
|
||||
saveModel.username = propLogin.value;
|
||||
saveModel.email = propEmail.value.trim();
|
||||
saveModel.username = propLogin.value.trim();
|
||||
|
||||
saveModel.password = this.formatChangePasswordModel(propPass.value);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.umb-button-group__toggle {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
float: right;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.umb-button-group__sub-buttons.-align-right {
|
||||
|
||||
@@ -535,7 +535,7 @@
|
||||
color: @gray-3;
|
||||
}
|
||||
|
||||
.umb-grid .umb-cell-rte textarea {
|
||||
.umb-grid .umb-cell-rte textarea.mceNoEditor {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
margin: 50px 0 0 0;
|
||||
border: 2px solid @gray-7;
|
||||
border-radius: 0 10px 10px 10px;
|
||||
position: relative;
|
||||
padding: 10px 10px 5px 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -34,6 +33,7 @@
|
||||
border: 1px dashed @gray-8;
|
||||
color: @turquoise-d1;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.umb-group-builder__group.-sortable {
|
||||
@@ -67,16 +67,18 @@
|
||||
}
|
||||
|
||||
.umb-group-builder__group-title-wrapper {
|
||||
position: absolute;
|
||||
left: -2px;
|
||||
top: -45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: -12px;
|
||||
margin-top: -55px;
|
||||
}
|
||||
|
||||
.umb-group-builder__group-title-wrapper.-placeholder {
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
top: -44px;
|
||||
margin-left: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.umb-group-builder__group-title {
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
.umb-node-preview__content {
|
||||
flex: 1 1 auto;
|
||||
margin-right: 25px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.umb-node-preview__name {
|
||||
@@ -49,6 +50,13 @@
|
||||
color: @gray-3;
|
||||
}
|
||||
|
||||
.umb-node-preview__name,
|
||||
.umb-node-preview__description {
|
||||
/*text-overflow: ellipsis;
|
||||
overflow: hidden;*/
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.umb-node-preview__actions {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
padding: 15px 10px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
border: 2px solid transparent;
|
||||
border: 1px solid @gray-8;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.umb-healthcheck-group:hover {
|
||||
border: 2px solid @turquoise;
|
||||
border: 1px solid @turquoise;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,21 +60,29 @@
|
||||
|
||||
//------------------- HISTORY ------------------
|
||||
|
||||
.history {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.history-line {
|
||||
width: 2px;
|
||||
height: 24px;
|
||||
height: 100%;
|
||||
margin: 0 0 0 14px;
|
||||
background-color: @gray-8;
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.history-row {
|
||||
.history-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.history-item__avatar{
|
||||
min-width: 30px;
|
||||
.history-item__avatar {
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.history-item__date {
|
||||
@@ -82,74 +90,18 @@
|
||||
color: @gray-5;
|
||||
}
|
||||
|
||||
.name-date-container {
|
||||
margin: 0 30px 0 7px;
|
||||
height: 30px;
|
||||
min-width: 170px;
|
||||
}
|
||||
|
||||
.last-history-item .history-line {
|
||||
display: none;
|
||||
}
|
||||
|
||||
//--------- META DATA --------
|
||||
.meta-data-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.meta-data-title {
|
||||
font-size: 15px;
|
||||
color: @black;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.meta-data-description {
|
||||
font-size: 13px;
|
||||
color: @gray-5;
|
||||
}
|
||||
|
||||
.meta-data {
|
||||
font-size: 15px;
|
||||
color: @gray-2;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* RESPONSIVE */
|
||||
|
||||
.history-item__break {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 230px;
|
||||
font-size: 14px;
|
||||
}
|
||||
@media (max-width: 979px) {
|
||||
|
||||
/* RESPONSIVE */
|
||||
@media (min-width: 1101px) and (max-width: 1365px), (max-width: 979px) {
|
||||
|
||||
.history-row {
|
||||
.history-item {
|
||||
display: block;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.history-item__break {
|
||||
padding: 7px 0;
|
||||
}
|
||||
|
||||
.history-line {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (max-width: 1535px) {
|
||||
|
||||
.history-line {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1101px) and (max-width: 1365px) {
|
||||
|
||||
.history-row {
|
||||
display: block;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.history-item__break {
|
||||
|
||||
@@ -281,6 +281,12 @@ ul.color-picker li a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.umb-sortable-thumbnails .umb-sortable-thumbnails__wrapper {
|
||||
width: 120px;
|
||||
height: 114px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.umb-sortable-thumbnails .umb-sortable-thumbnails__actions {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
@@ -308,6 +314,7 @@ ul.color-picker li a {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.umb-sortable-thumbnails .umb-sortable-thumbnails__action.-red {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<umb-control-group label="@general_url">
|
||||
<input id="url" class="umb-editor input-block-level" type="text" style="margin-bottom: 10px;" ng-model="model.embed.url" ng-keyup="$event.keyCode == 13 ? vm.showPreview() : null" required />
|
||||
<input type="button" ng-click="vm.showPreview()" class="btn" localize="value" value="@general_retrieve" />
|
||||
<input type="button" ng-click="vm.showPreview()" class="btn" value="@general_retrieve" localize="value" />
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group>
|
||||
@@ -12,15 +12,15 @@
|
||||
|
||||
<div ng-show="model.embed.supportsDimensions">
|
||||
<umb-control-group label="@general_width">
|
||||
<input type="text" ng-model="model.embed.width" on-blur="vm.changeSize('width')" />
|
||||
<input type="number" pattern="[0-9]*" min="0" ng-model="model.embed.width" on-blur="vm.changeSize('width')" />
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@general_height">
|
||||
<input type="text" ng-model="model.embed.height" on-blur="vm.changeSize('height')" />
|
||||
<input type="number" pattern="[0-9]*" min="0" ng-model="model.embed.height" on-blur="vm.changeSize('height')" />
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@general_constrain">
|
||||
<input id="constrain" type="checkbox" ng-model="model.embed.constrain" />
|
||||
<input id="constrain" type="checkbox" ng-model="model.embed.constrain" />
|
||||
</umb-control-group>
|
||||
</div>
|
||||
|
||||
|
||||
+2
@@ -22,6 +22,8 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController",
|
||||
selectedSearchResults: []
|
||||
};
|
||||
|
||||
$scope.showTarget = $scope.model.hideTarget !== true;
|
||||
|
||||
if (dialogOptions.currentTarget) {
|
||||
$scope.model.target = dialogOptions.currentTarget;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
ng-model="model.target.name" />
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@content_target">
|
||||
<umb-control-group ng-if="showTarget" label="@content_target">
|
||||
<label class="checkbox no-indent">
|
||||
<input type="checkbox" ng-model="model.target.target" ng-true-value="_blank" ng-false-value="" /> <localize key="defaultdialogs_openInNewWindow">Opens the linked document in a new window or tab</localize>
|
||||
</label>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<umb-editor-footer-content-right>
|
||||
|
||||
<umb-button
|
||||
alias="returnToListView"
|
||||
alias="returnToListView"
|
||||
ng-if="page.listViewPath"
|
||||
type="link"
|
||||
href="#{{page.listViewPath}}"
|
||||
@@ -70,7 +70,7 @@
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
alias="preview"
|
||||
alias="preview"
|
||||
ng-if="!page.isNew && content.allowPreview"
|
||||
type="button"
|
||||
button-style="info"
|
||||
@@ -80,7 +80,7 @@
|
||||
</umb-button>
|
||||
|
||||
<umb-button-group
|
||||
ng-if="defaultButton"
|
||||
ng-if="defaultButton && !content.trashed"
|
||||
default-button="defaultButton"
|
||||
sub-buttons="subButtons"
|
||||
state="page.buttonGroupState"
|
||||
@@ -88,6 +88,16 @@
|
||||
float="right">
|
||||
</umb-button-group>
|
||||
|
||||
<umb-button
|
||||
alias="restore"
|
||||
ng-if="content.trashed"
|
||||
type="button"
|
||||
button-style="primary"
|
||||
action="restore(content)"
|
||||
state="page.buttonRestore"
|
||||
label="Restore">
|
||||
</umb-button>
|
||||
|
||||
</umb-editor-footer-content-right>
|
||||
|
||||
</umb-editor-footer>
|
||||
|
||||
@@ -7,10 +7,14 @@
|
||||
<umb-box-content class="block-form">
|
||||
<ul class="nav nav-stacked" style="margin-bottom: 0;">
|
||||
<li ng-repeat="url in node.urls">
|
||||
<a href="{{url}}" target="_blank">
|
||||
<a ng-if="node.published" href="{{url}}" target="_blank">
|
||||
<i class="icon icon-window-popin"></i>
|
||||
<span>{{url}}</span>
|
||||
</a>
|
||||
<div ng-if="!node.published">
|
||||
<i class="icon icon-window-popin"></i>
|
||||
<span>{{url}}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</umb-box-content>
|
||||
@@ -32,10 +36,13 @@
|
||||
<localize key="content_noChanges"></localize>
|
||||
</umb-empty-state>
|
||||
</div>
|
||||
|
||||
<div class="history">
|
||||
|
||||
<div ng-if="auditTrail.length > 1" class="history-line"></div>
|
||||
|
||||
<div class="history-item" ng-repeat="item in auditTrail" ng-class="{'last-history-item': $last}">
|
||||
<div class="history-row">
|
||||
|
||||
<div class="history-item" ng-repeat="item in auditTrail">
|
||||
|
||||
<div class="history-item__break">
|
||||
<div class="history-item__avatar">
|
||||
<umb-avatar
|
||||
@@ -47,22 +54,22 @@
|
||||
</umb-avatar>
|
||||
</div>
|
||||
|
||||
<div class="name-date-container">
|
||||
<div>
|
||||
<div>{{ item.userName }}</div>
|
||||
<div class="history-item__date">{{ item.timestamp | amDateFormat:'MMMM Do YYYY, HH:mm' }}</div>
|
||||
<div class="history-item__date">{{item.timestampFormatted}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="history-item__break">
|
||||
<umb-badge
|
||||
size="s"
|
||||
size="xs"
|
||||
color="{{item.logTypeColor}}">
|
||||
{{ item.logType }}
|
||||
</umb-badge>
|
||||
<span>{{ item.comment }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="history-line"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -102,19 +109,19 @@
|
||||
</div>
|
||||
|
||||
<div class="date-container__date" ng-if="node.releaseDate">
|
||||
<div class="date-wrapper__date">{{node.releaseDate | amDateFormat:'MMMM'}} {{node.releaseDate | amDateFormat:'YYYY'}}</div>
|
||||
<div class="date-wrapper__date">{{node.releaseDateMonth}} {{node.releaseDate | amDateFormat:'YYYY'}}</div>
|
||||
<div class="date-wrapper__number">{{node.releaseDate | amDateFormat:'DD'}}</div>
|
||||
<div class="date-wrapper__date">{{node.releaseDate | amDateFormat:'dddd'}} {{node.releaseDate | amDateFormat:'HH:mm'}}</div>
|
||||
<div class="date-wrapper__date">{{node.releaseDateDay}} {{node.releaseDate | amDateFormat:'HH:mm'}}</div>
|
||||
</div>
|
||||
|
||||
<a href="" ng-if="!node.releaseDate" class="bold" style="color: #00aea2; text-decoration: underline;">Set date</a>
|
||||
<a href="" ng-if="!node.releaseDate" class="bold" style="color: #00aea2; text-decoration: underline;"><localize key="content_setDate">Set date</localize></a>
|
||||
|
||||
</div>
|
||||
|
||||
</umb-date-time-picker>
|
||||
|
||||
<a ng-if="node.releaseDate" ng-click="clearPublishDate()" href="" style="text-decoration: underline;">
|
||||
<small>Clear date</small>
|
||||
<small><localize key="content_removeDate">Clear date</localize></small>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
@@ -134,19 +141,19 @@
|
||||
</div>
|
||||
|
||||
<div class="date-container__date" ng-if="node.removeDate">
|
||||
<div class="date-wrapper__date">{{node.removeDate | amDateFormat:'MMMM'}} {{node.removeDate | amDateFormat:'YYYY'}}</div>
|
||||
<div class="date-wrapper__date">{{node.removeDateMonth}} {{node.removeDate | amDateFormat:'YYYY'}}</div>
|
||||
<div class="date-wrapper__number">{{node.removeDate | amDateFormat:'DD'}}</div>
|
||||
<div class="date-wrapper__date">{{node.removeDate | amDateFormat:'dddd'}} {{node.removeDate | amDateFormat:'HH:mm'}}</div>
|
||||
<div class="date-wrapper__date">{{node.removeDateDay}} {{node.removeDate | amDateFormat:'HH:mm'}}</div>
|
||||
</div>
|
||||
|
||||
<a href="" ng-if="!node.removeDate" class="bold" style="color: #00aea2; text-decoration: underline;">Set date</a>
|
||||
<a href="" ng-if="!node.removeDate" class="bold" style="color: #00aea2; text-decoration: underline;"><localize key="content_setDate">Set date</localize></a>
|
||||
|
||||
</div>
|
||||
|
||||
</umb-date-time-picker>
|
||||
|
||||
<a ng-if="node.removeDate" ng-click="clearUnpublishDate()" href="" style="text-decoration: underline;">
|
||||
<small>Clear date</small>
|
||||
<small><localize key="content_removeDate">Clear date</localize></small>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -158,9 +165,15 @@
|
||||
<umb-box data-element="node-info-general">
|
||||
<umb-box-header title-key="general_general"></umb-box-header>
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
<umb-control-group data-element="node-info-status" label="@general_status">
|
||||
<umb-badge size="xs" color="{{publishStatus.color}}">
|
||||
{{publishStatus.label}}
|
||||
</umb-badge>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group data-element="node-info-create-date" label="@template_createdDate">
|
||||
{{node.createDate | amDateFormat:'MMMM Do YYYY, HH:mm'}} by {{ node.owner.name }}
|
||||
{{node.createDateFormatted}} <localize key="general_by">by</localize> {{ node.owner.name }}
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group data-element="node-info-document-type" label="@content_documentType">
|
||||
@@ -191,4 +204,4 @@
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div data-element="editor-content" class="umb-editor-container umb-panel-body umb-scrollable row-fluid" ng-class="{'-stop-scrolling': numberOfOverlays > 0}">
|
||||
<div data-element="editor-container" class="umb-editor-container umb-panel-body umb-scrollable row-fluid" ng-class="{'-stop-scrolling': numberOfOverlays > 0}">
|
||||
<umb-overlay-backdrop></umb-overlay-backdrop>
|
||||
<div class="umb-pane">
|
||||
<div ng-transclude></div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="umb-tab-buttons umb-editor-drawer">
|
||||
<div data-element="editor-footer" class="umb-tab-buttons umb-editor-drawer">
|
||||
|
||||
<div class="umb-editor-drawer-content" ng-transclude>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<div class="umb-panel-header-left-side" ng-class="{'-top-position': tabs || !icon && hideDescription}">
|
||||
|
||||
<ng-form data-element=="editor-icon" name="iconForm">
|
||||
<ng-form data-element="editor-icon" name="iconForm">
|
||||
<div class="umb-panel-header-icon" ng-if="!hideIcon" ng-click="openIconPicker()" ng-class="{'-placeholder': $parent.icon==='' || $parent.icon===null}" title="{{$parent.icon}}">
|
||||
<i class="icon {{$parent.icon}}" ng-if="$parent.icon!=='' && $parent.icon!==null"></i>
|
||||
<div class="umb-panel-header-icon-text" ng-if="$parent.icon==='' || $parent.icon===null">
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<ul class="umb-group-builder__groups" ui-sortable="sortableOptionsGroup" ng-model="model.groups">
|
||||
|
||||
<li ng-repeat="tab in model.groups" ng-class="{'umb-group-builder__group-sortable': sortingMode}" data-element="group-{{$index}}">
|
||||
<li ng-repeat="tab in model.groups" ng-class="{'umb-group-builder__group-sortable': sortingMode}" data-element="group-{{tab.name}}">
|
||||
|
||||
<!-- TAB INIT STATE -->
|
||||
<a href="" class="umb-group-builder__group -placeholder" hotkey="alt+shift+t" ng-click="addGroup(tab)" ng-if="tab.tabState=='init' && !sortingMode" data-element="group-add">
|
||||
@@ -110,7 +110,7 @@
|
||||
|
||||
<ul class="umb-group-builder__properties" ui-sortable="sortableOptionsProperty" ng-model="tab.properties">
|
||||
|
||||
<li ng-class="{'umb-group-builder__property-sortable': sortingMode && !property.inherited}" ng-repeat="property in tab.properties">
|
||||
<li data-element="property-{{property.alias}}" ng-class="{'umb-group-builder__property-sortable': sortingMode && !property.inherited}" ng-repeat="property in tab.properties">
|
||||
|
||||
<!-- Init property / Property placeholder / add new property -->
|
||||
<a href=""
|
||||
|
||||
@@ -29,6 +29,17 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController"
|
||||
contentResource.getById($scope.relation.parentId).then(function (data) {
|
||||
$scope.target = data;
|
||||
|
||||
// make sure the target item isn't in the recycle bin
|
||||
if($scope.target.path.indexOf("-20") !== -1) {
|
||||
$scope.error = {
|
||||
errorMsg: "Cannot automatically restore this item",
|
||||
data: {
|
||||
Message: "The item you want to restore it under (" + $scope.target.name + ") is in the recycle bin. Use the Move menu item to move the item manually."
|
||||
}
|
||||
};
|
||||
$scope.success = false;
|
||||
}
|
||||
|
||||
}, function (err) {
|
||||
$scope.success = false;
|
||||
$scope.error = err;
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
<umb-pane>
|
||||
|
||||
<p class="abstract" ng-hide="error != null || success == true">
|
||||
Restore <strong>{{currentNode.name}}</strong> to under <strong>{{target.name}}</strong>?
|
||||
Restore <strong>{{currentNode.name}}</strong> under <strong>{{target.name}}</strong>?
|
||||
</p>
|
||||
|
||||
<div class="alert alert-error" ng-show="error != null">
|
||||
<h4>{{error.errorMsg}}</h4>
|
||||
<p>{{error.data.Message}}</p>
|
||||
<div><strong>{{error.errorMsg}}</strong></div>
|
||||
<div>{{error.data.Message}}</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success" ng-show="success == true">
|
||||
|
||||
+7
-9
@@ -1,5 +1,5 @@
|
||||
<h3>Hours of Umbraco training videos are only a click away</h3>
|
||||
<p>Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit <a class="btn-link -underline" href="http://umbraco.tv" target="_blank">umbraco.tv</a> for even more Umbraco videos</p>
|
||||
<p>Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco, then visit <a class="btn-link -underline" href="http://umbraco.tv" target="_blank">umbraco.tv</a> for even more Umbraco videos.</p>
|
||||
|
||||
<div class="row-fluid"
|
||||
ng-init="init('http://umbraco.tv/videos/developer/chapterrss?sort=no')"
|
||||
@@ -7,14 +7,12 @@
|
||||
|
||||
<ul class="thumbnails" >
|
||||
<li class="span2" ng-repeat="video in videos">
|
||||
<div class="thumbnail" style="margin-right: 20px; padding: 20px;">
|
||||
<a class="btn-link -underline" target="_blank" href="{{video.link}}" title="{{video.title}}">
|
||||
<img ng-src="{{video.thumbnail}}" alt="{{video.title}}">
|
||||
</a>
|
||||
<a target="_blank" href="{{video.link}}" title="{{video.title}}">
|
||||
<div style="font-weight: bold; text-align: center; margin: 20px 0 0;">{{video.title}}</div>
|
||||
</a>
|
||||
</div>
|
||||
<a class="btn-link" target="_blank" href="{{video.link}}" title="{{video.title}}">
|
||||
<div class="thumbnail" style="margin-right: 20px; padding: 20px;">
|
||||
<img ng-src="{{video.thumbnail}}" alt="{{video.title}}">
|
||||
<div style="font-weight: bold; text-align: center; margin: 20px 0 0;">{{video.title}}</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@
|
||||
<umb-overlay
|
||||
ng-if="vm.childNodeSelectorOverlay.show"
|
||||
model="vm.childNodeSelectorOverlay"
|
||||
position="center"
|
||||
position="target"
|
||||
view="vm.childNodeSelectorOverlay.view">
|
||||
</umb-overlay>
|
||||
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
function imageFilePickerController($scope) {
|
||||
|
||||
$scope.pick = function() {
|
||||
$scope.mediaPickerDialog = {};
|
||||
$scope.mediaPickerDialog.view = "mediapicker";
|
||||
$scope.mediaPickerDialog.show = true;
|
||||
|
||||
$scope.mediaPickerDialog.submit = function(model) {
|
||||
$scope.model.value = model.selectedImages[0].image;
|
||||
$scope.mediaPickerDialog.show = false;
|
||||
$scope.mediaPickerDialog = null;
|
||||
$scope.add = function() {
|
||||
$scope.mediaPickerOverlay = {
|
||||
view: "mediapicker",
|
||||
disableFolderSelect: true,
|
||||
onlyImages: true,
|
||||
show: true,
|
||||
submit: function (model) {
|
||||
$scope.model.value = model.selectedImages[0].image;
|
||||
$scope.mediaPickerOverlay.show = false;
|
||||
$scope.mediaPickerOverlay = null;
|
||||
},
|
||||
close: function () {
|
||||
$scope.mediaPickerOverlay.show = false;
|
||||
$scope.mediaPickerOverlay = null;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$scope.mediaPickerDialog.close = function(oldModel) {
|
||||
$scope.mediaPickerDialog.show = false;
|
||||
$scope.mediaPickerDialog = null;
|
||||
};
|
||||
$scope.remove = function () {
|
||||
$scope.model.value = null;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
<div ng-controller="Umbraco.PrevalueEditors.ImageFilePickerController" class="umb-editor umb-mediapicker">
|
||||
<ul class="umb-sortable-thumbnails">
|
||||
<li>
|
||||
<img ng-src="{{model.value}}" alt="" ng-show="model.value">
|
||||
<a href class="picked-image" ng-click="model.value = null" ng-show="model.value"><i class="icon icon-delete"></i></a>
|
||||
|
||||
<ul class="umb-sortable-thumbnails" ng-if="model.value">
|
||||
<li class="umb-sortable-thumbnails__wrapper">
|
||||
<img ng-src="{{model.value}}" alt="">
|
||||
|
||||
<div class="umb-sortable-thumbnails__actions">
|
||||
<!--<a class="umb-sortable-thumbnails__action" href="" ng-click="goToItem(image)">
|
||||
<i class="icon icon-edit"></i>
|
||||
</a>-->
|
||||
<a class="umb-sortable-thumbnails__action -red" href="" ng-click="remove()">
|
||||
<i class="icon icon-delete"></i>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<a href ng-click="pick()" class="umb-mediapicker add-link" ng-hide="model.value">
|
||||
<a href="#" class="add-link" ng-click="add()" ng-class="{'add-link-square': !model.value }" ng-hide="model.value" prevent-default>
|
||||
<i class="icon icon-add large"></i>
|
||||
</a>
|
||||
|
||||
<umb-overlay
|
||||
ng-if="mediaPickerDialog.show"
|
||||
model="mediaPickerDialog"
|
||||
view="mediaPickerDialog.view"
|
||||
ng-if="mediaPickerOverlay.show"
|
||||
model="mediaPickerOverlay"
|
||||
view="mediaPickerOverlay.view"
|
||||
position="right">
|
||||
</umb-overlay>
|
||||
|
||||
|
||||
@@ -640,16 +640,16 @@ angular.module("umbraco")
|
||||
var clear = true;
|
||||
|
||||
//settings indicator shortcut
|
||||
if ( ($scope.model.config.items.config && $scope.model.config.items.config.length > 0) || ($scope.model.config.items.styles && $scope.model.config.items.styles.length > 0)) {
|
||||
if (($scope.model.config.items.config && $scope.model.config.items.config.length > 0) || ($scope.model.config.items.styles && $scope.model.config.items.styles.length > 0)) {
|
||||
$scope.hasSettings = true;
|
||||
}
|
||||
|
||||
//ensure the grid has a column value set,
|
||||
//if nothing is found, set it to 12
|
||||
if ($scope.model.config.items.columns && angular.isString($scope.model.config.items.columns)) {
|
||||
$scope.model.config.items.columns = parseInt($scope.model.config.items.columns);
|
||||
} else {
|
||||
if (!$scope.model.config.items.columns){
|
||||
$scope.model.config.items.columns = 12;
|
||||
} else if (angular.isString($scope.model.config.items.columns)) {
|
||||
$scope.model.config.items.columns = parseInt($scope.model.config.items.columns);
|
||||
}
|
||||
|
||||
if ($scope.model.value && $scope.model.value.sections && $scope.model.value.sections.length > 0 && $scope.model.value.sections[0].rows && $scope.model.value.sections[0].rows.length > 0) {
|
||||
|
||||
@@ -299,7 +299,7 @@
|
||||
ng-if="editorOverlay.show"
|
||||
model="editorOverlay"
|
||||
view="editorOverlay.view"
|
||||
position="center">
|
||||
position="target">
|
||||
</umb-overlay>
|
||||
|
||||
<umb-overlay
|
||||
|
||||
+2
@@ -19,6 +19,8 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
|
||||
$scope.images = [];
|
||||
$scope.ids = [];
|
||||
|
||||
$scope.isMultiPicker = multiPicker;
|
||||
|
||||
if ($scope.model.value) {
|
||||
var ids = $scope.model.value.split(',');
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
<p ng-if="(images|filter:{trashed:true}).length == 1"><localize key="mediaPicker_pickedTrashedItem"></localize></p>
|
||||
<p ng-if="(images|filter:{trashed:true}).length > 1"><localize key="mediaPicker_pickedTrashedItems"></localize></p>
|
||||
|
||||
<div class="flex error">
|
||||
|
||||
<div data-element="sortable-thumbnails" class="flex error">
|
||||
<ul ui-sortable="sortableOptions" ng-model="images" class="umb-sortable-thumbnails">
|
||||
<li style="width: 120px; height: 100px; overflow: hidden;" ng-repeat="image in images">
|
||||
<li data-element="sortable-thumbnail-{{$index}}" class="umb-sortable-thumbnails__wrapper" ng-repeat="image in images track by $index">
|
||||
|
||||
<!-- IMAGE -->
|
||||
<img ng-class="{'trashed': image.trashed}" ng-src="{{image.thumbnail}}" alt="" ng-show="image.thumbnail" title="{{image.trashed ? 'Trashed: ' + image.name : image.name}}" />
|
||||
@@ -20,24 +20,25 @@
|
||||
<small>{{image.name}}</small>
|
||||
</span>
|
||||
|
||||
<div class="umb-sortable-thumbnails__actions">
|
||||
<a class="umb-sortable-thumbnails__action" href="" ng-click="goToItem(image)">
|
||||
<div class="umb-sortable-thumbnails__actions" data-element="sortable-thumbnail-actions">
|
||||
<a class="umb-sortable-thumbnails__action" data-element="action-edit" href="" ng-click="goToItem(image)">
|
||||
<i class="icon icon-edit"></i>
|
||||
</a>
|
||||
<a class="umb-sortable-thumbnails__action -red" href="" ng-click="remove($index)">
|
||||
<a class="umb-sortable-thumbnails__action -red" data-element="action-remove" href="" ng-click="remove($index)">
|
||||
<i class="icon icon-delete"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
<li style="border: none;">
|
||||
<a data-element="sortable-thumbnails-add" href="#" class="add-link" ng-click="add()" ng-class="{'add-link-square': (images.length === 0 || isMultiPicker)}" ng-if="showAdd()" prevent-default>
|
||||
<i class="icon icon-add large"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<a href="#" class="add-link" ng-click="add()" ng-class="{'add-link-square': images.length === 0}" ng-if="showAdd()" prevent-default>
|
||||
<i class="icon icon-add large"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<umb-overlay
|
||||
data-element="overlay-media-picker"
|
||||
ng-if="mediaPickerOverlay.show"
|
||||
model="mediaPickerOverlay"
|
||||
position="right"
|
||||
|
||||
@@ -374,6 +374,9 @@ angular.module("umbraco")
|
||||
// element might still be there even after the modal has been hidden.
|
||||
$scope.$on('$destroy', function () {
|
||||
unsubscribe();
|
||||
if (tinyMceEditor !== undefined && tinyMceEditor != null) {
|
||||
tinyMceEditor.destroy()
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function UserEditController($scope, $timeout, $location, $routeParams, formHelper, usersResource, userService, contentEditingHelper, localizationService, notificationsService, mediaHelper, Upload, umbRequestHelper, usersHelper, authResource, dateHelper) {
|
||||
function UserEditController($scope, eventsService, $q, $timeout, $location, $routeParams, formHelper, usersResource, userService, contentEditingHelper, localizationService, notificationsService, mediaHelper, Upload, umbRequestHelper, usersHelper, authResource, dateHelper) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB";
|
||||
vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes);
|
||||
vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail;
|
||||
|
||||
|
||||
//create the initial model for change password
|
||||
vm.changePasswordModel = {
|
||||
config: {},
|
||||
@@ -33,6 +33,7 @@
|
||||
vm.unlockUser = unlockUser;
|
||||
vm.clearAvatar = clearAvatar;
|
||||
vm.save = save;
|
||||
|
||||
vm.toggleChangePassword = toggleChangePassword;
|
||||
|
||||
function init() {
|
||||
@@ -117,38 +118,83 @@
|
||||
|
||||
function save() {
|
||||
|
||||
vm.page.saveButtonState = "busy";
|
||||
vm.user.resetPasswordValue = null;
|
||||
if (formHelper.submitForm({ scope: $scope, statusMessage: vm.labels.saving })) {
|
||||
|
||||
//anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here
|
||||
if(vm.user.changePassword) {
|
||||
vm.user.changePassword.reset = !vm.user.changePassword.oldPassword && !vm.user.isCurrentUser;
|
||||
//anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here
|
||||
if (vm.user.changePassword) {
|
||||
vm.user.changePassword.reset = !vm.user.changePassword.oldPassword && !vm.user.isCurrentUser;
|
||||
}
|
||||
|
||||
vm.page.saveButtonState = "busy";
|
||||
vm.user.resetPasswordValue = null;
|
||||
|
||||
//save current nav to be restored later so that the tabs dont change
|
||||
var currentNav = vm.user.navigation;
|
||||
|
||||
usersResource.saveUser(vm.user)
|
||||
.then(function (saved) {
|
||||
|
||||
//if the user saved, then try to execute all extended save options
|
||||
extendedSave(saved).then(function(result) {
|
||||
//if all is good, then reset the form
|
||||
formHelper.resetForm({ scope: $scope, notifications: saved.notifications });
|
||||
}, function(err) {
|
||||
//otherwise show the notifications for the user being saved
|
||||
formHelper.showNotifications(saved);
|
||||
});
|
||||
|
||||
vm.user = _.omit(saved, "navigation");
|
||||
//restore
|
||||
vm.user.navigation = currentNav;
|
||||
setUserDisplayState();
|
||||
formatDatesToLocal(vm.user);
|
||||
|
||||
vm.changePasswordModel.isChanging = false;
|
||||
//the user has a password if they are not states: Invited, NoCredentials
|
||||
vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4;
|
||||
|
||||
vm.page.saveButtonState = "success";
|
||||
|
||||
}, function (err) {
|
||||
|
||||
contentEditingHelper.handleSaveError({
|
||||
redirectOnFailure: false,
|
||||
err: err
|
||||
});
|
||||
//show any notifications
|
||||
if (err.data) {
|
||||
formHelper.showNotifications(err.data);
|
||||
}
|
||||
vm.page.saveButtonState = "error";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
contentEditingHelper.contentEditorPerformSave({
|
||||
statusMessage: vm.labels.saving,
|
||||
saveMethod: usersResource.saveUser,
|
||||
scope: $scope,
|
||||
content: vm.user,
|
||||
// We do not redirect on failure for users - this is because it is not possible to actually save a user
|
||||
// when server side validation fails - as opposed to content where we are capable of saving the content
|
||||
// item if server side validation fails
|
||||
redirectOnFailure: false,
|
||||
rebindCallback: function (orignal, saved) { }
|
||||
}).then(function (saved) {
|
||||
/**
|
||||
* Used to emit the save event and await any async operations being performed by editor extensions
|
||||
* @param {any} savedUser
|
||||
*/
|
||||
function extendedSave(savedUser) {
|
||||
|
||||
vm.user = saved;
|
||||
setUserDisplayState();
|
||||
formatDatesToLocal(vm.user);
|
||||
//used to track any promises added by the event handlers to be awaited
|
||||
var promises = [];
|
||||
|
||||
var args = {
|
||||
//getPromise: getPromise,
|
||||
user: savedUser,
|
||||
//a promise can be added by the event handler if the handler needs an async operation to be awaited
|
||||
addPromise: function (p) {
|
||||
promises.push(p);
|
||||
}
|
||||
};
|
||||
|
||||
vm.changePasswordModel.isChanging = false;
|
||||
vm.page.saveButtonState = "success";
|
||||
|
||||
//the user has a password if they are not states: Invited, NoCredentials
|
||||
vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4;
|
||||
}, function (err) {
|
||||
vm.page.saveButtonState = "error";
|
||||
});
|
||||
//emit the event
|
||||
eventsService.emit("editors.user.editController.save", args);
|
||||
|
||||
//await all promises to complete
|
||||
var resultPromise = $q.all(promises);
|
||||
|
||||
return resultPromise;
|
||||
}
|
||||
|
||||
function goToPage(ancestor) {
|
||||
|
||||
@@ -1,406 +1,27 @@
|
||||
<div ng-controller="Umbraco.Editors.Users.UserController as vm" class="clearfix">
|
||||
|
||||
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
|
||||
|
||||
<form name="editUserForm" novalidate val-form-manager>
|
||||
|
||||
<umb-editor-view>
|
||||
|
||||
<umb-editor-header
|
||||
name="vm.user.name"
|
||||
hide-icon="true"
|
||||
hide-description="true"
|
||||
hide-alias="true">
|
||||
<umb-editor-header name="vm.user.name"
|
||||
hide-icon="true"
|
||||
hide-description="true"
|
||||
hide-alias="true"
|
||||
navigation="vm.user.navigation">
|
||||
</umb-editor-header>
|
||||
|
||||
|
||||
<umb-editor-container>
|
||||
|
||||
<umb-load-indicator
|
||||
ng-if="vm.loading">
|
||||
</umb-load-indicator>
|
||||
|
||||
<div ng-if="!vm.loading" class="umb-packages-view-wrapper" style="padding: 0;">
|
||||
|
||||
<div class="umb-package-details">
|
||||
<div ng-if="!vm.loading" class="umb-packages-view-wrapper" style="padding: 0;">
|
||||
|
||||
<div class="umb-package-details__main-content">
|
||||
|
||||
<umb-box>
|
||||
|
||||
<umb-box-header title-key="user_profile"></umb-box-header>
|
||||
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
<umb-control-group label="@general_email" required="true">
|
||||
|
||||
<input
|
||||
type="email"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_enteremail"
|
||||
class="input-block-level"
|
||||
ng-model="vm.user.email"
|
||||
umb-auto-focus name="email"
|
||||
val-email
|
||||
required
|
||||
val-server-field="Email" />
|
||||
<span class="help-inline" val-msg-for="email" val-toggle-msg="required"><localize key="general_required">Required</localize></span>
|
||||
<span class="help-inline" val-msg-for="email" val-toggle-msg="valServerField"></span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@general_username" ng-if="!vm.usernameIsEmail" required="true">
|
||||
<input
|
||||
type="text"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_enterusername"
|
||||
class="input-block-level"
|
||||
ng-model="vm.user.username"
|
||||
umb-auto-focus name="username"
|
||||
required
|
||||
val-server-field="Username" />
|
||||
<span class="help-inline" val-msg-for="username" val-toggle-msg="required"><localize key="general_required">Required</localize></span>
|
||||
<span class="help-inline" val-msg-for="username" val-toggle-msg="valServerField"></span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@user_language" description="@user_languageHelp">
|
||||
<select
|
||||
class="input-block-level"
|
||||
ng-model="vm.user.culture"
|
||||
ng-options="key as value for (key, value) in vm.user.availableCultures"
|
||||
name="culture"
|
||||
required
|
||||
val-server-field="Culture">
|
||||
</select>
|
||||
<span class="help-inline" val-msg-for="culture" val-toggle-msg="required"><localize key="general_required">Required</localize></span>
|
||||
<span class="help-inline" val-msg-for="culture" val-toggle-msg="valServerField"></span>
|
||||
</umb-control-group>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<umb-box>
|
||||
<umb-box-header title-key="user_assignAccess"></umb-box-header>
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
<umb-control-group style="margin-bottom: 25px;" label="@general_groups" description="@user_groupsHelp" required="true">
|
||||
|
||||
<umb-user-group-preview
|
||||
ng-repeat="userGroup in vm.user.userGroups"
|
||||
icon="userGroup.icon"
|
||||
name="userGroup.name"
|
||||
sections="userGroup.sections"
|
||||
content-start-node="userGroup.contentStartNode"
|
||||
media-start-node="userGroup.mediaStartNode"
|
||||
allow-remove="!vm.user.isCurrentUser"
|
||||
on-remove="vm.removeSelectedItem($index, vm.user.userGroups)">
|
||||
</umb-user-group-preview>
|
||||
|
||||
<a href=""
|
||||
ng-if="!vm.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
class="umb-node-preview-add"
|
||||
ng-click="vm.openUserGroupPicker()"
|
||||
prevent-default>
|
||||
<localize key="general_add">Add</localize>
|
||||
</a>
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group style="margin-bottom: 25px;" label="@user_startnodes" description="@user_startnodeshelp">
|
||||
|
||||
<umb-node-preview
|
||||
style="max-width: 100%;"
|
||||
ng-repeat="node in vm.user.startContentIds"
|
||||
icon="node.icon"
|
||||
name="node.name"
|
||||
allow-remove="!vm.user.isCurrentUser"
|
||||
on-remove="vm.removeSelectedItem($index, vm.user.startContentIds)">
|
||||
</umb-node-preview>
|
||||
|
||||
<umb-node-preview
|
||||
ng-if="vm.user.startContentIds.length === 0 && vm.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
name="vm.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
|
||||
<a href=""
|
||||
ng-if="!vm.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
class="umb-node-preview-add"
|
||||
ng-click="vm.openContentPicker()"
|
||||
prevent-default>
|
||||
<localize key="general_add">Add</localize>
|
||||
</a>
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@user_mediastartnodes" description="@user_mediastartnodeshelp">
|
||||
|
||||
<umb-node-preview
|
||||
style="max-width: 100%;"
|
||||
ng-repeat="node in vm.user.startMediaIds"
|
||||
icon="node.icon"
|
||||
name="node.name"
|
||||
allow-remove="!vm.user.isCurrentUser"
|
||||
on-remove="vm.removeSelectedItem($index, vm.user.startMediaIds)">
|
||||
</umb-node-preview>
|
||||
|
||||
<umb-node-preview
|
||||
ng-if="vm.user.startMediaIds.length === 0 && vm.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
name="vm.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
|
||||
<a href=""
|
||||
ng-if="!vm.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
class="umb-node-preview-add"
|
||||
ng-click="vm.openMediaPicker()"
|
||||
prevent-default>
|
||||
<localize key="general_add">Add</localize>
|
||||
</a>
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<umb-box>
|
||||
<umb-box-header title-key="user_access" description-key="user_accessHelp"></umb-box-header>
|
||||
<umb-box-content class="block-form" style="padding-bottom: 0;">
|
||||
|
||||
<umb-control-group label="@sections_content">
|
||||
<umb-node-preview
|
||||
style="max-width: 100%;"
|
||||
ng-repeat="node in vm.user.calculatedStartContentIds"
|
||||
icon="node.icon"
|
||||
name="node.name">
|
||||
</umb-node-preview>
|
||||
<umb-node-preview
|
||||
ng-if="vm.user.calculatedStartContentIds.length === 0"
|
||||
style="max-width: 100%;"
|
||||
name="vm.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@sections_media">
|
||||
<umb-node-preview
|
||||
style="max-width: 100%;"
|
||||
ng-repeat="node in vm.user.calculatedStartMediaIds"
|
||||
icon="node.icon"
|
||||
name="node.name">
|
||||
</umb-node-preview>
|
||||
<umb-node-preview
|
||||
ng-if="vm.user.calculatedStartMediaIds.length === 0"
|
||||
style="max-width: 100%;"
|
||||
name="vm.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
</umb-control-group>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__sidebar">
|
||||
|
||||
<div class="umb-package-details__section">
|
||||
|
||||
<!-- Avatar -->
|
||||
<div style="margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #d8d7d9;">
|
||||
<ng-form name="avatarForm" class="flex flex-column justify-center items-center">
|
||||
|
||||
<umb-avatar
|
||||
style="margin-bottom: 15px;"
|
||||
color="secondary"
|
||||
size="xxl"
|
||||
name="{{vm.user.name}}"
|
||||
img-src="{{vm.user.avatars[3]}}"
|
||||
img-srcset="{{vm.user.avatars[4]}} 2x, {{vm.user.avatars[4]}} 3x">
|
||||
</umb-avatar>
|
||||
|
||||
<umb-progress-bar
|
||||
style="max-width: 120px;"
|
||||
ng-if="vm.avatarFile.uploadStatus === 'uploading'"
|
||||
progress="{{ vm.avatarFile.uploadProgress }}"
|
||||
size="s">
|
||||
</umb-progress-bar>
|
||||
|
||||
<div class="flex items-center" ng-if="vm.avatarFile.uploadStatus !== 'uploading'">
|
||||
|
||||
<a href=""
|
||||
class="umb-user-group-preview__action"
|
||||
ngf-select ng-model="filesHolder"
|
||||
ngf-change="changeAvatar($files, $event)"
|
||||
ngf-multiple="false"
|
||||
ngf-pattern="{{vm.acceptedFileTypes}}"
|
||||
ngf-max-size="{{ vm.maxFileSize }}">
|
||||
<localize key="user_changePhoto">Change photo</localize>
|
||||
</a>
|
||||
|
||||
<a href=""
|
||||
ng-if="vm.user.avatars"
|
||||
class="umb-user-group-preview__action umb-user-group-preview__action--red"
|
||||
ng-click="vm.clearAvatar()"
|
||||
prevent-default>
|
||||
<localize key="user_removePhoto">Remove photo</localize>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</ng-form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div style="margin-bottom: 20px;">
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
<umb-button
|
||||
ng-if="vm.user.userDisplayState.key === 'Disabled' && !vm.user.isCurrentUser"
|
||||
type="button"
|
||||
button-style="[success,block]"
|
||||
state="vm.enableUserButtonState"
|
||||
action="vm.enableUser()"
|
||||
label="Enable"
|
||||
label-key="actions_enable"
|
||||
size="s">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
<umb-button
|
||||
ng-if="vm.user.userDisplayState.key === 'LockedOut' && !vm.user.isCurrentUser"
|
||||
type="button"
|
||||
button-style="[success,block]"
|
||||
state="vm.unlockUserButtonState"
|
||||
action="vm.unlockUser()"
|
||||
label="Unlock"
|
||||
label-key="actions_unlock"
|
||||
size="s">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
<umb-button
|
||||
ng-if="vm.user.userDisplayState.key !== 'Disabled' && !vm.user.isCurrentUser"
|
||||
type="button"
|
||||
button-style="[info,block]"
|
||||
action="vm.disableUser()"
|
||||
state="vm.disableUserButtonState"
|
||||
label="Disable"
|
||||
label-key="actions_disable"
|
||||
size="s">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
<umb-button
|
||||
type="button"
|
||||
button-style="[info,block]"
|
||||
action="vm.toggleChangePassword()"
|
||||
label="Change password"
|
||||
label-key="general_changePassword"
|
||||
state="changePasswordButtonState"
|
||||
ng-if="vm.changePasswordModel.isChanging === false"
|
||||
size="s">
|
||||
</umb-button>
|
||||
|
||||
<ng-form ng-if="vm.changePasswordModel.isChanging" name="passwordForm" class="block-form" val-form-manager>
|
||||
|
||||
<change-password
|
||||
password-values="vm.user.changePassword"
|
||||
config="vm.changePasswordModel.config">
|
||||
</change-password>
|
||||
|
||||
<umb-button
|
||||
type="button"
|
||||
action="vm.toggleChangePassword()"
|
||||
label="Cancel"
|
||||
label-key="general_cancel"
|
||||
button-style="cancel">
|
||||
</umb-button>
|
||||
|
||||
</ng-form>
|
||||
|
||||
<div ng-if="vm.user.resetPasswordValue">
|
||||
<p><br />Password reset to value: <strong>{{vm.user.resetPasswordValue}}</strong></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- User stats -->
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="general_status">Status</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<umb-badge style="margin-top: 4px;" size="s" color="{{vm.user.userDisplayState.color}}">
|
||||
{{vm.user.userDisplayState.name}}
|
||||
</umb-badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_lastLogin">Last login</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<span ng-if="vm.user.lastLoginDate">{{ vm.user.formattedLastLogin }}</span>
|
||||
<span ng-if="!vm.user.lastLoginDate">{{ vm.user.name | umbWordLimit:1 }} <localize key="user_noLogin">has not logged in yet</localize></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_failedPasswordAttempts">Failed login attempts</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
{{ vm.user.failedPasswordAttempts }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_lastLockoutDate">Last lockout date</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<span ng-if="vm.user.lastLockoutDate === '0001-01-01T00:00:00'">
|
||||
{{ vm.user.name | umbWordLimit:1 }} <localize key="user_noLockouts">hasn't been locked out</localize>
|
||||
</span>
|
||||
<span ng-if="vm.user.lastLockoutDate !== '0001-01-01T00:00:00'">{{ vm.user.formattedLastLockoutDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_lastPasswordChangeDate">Password is last changed</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<span ng-if="vm.user.lastPasswordChangeDate === '0001-01-01T00:00:00'">
|
||||
<localize key="user_noPasswordChange">The password hasn't been changed</localize>
|
||||
</span>
|
||||
<span ng-if="vm.user.lastPasswordChangeDate !== '0001-01-01T00:00:00'">{{ vm.user.formattedLastPasswordChangeDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_createDate">User is created</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
{{ vm.user.formattedCreateDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_updateDate">User is last updated</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
{{ vm.user.formattedUpdateDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<umb-editor-sub-views
|
||||
ng-if="!vm.loading"
|
||||
sub-views="vm.user.navigation"
|
||||
model="vm">
|
||||
</umb-editor-sub-views>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -410,33 +31,30 @@
|
||||
|
||||
<umb-editor-footer-content-left>
|
||||
|
||||
<umb-breadcrumbs
|
||||
ancestors="vm.breadcrumbs"
|
||||
allow-on-open="true"
|
||||
on-open="vm.goToPage(ancestor)">
|
||||
<umb-breadcrumbs ancestors="vm.breadcrumbs"
|
||||
allow-on-open="true"
|
||||
on-open="vm.goToPage(ancestor)">
|
||||
</umb-breadcrumbs>
|
||||
|
||||
</umb-editor-footer-content-left>
|
||||
|
||||
<umb-editor-footer-content-right>
|
||||
|
||||
<umb-button
|
||||
type="button"
|
||||
action="vm.goToPage(vm.breadcrumbs[0])"
|
||||
label="Return to list"
|
||||
label-key="buttons_returnToList"
|
||||
disabled="vm.loading">
|
||||
<umb-button type="button"
|
||||
action="vm.goToPage(vm.breadcrumbs[0])"
|
||||
label="Return to list"
|
||||
label-key="buttons_returnToList"
|
||||
disabled="vm.loading">
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
type="button"
|
||||
action="vm.save()"
|
||||
state="vm.page.saveButtonState"
|
||||
button-style="success"
|
||||
shortcut="ctrl+s"
|
||||
label="Save"
|
||||
label-key="buttons_save"
|
||||
disabled="vm.loading">
|
||||
<umb-button type="button"
|
||||
action="vm.save()"
|
||||
state="vm.page.saveButtonState"
|
||||
button-style="success"
|
||||
shortcut="ctrl+s"
|
||||
label="Save"
|
||||
label-key="buttons_save"
|
||||
disabled="vm.loading">
|
||||
</umb-button>
|
||||
|
||||
</umb-editor-footer-content-right>
|
||||
@@ -447,25 +65,22 @@
|
||||
|
||||
</form>
|
||||
|
||||
<umb-overlay
|
||||
ng-if="vm.userGroupPicker.show"
|
||||
model="vm.userGroupPicker"
|
||||
view="vm.userGroupPicker.view"
|
||||
position="right">
|
||||
<umb-overlay ng-if="vm.userGroupPicker.show"
|
||||
model="vm.userGroupPicker"
|
||||
view="vm.userGroupPicker.view"
|
||||
position="right">
|
||||
</umb-overlay>
|
||||
|
||||
<umb-overlay
|
||||
ng-if="vm.contentPicker.show"
|
||||
model="vm.contentPicker"
|
||||
view="vm.contentPicker.view"
|
||||
position="right">
|
||||
<umb-overlay ng-if="vm.contentPicker.show"
|
||||
model="vm.contentPicker"
|
||||
view="vm.contentPicker.view"
|
||||
position="right">
|
||||
</umb-overlay>
|
||||
|
||||
<umb-overlay
|
||||
ng-if="vm.mediaPicker.show"
|
||||
model="vm.mediaPicker"
|
||||
view="vm.mediaPicker.view"
|
||||
position="right">
|
||||
<umb-overlay ng-if="vm.mediaPicker.show"
|
||||
model="vm.mediaPicker"
|
||||
view="vm.mediaPicker.view"
|
||||
position="right">
|
||||
</umb-overlay>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,361 @@
|
||||
<div class="umb-package-details">
|
||||
|
||||
<div class="umb-package-details__main-content">
|
||||
|
||||
<umb-box>
|
||||
|
||||
<umb-box-header title-key="user_profile"></umb-box-header>
|
||||
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
<umb-control-group label="@general_email" required="true">
|
||||
|
||||
<input type="email"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_enteremail"
|
||||
class="input-block-level"
|
||||
ng-model="model.user.email"
|
||||
umb-auto-focus name="email"
|
||||
val-email
|
||||
required
|
||||
val-server-field="Email" />
|
||||
<span class="help-inline" val-msg-for="email" val-toggle-msg="required"><localize key="general_required">Required</localize></span>
|
||||
<span class="help-inline" val-msg-for="email" val-toggle-msg="valServerField"></span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@general_username" ng-if="!model.usernameIsEmail" required="true">
|
||||
<input type="text"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_enterusername"
|
||||
class="input-block-level"
|
||||
ng-model="model.user.username"
|
||||
umb-auto-focus name="username"
|
||||
required
|
||||
val-server-field="Username" />
|
||||
<span class="help-inline" val-msg-for="username" val-toggle-msg="required"><localize key="general_required">Required</localize></span>
|
||||
<span class="help-inline" val-msg-for="username" val-toggle-msg="valServerField"></span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@user_language" description="@user_languageHelp">
|
||||
<select class="input-block-level"
|
||||
ng-model="model.user.culture"
|
||||
ng-options="key as value for (key, value) in model.user.availableCultures"
|
||||
name="culture"
|
||||
required
|
||||
val-server-field="Culture"></select>
|
||||
<span class="help-inline" val-msg-for="culture" val-toggle-msg="required"><localize key="general_required">Required</localize></span>
|
||||
<span class="help-inline" val-msg-for="culture" val-toggle-msg="valServerField"></span>
|
||||
</umb-control-group>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<umb-box>
|
||||
<umb-box-header title-key="user_assignAccess"></umb-box-header>
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
<umb-control-group style="margin-bottom: 25px;" label="@general_groups" description="@user_groupsHelp" required="true">
|
||||
|
||||
<umb-user-group-preview ng-repeat="userGroup in model.user.userGroups"
|
||||
icon="userGroup.icon"
|
||||
name="userGroup.name"
|
||||
sections="userGroup.sections"
|
||||
content-start-node="userGroup.contentStartNode"
|
||||
media-start-node="userGroup.mediaStartNode"
|
||||
allow-remove="!model.user.isCurrentUser"
|
||||
on-remove="model.removeSelectedItem($index, model.user.userGroups)">
|
||||
</umb-user-group-preview>
|
||||
|
||||
<a href=""
|
||||
ng-if="!model.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
class="umb-node-preview-add"
|
||||
ng-click="model.openUserGroupPicker()"
|
||||
prevent-default>
|
||||
<localize key="general_add">Add</localize>
|
||||
</a>
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group style="margin-bottom: 25px;" label="@user_startnodes" description="@user_startnodeshelp">
|
||||
|
||||
<umb-node-preview style="max-width: 100%;"
|
||||
ng-repeat="node in model.user.startContentIds"
|
||||
icon="node.icon"
|
||||
name="node.name"
|
||||
allow-remove="!model.user.isCurrentUser"
|
||||
on-remove="model.removeSelectedItem($index, model.user.startContentIds)">
|
||||
</umb-node-preview>
|
||||
|
||||
<umb-node-preview ng-if="model.user.startContentIds.length === 0 && model.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
name="model.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
|
||||
<a href=""
|
||||
ng-if="!model.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
class="umb-node-preview-add"
|
||||
ng-click="model.openContentPicker()"
|
||||
prevent-default>
|
||||
<localize key="general_add">Add</localize>
|
||||
</a>
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@user_mediastartnodes" description="@user_mediastartnodeshelp">
|
||||
|
||||
<umb-node-preview style="max-width: 100%;"
|
||||
ng-repeat="node in model.user.startMediaIds"
|
||||
icon="node.icon"
|
||||
name="node.name"
|
||||
allow-remove="!model.user.isCurrentUser"
|
||||
on-remove="model.removeSelectedItem($index, model.user.startMediaIds)">
|
||||
</umb-node-preview>
|
||||
|
||||
<umb-node-preview ng-if="model.user.startMediaIds.length === 0 && model.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
name="model.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
|
||||
<a href=""
|
||||
ng-if="!model.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
class="umb-node-preview-add"
|
||||
ng-click="model.openMediaPicker()"
|
||||
prevent-default>
|
||||
<localize key="general_add">Add</localize>
|
||||
</a>
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<umb-box>
|
||||
<umb-box-header title-key="user_access" description-key="user_accessHelp"></umb-box-header>
|
||||
<umb-box-content class="block-form" style="padding-bottom: 0;">
|
||||
|
||||
<umb-control-group label="@sections_content">
|
||||
<umb-node-preview style="max-width: 100%;"
|
||||
ng-repeat="node in model.user.calculatedStartContentIds"
|
||||
icon="node.icon"
|
||||
name="node.name">
|
||||
</umb-node-preview>
|
||||
<umb-node-preview ng-if="model.user.calculatedStartContentIds.length === 0"
|
||||
style="max-width: 100%;"
|
||||
name="model.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@sections_media">
|
||||
<umb-node-preview style="max-width: 100%;"
|
||||
ng-repeat="node in model.user.calculatedStartMediaIds"
|
||||
icon="node.icon"
|
||||
name="node.name">
|
||||
</umb-node-preview>
|
||||
<umb-node-preview ng-if="model.user.calculatedStartMediaIds.length === 0"
|
||||
style="max-width: 100%;"
|
||||
name="model.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
</umb-control-group>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__sidebar">
|
||||
|
||||
<div class="umb-package-details__section">
|
||||
|
||||
<!-- Avatar -->
|
||||
<div style="margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #d8d7d9;">
|
||||
<ng-form name="avatarForm" class="flex flex-column justify-center items-center">
|
||||
|
||||
<umb-avatar style="margin-bottom: 15px;"
|
||||
color="secondary"
|
||||
size="xxl"
|
||||
name="{{model.user.name}}"
|
||||
img-src="{{model.user.avatars[3]}}"
|
||||
img-srcset="{{model.user.avatars[4]}} 2x, {{model.user.avatars[4]}} 3x">
|
||||
</umb-avatar>
|
||||
|
||||
<umb-progress-bar style="max-width: 120px;"
|
||||
ng-if="model.avatarFile.uploadStatus === 'uploading'"
|
||||
progress="{{ model.avatarFile.uploadProgress }}"
|
||||
size="s">
|
||||
</umb-progress-bar>
|
||||
|
||||
<div class="flex items-center" ng-if="model.avatarFile.uploadStatus !== 'uploading'">
|
||||
|
||||
<a href=""
|
||||
class="umb-user-group-preview__action"
|
||||
ngf-select ng-model="filesHolder"
|
||||
ngf-change="changeAvatar($files, $event)"
|
||||
ngf-multiple="false"
|
||||
ngf-pattern="{{model.acceptedFileTypes}}"
|
||||
ngf-max-size="{{ model.maxFileSize }}">
|
||||
<localize key="user_changePhoto">Change photo</localize>
|
||||
</a>
|
||||
|
||||
<a href=""
|
||||
ng-if="model.user.avatars"
|
||||
class="umb-user-group-preview__action umb-user-group-preview__action--red"
|
||||
ng-click="model.clearAvatar()"
|
||||
prevent-default>
|
||||
<localize key="user_removePhoto">Remove photo</localize>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</ng-form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div style="margin-bottom: 20px;">
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
<umb-button ng-if="model.user.userDisplayState.key === 'Disabled' && !model.user.isCurrentUser"
|
||||
type="button"
|
||||
button-style="[success,block]"
|
||||
state="model.enableUserButtonState"
|
||||
action="model.enableUser()"
|
||||
label="Enable"
|
||||
label-key="actions_enable"
|
||||
size="s">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
<umb-button ng-if="model.user.userDisplayState.key === 'LockedOut' && !model.user.isCurrentUser"
|
||||
type="button"
|
||||
button-style="[success,block]"
|
||||
state="model.unlockUserButtonState"
|
||||
action="model.unlockUser()"
|
||||
label="Unlock"
|
||||
label-key="actions_unlock"
|
||||
size="s">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
<umb-button ng-if="model.user.userDisplayState.key !== 'Disabled' && !model.user.isCurrentUser"
|
||||
type="button"
|
||||
button-style="[info,block]"
|
||||
action="model.disableUser()"
|
||||
state="model.disableUserButtonState"
|
||||
label="Disable"
|
||||
label-key="actions_disable"
|
||||
size="s">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
<umb-button type="button"
|
||||
button-style="[info,block]"
|
||||
action="model.toggleChangePassword()"
|
||||
label="Change password"
|
||||
label-key="general_changePassword"
|
||||
state="changePasswordButtonState"
|
||||
ng-if="model.changePasswordModel.isChanging === false"
|
||||
size="s">
|
||||
</umb-button>
|
||||
|
||||
<ng-form ng-if="model.changePasswordModel.isChanging" name="passwordForm" class="block-form" val-form-manager>
|
||||
|
||||
<change-password password-values="model.user.changePassword"
|
||||
config="model.changePasswordModel.config">
|
||||
</change-password>
|
||||
|
||||
<umb-button type="button"
|
||||
action="model.toggleChangePassword()"
|
||||
label="Cancel"
|
||||
label-key="general_cancel"
|
||||
button-style="cancel">
|
||||
</umb-button>
|
||||
|
||||
</ng-form>
|
||||
|
||||
<div ng-if="model.user.resetPasswordValue">
|
||||
<p><br />Password reset to value: <strong>{{model.user.resetPasswordValue}}</strong></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- User stats -->
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="general_status">Status</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<umb-badge style="margin-top: 4px;" size="s" color="{{model.user.userDisplayState.color}}">
|
||||
{{model.user.userDisplayState.name}}
|
||||
</umb-badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_lastLogin">Last login</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<span ng-if="model.user.lastLoginDate">{{ model.user.formattedLastLogin }}</span>
|
||||
<span ng-if="!model.user.lastLoginDate">{{ model.user.name | umbWordLimit:1 }} <localize key="user_noLogin">has not logged in yet</localize></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_failedPasswordAttempts">Failed login attempts</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
{{ model.user.failedPasswordAttempts }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_lastLockoutDate">Last lockout date</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<span ng-if="model.user.lastLockoutDate === '0001-01-01T00:00:00'">
|
||||
{{ model.user.name | umbWordLimit:1 }} <localize key="user_noLockouts">hasn't been locked out</localize>
|
||||
</span>
|
||||
<span ng-if="model.user.lastLockoutDate !== '0001-01-01T00:00:00'">{{ model.user.formattedLastLockoutDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_lastPasswordChangeDate">Password is last changed</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<span ng-if="model.user.lastPasswordChangeDate === '0001-01-01T00:00:00'">
|
||||
<localize key="user_noPasswordChange">The password hasn't been changed</localize>
|
||||
</span>
|
||||
<span ng-if="model.user.lastPasswordChangeDate !== '0001-01-01T00:00:00'">{{ model.user.formattedLastPasswordChangeDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_createDate">User is created</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
{{ model.user.formattedCreateDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_updateDate">User is last updated</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
{{ model.user.formattedUpdateDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -127,11 +127,10 @@
|
||||
<Reference Include="dotless.Core, Version=1.5.2.0, Culture=neutral, PublicKeyToken=96b446c9e63eae34, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\dotless.1.5.2\lib\dotless.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Examine, Version=0.1.85.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Examine.0.1.85\lib\net45\Examine.dll</HintPath>
|
||||
<Reference Include="Examine, Version=0.1.89.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Examine.0.1.89\lib\net45\Examine.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ImageProcessor, Version=2.5.6.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
@@ -144,7 +143,6 @@
|
||||
<HintPath>..\packages\log4net.2.0.8\lib\net45-full\log4net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Lucene.Net, Version=2.9.4.1, Culture=neutral, PublicKeyToken=85089178b9ac3181, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AspNet.Identity.Core">
|
||||
@@ -335,8 +333,8 @@
|
||||
<Name>umbraco.providers</Name>
|
||||
</ProjectReference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="Umbraco.ModelsBuilder, Version=3.0.7.99, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Umbraco.ModelsBuilder.3.0.7\lib\Umbraco.ModelsBuilder.dll</HintPath>
|
||||
<Reference Include="Umbraco.ModelsBuilder, Version=3.0.8.100, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Umbraco.ModelsBuilder.3.0.8\lib\Umbraco.ModelsBuilder.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -422,7 +422,7 @@
|
||||
<key alias="showPageOnSend">Hvilken side skal vises etter at skjemaet er sendt</key>
|
||||
<key alias="size">Størrelse</key>
|
||||
<key alias="sort">Sorter</key>
|
||||
<key alias="submit">Submit</key> <!-- TODO: Translate this -->
|
||||
<key alias="submit">Send</key>
|
||||
<key alias="type">Type</key>
|
||||
<key alias="typeToSearch">Søk...</key>
|
||||
<key alias="up">Opp</key>
|
||||
@@ -439,6 +439,17 @@
|
||||
<key alias="yes">Ja</key>
|
||||
<key alias="folder">Mappe</key>
|
||||
<key alias="searchResults">Søkeresultater</key>
|
||||
<key alias="reorder">Sorter</key>
|
||||
<key alias="reorderDone">Avslutt sortering</key>
|
||||
<key alias="preview">Eksempel</key>
|
||||
<key alias="changePassword">Bytt passord</key>
|
||||
<key alias="to">til</key>
|
||||
<key alias="listView">Listevisning</key>
|
||||
<key alias="saving">Lagrer...</key>
|
||||
<key alias="current">nåværende</key>
|
||||
<key alias="embed">Innbygging</key>
|
||||
<key alias="retrieve">Hent</key>
|
||||
<key alias="selected">valgt</key>
|
||||
</area>
|
||||
<area alias="graphicheadline">
|
||||
<key alias="backgroundcolor">Bakgrunnsfarge</key>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"content": "<p>This is the <b>Tree</b> and is the main navigation inside a section.</p><p>In the Content section the tree is called the <b>Content tree</b> and here you can navigate the content of your website.</p>"
|
||||
},
|
||||
{
|
||||
"element": "[data-element='editor-content']",
|
||||
"element": "[data-element='editor-container']",
|
||||
"elementPreventClick": true,
|
||||
"title": "Dashboards",
|
||||
"content": "<p>A dashboard is the main view you are presented with when entering a section within the backoffice, and can be used to show valuable information to the users of the system.</p><p>Notice that some sections have multiple dashboards.</p>"
|
||||
@@ -410,7 +410,7 @@
|
||||
content: "...and information about when the media item has been created and edited."
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-content']",
|
||||
element: "[data-element='editor-container']",
|
||||
title: "Using media items",
|
||||
content: "You can reference a media item directly in a template by using the path or try adding a Media Picker to a document type property so you can select media items from the content section."
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
<!--Developer-->
|
||||
<add initialize="true" sortOrder="0" alias="packager" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.PackagesTreeController, umbraco" />
|
||||
<add initialize="true" sortOrder="1" alias="dataTypes" application="developer" title="Data Types" iconClosed="icon-folder" iconOpen="icon-folder" type="Umbraco.Web.Trees.DataTypeTreeController, umbraco" />
|
||||
<add application="developer" alias="macros" title="Macros" type="umbraco.loadMacros, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="2" />
|
||||
<add initialize="true" sortOrder="2" alias="macros" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MacroTreeController, umbraco" />
|
||||
<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 initialize="true" sortOrder="5" alias="xslt" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.XsltTreeController, umbraco" />
|
||||
<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-->
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
<!--Developer-->
|
||||
<add initialize="true" sortOrder="0" alias="packager" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.PackagesTreeController, umbraco" />
|
||||
<add initialize="true" sortOrder="1" alias="dataTypes" application="developer" title="Data Types" iconClosed="icon-folder" iconOpen="icon-folder" type="Umbraco.Web.Trees.DataTypeTreeController, umbraco" />
|
||||
<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.PartialViewMacrosTreeController, umbraco" silent="false" initialize="true" sortOrder="6" title="Partial View Macro Files" iconClosed="icon-folder" iconOpen="icon-folder" />
|
||||
<!--Users-->
|
||||
<add initialize="true" sortOrder="2" alias="macros" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MacroTreeController, umbraco" />
|
||||
<add application="developer" alias="relationTypes" title="Relation Types" type="umbraco.loadRelationTypes, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4" />
|
||||
<add initialize="true" sortOrder="5" alias="xslt" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.XsltTreeController, umbraco" />
|
||||
<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 initialize="true" sortOrder="0" alias="users" application="users" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.UserTreeController, umbraco" />
|
||||
<!--Members-->
|
||||
<add initialize="true" sortOrder="0" alias="member" application="member" title="Members" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MemberTreeController, umbraco" />
|
||||
@@ -39,5 +39,5 @@
|
||||
<add initialize="true" sortOrder="2" alias="datasource" application="forms" title="Datasources" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.DataSourceTreeController, Umbraco.Forms.Web" />
|
||||
<add initialize="true" sortOrder="0" alias="form" application="forms" title="Forms" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormTreeController, Umbraco.Forms.Web" />
|
||||
<add initialize="true" sortOrder="3" alias="prevaluesource" application="forms" title="Prevalue sources" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.PreValueSourceTreeController, Umbraco.Forms.Web" />
|
||||
<add initialize="true" sortOrder="3" alias="formsecurity" application="users" title="Forms Security" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormSecurityTreeController, Umbraco.Forms.Web" />
|
||||
<add initialize="true" sortOrder="3" alias="formsecurity" application="users" title="Forms Security" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormSecurityTreeController, Umbraco.Forms.Web" />
|
||||
</trees>
|
||||
@@ -4,7 +4,7 @@
|
||||
<package id="ClientDependency" version="1.9.2" targetFramework="net45" />
|
||||
<package id="ClientDependency-Mvc5" version="1.8.0.0" targetFramework="net45" />
|
||||
<package id="dotless" version="1.5.2" targetFramework="net45" />
|
||||
<package id="Examine" version="0.1.85" targetFramework="net45" />
|
||||
<package id="Examine" version="0.1.89" targetFramework="net45" />
|
||||
<package id="ImageProcessor" version="2.5.6" targetFramework="net45" />
|
||||
<package id="ImageProcessor.Web" version="4.8.7" targetFramework="net45" />
|
||||
<package id="ImageProcessor.Web.Config" version="2.3.1" targetFramework="net45" />
|
||||
@@ -36,5 +36,5 @@
|
||||
<package id="SqlServerCE" version="4.0.0.1" targetFramework="net45" />
|
||||
<package id="System.Collections.Immutable" version="1.1.36" targetFramework="net45" />
|
||||
<package id="System.Reflection.Metadata" version="1.0.21" targetFramework="net45" />
|
||||
<package id="Umbraco.ModelsBuilder" version="3.0.7" targetFramework="net45" />
|
||||
<package id="Umbraco.ModelsBuilder" version="3.0.8" targetFramework="net45" />
|
||||
</packages>
|
||||
@@ -26,7 +26,7 @@
|
||||
<key alias="notify">Notificeringer</key>
|
||||
<key alias="protect">Offentlig adgang</key>
|
||||
<key alias="publish">Udgiv</key>
|
||||
<key alias="unpublish">Fortryd udgivelse</key>
|
||||
<key alias="unpublish">Afpublicér</key>
|
||||
<key alias="refreshNode">Genindlæs elementer</key>
|
||||
<key alias="republish">Genudgiv hele sitet</key>
|
||||
<key alias="rename" version="7.3.0">Omdøb</key>
|
||||
@@ -182,6 +182,7 @@
|
||||
<key alias="membergroup">Medlemsgruppe</key>
|
||||
<key alias="memberrole">Rolle</key>
|
||||
<key alias="membertype">Medlemstype</key>
|
||||
<key alias="noChanges">Der er endnu ikke lavet nogle ændringer.</key>
|
||||
<key alias="noDate">Ingen dato valgt</key>
|
||||
<key alias="nodeName">Sidetitel</key>
|
||||
<key alias="otherElements">Egenskaber</key>
|
||||
@@ -192,15 +193,16 @@
|
||||
<key alias="publish">Udgivet</key>
|
||||
<key alias="publishStatus">Udgivelsesstatus</key>
|
||||
<key alias="releaseDate">Udgivelsesdato</key>
|
||||
<key alias="unpublishDate">Dato for Fortryd udgivelse</key>
|
||||
<key alias="unpublishDate">Afpubliceringsdato</key>
|
||||
<key alias="removeDate">Fjern dato</key>
|
||||
<key alias="setDate">Vælg dato</key>
|
||||
<key alias="sortDone">Sorteringsrækkefølgen er opdateret</key>
|
||||
<key alias="sortHelp">For at sortere, træk siderne eller klik på en af kolonnehovederne. Du kan vælge flere sider ved at holde "shift" eller "control" nede mens du vælger.</key>
|
||||
<key alias="statistics">Statistik</key>
|
||||
<key alias="titleOptional">Titel (valgfri)</key>
|
||||
<key alias="altTextOptional">Alternativ tekst (valgfri)</key>
|
||||
<key alias="type">Type</key>
|
||||
<key alias="unPublish">Fortryd udgivelse</key>
|
||||
<key alias="unPublish">Afpublicér</key>
|
||||
<key alias="updateDate">Sidst redigeret</key>
|
||||
<key alias="updateDateDesc" version="7.0">Tidspunkt for seneste redigering</key>
|
||||
<key alias="uploadClear">Fjern fil</key>
|
||||
@@ -530,6 +532,7 @@
|
||||
<key alias="height">Højde</key>
|
||||
<key alias="help">Hjælp</key>
|
||||
<key alias="hide">Skjul</key>
|
||||
<key alias="history">Historik</key>
|
||||
<key alias="icon">Ikon</key>
|
||||
<key alias="import">Importer</key>
|
||||
<key alias="innerMargin">Indre margen</key>
|
||||
@@ -576,6 +579,7 @@
|
||||
<key alias="retrieve">Hent</key>
|
||||
<key alias="retry">Prøv igen</key>
|
||||
<key alias="rights">Rettigheder</key>
|
||||
<key alias="scheduledPublishing">Planlagt publicering</key>
|
||||
<key alias="search">Søg</key>
|
||||
<key alias="searchNoResult">Beklager, vi kan ikke finde det, du leder efter.</key>
|
||||
<key alias="noItemsInList">Ingen elementer er blevet tilføjet</key>
|
||||
@@ -586,7 +590,6 @@
|
||||
<key alias="sort">Sortér</key>
|
||||
<key alias="status">Status</key>
|
||||
<key alias="submit">Indsend</key>
|
||||
<!-- TODO: Translate this -->
|
||||
<key alias="type">Type</key>
|
||||
<key alias="typeToSearch">Skriv for at søge...</key>
|
||||
<key alias="up">Op</key>
|
||||
@@ -612,6 +615,7 @@
|
||||
<key alias="saving">Gemmer...</key>
|
||||
<key alias="current">nuværende</key>
|
||||
<key alias="embed">Indlejring</key>
|
||||
<key alias="retrieve">Hent</key>
|
||||
<key alias="selected">valgt</key>
|
||||
</area>
|
||||
|
||||
@@ -1481,7 +1485,7 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="permissionsGranularHelp">Sæt rettigheder for specifikke noder</key>
|
||||
<key alias="profile">Profil</key>
|
||||
<key alias="searchAllChildren">Søg alle 'børn'</key>
|
||||
<key alias="startnode">Start node</key>
|
||||
<key alias="startnode">Startnode</key>
|
||||
<key alias="stateActive">Aktiv</key>
|
||||
<key alias="stateAll">Alle</key>
|
||||
<key alias="stateDisabled">Deaktiveret</key>
|
||||
|
||||
@@ -198,6 +198,7 @@
|
||||
<key alias="releaseDate">Publish at</key>
|
||||
<key alias="unpublishDate">Unpublish at</key>
|
||||
<key alias="removeDate">Clear Date</key>
|
||||
<key alias="setDate">Set date</key>
|
||||
<key alias="sortDone">Sortorder is updated</key>
|
||||
<key alias="sortHelp">To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting</key>
|
||||
<key alias="statistics">Statistics</key>
|
||||
@@ -637,6 +638,7 @@
|
||||
<key alias="saving">Saving...</key>
|
||||
<key alias="current">current</key>
|
||||
<key alias="embed">Embed</key>
|
||||
<key alias="retrieve">Retrieve</key>
|
||||
<key alias="selected">selected</key>
|
||||
</area>
|
||||
|
||||
@@ -2101,4 +2103,4 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<area alias="textbox">
|
||||
<key alias="characters_left">characters left</key>
|
||||
</area>
|
||||
</language>
|
||||
</language>
|
||||
|
||||
@@ -197,10 +197,13 @@
|
||||
<key alias="getUrlException">Could not get the url</key>
|
||||
<key alias="routeError">This document is published but its url would collide with content %0%</key>
|
||||
<key alias="publish">Publish</key>
|
||||
<key alias="published">Published</key>
|
||||
<key alias="publishedPendingChanges">Published (pending changes)</key>
|
||||
<key alias="publishStatus">Publication Status</key>
|
||||
<key alias="releaseDate">Publish at</key>
|
||||
<key alias="unpublishDate">Unpublish at</key>
|
||||
<key alias="removeDate">Clear Date</key>
|
||||
<key alias="setDate">Set date</key>
|
||||
<key alias="sortDone">Sortorder is updated</key>
|
||||
<key alias="sortHelp">To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting</key>
|
||||
<key alias="statistics">Statistics</key>
|
||||
@@ -208,6 +211,7 @@
|
||||
<key alias="altTextOptional">Alternative text (optional)</key>
|
||||
<key alias="type">Type</key>
|
||||
<key alias="unPublish">Unpublish</key>
|
||||
<key alias="unpublished">Unpublished</key>
|
||||
<key alias="updateDate">Last edited</key>
|
||||
<key alias="updateDateDesc" version="7.0">Date/time this document was edited</key>
|
||||
<key alias="uploadClear">Remove file(s)</key>
|
||||
@@ -645,6 +649,7 @@
|
||||
<key alias="saving">Saving...</key>
|
||||
<key alias="current">current</key>
|
||||
<key alias="embed">Embed</key>
|
||||
<key alias="retrieve">Retrieve</key>
|
||||
<key alias="selected">selected</key>
|
||||
</area>
|
||||
<area alias="colors">
|
||||
@@ -2100,4 +2105,4 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<area alias="textbox">
|
||||
<key alias="characters_left">characters left</key>
|
||||
</area>
|
||||
</language>
|
||||
</language>
|
||||
|
||||
@@ -32,11 +32,6 @@
|
||||
<key alias="sort">מיין</key>
|
||||
<key alias="translate">תרגם</key>
|
||||
<key alias="update">עדכן</key>
|
||||
<key alias="DeleteCMSImportAction">מחק</key>
|
||||
<key alias="ExecuteCMSImportWizardAction">בצע</key>
|
||||
<key alias="ScheduleCMSImportWizardAction">תזמן</key>
|
||||
<key alias="StartCMSImportWizardAction">ייבא נתונים</key>
|
||||
<key alias="StartCMSImportChildDefinitionAction">צור הגדרות ילדים</key>
|
||||
<key alias="LastRunInfoAction">יומן משימות מתוזמנות</key>
|
||||
</area>
|
||||
<area alias="assignDomain">
|
||||
|
||||
@@ -452,7 +452,7 @@
|
||||
<key alias="email">Email</key>
|
||||
<key alias="error">Fout</key>
|
||||
<key alias="findDocument">Vind</key>
|
||||
<key alias="height">Hogte</key>
|
||||
<key alias="height">Hoogte</key>
|
||||
<key alias="help">Help</key>
|
||||
<key alias="icon">Icoon</key>
|
||||
<key alias="import">Import</key>
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
<key alias="copy">Kopiuj</key>
|
||||
<key alias="create">Utwórz</key>
|
||||
<key alias="createPackage">Stwórz zbiór</key>
|
||||
<key alias="createGroup">Stwórz grupę</key>
|
||||
<key alias="delete">Usuń</key>
|
||||
<key alias="disable">Deaktywuj</key>
|
||||
<key alias="emptyTrashcan">Opróżnij kosz</key>
|
||||
<key alias="enable">Aktywuj</key>
|
||||
<key alias="exportDocumentType">Eksportuj typ dokumentu</key>
|
||||
<key alias="importDocumentType">Importuj typ dokumentu</key>
|
||||
<key alias="importPackage">Importuj zbiór</key>
|
||||
@@ -27,16 +29,49 @@
|
||||
<key alias="unpublish">Cofnij publikację</key>
|
||||
<key alias="refreshNode">Odśwież węzeł</key>
|
||||
<key alias="republish">Opublikuj ponownie całą stronę</key>
|
||||
<key alias="rename" version="7.3.0">Zmień nazwę</key>
|
||||
<key alias="restore" version="7.3.0">Przywróć</key>
|
||||
<key alias="SetPermissionsForThePage">Ustaw uprawnienia dla strony %0%</key>
|
||||
<key alias="chooseWhereToMove">Wybierz dokąd przenieść</key>
|
||||
<key alias="toInTheTreeStructureBelow">W strukturze drzewa poniżej</key>
|
||||
<key alias="rights">Uprawnienia</key>
|
||||
<key alias="rollback">Cofnij</key>
|
||||
<key alias="sendtopublish">Wyślij do publikacji</key>
|
||||
<key alias="sendToTranslate">Wyślij do tłumaczenia</key>
|
||||
<key alias="setGroup">Ustaw grupę</key>
|
||||
<key alias="sort">Sortuj</key>
|
||||
<key alias="translate">Przetłumacz</key>
|
||||
<key alias="update">Aktualizuj</key>
|
||||
<key alias="setPermissions">Ustaw uprawnienia</key>
|
||||
<key alias="unlock">Odblokuj</key>
|
||||
<key alias="createblueprint">Stwórz Szablon Zawartości</key>
|
||||
</area>
|
||||
<area alias="actionCategories">
|
||||
<key alias="content">Zawartość</key>
|
||||
<key alias="administration">Administracja</key>
|
||||
<key alias="structure">Struktura</key>
|
||||
<key alias="other">Inne</key>
|
||||
</area>
|
||||
<area alias="actionDescriptions">
|
||||
<key alias="assignDomain">Zezwól na dostęp do przydzielenia języka i hostów</key>
|
||||
<key alias="auditTrail">Zezwól na dostęp do wglądu w historię logów węzła</key>
|
||||
<key alias="browse">Zezwól na dostęp do widoku węzła</key>
|
||||
<key alias="changeDocType">Zezwól na dostęp do zmiany typu dokumentu dla węzła</key>
|
||||
<key alias="copy">Zezwól na dostęp do skopiowania węzła</key>
|
||||
<key alias="create">Zezwól na dostęp do stworzenia węzłów</key>
|
||||
<key alias="delete">Zezwól na dostęp do usunięcia węzłóws</key>
|
||||
<key alias="move">Zezwól na dostęp do przeniesienia węzła</key>
|
||||
<key alias="protect">Zezwól na dostęp do ustawienia i zmiany publicznego dostępu węzła</key>
|
||||
<key alias="publish">Zezwól na dostęp do publikacji węzła</key>
|
||||
<key alias="rights">Zezwól na dostęp do zmiany uprawnień węzła</key>
|
||||
<key alias="rollback">Zezwól na dostęp do cofnięcia węzła do poprzedniego stanu</key>
|
||||
<key alias="sendtopublish">Zezwól na dostęp do wysłania węzła do akceptacji przed publikacją</key>
|
||||
<key alias="sendToTranslate">Zezwól na dostęp do wysłania węzła do tłumaczenia</key>
|
||||
<key alias="sort">Zezwól na dostęp do zmiany kolejności sortowania węzłów</key>
|
||||
<key alias="translate">Zezwól na dostęp do tłumaczenia węzła</key>
|
||||
<key alias="update">Zezwól na dostęp do zapisania węzła</key>
|
||||
<key alias="createblueprint">Zezwól na dostęp do utworzenia Szablonu Zawartości</key>
|
||||
</area>
|
||||
<area alias="assignDomain">
|
||||
<key alias="permissionDenied">Brak odpowiednich uprawnień</key>
|
||||
<key alias="addNew">Dodaj nową domenę</key>
|
||||
@@ -50,14 +85,18 @@
|
||||
<key alias="domainDeleted">Domena '%0%' została skasowana</key>
|
||||
<key alias="domainExists">Domena '%0%' jest aktualnie przypisana</key>
|
||||
<key alias="domainUpdated">Domena '%0%' została zaktualizowana</key>
|
||||
<key alias="domainHelp"><![CDATA[Poprawne domeny to: "example.com", "www.example.com", "example.com:8080" lub
|
||||
"https://www.example.com/". Wspierane są jednopoziomowe ścieżki domen, np. "example.com/en" jednakże powinno się ich unikać.
|
||||
Preferowane jest użycie powyższych ustawień języka.]]></key>
|
||||
<key alias="orEdit">Edycja aktualnych domen</key>
|
||||
<key alias="orEdit">Edytuj Aktualne Domeny</key>
|
||||
<key alias="domainHelp">
|
||||
<![CDATA[Poprawne domeny to: "example.com", "www.example.com", "example.com:8080" lub
|
||||
"https://www.example.com/". Wspierane są jednopoziomowe ścieżki domen, np. "example.com/en" jednakże powinno się ich unikać.
|
||||
Preferowane jest użycie powyższych ustawień języka.]]>
|
||||
</key>
|
||||
<key alias="inherit">Odziedziczona</key>
|
||||
<key alias="setLanguage">Język</key>
|
||||
<key alias="setLanguageHelp"><![CDATA[Wybierz język dla węzła,<br /> lub wybierz dziedziczenie języka z węzła rodzica. Zostanie to zastosowane<br />
|
||||
także do obecnego węzła, o ile poniższa domena również do niego należy.]]></key>
|
||||
<key alias="setLanguageHelp">
|
||||
<![CDATA[Wybierz język dla węzła,<br /> lub wybierz dziedziczenie języka z węzła rodzica. Zostanie to zastosowane<br />
|
||||
także do obecnego węzła, o ile poniższa domena również do niego należy.]]>
|
||||
</key>
|
||||
<key alias="setDomains">Domeny</key>
|
||||
</area>
|
||||
<area alias="auditTrails">
|
||||
@@ -96,6 +135,7 @@ także do obecnego węzła, o ile poniższa domena również do niego należy.]]
|
||||
<key alias="styleShow">Pokaż style</key>
|
||||
<key alias="tableInsert">Wstaw tabelę</key>
|
||||
<key alias="generateModels">Wygeneruj modele</key>
|
||||
<key alias="saveAndGenerateModels">Zapisz i wygeneruj modele</key>
|
||||
<key alias="undo">Cofnij</key>
|
||||
<key alias="redo">Powtórz</key>
|
||||
</area>
|
||||
@@ -139,6 +179,8 @@ także do obecnego węzła, o ile poniższa domena również do niego należy.]]
|
||||
<key alias="lastPublished">Opublikowane</key>
|
||||
<key alias="noItemsToShow">Nie ma żadnych elementów do wyświetlenia</key>
|
||||
<key alias="listViewNoItems" version="7.1.5">Nie ma żadnych elementów do wyświetlenia w liście.</key>
|
||||
<key alias="listViewNoContent">Nie dodano żadnej zawartości</key>
|
||||
<key alias="listViewNoMembers">Nie dodano żadnych członków</key>
|
||||
<key alias="mediatype">Typ mediów</key>
|
||||
<key alias="mediaLinks">Link do elementu(ów) mediów</key>
|
||||
<key alias="membergroup">Członek grupy</key>
|
||||
@@ -173,16 +215,30 @@ także do obecnego węzła, o ile poniższa domena również do niego należy.]]
|
||||
<key alias="target" version="7.0">Cel</key>
|
||||
<key alias="scheduledPublishServerTime">Oznacza to następującą godzinę na serwerze:</key>
|
||||
<key alias="scheduledPublishDocumentation"><![CDATA[<a href="https://our.umbraco.org/documentation/Getting-Started/Data/Scheduled-Publishing/#timezones" target="_blank">Co to oznacza?</a>]]></key>
|
||||
<key alias="nestedContentDeleteItem">Czy na pewno chcesz usunąć ten element?</key>
|
||||
<key alias="nestedContentEditorNotSupported">Właściwość %0% używa edytora %1%, który nie jest wspierany przez Nested Content.</key>
|
||||
<key alias="addTextBox">Dodaj kolejne pole tekstowe</key>
|
||||
<key alias="removeTextBox">Usuń te pole tekstowe</key>
|
||||
<key alias="contentRoot">Korzeń zawartości</key>
|
||||
</area>
|
||||
<area alias="blueprints">
|
||||
<key alias="createBlueprintFrom">Stwórz nowy Szablon Zawartości z '%0%'</key>
|
||||
<key alias="blankBlueprint">Pusty</key>
|
||||
<key alias="selectBlueprint">Wybierz Szablon Zawartości</key>
|
||||
<key alias="createdBlueprintHeading">Szablon Zawartości został stworzony</key>
|
||||
<key alias="createdBlueprintMessage">Szablon Zawartości został stworzony z '%0%'</key>
|
||||
<key alias="duplicateBlueprintMessage">Szablon Zawartości o tej samej nazwie już istnieje</key>
|
||||
<key alias="blueprintDescription">Szablon Zawartości to predefiniowana zawartość, którą edytor może wybrać, aby użyć jej jako podstawę do stworzenia nowej zawartości</key>
|
||||
</area>
|
||||
<area alias="media">
|
||||
<key alias="clickToUpload">Kliknij, aby załadować plik</key>
|
||||
<key alias="dropFilesHere">Przerzuć swoje pliki tutaj...</key>
|
||||
<key alias="urls">Link do mediów</key>
|
||||
<key alias="urls">Link do mediów</key>
|
||||
<key alias="orClickHereToUpload">lub kliknij tutaj, aby wybrać pliki</key>
|
||||
<key alias="onlyAllowedFiles">Jedyne dozwolone typy plików to</key>
|
||||
<key alias="disallowedFileType">Nie można załadować pliku, typ pliku nie jest akceptowany</key>
|
||||
<key alias="maxFileSize">Maksymalny rozmiar pliku to</key>
|
||||
<key alias="mediaRoot">Korzeń mediów</key>
|
||||
</area>
|
||||
<area alias="member">
|
||||
<key alias="createNewMember">Stwórz nowego członka</key>
|
||||
@@ -191,6 +247,7 @@ także do obecnego węzła, o ile poniższa domena również do niego należy.]]
|
||||
<area alias="create">
|
||||
<key alias="chooseNode">Gdzie chcesz stworzyć nowy %0%?</key>
|
||||
<key alias="createUnder">Utwórz w</key>
|
||||
<key alias="createContentBlueprint">Wybierz typ dokumentu, dla którego chcesz stworzyć szablon zawartości</key>
|
||||
<key alias="updateData">Wybierz rodzaj oraz tytuł</key>
|
||||
<key alias="noDocumentTypes" version="7.0"><![CDATA[Brak dostępnych dozwolonych typów dokumentów. Należy włączyć je w ustawieniach sekcji pod <strong>"typy dokumentów"</strong>.]]></key>
|
||||
<key alias="noMediaTypes" version="7.0"><![CDATA[Brak dostępnych dozwolonych typów mediów. Należy włączyć je w ustawieniach sekcji pod <strong>"typy mediów"</strong>.]]></key>
|
||||
@@ -249,6 +306,8 @@ także do obecnego węzła, o ile poniższa domena również do niego należy.]]
|
||||
<key alias="copiedItemOfItems">Skopiowano %0% z %1% elementów</key>
|
||||
</area>
|
||||
<area alias="defaultdialogs">
|
||||
<key alias="nodeNameLinkPicker">Tytuł linku</key>
|
||||
<key alias="urlLinkPicker">Link</key>
|
||||
<key alias="anchorInsert">Nazwa</key>
|
||||
<key alias="assignDomain">Zarządzaj nazwami hostów</key>
|
||||
<key alias="closeThisWindow">Zamknij to okno</key>
|
||||
@@ -276,6 +335,9 @@ także do obecnego węzła, o ile poniższa domena również do niego należy.]]
|
||||
<key alias="macroDoesNotHaveProperties">To makro nie posiada żadnych właściwości, które można edytować</key>
|
||||
<key alias="paste">Wklej</key>
|
||||
<key alias="permissionsEdit">Edytuj Uprawnienia dla</key>
|
||||
<key alias="permissionsSet">Ustaw uprawnienia dla</key>
|
||||
<key alias="permissionsSetForGroup">Ustaw uprawnienia dla %0% dla grupy użytkownika %1%</key>
|
||||
<key alias="permissionsHelp">Wybierz grupy użytkowników, dla których chcesz ustawić uprawnienia</key>
|
||||
<key alias="recycleBinDeleting">Zawartość kosza jest teraz usuwana. Proszę nie zamykać tego okna do momentu zakończenia procesu.</key>
|
||||
<key alias="recycleBinIsEmpty">Zawartość kosza została usunięta</key>
|
||||
<key alias="recycleBinWarning">Usunięcie elementów z kosza powoduje ich trwałe i nieodwracalne skasowanie</key>
|
||||
@@ -288,10 +350,14 @@ także do obecnego węzła, o ile poniższa domena również do niego należy.]]
|
||||
<key alias="siterepublishHelp">Cache strony zostanie odświeżone. Cała zawartość opublikowana będzie aktualna, lecz nieopublikowana zawartość pozostanie niewidoczna</key>
|
||||
<key alias="tableColumns">Liczba kolumn</key>
|
||||
<key alias="tableRows">Liczba wierszy</key>
|
||||
<key alias="templateContentAreaHelp"><![CDATA[<strong>Ustaw zastępczy ID</strong> Ustawiając ID na tym elemencie możesz później łączyć treść z podrzędnych szablonów,
|
||||
ustawiając dowiązanie do tego ID na elemencie <code><asp:treści /></code>]]></key>
|
||||
<key alias="templateContentPlaceHolderHelp"><![CDATA[<strong>Wybierz zastępczy ID</strong> z poniższej listy. Możesz wybierać tylko
|
||||
spośród ID na szablonie nadrzędnym tego formularza.]]></key>
|
||||
<key alias="templateContentAreaHelp">
|
||||
<![CDATA[<strong>Ustaw zastępczy ID</strong> Ustawiając ID na tym elemencie możesz później łączyć treść z podrzędnych szablonów,
|
||||
ustawiając dowiązanie do tego ID na elemencie <code><asp:treści /></code>]]>
|
||||
</key>
|
||||
<key alias="templateContentPlaceHolderHelp">
|
||||
<![CDATA[<strong>Wybierz zastępczy ID</strong> z poniższej listy. Możesz wybierać tylko
|
||||
spośród ID na szablonie nadrzędnym tego formularza.]]>
|
||||
</key>
|
||||
<key alias="thumbnailimageclickfororiginal">Kliknij na obrazie, aby zobaczyć go w pełnym rozmiarze</key>
|
||||
<key alias="treepicker">Wybierz element</key>
|
||||
<key alias="viewCacheItem">Podgląd elementów Cache</key>
|
||||
@@ -303,14 +369,19 @@ także do obecnego węzła, o ile poniższa domena również do niego należy.]]
|
||||
<key alias="openInNewWindow">Otwórz zlinkowany dokument w nowym oknie lub zakładce</key>
|
||||
<key alias="linkToMedia">Link do mediów</key>
|
||||
<key alias="linkToFile">Link do plików</key>
|
||||
<key alias="selectContentStartNode">Wybierz węzeł początkowy zawartości</key>
|
||||
<key alias="selectMedia">Wybierz media</key>
|
||||
<key alias="selectIcon">Wybierz ikonę</key>
|
||||
<key alias="selectItem">Wybierz element</key>
|
||||
<key alias="selectLink">Wybierz link</key>
|
||||
<key alias="selectMacro">Wybierz makro</key>
|
||||
<key alias="selectContent">Wybierz zawartość</key>
|
||||
<key alias="selectMediaStartNode">Wybierz węzeł początkowy mediów</key>
|
||||
<key alias="selectMember">Wybierz członka</key>
|
||||
<key alias="selectMemberGroup">Wybierz członka grupy</key>
|
||||
<key alias="selectNode">Wybierz węzeł</key>
|
||||
<key alias="selectSections">Wybierz sekcje</key>
|
||||
<key alias="selectUsers">Wybierz użytkowników</key>
|
||||
<key alias="noIconsFound">Nie znaleziono ikon</key>
|
||||
<key alias="noMacroParams">Te makro nie ma żadnych właściwości</key>
|
||||
<key alias="noMacros">Brak dostępnych makro do wstawienia</key>
|
||||
@@ -325,9 +396,11 @@ także do obecnego węzła, o ile poniższa domena również do niego należy.]]
|
||||
<key alias="selectSnippet">Wybierz snippet</key>
|
||||
</area>
|
||||
<area alias="dictionaryItem">
|
||||
<key alias="description"><![CDATA[
|
||||
Edytuj różne wersje językowe dla elementu słownika '<em>%0%</em>' poniżej.<br/>
|
||||
Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]></key>
|
||||
<key alias="description">
|
||||
<![CDATA[
|
||||
Edytuj różne wersje językowe dla elementu słownika '<em>%0%</em>' poniżej.<br/>
|
||||
Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]>
|
||||
</key>
|
||||
<key alias="displayName">Nazwa języka</key>
|
||||
<key alias="changeKey">Edytuj klucz elementu słownika.</key>
|
||||
<key alias="changeKeyError">
|
||||
@@ -342,12 +415,15 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]></key>
|
||||
<key alias="confirmPassword">Potwierdź hasło</key>
|
||||
<key alias="nameentity">Nazwij %0%...</key>
|
||||
<key alias="entername">Wpisz nazwę...</key>
|
||||
<key alias="enteremail">Wpisz adres e-mail...</key>
|
||||
<key alias="enterusername">Wpisz nazwę użytkownika...</key>
|
||||
<key alias="label">Etykieta...</key>
|
||||
<key alias="enterDescription">Wpisz opis...</key>
|
||||
<key alias="search">Wpisz, aby wyszukać...</key>
|
||||
<key alias="filter">Wpisz, aby filtrować...</key>
|
||||
<key alias="enterTags">Wpisz, aby dodać tagi (naciśnij enter po każdym tagu)...</key>
|
||||
<key alias="email">Wpisz adres e-mail</key>
|
||||
<key alias="enterMessage">Wpisz wiadomość...</key>
|
||||
<key alias="usernameHint">Twoja nazwa użytkownika to przeważnie Twój adres e-mail</key>
|
||||
</area>
|
||||
<area alias="editcontenttype">
|
||||
|
||||
@@ -153,6 +153,7 @@
|
||||
<key alias="listNumeric">Нумерованный список</key>
|
||||
<key alias="macroInsert">Вставить макрос</key>
|
||||
<key alias="pictureInsert">Вставить изображение</key>
|
||||
<key alias="redo">Повторить</key>
|
||||
<key alias="relations">Править связи</key>
|
||||
<key alias="returnToList">Вернуться к списку</key>
|
||||
<key alias="save">Сохранить</key>
|
||||
@@ -168,6 +169,7 @@
|
||||
<key alias="styleChoose">Выбрать стиль</key>
|
||||
<key alias="styleShow">Показать стили</key>
|
||||
<key alias="tableInsert">Вставить таблицу</key>
|
||||
<key alias="undo">Отменить</key>
|
||||
</area>
|
||||
<area alias="changeDocType">
|
||||
<key alias="changeDocTypeInstruction">Чтобы сменить тип документа для выбранного узла, сначала выберите тип из списка разрешенных для данного расположения.</key>
|
||||
@@ -375,6 +377,7 @@
|
||||
<key alias="macroContainerSettings">Свойства макроса</key>
|
||||
<key alias="macroDoesNotHaveProperties">Этот макрос не имеет редактируемых свойств</key>
|
||||
<key alias="nodeNameLinkPicker">Заголовок ссылки</key>
|
||||
<key alias="noIconsFound">Ни одной пиктограммы не найдено</key>
|
||||
<key alias="paste">Вставить</key>
|
||||
<key alias="permissionsEdit">Изменить разрешения для</key>
|
||||
<key alias="permissionsSet">Установить разрешения для</key>
|
||||
@@ -596,6 +599,7 @@
|
||||
<key alias="new">Новый</key>
|
||||
<key alias="next">След</key>
|
||||
<key alias="no">Нет</key>
|
||||
<key alias="noItemsInList">Здесь пока нет элементов</key>
|
||||
<key alias="of">из</key>
|
||||
<key alias="off">Выкл</key>
|
||||
<key alias="ok">Ok</key>
|
||||
@@ -696,6 +700,8 @@
|
||||
<key alias="chooseExtra">Выбрать дополнительно</key>
|
||||
<key alias="chooseDefault">Выбрать по-умолчанию</key>
|
||||
<key alias="areAdded">добавлены</key>
|
||||
<key alias="maxItemsDescription">Оставьте пустым или задайте 0 для снятия лимита</key>
|
||||
<key alias="maxItems">Максимальное количество</key>
|
||||
</area>
|
||||
<area alias="headers">
|
||||
<key alias="page">Страница</key>
|
||||
@@ -1049,7 +1055,7 @@
|
||||
<area alias="packager">
|
||||
<key alias="chooseLocalPackageText"><![CDATA[
|
||||
Выберите файл пакета на своем компьютере, нажав на кнопку 'Обзор'<br />
|
||||
и указав на нужный файл. Пакеты Umbraco обычно являются архивами с расширением ".umb" или ".zip".
|
||||
и указав на нужный файл. Пакеты Umbraco обычно являются архивами с расширением '.zip'.
|
||||
]]></key>
|
||||
<key alias="dropHere">Перетащите сюда</key>
|
||||
<key alias="orClickHereToUpload">или нажмите здесь для выбора файла пакета</key>
|
||||
@@ -1215,12 +1221,12 @@
|
||||
</area>
|
||||
<area alias="relatedlinks">
|
||||
<key alias="caption">Заголовок</key>
|
||||
<key alias="captionPlaceholder">Укажите заголовок</key>
|
||||
<key alias="captionPlaceholder">Укажите заголовок ссылки</key>
|
||||
<key alias="chooseInternal">выбрать страницу сайта</key>
|
||||
<key alias="enterExternal">указать внешнюю ссылку</key>
|
||||
<key alias="externalLinkPlaceholder">Укажите ссылку</key>
|
||||
<key alias="link">Ссылка</key>
|
||||
<key alias="newWindow">В новом окне</key>
|
||||
<key alias="newWindow">Открыть в новом окне</key>
|
||||
</area>
|
||||
<area alias="renamecontainer">
|
||||
<key alias="renamed">Переименована</key>
|
||||
@@ -1393,8 +1399,6 @@
|
||||
<key alias="disableUserSuccess">'%0%' сейчас заблокирован</key>
|
||||
<key alias="disableUserError">При блокировке пользователя произошла ошибка</key>
|
||||
<key alias="setUserGroupOnUsersSuccess">Группы пользователей установлены</key>
|
||||
<key alias="deleteUserGroupsSuccess">Удалено %0% групп пользователей</key>
|
||||
<key alias="deleteUserGroupSuccess">'%0%' была удалена</key>
|
||||
<key alias="unlockUsersSuccess">Разблокировано %0% пользователей</key>
|
||||
<key alias="unlockUsersError">При разблокировке пользователей произошла ошибка</key>
|
||||
<key alias="unlockUserSuccess">'%0%' сейчас разблокирован</key>
|
||||
@@ -1517,8 +1521,8 @@
|
||||
<key alias="fallbackField">Поле замены</key>
|
||||
<key alias="addDefaultValue">Добавить значение по-умолчанию</key>
|
||||
<key alias="defaultValue">Значение по-умолчанию</key>
|
||||
<key alias="alternativeField">Альтернативное поле</key>
|
||||
<key alias="alternativeText">Текст по-умолчанию</key>
|
||||
<key alias="alternativeField">Поле замены</key>
|
||||
<key alias="alternativeText">Значение по-умолчанию</key>
|
||||
<key alias="casing">Регистр</key>
|
||||
<key alias="chooseField">Выбрать поле</key>
|
||||
<key alias="convertLineBreaks">Преобразовать переводы строк</key>
|
||||
@@ -1715,11 +1719,22 @@
|
||||
<key alias="profile">Профиль</key>
|
||||
<key alias="resetPassword">Сбросить пароль</key>
|
||||
<key alias="searchAllChildren">Поиск всех дочерних документов</key>
|
||||
<key alias="selectUserGroups">Выбрать группы пользователей</key>
|
||||
<key alias="sendInvite">Отправить приглашение</key>
|
||||
<key alias="sessionExpires" version="7.0">Сессия истекает через</key>
|
||||
<key alias="sectionsHelp">Разделы, доступные пользователю</key>
|
||||
<key alias="noStartNode">Начальный узел не задан</key>
|
||||
<key alias="noStartNodes">Начальные узлы не заданы</key>
|
||||
<key alias="sortNameAscending">Имя (А-Я)</key>
|
||||
<key alias="sortNameDescending">Имя (Я-А)</key>
|
||||
<key alias="sortCreateDateDescending">Сначала новые</key>
|
||||
<key alias="sortCreateDateAscending">Сначала старые</key>
|
||||
<key alias="sortLastLoginDateDescending">Недавно зашедшие</key>
|
||||
<key alias="stateActive">Активные</key>
|
||||
<key alias="stateAll">Все</key>
|
||||
<key alias="stateDisabled">Отключенные</key>
|
||||
<key alias="stateLockedOut">Заблокированные</key>
|
||||
<key alias="stateInvited">Приглашенные</key>
|
||||
<key alias="startnode">Начальный узел содержимого</key>
|
||||
<key alias="startnodehelp">Можно ограничить доступ к дереву содержимого (какой-либо его части), задав начальный узел</key>
|
||||
<key alias="startnodes">Начальные узлы содержимого</key>
|
||||
@@ -1736,6 +1751,7 @@
|
||||
<key alias="userInvitedSuccessHelp">Новому пользователю было отправлено приглашение, в котором содержатся инструкции для входа в панель Umbraco.</key>
|
||||
<key alias="userinviteWelcomeMessage">Здравствуйте и добро пожаловать в Umbraco! Все будет готово в течении пары минут, нам лишь нужно задать Ваш пароль для входа и добавить аватар.</key>
|
||||
<key alias="userinviteAvatarMessage">Загрузите изображение, это поможет другим пользователям идентифицировать Вас.</key>
|
||||
<key alias="userManagement">Управление пользователями</key>
|
||||
<key alias="userPermissions">Разрешения для пользователя</key>
|
||||
<key alias="writer">Автор</key>
|
||||
<key alias="yourHistory" version="7.0">Ваша недавняя история</key>
|
||||
|
||||
@@ -411,7 +411,7 @@
|
||||
<key alias="showPageOnSend">Vilken sida skall visas när formuläret är skickat</key>
|
||||
<key alias="size">Storlek</key>
|
||||
<key alias="sort">Sortera</key>
|
||||
<key alias="submit">Submit</key> <!-- TODO: Translate this -->
|
||||
<key alias="submit">Skicka</key>
|
||||
<key alias="type">Skriv</key>
|
||||
<key alias="typeToSearch">Skriv för att söka...</key>
|
||||
<key alias="up">Upp</key>
|
||||
@@ -426,8 +426,17 @@
|
||||
<key alias="width">Bredd</key>
|
||||
<key alias="view">Titta på</key>
|
||||
<key alias="yes">Ja</key>
|
||||
<key alias="reorder">Reorder</key>
|
||||
<key alias="reorderDone">I am done reordering</key>
|
||||
<key alias="reorder">Sortera</key>
|
||||
<key alias="reorderDone">Avsluta sortering</key>
|
||||
<key alias="preview">Förhandsvisning</key>
|
||||
<key alias="changePassword">Ändra lösenord</key>
|
||||
<key alias="to">till</key>
|
||||
<key alias="listView">Listvy</key>
|
||||
<key alias="saving">Sparar...</key>
|
||||
<key alias="current">nuvarande</key>
|
||||
<key alias="embed">Inbäddning</key>
|
||||
<key alias="retrieve">Hämta</key>
|
||||
<key alias="selected">valgt</key>
|
||||
</area>
|
||||
<area alias="graphicheadline">
|
||||
<key alias="backgroundcolor">Bakgrundsfärg</key>
|
||||
|
||||
@@ -805,13 +805,13 @@ namespace Umbraco.Web.Cache
|
||||
/// <summary>
|
||||
/// Used to cache all found event handlers
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<IEventDefinition, MethodInfo> FoundHandlers = new ConcurrentDictionary<IEventDefinition, MethodInfo>();
|
||||
private static readonly ConcurrentDictionary<string, MethodInfo> FoundHandlers = new ConcurrentDictionary<string, MethodInfo>();
|
||||
|
||||
internal static MethodInfo FindHandler(IEventDefinition eventDefinition)
|
||||
{
|
||||
var name = eventDefinition.Sender.GetType().Name + "_" + eventDefinition.EventName;
|
||||
|
||||
return FoundHandlers.GetOrAdd(eventDefinition, _ => CandidateHandlers.Value.FirstOrDefault(x => x.Name == name));
|
||||
return FoundHandlers.GetOrAdd(name, n => CandidateHandlers.Value.FirstOrDefault(x => x.Name == n));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,6 +373,19 @@ namespace Umbraco.Web.Editors
|
||||
if (loginInfo == null) throw new ArgumentNullException("loginInfo");
|
||||
if (response == null) throw new ArgumentNullException("response");
|
||||
|
||||
|
||||
//Here we can check if the provider associated with the request has been configured to allow
|
||||
// new users (auto-linked external accounts). This would never be used with public providers such as
|
||||
// Google, unless you for some reason wanted anybody to be able to access the backend if they have a Google account
|
||||
// .... not likely!
|
||||
var authType = OwinContext.Authentication.GetExternalAuthenticationTypes().FirstOrDefault(x => x.AuthenticationType == loginInfo.Login.LoginProvider);
|
||||
if (authType == null)
|
||||
{
|
||||
Logger.Warn<BackOfficeController>("Could not find external authentication provider registered: " + loginInfo.Login.LoginProvider);
|
||||
}
|
||||
|
||||
var autoLinkOptions = authType.GetExternalAuthenticationOptions();
|
||||
|
||||
// Sign in the user with this external login provider if the user already has a login
|
||||
var user = await UserManager.FindAsync(loginInfo.Login);
|
||||
if (user != null)
|
||||
@@ -383,12 +396,25 @@ namespace Umbraco.Web.Editors
|
||||
// ticket format, etc.. to create our back office user including the claims assigned and in this method we'd just ensure
|
||||
// that the ticket is created and stored and that the user is logged in.
|
||||
|
||||
//sign in
|
||||
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
|
||||
var shouldSignIn = true;
|
||||
if (autoLinkOptions != null && autoLinkOptions.OnExternalLogin != null)
|
||||
{
|
||||
shouldSignIn = autoLinkOptions.OnExternalLogin(user, loginInfo);
|
||||
if (shouldSignIn == false)
|
||||
{
|
||||
Logger.Warn<BackOfficeController>("The AutoLinkOptions of the external authentication provider '" + loginInfo.Login.LoginProvider + "' have refused the login based on the OnExternalLogin method. Affected user id: '" + user.Id + "'");
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSignIn)
|
||||
{
|
||||
//sign in
|
||||
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await AutoLinkAndSignInExternalAccount(loginInfo) == false)
|
||||
if (await AutoLinkAndSignInExternalAccount(loginInfo, autoLinkOptions) == false)
|
||||
{
|
||||
ViewData[TokenExternalSignInError] = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to to an account" };
|
||||
}
|
||||
@@ -403,97 +429,81 @@ namespace Umbraco.Web.Editors
|
||||
return response();
|
||||
}
|
||||
|
||||
private async Task<bool> AutoLinkAndSignInExternalAccount(ExternalLoginInfo loginInfo)
|
||||
private async Task<bool> AutoLinkAndSignInExternalAccount(ExternalLoginInfo loginInfo, ExternalSignInAutoLinkOptions autoLinkOptions)
|
||||
{
|
||||
//Here we can check if the provider associated with the request has been configured to allow
|
||||
// new users (auto-linked external accounts). This would never be used with public providers such as
|
||||
// Google, unless you for some reason wanted anybody to be able to access the backend if they have a Google account
|
||||
// .... not likely!
|
||||
|
||||
var authType = OwinContext.Authentication.GetExternalAuthenticationTypes().FirstOrDefault(x => x.AuthenticationType == loginInfo.Login.LoginProvider);
|
||||
if (authType == null)
|
||||
{
|
||||
Logger.Warn<BackOfficeController>("Could not find external authentication provider registered: " + loginInfo.Login.LoginProvider);
|
||||
if (autoLinkOptions == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
var autoLinkOptions = authType.GetExternalAuthenticationOptions();
|
||||
if (autoLinkOptions != null)
|
||||
if (autoLinkOptions.ShouldAutoLinkExternalAccount(UmbracoContext, loginInfo) == false)
|
||||
return true;
|
||||
|
||||
//we are allowing auto-linking/creating of local accounts
|
||||
if (loginInfo.Email.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (autoLinkOptions.ShouldAutoLinkExternalAccount(UmbracoContext, loginInfo))
|
||||
ViewData[TokenExternalSignInError] = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not provided an email address, the account cannot be linked." };
|
||||
}
|
||||
else
|
||||
{
|
||||
//Now we need to perform the auto-link, so first we need to lookup/create a user with the email address
|
||||
var foundByEmail = Services.UserService.GetByEmail(loginInfo.Email);
|
||||
if (foundByEmail != null)
|
||||
{
|
||||
//we are allowing auto-linking/creating of local accounts
|
||||
if (loginInfo.Email.IsNullOrWhiteSpace())
|
||||
ViewData[TokenExternalSignInError] = new[] { "A user with this email address already exists locally. You will need to login locally to Umbraco and link this external provider: " + loginInfo.Login.LoginProvider };
|
||||
}
|
||||
else
|
||||
{
|
||||
if (loginInfo.Email.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Email value cannot be null");
|
||||
if (loginInfo.ExternalIdentity.Name.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Name value cannot be null");
|
||||
|
||||
var groups = Services.UserService.GetUserGroupsByAlias(autoLinkOptions.GetDefaultUserGroups(UmbracoContext, loginInfo));
|
||||
|
||||
var autoLinkUser = BackOfficeIdentityUser.CreateNew(
|
||||
loginInfo.Email,
|
||||
loginInfo.Email,
|
||||
autoLinkOptions.GetDefaultCulture(UmbracoContext, loginInfo));
|
||||
autoLinkUser.Name = loginInfo.ExternalIdentity.Name;
|
||||
foreach (var userGroup in groups)
|
||||
{
|
||||
ViewData[TokenExternalSignInError] = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not provided an email address, the account cannot be linked." };
|
||||
autoLinkUser.AddRole(userGroup.Alias);
|
||||
}
|
||||
|
||||
//call the callback if one is assigned
|
||||
if (autoLinkOptions.OnAutoLinking != null)
|
||||
{
|
||||
autoLinkOptions.OnAutoLinking(autoLinkUser, loginInfo);
|
||||
}
|
||||
|
||||
var userCreationResult = await UserManager.CreateAsync(autoLinkUser);
|
||||
|
||||
if (userCreationResult.Succeeded == false)
|
||||
{
|
||||
ViewData[TokenExternalSignInError] = userCreationResult.Errors;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
//Now we need to perform the auto-link, so first we need to lookup/create a user with the email address
|
||||
var foundByEmail = Services.UserService.GetByEmail(loginInfo.Email);
|
||||
if (foundByEmail != null)
|
||||
var linkResult = await UserManager.AddLoginAsync(autoLinkUser.Id, loginInfo.Login);
|
||||
if (linkResult.Succeeded == false)
|
||||
{
|
||||
ViewData[TokenExternalSignInError] = new[] { "A user with this email address already exists locally. You will need to login locally to Umbraco and link this external provider: " + loginInfo.Login.LoginProvider };
|
||||
ViewData[TokenExternalSignInError] = linkResult.Errors;
|
||||
|
||||
//If this fails, we should really delete the user since it will be in an inconsistent state!
|
||||
var deleteResult = await UserManager.DeleteAsync(autoLinkUser);
|
||||
if (deleteResult.Succeeded == false)
|
||||
{
|
||||
//DOH! ... this isn't good, combine all errors to be shown
|
||||
ViewData[TokenExternalSignInError] = linkResult.Errors.Concat(deleteResult.Errors);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (loginInfo.Email.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Email value cannot be null");
|
||||
if (loginInfo.ExternalIdentity.Name.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Name value cannot be null");
|
||||
|
||||
var groups = Services.UserService.GetUserGroupsByAlias(autoLinkOptions.GetDefaultUserGroups(UmbracoContext, loginInfo));
|
||||
|
||||
var autoLinkUser = BackOfficeIdentityUser.CreateNew(
|
||||
loginInfo.Email,
|
||||
loginInfo.Email,
|
||||
autoLinkOptions.GetDefaultCulture(UmbracoContext, loginInfo));
|
||||
autoLinkUser.Name = loginInfo.ExternalIdentity.Name;
|
||||
foreach (var userGroup in groups)
|
||||
{
|
||||
autoLinkUser.AddRole(userGroup.Alias);
|
||||
}
|
||||
|
||||
//call the callback if one is assigned
|
||||
if (autoLinkOptions.OnAutoLinking != null)
|
||||
{
|
||||
autoLinkOptions.OnAutoLinking(autoLinkUser, loginInfo);
|
||||
}
|
||||
|
||||
var userCreationResult = await UserManager.CreateAsync(autoLinkUser);
|
||||
|
||||
if (userCreationResult.Succeeded == false)
|
||||
{
|
||||
ViewData[TokenExternalSignInError] = userCreationResult.Errors;
|
||||
}
|
||||
else
|
||||
{
|
||||
var linkResult = await UserManager.AddLoginAsync(autoLinkUser.Id, loginInfo.Login);
|
||||
if (linkResult.Succeeded == false)
|
||||
{
|
||||
ViewData[TokenExternalSignInError] = linkResult.Errors;
|
||||
|
||||
//If this fails, we should really delete the user since it will be in an inconsistent state!
|
||||
var deleteResult = await UserManager.DeleteAsync(autoLinkUser);
|
||||
if (deleteResult.Succeeded == false)
|
||||
{
|
||||
//DOH! ... this isn't good, combine all errors to be shown
|
||||
ViewData[TokenExternalSignInError] = linkResult.Errors.Concat(deleteResult.Errors);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//sign in
|
||||
await SignInManager.SignInAsync(autoLinkUser, isPersistent: false, rememberBrowser: false);
|
||||
}
|
||||
}
|
||||
//sign in
|
||||
await SignInManager.SignInAsync(autoLinkUser, isPersistent: false, rememberBrowser: false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -532,6 +532,7 @@ namespace Umbraco.Web.Editors
|
||||
/// <returns></returns>
|
||||
[FileUploadCleanupFilter]
|
||||
[ContentPostValidate]
|
||||
[OutgoingEditorModelEvent]
|
||||
public ContentItemDisplay PostSave(
|
||||
[ModelBinder(typeof(ContentItemBinder))]
|
||||
ContentItemSave contentItem)
|
||||
@@ -792,11 +793,8 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
var contentService = Services.ContentService;
|
||||
|
||||
// content service GetByIds does order the content items based on the order of Ids passed in
|
||||
var content = contentService.GetByIds(sorted.IdSortOrder);
|
||||
|
||||
// Save content with new sort order and update content xml in db accordingly
|
||||
if (contentService.Sort(content) == false)
|
||||
if (contentService.Sort(sorted.IdSortOrder) == false)
|
||||
{
|
||||
LogHelper.Warn<ContentController>("Content sorting failed, this was probably caused by an event being cancelled");
|
||||
return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled");
|
||||
@@ -850,6 +848,7 @@ namespace Umbraco.Web.Editors
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[EnsureUserPermissionForContent("id", 'U')]
|
||||
[OutgoingEditorModelEvent]
|
||||
public ContentItemDisplay PostUnPublish(int id)
|
||||
{
|
||||
var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
public sealed class EditorModelEventArgs<T> : EditorModelEventArgs
|
||||
{
|
||||
public EditorModelEventArgs(EditorModelEventArgs baseArgs)
|
||||
: base(baseArgs.Model, baseArgs.UmbracoContext)
|
||||
{
|
||||
Model = (T)baseArgs.Model;
|
||||
}
|
||||
|
||||
public EditorModelEventArgs(T model, UmbracoContext umbracoContext)
|
||||
: base(model, umbracoContext)
|
||||
{
|
||||
Model = model;
|
||||
}
|
||||
|
||||
public new T Model { get; private set; }
|
||||
}
|
||||
|
||||
public class EditorModelEventArgs : EventArgs
|
||||
{
|
||||
public EditorModelEventArgs(object model, UmbracoContext umbracoContext)
|
||||
{
|
||||
Model = model;
|
||||
UmbracoContext = umbracoContext;
|
||||
}
|
||||
|
||||
public object Model { get; private set; }
|
||||
public UmbracoContext UmbracoContext { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,9 @@
|
||||
using System;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
public class EditorModelEventArgs : EventArgs
|
||||
{
|
||||
public EditorModelEventArgs(object model, UmbracoContext umbracoContext)
|
||||
{
|
||||
Model = model;
|
||||
UmbracoContext = umbracoContext;
|
||||
}
|
||||
|
||||
public object Model { get; private set; }
|
||||
public UmbracoContext UmbracoContext { get; private set; }
|
||||
}
|
||||
|
||||
public sealed class EditorModelEventArgs<T> : EditorModelEventArgs
|
||||
{
|
||||
public EditorModelEventArgs(EditorModelEventArgs baseArgs)
|
||||
: base(baseArgs.Model, baseArgs.UmbracoContext)
|
||||
{
|
||||
Model = (T)baseArgs.Model;
|
||||
}
|
||||
|
||||
public EditorModelEventArgs(T model, UmbracoContext umbracoContext)
|
||||
: base(model, umbracoContext)
|
||||
{
|
||||
Model = model;
|
||||
}
|
||||
|
||||
public new T Model { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to emit events for editor models in the back office
|
||||
/// </summary>
|
||||
@@ -42,6 +12,13 @@ namespace Umbraco.Web.Editors
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<ContentItemDisplay>> SendingContentModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<MediaItemDisplay>> SendingMediaModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<MemberDisplay>> SendingMemberModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<UserDisplay>> SendingUserModel;
|
||||
|
||||
private static void OnSendingUserModel(HttpActionExecutedContext sender, EditorModelEventArgs<UserDisplay> e)
|
||||
{
|
||||
var handler = SendingUserModel;
|
||||
if (handler != null) handler(sender, e);
|
||||
}
|
||||
|
||||
private static void OnSendingContentModel(HttpActionExecutedContext sender, EditorModelEventArgs<ContentItemDisplay> e)
|
||||
{
|
||||
@@ -85,6 +62,12 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
OnSendingMemberModel(sender, new EditorModelEventArgs<MemberDisplay>(e));
|
||||
}
|
||||
|
||||
var userDisplay = e.Model as UserDisplay;
|
||||
if (userDisplay != null)
|
||||
{
|
||||
OnSendingUserModel(sender, new EditorModelEventArgs<UserDisplay>(e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -461,6 +461,7 @@ namespace Umbraco.Web.Editors
|
||||
/// <returns></returns>
|
||||
[FileUploadCleanupFilter]
|
||||
[MediaPostValidate]
|
||||
[OutgoingEditorModelEvent]
|
||||
public MediaItemDisplay PostSave(
|
||||
[ModelBinder(typeof(MediaItemBinder))]
|
||||
MediaItemSave contentItem)
|
||||
|
||||
@@ -256,6 +256,7 @@ namespace Umbraco.Web.Editors
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[FileUploadCleanupFilter]
|
||||
[OutgoingEditorModelEvent]
|
||||
public MemberDisplay PostSave(
|
||||
[ModelBinder(typeof(MemberBinder))]
|
||||
MemberSave contentItem)
|
||||
@@ -393,7 +394,7 @@ namespace Umbraco.Web.Editors
|
||||
UpdateName(contentItem);
|
||||
|
||||
//map the custom properties - this will already be set for new entities in our member binder
|
||||
contentItem.PersistedContent.Email = contentItem.Email;
|
||||
contentItem.PersistedContent.Email = contentItem.Email;
|
||||
contentItem.PersistedContent.Username = contentItem.Username;
|
||||
|
||||
//use the base method to map the rest of the properties
|
||||
@@ -555,7 +556,7 @@ namespace Umbraco.Web.Editors
|
||||
var builtInAliases = Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray();
|
||||
foreach (var p in contentItem.PersistedContent.Properties)
|
||||
{
|
||||
var valueMapped = currProps.SingleOrDefault(x => x.Alias == p.Alias);
|
||||
var valueMapped = currProps.FirstOrDefault(x => x.Alias == p.Alias);
|
||||
if (builtInAliases.Contains(p.Alias) == false && valueMapped != null)
|
||||
{
|
||||
p.Value = valueMapped.Value;
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
@@ -11,29 +15,54 @@ using Constants = Umbraco.Core.Constants;
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
[UmbracoApplicationAuthorize(Constants.Applications.Content)]
|
||||
[UmbracoApplicationAuthorize(Constants.Applications.Content)]
|
||||
public class TourController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
//TODO: Strongly type this for final release!
|
||||
public JArray GetTours()
|
||||
public IEnumerable<BackOfficeTourFile> GetTours()
|
||||
{
|
||||
//TODO: Add error checking to this for final release!
|
||||
|
||||
var result = new JArray();
|
||||
var result = new List<BackOfficeTourFile>();
|
||||
|
||||
if (UmbracoConfig.For.UmbracoSettings().BackOffice.Tours.EnableTours == false)
|
||||
return result;
|
||||
|
||||
var tourFiles = Directory.GetFiles(
|
||||
Path.Combine(IOHelper.MapPath(SystemDirectories.Config), "BackOfficeTours"), "*.json")
|
||||
.OrderBy(x => x, StringComparer.InvariantCultureIgnoreCase);
|
||||
foreach (var tourFile in tourFiles)
|
||||
var coreTourFiles = Directory.GetFiles(
|
||||
Path.Combine(IOHelper.MapPath(SystemDirectories.Config), "BackOfficeTours"), "*.json");
|
||||
|
||||
foreach (var tourFile in coreTourFiles)
|
||||
{
|
||||
var contents = File.ReadAllText(tourFile);
|
||||
result.Add(JArray.Parse(contents));
|
||||
|
||||
result.Add(new BackOfficeTourFile
|
||||
{
|
||||
FileName = Path.GetFileNameWithoutExtension(tourFile),
|
||||
Tours = JsonConvert.DeserializeObject<BackOfficeTour[]>(contents)
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
//collect all tour files in packges
|
||||
foreach (var plugin in Directory.EnumerateDirectories(IOHelper.MapPath(SystemDirectories.AppPlugins)))
|
||||
{
|
||||
var pluginName = Path.GetFileName(plugin.TrimEnd('\\'));
|
||||
|
||||
foreach (var backofficeDir in Directory.EnumerateDirectories(plugin, "backoffice"))
|
||||
{
|
||||
foreach (var tourDir in Directory.EnumerateDirectories(backofficeDir, "tours"))
|
||||
{
|
||||
foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json"))
|
||||
{
|
||||
var contents = File.ReadAllText(tourFile);
|
||||
result.Add(new BackOfficeTourFile
|
||||
{
|
||||
FileName = Path.GetFileNameWithoutExtension(tourFile),
|
||||
PluginName = pluginName,
|
||||
Tours = JsonConvert.DeserializeObject<BackOfficeTour[]>(contents)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,6 +182,7 @@ namespace Umbraco.Web.Editors
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[OutgoingEditorModelEvent]
|
||||
public UserDisplay GetById(int id)
|
||||
{
|
||||
var user = Services.UserService.GetUserById(id);
|
||||
@@ -494,6 +495,7 @@ namespace Umbraco.Web.Editors
|
||||
/// </summary>
|
||||
/// <param name="userSave"></param>
|
||||
/// <returns></returns>
|
||||
[OutgoingEditorModelEvent]
|
||||
public async Task<UserDisplay> PostSaveUser(UserSave userSave)
|
||||
{
|
||||
if (userSave == null) throw new ArgumentNullException("userSave");
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Examine;
|
||||
using Examine.Config;
|
||||
using Examine.LuceneEngine;
|
||||
using Examine.LuceneEngine.Providers;
|
||||
using Examine.Providers;
|
||||
using Lucene.Net.Index;
|
||||
using Lucene.Net.Store;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using UmbracoExamine;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to configure Examine during startup for the web application
|
||||
/// </summary>
|
||||
internal class ExamineStartup
|
||||
{
|
||||
private readonly List<BaseIndexProvider> _indexesToRebuild = new List<BaseIndexProvider>();
|
||||
private readonly ApplicationContext _appCtx;
|
||||
private readonly ProfilingLogger _profilingLogger;
|
||||
private static bool _isConfigured = false;
|
||||
|
||||
//this is used if we are not the MainDom, in which case we need to ensure that if indexes need rebuilding that this
|
||||
//doesn't occur since that should only occur when we are MainDom
|
||||
private bool _disableExamineIndexing = false;
|
||||
|
||||
public ExamineStartup(ApplicationContext appCtx)
|
||||
{
|
||||
_appCtx = appCtx;
|
||||
_profilingLogger = appCtx.ProfilingLogger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called during the initialize operation of the boot manager process
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
//We want to manage Examine's appdomain shutdown sequence ourselves so first we'll disable Examine's default behavior
|
||||
//and then we'll use MainDom to control Examine's shutdown
|
||||
ExamineManager.DisableDefaultHostingEnvironmentRegistration();
|
||||
|
||||
//we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain
|
||||
//terminates and in some rare cases would only allow unlocking of the file if IIS is forcefully terminated. Instead we'll rely on the simplefslock
|
||||
//which simply checks the existence of the lock file
|
||||
DirectoryTracker.DefaultLockFactory = d =>
|
||||
{
|
||||
var simpleFsLockFactory = new NoPrefixSimpleFsLockFactory(d);
|
||||
return simpleFsLockFactory;
|
||||
};
|
||||
|
||||
//This is basically a hack for this item: http://issues.umbraco.org/issue/U4-5976
|
||||
// when Examine initializes it will try to rebuild if the indexes are empty, however in many cases not all of Examine's
|
||||
// event handlers will be assigned during bootup when the rebuilding starts which is a problem. So with the examine 0.1.58.2941 build
|
||||
// it has an event we can subscribe to in order to cancel this rebuilding process, but what we'll do is cancel it and postpone the rebuilding until the
|
||||
// boot process has completed. It's a hack but it works.
|
||||
ExamineManager.Instance.BuildingEmptyIndexOnStartup += OnInstanceOnBuildingEmptyIndexOnStartup;
|
||||
|
||||
//let's deal with shutting down Examine with MainDom
|
||||
var examineShutdownRegistered = _appCtx.MainDom.Register(() =>
|
||||
{
|
||||
using (_profilingLogger.TraceDuration<ExamineStartup>("Examine shutting down"))
|
||||
{
|
||||
//Due to the way Examine's own IRegisteredObject works, we'll first run it with immediate=false and then true so that
|
||||
//it's correct subroutines are executed (otherwise we'd have to run this logic manually ourselves)
|
||||
ExamineManager.Instance.Stop(false);
|
||||
ExamineManager.Instance.Stop(true);
|
||||
}
|
||||
});
|
||||
if (examineShutdownRegistered)
|
||||
{
|
||||
_profilingLogger.Logger.Debug<ExamineStartup>("Examine shutdown registered with MainDom");
|
||||
}
|
||||
else
|
||||
{
|
||||
_profilingLogger.Logger.Debug<ExamineStartup>("Examine shutdown not registered, this appdomain is not the MainDom");
|
||||
|
||||
//if we could not register the shutdown examine ourselves, it means we are not maindom! in this case all of examine should be disabled
|
||||
//from indexing anything on startup!!
|
||||
_disableExamineIndexing = true;
|
||||
Suspendable.ExamineEvents.SuspendIndexers();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called during the Complete operation of the boot manager process
|
||||
/// </summary>
|
||||
public void Complete()
|
||||
{
|
||||
EnsureUnlockedAndConfigured();
|
||||
|
||||
//Ok, now that everything is complete we'll check if we've stored any references to index that need rebuilding and run them
|
||||
// (see the initialize method for notes) - we'll ensure we remove the event handler too in case examine manager doesn't actually
|
||||
// initialize during startup, in which case we want it to rebuild the indexes itself.
|
||||
ExamineManager.Instance.BuildingEmptyIndexOnStartup -= OnInstanceOnBuildingEmptyIndexOnStartup;
|
||||
|
||||
//don't do anything if we have disabled this
|
||||
if (_disableExamineIndexing == false)
|
||||
{
|
||||
foreach (var indexer in _indexesToRebuild)
|
||||
{
|
||||
indexer.RebuildIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to perform the rebuilding indexes on startup if the indexes don't exist
|
||||
/// </summary>
|
||||
public void RebuildIndexes()
|
||||
{
|
||||
//don't do anything if we have disabled this
|
||||
if (_disableExamineIndexing) return;
|
||||
|
||||
EnsureUnlockedAndConfigured();
|
||||
|
||||
//If the developer has explicitly opted out of rebuilding indexes on startup then we
|
||||
// should adhere to that and not do it, this means that if they are load balancing things will be
|
||||
// out of sync if they are auto-scaling but there's not much we can do about that.
|
||||
if (ExamineSettings.Instance.RebuildOnAppStart == false) return;
|
||||
|
||||
foreach (var indexer in GetIndexesForColdBoot())
|
||||
{
|
||||
indexer.RebuildIndex();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method used to create indexes on a cold boot
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A cold boot is when the server determines it will not (or cannot) process instructions in the cache table and
|
||||
/// will rebuild it's own caches itself.
|
||||
/// </remarks>
|
||||
public IEnumerable<BaseIndexProvider> GetIndexesForColdBoot()
|
||||
{
|
||||
// NOTE: This is IMPORTANT! ... we don't want to rebuild any index that is already flagged to be re-indexed
|
||||
// on startup based on our _indexesToRebuild variable and how Examine auto-rebuilds when indexes are empty.
|
||||
// This callback is used above for the DatabaseServerMessenger startup options.
|
||||
|
||||
// all indexes
|
||||
IEnumerable<BaseIndexProvider> indexes = ExamineManager.Instance.IndexProviderCollection;
|
||||
|
||||
// except those that are already flagged
|
||||
// and are processed in Complete()
|
||||
if (_indexesToRebuild.Any())
|
||||
indexes = indexes.Except(_indexesToRebuild);
|
||||
|
||||
// return
|
||||
foreach (var index in indexes)
|
||||
yield return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Must be called to configure each index and ensure it's unlocked before any indexing occurs
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before
|
||||
/// either of these happens, we need to configure the indexes.
|
||||
/// </remarks>
|
||||
private void EnsureUnlockedAndConfigured()
|
||||
{
|
||||
if (_isConfigured) return;
|
||||
|
||||
_isConfigured = true;
|
||||
|
||||
foreach (var luceneIndexer in ExamineManager.Instance.IndexProviderCollection.OfType<LuceneIndexer>())
|
||||
{
|
||||
//We now need to disable waiting for indexing for Examine so that the appdomain is shutdown immediately and doesn't wait for pending
|
||||
//indexing operations. We used to wait for indexing operations to complete but this can cause more problems than that is worth because
|
||||
//that could end up halting shutdown for a very long time causing overlapping appdomains and many other problems.
|
||||
luceneIndexer.WaitForIndexQueueOnShutdown = false;
|
||||
|
||||
//we should check if the index is locked ... it shouldn't be! We are using simple fs lock now and we are also ensuring that
|
||||
//the indexes are not operational unless MainDom is true so if _disableExamineIndexing is false then we should be in charge
|
||||
if (_disableExamineIndexing == false)
|
||||
{
|
||||
var dir = luceneIndexer.GetLuceneDirectory();
|
||||
if (IndexWriter.IsLocked(dir))
|
||||
{
|
||||
_profilingLogger.Logger.Info<ExamineStartup>("Forcing index " + luceneIndexer.IndexSetName + " to be unlocked since it was left in a locked state");
|
||||
IndexWriter.Unlock(dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInstanceOnBuildingEmptyIndexOnStartup(object sender, BuildingEmptyIndexOnStartupEventArgs args)
|
||||
{
|
||||
//store the indexer that needs rebuilding because it's empty for when the boot process
|
||||
// is complete and cancel this current event so the rebuild process doesn't start right now.
|
||||
args.Cancel = true;
|
||||
_indexesToRebuild.Add((BaseIndexProvider)args.Indexer);
|
||||
|
||||
//check if the index is rebuilding due to an error and log it
|
||||
if (args.IsHealthy == false)
|
||||
{
|
||||
var baseIndex = args.Indexer as BaseIndexProvider;
|
||||
var name = baseIndex != null ? baseIndex.Name : "[UKNOWN]";
|
||||
|
||||
_profilingLogger.Logger.Error<ExamineStartup>(string.Format("The index {0} is rebuilding due to being unreadable/corrupt", name), args.UnhealthyException);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Umbraco.Core.Collections;
|
||||
using Umbraco.Web.WebApi;
|
||||
|
||||
namespace Umbraco.Web.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents disabled features.
|
||||
/// </summary>
|
||||
internal class DisabledFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DisabledFeatures"/> class.
|
||||
/// </summary>
|
||||
public DisabledFeatures()
|
||||
{
|
||||
Controllers = new TypeList<UmbracoApiControllerBase>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the disabled controllers.
|
||||
/// </summary>
|
||||
public TypeList<UmbracoApiControllerBase> Controllers { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Umbraco.Core.ObjectResolution;
|
||||
|
||||
namespace Umbraco.Web.Features
|
||||
{
|
||||
internal class FeaturesResolver : SingleObjectResolverBase<FeaturesResolver, UmbracoFeatures>
|
||||
{
|
||||
public FeaturesResolver(UmbracoFeatures value)
|
||||
: base(value)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the features.
|
||||
/// </summary>
|
||||
/// <remarks>For developers, at application startup.</remarks>
|
||||
public void SetFeatures(UmbracoFeatures features)
|
||||
{
|
||||
Value = features;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the features.
|
||||
/// </summary>
|
||||
public UmbracoFeatures Features
|
||||
{
|
||||
get { return Value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Umbraco.Web.WebApi;
|
||||
|
||||
namespace Umbraco.Web.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Umbraco features.
|
||||
/// </summary>
|
||||
internal class UmbracoFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoFeatures"/> class.
|
||||
/// </summary>
|
||||
public UmbracoFeatures()
|
||||
{
|
||||
Disabled = new DisabledFeatures();
|
||||
}
|
||||
|
||||
// note
|
||||
// currently, the only thing a FeatureSet does is list disabled controllers,
|
||||
// but eventually we could enable and disable more parts of Umbraco. and then
|
||||
// we would need some logic to figure out what's enabled/disabled - hence it's
|
||||
// better to use IsEnabled, where the logic would go, rather than directly
|
||||
// accessing the Disabled collection.
|
||||
|
||||
/// <summary>
|
||||
/// Gets the disabled features.
|
||||
/// </summary>
|
||||
public DisabledFeatures Disabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a feature is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled(Type feature)
|
||||
{
|
||||
if (typeof(UmbracoApiControllerBase).IsAssignableFrom(feature))
|
||||
return Disabled.Controllers.Contains(feature) == false;
|
||||
|
||||
throw new NotSupportedException("Not a supported feature type.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user