Compare commits
279 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2dc11f6fa1 | |||
| fe2e689865 | |||
| ece5fecf12 | |||
| b21dea84d9 | |||
| 492b0aadc8 | |||
| b399630202 | |||
| b9b1fc3a70 | |||
| 1d52f59657 | |||
| 78f6b8d8bc | |||
| 0afee71e9e | |||
| ffa7b922e8 | |||
| e6de9e0004 | |||
| 08aff2654a | |||
| cd0fdae47b | |||
| 0f3a76809b | |||
| 4f88da2c26 | |||
| 4ce0553630 | |||
| b1b285b673 | |||
| c773714e2d | |||
| 2a90478ba7 | |||
| 93756a62f4 | |||
| 2343004be1 | |||
| d6fe350a4a | |||
| 23321f8ac9 | |||
| 71902f3b9b | |||
| 13b544521d | |||
| 2cf8c1207d | |||
| ab8653d3de | |||
| 748a3c167e | |||
| 5b93b35882 | |||
| b29ee4df60 | |||
| d8fe3c6faf | |||
| 9b7a9fc988 | |||
| 62485157ad | |||
| 4ce7b089ca | |||
| 9e91098ebd | |||
| 49356b31a1 | |||
| 507c2353e9 | |||
| d02139bbcd | |||
| 8a19b97900 | |||
| 981fad1ec8 | |||
| 019147c1a1 | |||
| 782d6100f0 | |||
| e17690fbed | |||
| d7d1b3f2a0 | |||
| 5b19d5e0d8 | |||
| 7c21889239 | |||
| 52d35c06eb | |||
| 52c3ea24d5 | |||
| 03756ab894 | |||
| 8e35d904cb | |||
| 9808492509 | |||
| 81788338e3 | |||
| 3ffe1517e0 | |||
| 641e74b747 | |||
| 49c316e13d | |||
| 5235053523 | |||
| af6d2df823 | |||
| bbe80d882a | |||
| 408ec17201 | |||
| a4ef487fab | |||
| 8208f8de51 | |||
| e8ea7f9d29 | |||
| e431a41fc1 | |||
| 1f5c9d2e1b | |||
| 372f981d33 | |||
| 6f03e221c6 | |||
| 90a5adec8f | |||
| 62939385a0 | |||
| d4729e1483 | |||
| 9de35f0202 | |||
| 773d249d36 | |||
| d12a492cb6 | |||
| ef0b521083 | |||
| 6261447923 | |||
| d18881c8ad | |||
| 89596dbe0b | |||
| 358536ffaa | |||
| 4592f8e8d8 | |||
| 0038d173a2 | |||
| 68e6ad76fb | |||
| 6462329494 | |||
| 2fdd1c0bd2 | |||
| 891bea81bf | |||
| f71577f86e | |||
| e3e88b3679 | |||
| 5dc993890d | |||
| a2025c14b1 | |||
| 34f811c128 | |||
| 7e4c284abb | |||
| 664d0c5160 | |||
| eb26fb901f | |||
| 3141b04376 | |||
| 201fb5dc01 | |||
| 15e668c056 | |||
| a42f425971 | |||
| 3f4774855e | |||
| 28f995cea6 | |||
| cfb14d7089 | |||
| ef4d41c80a | |||
| 584e47f51f | |||
| 66e40fcbb0 | |||
| 4ed236249a | |||
| 00a4287945 | |||
| 92d52bf99b | |||
| 2e611c20d4 | |||
| ae10f3d21f | |||
| 7a9b392f69 | |||
| 493d71dba9 | |||
| 55d4e4072a | |||
| f687b34c14 | |||
| 507517c1ad | |||
| ae8cfb2b23 | |||
| 9048252826 | |||
| e76819a570 | |||
| 87c311d6e7 | |||
| 86d8eae984 | |||
| 6aac216763 | |||
| b7c3fb7f30 | |||
| a0a3b7e9c9 | |||
| d98db5e6bc | |||
| 48adc4c98d | |||
| 79d4562cb1 | |||
| f48263e5a2 | |||
| 63196d7e53 | |||
| 72a0d614af | |||
| 4f8bb7fb51 | |||
| 1c883be16b | |||
| 73d681d674 | |||
| 8daa558997 | |||
| b78fd0f6ac | |||
| 831eb3ef5e | |||
| fa68bee762 | |||
| 796700b4fe | |||
| ab09042fdc | |||
| 041d4a4a8b | |||
| ee79d7ecdc | |||
| e33456d168 | |||
| df2c104002 | |||
| f265fa683c | |||
| 12093ae7ea | |||
| 6a980e64eb | |||
| 37071ada78 | |||
| 3c7e820773 | |||
| 203837aa84 | |||
| 5fe63c2c1a | |||
| 2c1d85989f | |||
| d518ab908e | |||
| bc32ff31d4 | |||
| 7804677f2b | |||
| 27c884f4eb | |||
| a966f63bbd | |||
| 76f9e6baee | |||
| 49f17be9df | |||
| 8a114a105e | |||
| 5f9865cc73 | |||
| b891b18b20 | |||
| bc461b1646 | |||
| b0eeaecf1c | |||
| 2708f11fcd | |||
| c59ac4ee0e | |||
| a03aa9cbab | |||
| 89b714c960 | |||
| d9b0b0112e | |||
| 6640039cdf | |||
| 70deffd418 | |||
| f31d5b12da | |||
| 7fd6155807 | |||
| 043c677ddc | |||
| f634d9f8b1 | |||
| db637d9c31 | |||
| 24b521881c | |||
| 0b3a38ec8f | |||
| 330673bef9 | |||
| 661ff0e726 | |||
| e1c45fda9a | |||
| b45a4a18f2 | |||
| 1456e7028a | |||
| b38ee33e5a | |||
| 903a9f93c3 | |||
| b08376651a | |||
| 75514c7c42 | |||
| ea6f561c96 | |||
| 053544a578 | |||
| 4308718c88 | |||
| 1b59ca0412 | |||
| 282cc0c722 | |||
| 4584abaa72 | |||
| c1ca0c9c2f | |||
| 27c260cd03 | |||
| 73a5c55547 | |||
| b8831c253e | |||
| b2d3e75566 | |||
| cb526170e2 | |||
| 93237bfdb8 | |||
| 1c39c741b6 | |||
| ec2589f517 | |||
| 2ca6e8be9a | |||
| b9d1a776cb | |||
| 5eb609d8a0 | |||
| 4d5069726b | |||
| 0da8644e77 | |||
| e643189b10 | |||
| 97716d5570 | |||
| 5517252200 | |||
| 92a5c31ce6 | |||
| 345c038e8b | |||
| 59defa6b11 | |||
| 7e0d7ebbd4 | |||
| c13d4ed235 | |||
| a47b5035fe | |||
| f6be389f4f | |||
| 252f0fe88e | |||
| 387faca71e | |||
| 62cda02ded | |||
| 3e2a6424bd | |||
| c0f695bcf9 | |||
| 2e1e2e4325 | |||
| 1316bb5d5b | |||
| fe97860694 | |||
| 98b4aa6bae | |||
| eb7f4e74f2 | |||
| 4a9e326267 | |||
| 67edc6cf9a | |||
| e740838ef7 | |||
| dec44b1288 | |||
| 98a0729549 | |||
| 97868593f8 | |||
| dcdfb074af | |||
| a36b1097c5 | |||
| e268a05f6e | |||
| fb7d22dca8 | |||
| bac53bc241 | |||
| ce690acda0 | |||
| cb8157dfca | |||
| 4c7c6e37d1 | |||
| 116e53e07d | |||
| a74c4433f7 | |||
| 2ccf74a921 | |||
| f3a81d82be | |||
| 5451d28fe4 | |||
| db57268440 | |||
| ac265067e8 | |||
| 5330830362 | |||
| 4b6b24b2f5 | |||
| 7d5d89e9cb | |||
| ab85cbf599 | |||
| c6937618c2 | |||
| ef84d63982 | |||
| 280a7445f4 | |||
| 5ae8a47f4b | |||
| bc148d08fa | |||
| 57a1e73383 | |||
| c7b9a57795 | |||
| eb1edf94d3 | |||
| c0b230a95e | |||
| 607c2cc8e7 | |||
| 48d3a3defd | |||
| 8509285aae | |||
| 2413e9951f | |||
| 1a444fd703 | |||
| bd68a140ba | |||
| 7b34211bb1 | |||
| fa78223229 | |||
| 39c19dc1d4 | |||
| 7b4b6357cb | |||
| 4454290820 | |||
| d94cd9dc8d | |||
| 8c07af9505 | |||
| d32fb44187 | |||
| dd38480ce2 | |||
| 08e8e3918e | |||
| 7a3591d1bd | |||
| 59ab7f5c8d | |||
| c2b2182182 | |||
| 66b656f058 | |||
| c8f7ecabf4 | |||
| 9137249510 | |||
| 841074fc27 |
@@ -27,19 +27,18 @@
|
||||
<dependency id="MiniProfiler" version="[2.1.0, 3.0.0)" />
|
||||
<dependency id="HtmlAgilityPack" version="[1.4.9.5, 2.0.0)" />
|
||||
<dependency id="Lucene.Net" version="[2.9.4.1, 3.0.0.0)" />
|
||||
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
|
||||
<dependency id="MySql.Data" version="[6.9.9, 7.0.0)" />
|
||||
<dependency id="ClientDependency" version="[1.9.2, 2.0.0)" />
|
||||
<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.89, 1.0.0)" />
|
||||
<dependency id="Examine" version="[0.1.85, 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.8, 4.0.0)" />
|
||||
<dependency id="Umbraco.ModelsBuilder" version="[3.0.7, 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>
|
||||
|
||||
@@ -49,9 +49,11 @@
|
||||
<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" />
|
||||
|
||||
+2
-2
@@ -11,5 +11,5 @@ using System.Resources;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyFileVersion("7.7.8")]
|
||||
[assembly: AssemblyInformationalVersion("7.7.8")]
|
||||
[assembly: AssemblyFileVersion("7.8.0")]
|
||||
[assembly: AssemblyInformationalVersion("7.8.0-beta")]
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Threading;
|
||||
using Semver;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.ObjectResolution;
|
||||
@@ -261,7 +261,7 @@ namespace Umbraco.Core
|
||||
/// - http://issues.umbraco.org/issue/U4-5728
|
||||
/// - http://issues.umbraco.org/issue/U4-5391
|
||||
/// </remarks>
|
||||
public string UmbracoApplicationUrl
|
||||
internal string UmbracoApplicationUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -269,21 +269,9 @@ namespace Umbraco.Core
|
||||
return _umbracoApplicationUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the url.
|
||||
/// </summary>
|
||||
public void ResetUmbracoApplicationUrl()
|
||||
{
|
||||
_umbracoApplicationUrl = null;
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
internal string _umbracoApplicationUrl;
|
||||
|
||||
internal List<string> _umbracoApplicationDomains = new List<string>();
|
||||
|
||||
internal string _umbracoApplicationDeploymentId;
|
||||
internal string _umbracoApplicationUrl;
|
||||
|
||||
private Lazy<bool> _configured;
|
||||
internal MainDom MainDom { get; private set; }
|
||||
@@ -354,6 +342,52 @@ namespace Umbraco.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Current Version of the Umbraco Site before an upgrade
|
||||
/// by using the last/most recent Umbraco Migration that has been run
|
||||
/// </summary>
|
||||
/// <returns>A SemVersion of the latest Umbraco DB Migration run</returns>
|
||||
/// <remarks>
|
||||
/// NOTE: This existed in the InstallHelper previously but should really be here so it can be re-used if necessary
|
||||
/// </remarks>
|
||||
internal SemVersion CurrentVersion()
|
||||
{
|
||||
//Set a default version of 0.0.0
|
||||
var version = new SemVersion(0);
|
||||
|
||||
//If we have a db context available, if we don't then we are not installed anyways
|
||||
if (DatabaseContext.IsDatabaseConfigured && DatabaseContext.CanConnect)
|
||||
version = DatabaseContext.ValidateDatabaseSchema().DetermineInstalledVersionByMigrations(Services.MigrationEntryService);
|
||||
|
||||
if (version != new SemVersion(0))
|
||||
return version;
|
||||
|
||||
// If we aren't able to get a result from the umbracoMigrations table then use the version in web.config, if it's available
|
||||
if (string.IsNullOrWhiteSpace(GlobalSettings.ConfigurationStatus))
|
||||
return version;
|
||||
|
||||
var configuredVersion = GlobalSettings.ConfigurationStatus;
|
||||
|
||||
string currentComment = null;
|
||||
|
||||
var current = configuredVersion.Split('-');
|
||||
if (current.Length > 1)
|
||||
currentComment = current[1];
|
||||
|
||||
Version currentVersion;
|
||||
if (Version.TryParse(current[0], out currentVersion))
|
||||
{
|
||||
version = new SemVersion(
|
||||
currentVersion.Major,
|
||||
currentVersion.Minor,
|
||||
currentVersion.Build,
|
||||
string.IsNullOrWhiteSpace(currentComment) ? null : currentComment,
|
||||
currentVersion.Revision > 0 ? currentVersion.Revision.ToString() : null);
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private void AssertIsNotReady()
|
||||
{
|
||||
if (this.IsReady)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Configuration;
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
internal class BackOfficeElement : UmbracoConfigurationElement, IBackOfficeSection
|
||||
{
|
||||
[ConfigurationProperty("tours")]
|
||||
internal TourConfigElement Tours
|
||||
{
|
||||
get { return (TourConfigElement)this["tours"]; }
|
||||
}
|
||||
|
||||
ITourSection IBackOfficeSection.Tours
|
||||
{
|
||||
get { return Tours; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
public interface IBackOfficeSection
|
||||
{
|
||||
ITourSection Tours { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
public interface ITourSection
|
||||
{
|
||||
bool EnableTours { get; }
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
public interface IUmbracoSettingsSection : IUmbracoConfigurationSection
|
||||
{
|
||||
IBackOfficeSection BackOffice { get; }
|
||||
|
||||
IContentSection Content { get; }
|
||||
|
||||
ISecuritySection Security { get; }
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Configuration;
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
internal class TourConfigElement : UmbracoConfigurationElement, ITourSection
|
||||
{
|
||||
//disabled by default so that upgraders don't get it enabled by default
|
||||
//TODO: we probably just want to disable the initial one from automatically loading ?
|
||||
[ConfigurationProperty("enable", DefaultValue = false)]
|
||||
public bool EnableTours
|
||||
{
|
||||
get { return (bool)this["enable"]; }
|
||||
}
|
||||
|
||||
//TODO: We could have additional filters, etc... defined here
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
|
||||
public class UmbracoSettingsSection : ConfigurationSection, IUmbracoSettingsSection
|
||||
{
|
||||
[ConfigurationProperty("backOffice")]
|
||||
internal BackOfficeElement BackOffice
|
||||
{
|
||||
get { return (BackOfficeElement)this["backOffice"]; }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("content")]
|
||||
internal ContentElement Content
|
||||
{
|
||||
@@ -149,6 +155,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
get { return Templates; }
|
||||
}
|
||||
|
||||
IBackOfficeSection IUmbracoSettingsSection.BackOffice
|
||||
{
|
||||
get { return BackOffice; }
|
||||
}
|
||||
|
||||
IDeveloperSection IUmbracoSettingsSection.Developer
|
||||
{
|
||||
get { return Developer; }
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
|
||||
{
|
||||
public class UmbracoVersion
|
||||
{
|
||||
private static readonly Version Version = new Version("7.7.8");
|
||||
private static readonly Version Version = new Version("7.8.0");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current version of Umbraco.
|
||||
|
||||
@@ -76,257 +76,260 @@ namespace Umbraco.Core.Events
|
||||
{
|
||||
if (_events == null)
|
||||
return Enumerable.Empty<IEventDefinition>();
|
||||
|
||||
IReadOnlyList<IEventDefinition> events;
|
||||
|
||||
switch (filter)
|
||||
{
|
||||
case EventDefinitionFilter.All:
|
||||
events = _events;
|
||||
break;
|
||||
return FilterSupersededAndUpdateToLatestEntity(_events);
|
||||
case EventDefinitionFilter.FirstIn:
|
||||
var l1 = new OrderedHashSet<IEventDefinition>();
|
||||
foreach (var e in _events)
|
||||
{
|
||||
l1.Add(e);
|
||||
events = l1;
|
||||
break;
|
||||
}
|
||||
return FilterSupersededAndUpdateToLatestEntity(l1);
|
||||
case EventDefinitionFilter.LastIn:
|
||||
var l2 = new OrderedHashSet<IEventDefinition>(keepOldest: false);
|
||||
foreach (var e in _events)
|
||||
{
|
||||
l2.Add(e);
|
||||
events = l2;
|
||||
break;
|
||||
}
|
||||
return FilterSupersededAndUpdateToLatestEntity(l2);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("filter", filter, null);
|
||||
}
|
||||
|
||||
return FilterSupersededAndUpdateToLatestEntity(events);
|
||||
}
|
||||
|
||||
private class EventDefinitionInfos
|
||||
|
||||
private class EventDefinitionTypeData
|
||||
{
|
||||
public IEventDefinition EventDefinition { get; set; }
|
||||
public Type[] SupersedeTypes { get; set; }
|
||||
public Type EventArgType { get; set; }
|
||||
public SupersedeEventAttribute[] SupersedeAttributes { get; set; }
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// keeps the 'latest' entity and associated event data
|
||||
var entities = new List<Tuple<IEntity, EventDefinitionInfos>>();
|
||||
//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>();
|
||||
|
||||
// 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>();
|
||||
|
||||
// eagerly fetch superceeded arg types for each arg type
|
||||
var argTypeSuperceeding = events.Select(x => x.Args.GetType())
|
||||
//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())
|
||||
.Distinct()
|
||||
.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
|
||||
.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)
|
||||
for (var index = events.Count - 1; index >= 0; index--)
|
||||
{
|
||||
var def = events[index];
|
||||
var eventDefinition = events[index];
|
||||
|
||||
var infos = new EventDefinitionInfos
|
||||
var argType = eventDefinition.Args.GetType();
|
||||
var attributes = allArgTypesWithAttributes[eventDefinition.Args.GetType()];
|
||||
|
||||
var meta = new EventDefinitionTypeData
|
||||
{
|
||||
EventDefinition = def,
|
||||
SupersedeTypes = argTypeSuperceeding[def.Args.GetType()]
|
||||
EventDefinition = eventDefinition,
|
||||
EventArgType = argType,
|
||||
SupersedeAttributes = attributes
|
||||
};
|
||||
|
||||
var args = def.Args as CancellableObjectEventArgs;
|
||||
if (args == null)
|
||||
var args = eventDefinition.Args as CancellableObjectEventArgs;
|
||||
if (args != 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)
|
||||
var list = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);
|
||||
|
||||
if (list == 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)
|
||||
//extract the event object
|
||||
var obj = args.EventObject as IEntity;
|
||||
if (obj != null)
|
||||
{
|
||||
result.Add(def);
|
||||
continue;
|
||||
//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));
|
||||
}
|
||||
}
|
||||
|
||||
// look for this entity in superceding event args
|
||||
// found = must be removed (ie not added), else track
|
||||
if (IsSuperceeded(eventEntity, infos, entities) == false)
|
||||
else
|
||||
{
|
||||
// track
|
||||
entities.Add(Tuple.Create(eventEntity, infos));
|
||||
|
||||
// track result arguments
|
||||
// include event definition in result
|
||||
resultArgs.Add(args);
|
||||
result.Add(def);
|
||||
//Can't retrieve the entity so cant' filter or inspect, just add to the output
|
||||
result.Add(eventDefinition);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// enumerable of objects
|
||||
var toRemove = new List<IEntity>();
|
||||
foreach (var eventObject in eventObjects)
|
||||
foreach (var entity in list)
|
||||
{
|
||||
// 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);
|
||||
//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));
|
||||
}
|
||||
}
|
||||
else
|
||||
entities.Add(Tuple.Create(eventEntity, infos));
|
||||
{
|
||||
//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
|
||||
}
|
||||
}
|
||||
|
||||
// remove superceded entities
|
||||
//remove anything that has been filtered
|
||||
foreach (var entity in toRemove)
|
||||
eventObjects.Remove(entity);
|
||||
{
|
||||
list.Remove(entity);
|
||||
}
|
||||
|
||||
// if there are still entities in the list, keep the event definition
|
||||
if (eventObjects.Count > 0)
|
||||
//track the event and include in the response if there's still entities remaining in the list
|
||||
if (list.Count > 0)
|
||||
{
|
||||
if (toRemove.Count > 0)
|
||||
{
|
||||
// re-assign if changed
|
||||
args.EventObject = eventObjects;
|
||||
//re-assign if the items have changed
|
||||
args.EventObject = list;
|
||||
}
|
||||
|
||||
// track result arguments
|
||||
// include event definition in result
|
||||
resultArgs.Add(args);
|
||||
result.Add(def);
|
||||
cancelableArgs.Add(args);
|
||||
result.Add(eventDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//it's not a cancelable event arg so we just include it in the result
|
||||
result.Add(eventDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
// go over all args in result, and update them with the latest instanceof each entity
|
||||
UpdateToLatestEntities(entities, resultArgs);
|
||||
//Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args
|
||||
UpdateToLatestEntities(allEntities, cancelableArgs);
|
||||
|
||||
// reverse, since we processed the list in reverse
|
||||
//we need to reverse the result since we've been adding by latest added events first!
|
||||
result.Reverse();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// edits event args to use the latest instance of each entity
|
||||
private static void UpdateToLatestEntities(IEnumerable<Tuple<IEntity, EventDefinitionInfos>> entities, IEnumerable<CancellableObjectEventArgs> args)
|
||||
private static void UpdateToLatestEntities(IEnumerable<Tuple<IEntity, EventDefinitionTypeData>> allEntities, IEnumerable<CancellableObjectEventArgs> cancelableArgs)
|
||||
{
|
||||
// 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 entities.OrderByDescending(entity => entity.Item1.UpdateDate))
|
||||
latestEntities.Add(entity.Item1);
|
||||
//Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args
|
||||
|
||||
foreach (var arg in args)
|
||||
var latestEntities = new OrderedHashSet<IEntity>(keepOldest: true);
|
||||
foreach (var entity in allEntities.OrderByDescending(entity => entity.Item1.UpdateDate))
|
||||
{
|
||||
// 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)
|
||||
latestEntities.Add(entity.Item1);
|
||||
}
|
||||
|
||||
foreach (var args in cancelableArgs)
|
||||
{
|
||||
var list = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);
|
||||
if (list == null)
|
||||
{
|
||||
// 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));
|
||||
//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));
|
||||
if (foundEntity != null)
|
||||
arg.EventObject = foundEntity;
|
||||
{
|
||||
args.EventObject = foundEntity;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// enumerable of objects
|
||||
// same as above but for each object
|
||||
var updated = false;
|
||||
for (var i = 0; i < eventObjects.Count; i++)
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, eventObjects[i]));
|
||||
if (foundEntity == null) continue;
|
||||
eventObjects[i] = foundEntity;
|
||||
updated = true;
|
||||
//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;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
arg.EventObject = eventObjects;
|
||||
{
|
||||
args.EventObject = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
/// <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)
|
||||
{
|
||||
//var argType = meta.EventArgsType;
|
||||
var argType = infos.EventDefinition.Args.GetType();
|
||||
var argType = eventDef.EventDefinition.Args.GetType();
|
||||
|
||||
// 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
|
||||
//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))
|
||||
.ToArray();
|
||||
|
||||
// first time we see this entity = not filtered
|
||||
if (superceeding.Length == 0)
|
||||
//no args have been processed with this entity so it should not be filtered
|
||||
if (foundByEntity.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)
|
||||
{
|
||||
// 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));
|
||||
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)));
|
||||
return supercededBy != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// non-generic, can compare types 1:1
|
||||
var supercededBy = superceeding.FirstOrDefault(x =>
|
||||
x.Item2.SupersedeTypes.Any(y => y == argType));
|
||||
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));
|
||||
return supercededBy != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ namespace Umbraco.Core
|
||||
var ipAddress = httpContext.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
|
||||
|
||||
if (string.IsNullOrEmpty(ipAddress))
|
||||
return httpContext.Request.ServerVariables["REMOTE_ADDR"];
|
||||
return httpContext.Request.UserHostAddress;
|
||||
|
||||
var addresses = ipAddress.Split(',');
|
||||
if (addresses.Length != 0)
|
||||
return addresses[0];
|
||||
|
||||
return httpContext.Request.ServerVariables["REMOTE_ADDR"];
|
||||
return httpContext.Request.UserHostAddress;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,30 @@
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public sealed class AuditItem : Entity, IAggregateRoot
|
||||
public sealed class AuditItem : Entity, IAuditItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor for creating an item to be created
|
||||
/// </summary>
|
||||
/// <param name="objectId"></param>
|
||||
/// <param name="comment"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="userId"></param>
|
||||
public AuditItem(int objectId, string comment, AuditType type, int userId)
|
||||
{
|
||||
DisableChangeTracking();
|
||||
|
||||
Id = objectId;
|
||||
Comment = comment;
|
||||
AuditType = type;
|
||||
UserId = userId;
|
||||
|
||||
EnableChangeTracking();
|
||||
}
|
||||
|
||||
public string Comment { get; private set; }
|
||||
public AuditType AuditType { get; private set; }
|
||||
public int UserId { get; private set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public interface IAuditItem : IAggregateRoot
|
||||
{
|
||||
string Comment { get; }
|
||||
AuditType AuditType { get; }
|
||||
int UserId { get; }
|
||||
}
|
||||
}
|
||||
@@ -40,19 +40,6 @@ namespace Umbraco.Core.Models.Identity
|
||||
identityUser.EnableChangeTracking();
|
||||
});
|
||||
|
||||
config.CreateMap<BackOfficeIdentityUser, UserData>()
|
||||
.ConstructUsing((BackOfficeIdentityUser user) => new UserData(Guid.NewGuid().ToString("N"))) //this is the 'session id'
|
||||
.ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id))
|
||||
.ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections))
|
||||
.ForMember(detail => detail.Roles, opt => opt.MapFrom(user => user.Roles.Select(x => x.RoleId).ToArray()))
|
||||
.ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name))
|
||||
//When mapping to UserData which is used in the authcookie we want ALL start nodes including ones defined on the groups
|
||||
.ForMember(detail => detail.StartContentNodes, opt => opt.MapFrom(user => user.CalculatedContentStartNodeIds))
|
||||
//When mapping to UserData which is used in the authcookie we want ALL start nodes including ones defined on the groups
|
||||
.ForMember(detail => detail.StartMediaNodes, opt => opt.MapFrom(user => user.CalculatedMediaStartNodeIds))
|
||||
.ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.UserName))
|
||||
.ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture))
|
||||
.ForMember(detail => detail.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp));
|
||||
}
|
||||
|
||||
private string GetPasswordHash(string storedPass)
|
||||
|
||||
@@ -69,6 +69,11 @@ namespace Umbraco.Core.Models.Membership
|
||||
/// <summary>
|
||||
/// Will hold the media file system relative path of the users custom avatar if they uploaded one
|
||||
/// </summary>
|
||||
string Avatar { get; set; }
|
||||
string Avatar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A Json blob stored for recording tour data for a user
|
||||
/// </summary>
|
||||
string TourData { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,7 @@ namespace Umbraco.Core.Models.Membership
|
||||
private string _name;
|
||||
private string _securityStamp;
|
||||
private string _avatar;
|
||||
private string _tourData;
|
||||
private int _sessionTimeout;
|
||||
private int[] _startContentIds;
|
||||
private int[] _startMediaIds;
|
||||
@@ -132,6 +133,7 @@ namespace Umbraco.Core.Models.Membership
|
||||
|
||||
public readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.SecurityStamp);
|
||||
public readonly PropertyInfo AvatarSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.Avatar);
|
||||
public readonly PropertyInfo TourDataSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.TourData);
|
||||
public readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.SessionTimeout);
|
||||
public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo<User, int[]>(x => x.StartContentIds);
|
||||
public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo<User, int[]>(x => x.StartMediaIds);
|
||||
@@ -464,8 +466,18 @@ namespace Umbraco.Core.Models.Membership
|
||||
{
|
||||
get { return _avatar; }
|
||||
set { SetPropertyValueAndDetectChanges(value, ref _avatar, Ps.Value.AvatarSelector); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A Json blob stored for recording tour data for a user
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string TourData
|
||||
{
|
||||
get { return _tourData; }
|
||||
set { SetPropertyValueAndDetectChanges(value, ref _tourData, Ps.Value.TourDataSelector); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the session timeout.
|
||||
/// </summary>
|
||||
|
||||
@@ -27,5 +27,10 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Length(500)]
|
||||
public string OriginIdentity { get; set; }
|
||||
|
||||
[Column("instructionCount")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Constraint(Default = 1)]
|
||||
public int InstructionCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -24,4 +24,5 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[ResultColumn]
|
||||
public NodeDto NodeDto { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,6 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[Column("logComment")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(4000)]
|
||||
public string Comment { get; set; }
|
||||
public string Comment { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
[TableName("cmsMedia")]
|
||||
[PrimaryKey("versionId", autoIncrement = false)]
|
||||
[ExplicitColumns]
|
||||
internal class MediaDto
|
||||
{
|
||||
[Column("nodeId")]
|
||||
[ForeignKey(typeof(ContentDto), Column = "nodeId")]
|
||||
[ForeignKey(typeof(NodeDto))]
|
||||
[Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMedia", ForColumns = "nodeId, versionId, mediaPath")]
|
||||
public int NodeId { get; set; }
|
||||
|
||||
[Column("versionId")]
|
||||
[PrimaryKeyColumn(AutoIncrement = false)]
|
||||
public Guid VersionId { get; set; }
|
||||
|
||||
[Column("mediaPath")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public string MediaPath { get; set; }
|
||||
|
||||
[ResultColumn]
|
||||
public ContentVersionDto ContentVersionDto { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[ForeignKey(typeof(PropertyTypeGroupDto))]
|
||||
public int? PropertyTypeGroupId { get; set; }
|
||||
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyTypeAlias")]
|
||||
[Column("Alias")]
|
||||
public string Alias { get; set; }
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
UserGroupDtos = new List<UserGroupDto>();
|
||||
UserStartNodeDtos = new HashSet<UserStartNodeDto>();
|
||||
}
|
||||
}
|
||||
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn(Name = "PK_user")]
|
||||
@@ -40,7 +40,7 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[Column("userPassword")]
|
||||
[Length(500)]
|
||||
public string Password { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This will represent a JSON structure of how the password has been created (i.e hash algorithm, iterations)
|
||||
/// </summary>
|
||||
@@ -95,7 +95,7 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime UpdateDate { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Will hold the media file system relative path of the users custom avatar if they uploaded one
|
||||
/// </summary>
|
||||
@@ -103,6 +103,14 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(500)]
|
||||
public string Avatar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A Json blob stored for recording tour data for a user
|
||||
/// </summary>
|
||||
[Column("tourData")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[SpecialDbType(SpecialDbTypes.NTEXT)]
|
||||
public string TourData { get; set; }
|
||||
|
||||
[ResultColumn]
|
||||
public List<UserGroupDto> UserGroupDtos { get; set; }
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
[TableName("umbracoUserLogin")]
|
||||
[PrimaryKey("sessionId", autoIncrement = false)]
|
||||
[ExplicitColumns]
|
||||
internal class UserLoginDto
|
||||
{
|
||||
[Column("sessionId")]
|
||||
[PrimaryKeyColumn(AutoIncrement = false)]
|
||||
public Guid SessionId { get; set; }
|
||||
|
||||
[Column("userId")]
|
||||
[ForeignKey(typeof(UserDto), Name = "FK_umbracoUserLogin_umbracoUser_id")]
|
||||
public int UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tracks when the session is created
|
||||
/// </summary>
|
||||
[Column("loggedInUtc")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public DateTime LoggedInUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updated every time a user's session is validated
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This allows us to guess if a session is timed out if a user doesn't actively log out
|
||||
/// and also allows us to trim the data in the table
|
||||
/// </remarks>
|
||||
[Column("lastValidatedUtc")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public DateTime LastValidatedUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tracks when the session is removed when the user's account is logged out
|
||||
/// </summary>
|
||||
[Column("loggedOutUtc")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? LoggedOutUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logs the IP address of the session if available
|
||||
/// </summary>
|
||||
[Column("ipAddress")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public string IpAddress { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
[Obsolete("Table no longer exists as of 7.6 - retained only to support migrations from previous versions")]
|
||||
[Obsolete("Table no longer exists as of 7.7 - retained only to support migrations from previous versions")]
|
||||
[TableName("umbracoUserType")]
|
||||
[PrimaryKey("id")]
|
||||
[ExplicitColumns]
|
||||
|
||||
@@ -188,7 +188,7 @@ namespace Umbraco.Core.Models
|
||||
Language,
|
||||
|
||||
/// <summary>
|
||||
/// Document Blueprint
|
||||
/// Document
|
||||
/// </summary>
|
||||
[UmbracoObjectType(Constants.ObjectTypes.DocumentBlueprint, typeof(IContent))]
|
||||
[FriendlyName("DocumentBlueprint")]
|
||||
|
||||
@@ -30,12 +30,11 @@ namespace Umbraco.Core.Models
|
||||
/// Tries to lookup the user's gravatar to see if the endpoint can be reached, if so it returns the valid URL
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="userService"></param>
|
||||
/// <param name="staticCache"></param>
|
||||
/// <returns>
|
||||
/// A list of 5 different sized avatar URLs
|
||||
/// </returns>
|
||||
internal static string[] GetCurrentUserAvatarUrls(this IUser user, IUserService userService, ICacheProvider staticCache)
|
||||
internal static string[] GetUserAvatarUrls(this IUser user, ICacheProvider staticCache)
|
||||
{
|
||||
//check if the user has explicitly removed all avatars including a gravatar, this will be possible and the value will be "none"
|
||||
if (user.Avatar == "none")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace Umbraco.Core.Packaging
|
||||
{
|
||||
@@ -14,17 +14,17 @@ namespace Umbraco.Core.Packaging
|
||||
bool fileFound = false;
|
||||
string foundDir = null;
|
||||
|
||||
ReadZipfileEntries(packageFilePath, (entry, stream) =>
|
||||
ReadZipfileEntries(packageFilePath, entry =>
|
||||
{
|
||||
string fileName = Path.GetFileName(entry.Name);
|
||||
|
||||
if (string.IsNullOrEmpty(fileName) == false &&
|
||||
fileName.Equals(fileToRead, StringComparison.CurrentCultureIgnoreCase))
|
||||
if (string.IsNullOrEmpty(fileName) == false && fileName.Equals(fileToRead, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
|
||||
foundDir = entry.Name.Substring(0, entry.Name.Length - fileName.Length);
|
||||
fileFound = true;
|
||||
using (var reader = new StreamReader(stream))
|
||||
using (var entryStream = entry.Open())
|
||||
using (var reader = new StreamReader(entryStream))
|
||||
{
|
||||
retVal = reader.ReadToEnd();
|
||||
return false;
|
||||
@@ -77,7 +77,7 @@ namespace Umbraco.Core.Packaging
|
||||
var d = sourceDestination.ToDictionary(k => k.Key.ToLower(), v => v.Value);
|
||||
|
||||
|
||||
ReadZipfileEntries(packageFilePath, (entry, stream) =>
|
||||
ReadZipfileEntries(packageFilePath, entry =>
|
||||
{
|
||||
string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower();
|
||||
if (fileName == string.Empty) { return true; }
|
||||
@@ -86,8 +86,9 @@ namespace Umbraco.Core.Packaging
|
||||
if (string.IsNullOrEmpty(fileName) == false && d.TryGetValue(fileName, out destination))
|
||||
{
|
||||
using (var streamWriter = File.Open(destination, FileMode.Create))
|
||||
using (var entryStream = entry.Open())
|
||||
{
|
||||
stream.CopyTo(streamWriter);
|
||||
entryStream.CopyTo(streamWriter);
|
||||
}
|
||||
|
||||
d.Remove(fileName);
|
||||
@@ -106,7 +107,7 @@ namespace Umbraco.Core.Packaging
|
||||
{
|
||||
var retVal = expectedFiles.ToList();
|
||||
|
||||
ReadZipfileEntries(packageFilePath, (zipEntry, stream) =>
|
||||
ReadZipfileEntries(packageFilePath, zipEntry =>
|
||||
{
|
||||
string fileName = Path.GetFileName(zipEntry.Name);
|
||||
|
||||
@@ -125,7 +126,7 @@ namespace Umbraco.Core.Packaging
|
||||
var dictionary = new Dictionary<string, List<string>>();
|
||||
|
||||
|
||||
ReadZipfileEntries(packageFilePath, (entry, stream) =>
|
||||
ReadZipfileEntries(packageFilePath, entry =>
|
||||
{
|
||||
string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower();
|
||||
|
||||
@@ -148,53 +149,42 @@ namespace Umbraco.Core.Packaging
|
||||
{
|
||||
CheckPackageExists(packageFilePath);
|
||||
|
||||
var files = new HashSet<string>(filesToGet.Select(f => f.ToLower()));
|
||||
var files = new HashSet<string>(filesToGet.Select(f => f.ToLowerInvariant()));
|
||||
|
||||
using (var fs = File.OpenRead(packageFilePath))
|
||||
using (var fs = File.OpenRead(packageFilePath))
|
||||
using (var zipArchive = new ZipArchive(fs))
|
||||
{
|
||||
using (var zipInputStream = new ZipInputStream(fs))
|
||||
foreach (var zipEntry in zipArchive.Entries)
|
||||
{
|
||||
ZipEntry zipEntry;
|
||||
while ((zipEntry = zipInputStream.GetNextEntry()) != null)
|
||||
{
|
||||
|
||||
if (zipEntry.IsDirectory) continue;
|
||||
if (zipEntry.Name.IsNullOrWhiteSpace() && zipEntry.FullName.EndsWith("/")) continue;
|
||||
|
||||
if (files.Contains(zipEntry.Name))
|
||||
if (files.Contains(zipEntry.Name.ToLowerInvariant()))
|
||||
{
|
||||
using (var memStream = new MemoryStream())
|
||||
using (var entryStream = zipEntry.Open())
|
||||
{
|
||||
using (var memStream = new MemoryStream())
|
||||
{
|
||||
zipInputStream.CopyTo(memStream);
|
||||
yield return memStream.ToArray();
|
||||
memStream.Close();
|
||||
}
|
||||
entryStream.CopyTo(memStream);
|
||||
memStream.Close();
|
||||
yield return memStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
zipInputStream.Close();
|
||||
}
|
||||
fs.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadZipfileEntries(string packageFilePath, Func<ZipEntry, ZipInputStream, bool> entryFunc, bool skipsDirectories = true)
|
||||
private void ReadZipfileEntries(string packageFilePath, Func<ZipArchiveEntry, bool> entryFunc, bool skipsDirectories = true)
|
||||
{
|
||||
CheckPackageExists(packageFilePath);
|
||||
|
||||
using (var fs = File.OpenRead(packageFilePath))
|
||||
using (var zipArchive = new ZipArchive(fs))
|
||||
{
|
||||
using (var zipInputStream = new ZipInputStream(fs))
|
||||
foreach (var zipEntry in zipArchive.Entries)
|
||||
{
|
||||
ZipEntry zipEntry;
|
||||
while ((zipEntry = zipInputStream.GetNextEntry()) != null)
|
||||
{
|
||||
if (zipEntry.IsDirectory && skipsDirectories) continue;
|
||||
if (entryFunc(zipEntry, zipInputStream) == false) break;
|
||||
}
|
||||
|
||||
zipInputStream.Close();
|
||||
if (zipEntry.Name.IsNullOrWhiteSpace() && zipEntry.FullName.EndsWith("/") && skipsDirectories)
|
||||
continue;
|
||||
if (entryFunc(zipEntry) == false) break;
|
||||
}
|
||||
fs.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,18 +98,18 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
//NOTE Currently doesn't add Alias (legacy that eventually will go away)
|
||||
var documentDto = new DocumentDto
|
||||
{
|
||||
Newest = true,
|
||||
NodeId = entity.Id,
|
||||
Published = entity.Published,
|
||||
Text = entity.Name,
|
||||
UpdateDate = entity.UpdateDate,
|
||||
WriterUserId = entity.WriterId,
|
||||
VersionId = entity.Version,
|
||||
ExpiresDate = null,
|
||||
ReleaseDate = null,
|
||||
ContentVersionDto = BuildContentVersionDto(entity)
|
||||
};
|
||||
{
|
||||
Newest = true,
|
||||
NodeId = entity.Id,
|
||||
Published = entity.Published,
|
||||
Text = entity.Name,
|
||||
UpdateDate = entity.UpdateDate,
|
||||
WriterUserId = entity.WriterId,
|
||||
VersionId = entity.Version,
|
||||
ExpiresDate = null,
|
||||
ReleaseDate = null,
|
||||
ContentVersionDto = BuildContentVersionDto(entity)
|
||||
};
|
||||
|
||||
if (entity.Template != null && entity.Template.Id > 0)
|
||||
documentDto.TemplateId = entity.Template.Id;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
|
||||
internal class MacroFactory
|
||||
{
|
||||
public IMacro BuildEntity(MacroDto dto)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
@@ -9,20 +10,17 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
private readonly IMediaType _contentType;
|
||||
private readonly Guid _nodeObjectTypeId;
|
||||
private readonly int _id;
|
||||
private int _primaryKey;
|
||||
|
||||
public MediaFactory(IMediaType contentType, Guid nodeObjectTypeId, int id)
|
||||
public MediaFactory(IMediaType contentType, Guid nodeObjectTypeId)
|
||||
{
|
||||
_contentType = contentType;
|
||||
_nodeObjectTypeId = nodeObjectTypeId;
|
||||
_id = id;
|
||||
}
|
||||
|
||||
public MediaFactory(Guid nodeObjectTypeId, int id)
|
||||
public MediaFactory(Guid nodeObjectTypeId)
|
||||
{
|
||||
_nodeObjectTypeId = nodeObjectTypeId;
|
||||
_id = id;
|
||||
}
|
||||
|
||||
#region Implementation of IEntityFactory<IMedia,ContentVersionDto>
|
||||
@@ -64,15 +62,27 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
return BuildEntity(dto, _contentType);
|
||||
}
|
||||
|
||||
public ContentVersionDto BuildDto(IMedia entity)
|
||||
public MediaDto BuildDto(IMedia entity)
|
||||
{
|
||||
var dto = new ContentVersionDto
|
||||
{
|
||||
NodeId = entity.Id,
|
||||
VersionDate = entity.UpdateDate,
|
||||
VersionId = entity.Version,
|
||||
ContentDto = BuildContentDto(entity)
|
||||
};
|
||||
var versionDto = new ContentVersionDto
|
||||
{
|
||||
NodeId = entity.Id,
|
||||
VersionDate = entity.UpdateDate,
|
||||
VersionId = entity.Version,
|
||||
ContentDto = BuildContentDto(entity)
|
||||
};
|
||||
|
||||
//Extract the media path for storage
|
||||
string mediaPath;
|
||||
TryMatch(entity.GetValue<string>("umbracoFile"), out mediaPath);
|
||||
|
||||
var dto = new MediaDto()
|
||||
{
|
||||
NodeId = entity.Id,
|
||||
ContentVersionDto = versionDto,
|
||||
MediaPath = mediaPath,
|
||||
VersionId = entity.Version
|
||||
};
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -119,5 +129,30 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
return nodeDto;
|
||||
}
|
||||
|
||||
private static readonly Regex MediaPathPattern = new Regex(@"(/media/.+?)(?:['""]|$)", RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// Try getting a media path out of the string being stored for media
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="mediaPath"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool TryMatch(string text, out string mediaPath)
|
||||
{
|
||||
mediaPath = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return false;
|
||||
|
||||
var match = MediaPathPattern.Match(text);
|
||||
if (match.Success == false || match.Groups.Count != 2)
|
||||
return false;
|
||||
|
||||
|
||||
var url = match.Groups[1].Value;
|
||||
mediaPath = url;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
user.Avatar = dto.Avatar;
|
||||
user.EmailConfirmedDate = dto.EmailConfirmedDate;
|
||||
user.InvitedDate = dto.InvitedDate;
|
||||
user.TourData = dto.TourData;
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
// http://issues.umbraco.org/issue/U4-1946
|
||||
@@ -69,7 +70,8 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
UpdateDate = entity.UpdateDate,
|
||||
Avatar = entity.Avatar,
|
||||
EmailConfirmedDate = entity.EmailConfirmedDate,
|
||||
InvitedDate = entity.InvitedDate
|
||||
InvitedDate = entity.InvitedDate,
|
||||
TourData = entity.TourData
|
||||
};
|
||||
|
||||
foreach (var startNodeId in entity.StartContentIds)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Mappers
|
||||
{
|
||||
[MapperFor(typeof(AuditItem))]
|
||||
[MapperFor(typeof(IAuditItem))]
|
||||
public sealed class AuditMapper : BaseMapper
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance = new ConcurrentDictionary<string, DtoMapModel>();
|
||||
|
||||
public AuditMapper(ISqlSyntaxProvider sqlSyntax) : base(sqlSyntax)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it
|
||||
// otherwise that would fail because there is no public constructor.
|
||||
public AuditMapper()
|
||||
{
|
||||
BuildMap();
|
||||
}
|
||||
|
||||
#region Overrides of BaseMapper
|
||||
|
||||
internal override ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache
|
||||
{
|
||||
get { return PropertyInfoCacheInstance; }
|
||||
}
|
||||
|
||||
internal override void BuildMap()
|
||||
{
|
||||
if (PropertyInfoCache.IsEmpty)
|
||||
{
|
||||
CacheMap<AuditItem, LogDto>(src => src.Id, dto => dto.NodeId);
|
||||
CacheMap<AuditItem, LogDto>(src => src.CreateDate, dto => dto.Datestamp);
|
||||
CacheMap<AuditItem, LogDto>(src => src.UserId, dto => dto.UserId);
|
||||
CacheMap<AuditItem, LogDto>(src => src.AuditType, dto => dto.Header);
|
||||
CacheMap<AuditItem, LogDto>(src => src.Comment, dto => dto.Comment);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -43,27 +43,28 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
{3, typeof (ContentDto)},
|
||||
{4, typeof (ContentVersionDto)},
|
||||
{5, typeof (DocumentDto)},
|
||||
{6, typeof (MediaDto)},
|
||||
|
||||
{6, typeof (ContentTypeTemplateDto)},
|
||||
{7, typeof (DataTypeDto)},
|
||||
{8, typeof (DataTypePreValueDto)},
|
||||
{9, typeof (DictionaryDto)},
|
||||
{10, typeof (LanguageDto)},
|
||||
{11, typeof (LanguageTextDto)},
|
||||
{12, typeof (DomainDto)},
|
||||
{13, typeof (LogDto)},
|
||||
{14, typeof (MacroDto)},
|
||||
{15, typeof (MacroPropertyDto)},
|
||||
{16, typeof (MemberTypeDto)},
|
||||
{17, typeof (MemberDto)},
|
||||
{18, typeof (Member2MemberGroupDto)},
|
||||
{19, typeof (ContentXmlDto)},
|
||||
{20, typeof (PreviewXmlDto)},
|
||||
{21, typeof (PropertyTypeGroupDto)},
|
||||
{22, typeof (PropertyTypeDto)},
|
||||
{23, typeof (PropertyDataDto)},
|
||||
{24, typeof (RelationTypeDto)},
|
||||
{25, typeof (RelationDto)},
|
||||
{7, typeof (ContentTypeTemplateDto)},
|
||||
{8, typeof (DataTypeDto)},
|
||||
{9, typeof (DataTypePreValueDto)},
|
||||
{10, typeof (DictionaryDto)},
|
||||
{11, typeof (LanguageDto)},
|
||||
{12, typeof (LanguageTextDto)},
|
||||
{13, typeof (DomainDto)},
|
||||
{14, typeof (LogDto)},
|
||||
{15, typeof (MacroDto)},
|
||||
{16, typeof (MacroPropertyDto)},
|
||||
{17, typeof (MemberTypeDto)},
|
||||
{18, typeof (MemberDto)},
|
||||
{19, typeof (Member2MemberGroupDto)},
|
||||
{20, typeof (ContentXmlDto)},
|
||||
{21, typeof (PreviewXmlDto)},
|
||||
{22, typeof (PropertyTypeGroupDto)},
|
||||
{23, typeof (PropertyTypeDto)},
|
||||
{24, typeof (PropertyDataDto)},
|
||||
{25, typeof (RelationTypeDto)},
|
||||
{26, typeof (RelationDto)},
|
||||
|
||||
{28, typeof (TagDto)},
|
||||
{29, typeof (TagRelationshipDto)},
|
||||
@@ -91,6 +92,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
{52, typeof (UserGroup2NodePermissionDto) },
|
||||
{53, typeof (UserGroup2AppDto) },
|
||||
{54, typeof (UserStartNodeDto) },
|
||||
{55, typeof (UserLoginDto)}
|
||||
};
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -148,6 +148,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
return new Version(7, 6, 0);
|
||||
}
|
||||
|
||||
//if the error is for cmsMedia it must be the previous version to 7.8 since that is when it is added
|
||||
if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("cmsMedia"))))
|
||||
{
|
||||
return new Version(7, 7, 0);
|
||||
}
|
||||
|
||||
return UmbracoVersion.Current;
|
||||
}
|
||||
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.Factories;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
|
||||
{
|
||||
[Migration("7.8.0", 1, Constants.System.UmbracoMigrationName)]
|
||||
public class AddCmsMediaTable : MigrationBase
|
||||
{
|
||||
public AddCmsMediaTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
|
||||
|
||||
if (tables.InvariantContains("cmsMedia") == false)
|
||||
{
|
||||
Create.Table<MediaDto>();
|
||||
|
||||
MigrateMediaPaths();
|
||||
}
|
||||
}
|
||||
|
||||
private void MigrateMediaPaths()
|
||||
{
|
||||
Execute.Code(database =>
|
||||
{
|
||||
//Due to how much data there can be and that this query will result in executing an Index Scan on the cmsPropertyData PK
|
||||
//which will literally iterate over every row in there (assume there's 1,000,000 and you can see why this will perform
|
||||
//horribly) ultimately we need to fix this: http://issues.umbraco.org/issue/U4-10286
|
||||
//so in the meantime I've found an ugly query to work around this Index Scan issue which is to use a sub-query of Ids.
|
||||
//Doing this results in the same Index Scan taking place but it only visits the explicit rows that are provided, not every row
|
||||
//in the cmsPropertyData table. This results in a tremendously faster query when there is tons of data.
|
||||
|
||||
//The will return any media property in nvarchar or ntext that is an umbracoFile that is not null.
|
||||
//This will give us the least amount of data returned to work with.
|
||||
var sql = @"SELECT cmsPropertyData.dataNvarchar, cmsPropertyData.dataNtext, umbracoNode.id, cmsContentVersion.VersionId
|
||||
FROM cmsPropertyData
|
||||
INNER JOIN cmsPropertyType ON cmsPropertyType.id = cmsPropertyData.propertytypeid
|
||||
INNER JOIN umbracoNode ON umbracoNode.id = cmsPropertyData.contentNodeId
|
||||
INNER JOIN cmsContentVersion ON cmsContentVersion.ContentId = umbracoNode.id
|
||||
WHERE cmsPropertyType.Alias = (@alias)
|
||||
AND umbracoNode.id IN (SELECT umbracoNode.id
|
||||
FROM umbracoNode
|
||||
INNER JOIN cmsContent ON cmsContent.nodeId = umbracoNode.id
|
||||
INNER JOIN cmsContentType ON cmsContentType.nodeId = cmsContent.contentType
|
||||
INNER JOIN cmsPropertyType ON cmsPropertyType.contentTypeId = cmsContentType.nodeId
|
||||
WHERE cmsPropertyType.Alias = (@alias) AND umbracoNode.nodeObjectType = (@nodeObjectType))
|
||||
AND (cmsPropertyData.dataNvarchar IS NOT NULL OR cmsPropertyData.dataNtext IS NOT NULL)";
|
||||
|
||||
var paths = new List<MediaDto>();
|
||||
|
||||
//using QUERY = a db cursor, we won't load this all into memory first, just row by row
|
||||
foreach (var row in database.Query<dynamic>(sql, new {alias = "umbracoFile", nodeObjectType = Constants.ObjectTypes.Media}))
|
||||
{
|
||||
var id = (int) row.id;
|
||||
var versionId = (Guid)row.VersionId;
|
||||
|
||||
string mediaPath = null;
|
||||
|
||||
//if there's values in dataNvarchar then ensure there's a media path match and extract it
|
||||
if (row.dataNvarchar != null && MediaFactory.TryMatch((string) row.dataNvarchar, out mediaPath))
|
||||
{
|
||||
paths.Add(new MediaDto
|
||||
{
|
||||
MediaPath = mediaPath,
|
||||
NodeId = id,
|
||||
VersionId = versionId
|
||||
});
|
||||
}
|
||||
//if there's values in dataNtext then ensure there's a media path match and extract it
|
||||
else if (row.dataNtext != null && MediaFactory.TryMatch((string) row.dataNtext, out mediaPath))
|
||||
{
|
||||
paths.Add(new MediaDto
|
||||
{
|
||||
MediaPath = mediaPath,
|
||||
NodeId = id,
|
||||
VersionId = versionId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//now we need to insert paths for each item
|
||||
database.BulkInsertRecords(paths, SqlSyntax);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
|
||||
{
|
||||
[Migration("7.8.0", 0, Constants.System.UmbracoMigrationName)]
|
||||
public class AddIndexToPropertyTypeAliasColumn : MigrationBase
|
||||
{
|
||||
public AddIndexToPropertyTypeAliasColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
Execute.Code(database =>
|
||||
{
|
||||
var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database);
|
||||
|
||||
//make sure it doesn't already exist
|
||||
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsPropertyTypeAlias")) == false)
|
||||
{
|
||||
var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger);
|
||||
|
||||
//we can apply the index
|
||||
localContext.Create.Index("IX_cmsPropertyTypeAlias").OnTable("cmsPropertyType")
|
||||
.OnColumn("Alias")
|
||||
.Ascending()
|
||||
.WithOptions()
|
||||
.NonClustered();
|
||||
|
||||
return localContext.GetSql();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
Delete.Index("IX_cmsPropertyTypeAlias").OnTable("cmsPropertyType");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
|
||||
{
|
||||
[Migration("7.8.0", 2, Constants.System.UmbracoMigrationName)]
|
||||
public class AddInstructionCountColumn : MigrationBase
|
||||
{
|
||||
public AddInstructionCountColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
//Don't exeucte if the column is already there
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
|
||||
|
||||
if (columns.Any(x => x.TableName.InvariantEquals("umbracoCacheInstruction") && x.ColumnName.InvariantEquals("instructionCount")) == false)
|
||||
Create.Column("instructionCount")
|
||||
.OnTable("umbracoCacheInstruction")
|
||||
.AsInt32()
|
||||
.WithDefaultValue(1)
|
||||
.NotNullable();
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
|
||||
{
|
||||
[Migration("7.8.0", 1, Constants.System.UmbracoMigrationName)]
|
||||
public class AddTourDataUserColumn : MigrationBase
|
||||
{
|
||||
public AddTourDataUserColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
//Don't exeucte if the column is already there
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
|
||||
|
||||
if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("tourData")) == false)
|
||||
{
|
||||
var textType = SqlSyntax.GetSpecialDbType(SpecialDbTypes.NTEXT);
|
||||
Create.Column("tourData").OnTable("umbracoUser").AsCustom(textType).Nullable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
|
||||
{
|
||||
[Migration("7.8.0", 4, Constants.System.UmbracoMigrationName)]
|
||||
public class AddUserLoginTable : MigrationBase
|
||||
{
|
||||
public AddUserLoginTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
|
||||
|
||||
if (tables.InvariantContains("umbracoUserLogin") == false)
|
||||
{
|
||||
Create.Table<UserLoginDto>();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using Umbraco.Core.Persistence.FaultHandling;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using MySql.Data.MySqlClient;
|
||||
using StackExchange.Profiling.Data;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
|
||||
@@ -38,8 +38,12 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
_tableDefinition = DefinitionFactory.GetTableDefinition(sqlSyntaxProvider, pd.type);
|
||||
if (_tableDefinition == null) throw new InvalidOperationException("No table definition found for type " + pd.type);
|
||||
|
||||
_readerColumns = pd.Columns.Select(x => x.Value).ToArray();
|
||||
|
||||
//Only return real columns, do not include columns that are result columns
|
||||
_readerColumns = pd.Columns
|
||||
.Where(x => x.Value.ResultColumn == false)
|
||||
.Select(x => x.Value)
|
||||
.ToArray();
|
||||
_sqlSyntaxProvider = sqlSyntaxProvider;
|
||||
_enumerator = dataSource.GetEnumerator();
|
||||
_columnDefinitions = _tableDefinition.Columns.ToArray();
|
||||
|
||||
@@ -1,29 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
internal class AuditRepository : PetaPocoRepositoryBase<int, AuditItem>, IAuditRepository
|
||||
internal class AuditRepository : PetaPocoRepositoryBase<int, IAuditItem>, IAuditRepository
|
||||
{
|
||||
public AuditRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
|
||||
: base(work, cache, logger, sqlSyntax)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void PersistNewItem(AuditItem entity)
|
||||
/// <summary>
|
||||
/// Return the audit items as paged result
|
||||
/// </summary>
|
||||
/// <param name="query">
|
||||
/// The query coming from the service
|
||||
/// </param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection"></param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// A user supplied custom filter
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IAuditItem> GetPagedResultsByQuery(IQuery<IAuditItem> query, long pageIndex, int pageSize,
|
||||
out long totalRecords, Direction orderDirection,
|
||||
AuditType[] auditTypeFilter,
|
||||
IQuery<IAuditItem> customFilter)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (auditTypeFilter == null) auditTypeFilter = new AuditType[0];
|
||||
|
||||
var sql = GetBaseQuery(false);
|
||||
|
||||
if (query == null) query = new Query<IAuditItem>();
|
||||
var translatorIds = new SqlTranslator<IAuditItem>(sql, query);
|
||||
var translatedQuery = translatorIds.Translate();
|
||||
|
||||
var customFilterWheres = customFilter != null ? customFilter.GetWhereClauses().ToArray() : null;
|
||||
var hasCustomFilter = customFilterWheres != null && customFilterWheres.Length > 0;
|
||||
if (hasCustomFilter)
|
||||
{
|
||||
var filterSql = new Sql();
|
||||
var first = true;
|
||||
foreach (var filterClaus in customFilterWheres)
|
||||
{
|
||||
if (first == false)
|
||||
{
|
||||
filterSql.Append(" AND ");
|
||||
}
|
||||
filterSql.Append(string.Format("({0})", filterClaus.Item1), filterClaus.Item2);
|
||||
first = false;
|
||||
}
|
||||
|
||||
translatedQuery = GetFilteredSqlForPagedResults(translatedQuery, filterSql);
|
||||
}
|
||||
|
||||
if (auditTypeFilter.Length > 0)
|
||||
{
|
||||
var filterSql = new Sql();
|
||||
var first = true;
|
||||
foreach (var filterClaus in auditTypeFilter)
|
||||
{
|
||||
if (first == false || hasCustomFilter)
|
||||
{
|
||||
filterSql.Append(" AND ");
|
||||
}
|
||||
filterSql.Append("(logHeader = @logHeader)", new {logHeader = filterClaus.ToString() });
|
||||
first = false;
|
||||
}
|
||||
|
||||
translatedQuery = GetFilteredSqlForPagedResults(translatedQuery, filterSql);
|
||||
}
|
||||
|
||||
if (orderDirection == Direction.Descending)
|
||||
translatedQuery.OrderByDescending("Datestamp");
|
||||
else
|
||||
translatedQuery.OrderBy("Datestamp");
|
||||
|
||||
// Get page of results and total count
|
||||
var pagedResult = Database.Page<LogDto>(pageIndex + 1, pageSize, translatedQuery);
|
||||
totalRecords = pagedResult.TotalItems;
|
||||
|
||||
var pages = pagedResult.Items.Select(
|
||||
dto => new AuditItem(dto.Id, dto.Comment, Enum<AuditType>.Parse(dto.Header), dto.UserId)).ToArray();
|
||||
|
||||
//Mapping the DateStamp
|
||||
for (int i = 0; i < pages.Length; i++)
|
||||
{
|
||||
pages[i].CreateDate = pagedResult.Items[i].Datestamp;
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
||||
#region Not Implemented - not needed
|
||||
|
||||
protected override void PersistUpdatedItem(AuditItem entity)
|
||||
protected override void PersistUpdatedItem(IAuditItem entity)
|
||||
{
|
||||
Database.Insert(new LogDto
|
||||
{
|
||||
@@ -35,26 +119,40 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
});
|
||||
}
|
||||
|
||||
protected override AuditItem PerformGet(int id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IEnumerable<AuditItem> PerformGetAll(params int[] ids)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IEnumerable<AuditItem> PerformGetByQuery(IQuery<AuditItem> query)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Sql GetBaseQuery(bool isCount)
|
||||
{
|
||||
var sql = new Sql()
|
||||
.Select(isCount ? "COUNT(*)" : "umbracoLog.id, umbracoLog.userId, umbracoLog.NodeId, umbracoLog.Datestamp, umbracoLog.logHeader, umbracoLog.logComment, umbracoUser.userName, umbracoUser.avatar as userAvatar")
|
||||
.From<LogDto>(SqlSyntax);
|
||||
if (isCount == false)
|
||||
{
|
||||
sql = sql.LeftJoin<UserDto>(SqlSyntax).On<UserDto, LogDto>(SqlSyntax, dto => dto.Id, dto => dto.UserId);
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
|
||||
#region Not Implemented - not needed currently
|
||||
|
||||
protected override void PersistNewItem(IAuditItem entity)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IAuditItem PerformGet(int id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IEnumerable<IAuditItem> PerformGetAll(params int[] ids)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IEnumerable<IAuditItem> PerformGetByQuery(IQuery<IAuditItem> query)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override string GetBaseWhereClause()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@@ -71,6 +169,33 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
#endregion
|
||||
|
||||
private Sql GetFilteredSqlForPagedResults(Sql sql, Sql filterSql)
|
||||
{
|
||||
Sql filteredSql;
|
||||
|
||||
// Apply filter
|
||||
if (filterSql != null)
|
||||
{
|
||||
var sqlFilter = " WHERE " + filterSql.SQL.TrimStart("AND ");
|
||||
|
||||
//NOTE: this is certainly strange - NPoco handles this much better but we need to re-create the sql
|
||||
// instance a couple of times to get the parameter order correct, for some reason the first
|
||||
// time the arguments don't show up correctly but the SQL argument parameter names are actually updated
|
||||
// accordingly - so we re-create it again. In v8 we don't need to do this and it's already taken care of.
|
||||
|
||||
filteredSql = new Sql(sql.SQL, sql.Arguments);
|
||||
var args = filteredSql.Arguments.Concat(filterSql.Arguments).ToArray();
|
||||
filteredSql = new Sql(
|
||||
string.Format("{0} {1}", filteredSql.SQL, sqlFilter),
|
||||
args);
|
||||
filteredSql = new Sql(filteredSql.SQL, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
//copy to var so that the original isn't changed
|
||||
filteredSql = new Sql(sql.SQL, sql.Arguments);
|
||||
}
|
||||
return filteredSql;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -901,7 +901,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
|
||||
public void AddOrUpdatePermissions(ContentPermissionSet permission)
|
||||
{
|
||||
_permissionRepository.AddOrUpdate(permission);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to remove the content xml for a content item
|
||||
@@ -1075,8 +1075,8 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
protected override string GetDatabaseFieldNameForOrderBy(string orderBy)
|
||||
{
|
||||
//Some custom ones
|
||||
|
||||
@@ -1,11 +1,37 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Auditing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IAuditRepository : IRepository<int, AuditItem>
|
||||
public interface IAuditRepository : IRepository<int, IAuditItem>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Return the audit items as paged result
|
||||
/// </summary>
|
||||
/// <param name="query">
|
||||
/// The query coming from the service
|
||||
/// </param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection"></param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// A user supplied custom filter
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IAuditItem> GetPagedResultsByQuery(
|
||||
IQuery<IAuditItem> query,
|
||||
long pageIndex, int pageSize, out long totalRecords,
|
||||
Direction orderDirection,
|
||||
AuditType[] auditTypeFilter,
|
||||
IQuery<IAuditItem> customFilter);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IMediaRepository : IRepositoryVersionable<int, IMedia>, IRecycleBinRepository<IMedia>, IReadRepository<Guid, IMedia>, IDeleteMediaFilesRepository
|
||||
{
|
||||
IMedia GetMediaByPath(string mediaPath);
|
||||
|
||||
/// <summary>
|
||||
/// Used to add/update published xml for the media item
|
||||
/// </summary>
|
||||
|
||||
@@ -91,7 +91,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
IProfile GetProfile(string username);
|
||||
IProfile GetProfile(int id);
|
||||
IDictionary<UserState, int> GetUserStates();
|
||||
|
||||
IDictionary<UserState, int> GetUserStates();
|
||||
|
||||
Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true);
|
||||
bool ValidateLoginSession(int userId, Guid sessionId);
|
||||
int ClearLoginSessions(int userId);
|
||||
int ClearLoginSessions(TimeSpan timespan);
|
||||
void ClearLoginSession(Guid sessionId);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -87,7 +87,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
#region Overrides of PetaPocoRepositoryBase<int,IMedia>
|
||||
|
||||
protected override Sql GetBaseQuery(BaseQueryType queryType)
|
||||
private Sql GetBaseQuery(BaseQueryType queryType, bool includeFilePaths)
|
||||
{
|
||||
var sql = new Sql();
|
||||
sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsContentVersion.contentId" : "*"))
|
||||
@@ -95,14 +95,26 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
.InnerJoin<ContentDto>(SqlSyntax)
|
||||
.On<ContentVersionDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<NodeDto>(SqlSyntax)
|
||||
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId, SqlSyntax)
|
||||
//TODO: IF we want to enable querying on content type information this will need to be joined
|
||||
//.InnerJoin<ContentTypeDto>(SqlSyntax)
|
||||
//.On<ContentDto, ContentTypeDto>(SqlSyntax, left => left.ContentTypeId, right => right.NodeId, SqlSyntax);
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
|
||||
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId);
|
||||
|
||||
if (includeFilePaths)
|
||||
{
|
||||
sql.InnerJoin<MediaDto>(SqlSyntax)
|
||||
.On<MediaDto, ContentVersionDto>(SqlSyntax, left => left.VersionId, right => right.VersionId);
|
||||
}
|
||||
|
||||
//TODO: IF we want to enable querying on content type information this will need to be joined
|
||||
//.InnerJoin<ContentTypeDto>(SqlSyntax)
|
||||
//.On<ContentDto, ContentTypeDto>(SqlSyntax, left => left.ContentTypeId, right => right.NodeId, SqlSyntax);
|
||||
sql.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
|
||||
return sql;
|
||||
}
|
||||
|
||||
protected override Sql GetBaseQuery(BaseQueryType queryType)
|
||||
{
|
||||
return GetBaseQuery(queryType, false);
|
||||
}
|
||||
|
||||
protected override Sql GetBaseQuery(bool isCount)
|
||||
{
|
||||
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle);
|
||||
@@ -128,6 +140,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
"DELETE FROM cmsDocument WHERE nodeId = @Id",
|
||||
"DELETE FROM cmsPropertyData WHERE contentNodeId = @Id",
|
||||
"DELETE FROM cmsPreviewXml WHERE nodeId = @Id",
|
||||
"DELETE FROM cmsMedia WHERE nodeId = @Id",
|
||||
"DELETE FROM cmsContentVersion WHERE ContentId = @Id",
|
||||
"DELETE FROM cmsContentXml WHERE nodeId = @Id",
|
||||
"DELETE FROM cmsContent WHERE nodeId = @Id",
|
||||
@@ -367,7 +380,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//Ensure that strings don't contain characters that are invalid in XML
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
var factory = new MediaFactory(NodeObjectTypeId, entity.Id);
|
||||
var factory = new MediaFactory(NodeObjectTypeId);
|
||||
var dto = factory.BuildDto(entity);
|
||||
|
||||
//NOTE Should the logic below have some kind of fallback for empty parent ids ?
|
||||
@@ -380,7 +393,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
var sortOrder = maxSortOrder + 1;
|
||||
|
||||
//Create the (base) node data - umbracoNode
|
||||
var nodeDto = dto.ContentDto.NodeDto;
|
||||
var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto;
|
||||
nodeDto.Path = parent.Path;
|
||||
nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture));
|
||||
nodeDto.SortOrder = sortOrder;
|
||||
@@ -415,15 +428,21 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
entity.Level = level;
|
||||
|
||||
//Create the Content specific data - cmsContent
|
||||
var contentDto = dto.ContentDto;
|
||||
var contentDto = dto.ContentVersionDto.ContentDto;
|
||||
contentDto.NodeId = nodeDto.NodeId;
|
||||
Database.Insert(contentDto);
|
||||
|
||||
//Create the first version - cmsContentVersion
|
||||
//Assumes a new Version guid and Version date (modified date) has been set
|
||||
var contentVersionDto = dto.ContentVersionDto;
|
||||
contentVersionDto.NodeId = nodeDto.NodeId;
|
||||
Database.Insert(contentVersionDto);
|
||||
|
||||
//Create the Media specific data for this version - cmsMedia
|
||||
//Assumes a new Version guid has been generated
|
||||
dto.NodeId = nodeDto.NodeId;
|
||||
Database.Insert(dto);
|
||||
|
||||
|
||||
//Create the PropertyData for this version - cmsPropertyData
|
||||
var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id);
|
||||
var propertyDataDtos = propertyFactory.BuildDto(entity.Properties);
|
||||
@@ -471,14 +490,14 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
entity.SortOrder = maxSortOrder + 1;
|
||||
}
|
||||
|
||||
var factory = new MediaFactory(NodeObjectTypeId, entity.Id);
|
||||
var factory = new MediaFactory(NodeObjectTypeId);
|
||||
//Look up Content entry to get Primary for updating the DTO
|
||||
var contentDto = Database.SingleOrDefault<ContentDto>("WHERE nodeId = @Id", new { Id = entity.Id });
|
||||
var contentDto = Database.First<ContentDto>("WHERE nodeId = @Id", new { Id = entity.Id });
|
||||
factory.SetPrimaryKey(contentDto.PrimaryKey);
|
||||
var dto = factory.BuildDto(entity);
|
||||
|
||||
//Updates the (base) node data - umbracoNode
|
||||
var nodeDto = dto.ContentDto.NodeDto;
|
||||
var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto;
|
||||
nodeDto.ValidatePathWithException();
|
||||
var o = Database.Update(nodeDto);
|
||||
|
||||
@@ -486,15 +505,18 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
if (contentDto.ContentTypeId != entity.ContentTypeId)
|
||||
{
|
||||
//Create the Content specific data - cmsContent
|
||||
var newContentDto = dto.ContentDto;
|
||||
var newContentDto = dto.ContentVersionDto.ContentDto;
|
||||
Database.Update(newContentDto);
|
||||
}
|
||||
|
||||
//In order to update the ContentVersion we need to retrieve its primary key id
|
||||
var contentVerDto = Database.SingleOrDefault<ContentVersionDto>("WHERE VersionId = @Version", new { Version = entity.Version });
|
||||
dto.Id = contentVerDto.Id;
|
||||
var contentVerDto = Database.First<ContentVersionDto>("WHERE VersionId = @Version", new { Version = entity.Version });
|
||||
dto.ContentVersionDto.Id = contentVerDto.Id;
|
||||
//Updates the current version - cmsContentVersion
|
||||
//Assumes a Version guid exists and Version date (modified date) has been set/updated
|
||||
Database.Update(dto.ContentVersionDto);
|
||||
|
||||
//now update the media entry
|
||||
Database.Update(dto);
|
||||
|
||||
//Create the PropertyData for this version - cmsPropertyData
|
||||
@@ -639,7 +661,39 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMedia"/> object from the path stored in the for the media item.
|
||||
/// </summary>
|
||||
/// <param name="mediaPath">Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg)</param>
|
||||
/// <returns><see cref="IMedia"/></returns>
|
||||
public IMedia GetMediaByPath(string mediaPath)
|
||||
{
|
||||
var umbracoFileValue = mediaPath;
|
||||
|
||||
const string pattern = ".*[_][0-9]+[x][0-9]+[.].*";
|
||||
var isResized = Regex.IsMatch(mediaPath, pattern);
|
||||
|
||||
// If the image has been resized we strip the "_403x328" of the original "/media/1024/koala_403x328.jpg" url.
|
||||
if (isResized)
|
||||
{
|
||||
var underscoreIndex = mediaPath.LastIndexOf('_');
|
||||
var dotIndex = mediaPath.LastIndexOf('.');
|
||||
umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex));
|
||||
}
|
||||
|
||||
var sql = GetBaseQuery(BaseQueryType.FullSingle, true);
|
||||
sql.Where<MediaDto>(mediaDto => mediaDto.MediaPath == umbracoFileValue, SqlSyntax);
|
||||
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
|
||||
var dto = Database.Fetch<ContentVersionDto, ContentDto, NodeDto>(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
|
||||
if (dto == null)
|
||||
return null;
|
||||
|
||||
var content = CreateMediaFromDto(dto, sql);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets paged media results
|
||||
@@ -703,8 +757,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
if (EnsureUniqueNaming == false)
|
||||
return nodeName;
|
||||
|
||||
var names = Database.Fetch<SimilarNodeName>("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId",
|
||||
new { objectType = NodeObjectTypeId, parentId });
|
||||
var names = Database.Fetch<SimilarNodeName>("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId",
|
||||
new { objectType = NodeObjectTypeId, parentId });
|
||||
|
||||
return SimilarNodeName.GetUniqueName(names, id, nodeName);
|
||||
}
|
||||
|
||||
@@ -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("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 });
|
||||
|
||||
.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});
|
||||
|
||||
return Database.Fetch<NodeDto>(sql)
|
||||
.DistinctBy(dto => dto.NodeId)
|
||||
.Select(x => _modelFactory.BuildEntity(x));
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
@@ -53,15 +48,16 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
@"DELETE FROM umbracoRedirectUrl WHERE umbracoRedirectUrl.id IN(
|
||||
SELECT TB1.id FROM umbracoRedirectUrl as TB1
|
||||
INNER JOIN umbracoNode as TB2 ON TB1.contentKey = TB2.uniqueId
|
||||
WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)",
|
||||
WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)",
|
||||
FormatDeleteStatement("umbracoUserStartNode", "startNode"),
|
||||
FormatUpdateStatement("umbracoUserGroup", "startContentId"),
|
||||
FormatUpdateStatement("umbracoUserGroup", "startMediaId"),
|
||||
FormatUpdateStatement("umbracoUserGroup", "startMediaId"),
|
||||
FormatDeleteStatement("umbracoRelation", "parentId"),
|
||||
FormatDeleteStatement("umbracoRelation", "childId"),
|
||||
FormatDeleteStatement("cmsTagRelationship", "nodeId"),
|
||||
FormatDeleteStatement("umbracoDomains", "domainRootStructureID"),
|
||||
FormatDeleteStatement("cmsDocument", "nodeId"),
|
||||
FormatDeleteStatement("cmsMedia", "nodeId"),
|
||||
FormatDeleteStatement("cmsPropertyData", "contentNodeId"),
|
||||
FormatDeleteStatement("cmsPreviewXml", "nodeId"),
|
||||
FormatDeleteStatement("cmsContentVersion", "ContentId"),
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Linq;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Web.Security;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
@@ -187,18 +188,95 @@ UNION
|
||||
SELECT '5CountOfInvited' AS colName, COUNT(id) AS num FROM umbracoUser WHERE lastLoginDate IS NULL AND userDisabled = 1 AND invitedDate IS NOT NULL
|
||||
ORDER BY colName";
|
||||
|
||||
var result = Database.Fetch<dynamic>(sql);
|
||||
|
||||
var result = Database.Fetch<dynamic>(sql);
|
||||
|
||||
return new Dictionary<UserState, int>
|
||||
{
|
||||
{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}
|
||||
{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}
|
||||
};
|
||||
}
|
||||
|
||||
public Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true)
|
||||
{
|
||||
//TODO: I know this doesn't follow the normal repository conventions which would require us to crete a UserSessionRepository
|
||||
//and also business logic models for these objects but that's just so overkill for what we are doing
|
||||
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
|
||||
var now = DateTime.UtcNow;
|
||||
var dto = new UserLoginDto
|
||||
{
|
||||
UserId = userId,
|
||||
IpAddress = requestingIpAddress,
|
||||
LoggedInUtc = now,
|
||||
LastValidatedUtc = now,
|
||||
LoggedOutUtc = null,
|
||||
SessionId = Guid.NewGuid()
|
||||
};
|
||||
Database.Insert(dto);
|
||||
|
||||
if (cleanStaleSessions)
|
||||
{
|
||||
ClearLoginSessions(TimeSpan.FromDays(15));
|
||||
}
|
||||
|
||||
return dto.SessionId;
|
||||
}
|
||||
|
||||
public bool ValidateLoginSession(int userId, Guid sessionId)
|
||||
{
|
||||
var found = Database.FirstOrDefault<UserLoginDto>("WHERE sessionId=@sessionId", new {sessionId = sessionId});
|
||||
if (found == null || found.UserId != userId || found.LoggedOutUtc.HasValue)
|
||||
return false;
|
||||
|
||||
//now detect if there's been a timeout
|
||||
if (DateTime.UtcNow - found.LastValidatedUtc > TimeSpan.FromMinutes(GlobalSettings.TimeOutInMinutes))
|
||||
{
|
||||
//timeout detected, update the record
|
||||
ClearLoginSession(sessionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
//update the validate date
|
||||
found.LastValidatedUtc = DateTime.UtcNow;
|
||||
Database.Update(found);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int ClearLoginSessions(int userId)
|
||||
{
|
||||
//TODO: I know this doesn't follow the normal repository conventions which would require us to crete a UserSessionRepository
|
||||
//and also business logic models for these objects but that's just so overkill for what we are doing
|
||||
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
|
||||
var count = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoUserLogin WHERE userId=@userId", new { userId = userId });
|
||||
Database.Execute("DELETE FROM umbracoUserLogin WHERE userId=@userId", new {userId = userId});
|
||||
return count;
|
||||
}
|
||||
|
||||
public int ClearLoginSessions(TimeSpan timespan)
|
||||
{
|
||||
//TODO: I know this doesn't follow the normal repository conventions which would require us to crete a UserSessionRepository
|
||||
//and also business logic models for these objects but that's just so overkill for what we are doing
|
||||
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
|
||||
|
||||
var fromDate = DateTime.UtcNow - timespan;
|
||||
|
||||
var count = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoUserLogin WHERE lastValidatedUtc=@fromDate", new { fromDate = fromDate });
|
||||
Database.Execute("DELETE FROM umbracoUserLogin WHERE lastValidatedUtc=@fromDate", new { fromDate = fromDate });
|
||||
return count;
|
||||
}
|
||||
|
||||
public void ClearLoginSession(Guid sessionId)
|
||||
{
|
||||
//TODO: I know this doesn't follow the normal repository conventions which would require us to crete a UserSessionRepository
|
||||
//and also business logic models for these objects but that's just so overkill for what we are doing
|
||||
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
|
||||
Database.Execute("UPDATE umbracoUserLogin SET loggedOutUtc=@now WHERE sessionId=@sessionId",
|
||||
new { now = DateTime.UtcNow, sessionId = sessionId });
|
||||
}
|
||||
|
||||
protected override IEnumerable<IUser> PerformGetAll(params int[] ids)
|
||||
{
|
||||
var sql = GetQueryWithGroups();
|
||||
@@ -401,7 +479,8 @@ ORDER BY colName";
|
||||
{"updateDate", "UpdateDate"},
|
||||
{"avatar", "Avatar"},
|
||||
{"emailConfirmedDate", "EmailConfirmedDate"},
|
||||
{"invitedDate", "InvitedDate"}
|
||||
{"invitedDate", "InvitedDate"},
|
||||
{"tourData", "TourData"}
|
||||
};
|
||||
|
||||
//create list of properties that have changed
|
||||
@@ -834,14 +913,6 @@ ORDER BY colName";
|
||||
{
|
||||
return dtos.Select(UserFactory.BuildEntity);
|
||||
}
|
||||
|
||||
//private IEnumerable<IUserGroup> ConvertFromDtos(IEnumerable<UserGroupDto> dtos)
|
||||
//{
|
||||
// return dtos.Select(dto =>
|
||||
// {
|
||||
// var userGroupFactory = new UserGroupFactory();
|
||||
// return userGroupFactory.BuildEntity(dto);
|
||||
// });
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,14 @@ using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using AutoMapper;
|
||||
using Microsoft.Owin;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Logging;
|
||||
using IUser = Umbraco.Core.Models.Membership.IUser;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
@@ -241,8 +243,11 @@ namespace Umbraco.Core.Security
|
||||
/// <param name="userdata"></param>
|
||||
public static FormsAuthenticationTicket CreateUmbracoAuthTicket(this HttpContextBase http, UserData userdata)
|
||||
{
|
||||
//ONLY used by BasePage.doLogin!
|
||||
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
if (userdata == null) throw new ArgumentNullException("userdata");
|
||||
if (userdata == null) throw new ArgumentNullException("userdata");
|
||||
|
||||
var userDataString = JsonConvert.SerializeObject(userdata);
|
||||
return CreateAuthTicketAndCookie(
|
||||
http,
|
||||
@@ -254,14 +259,7 @@ namespace Umbraco.Core.Security
|
||||
1440,
|
||||
UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName,
|
||||
UmbracoConfig.For.UmbracoSettings().Security.AuthCookieDomain);
|
||||
}
|
||||
|
||||
internal static FormsAuthenticationTicket CreateUmbracoAuthTicket(this HttpContext http, UserData userdata)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
if (userdata == null) throw new ArgumentNullException("userdata");
|
||||
return new HttpContextWrapper(http).CreateUmbracoAuthTicket(userdata);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the number of seconds the user has until their auth session times out
|
||||
@@ -331,7 +329,23 @@ namespace Umbraco.Core.Security
|
||||
/// <param name="http"></param>
|
||||
/// <param name="cookieName"></param>
|
||||
private static void Logout(this HttpContextBase http, string cookieName)
|
||||
{
|
||||
{
|
||||
//We need to clear the sessionId from the database. This is legacy code to do any logging out and shouldn't really be used at all but in any case
|
||||
//we need to make sure the session is cleared. Due to the legacy nature of this it means we need to use singletons
|
||||
if (http.User != null)
|
||||
{
|
||||
var claimsIdentity = http.User.Identity as ClaimsIdentity;
|
||||
if (claimsIdentity != null)
|
||||
{
|
||||
var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
Guid guidSession;
|
||||
if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out guidSession))
|
||||
{
|
||||
ApplicationContext.Current.Services.UserService.ClearLoginSession(guidSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
//clear the preview cookie and external login
|
||||
var cookies = new[] { cookieName, Constants.Web.PreviewCookieName, Constants.Security.BackOfficeExternalCookieName };
|
||||
|
||||
@@ -2,16 +2,29 @@
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
public class BackOfficeClaimsIdentityFactory<T> : ClaimsIdentityFactory<T, int>
|
||||
where T: BackOfficeIdentityUser
|
||||
{
|
||||
private readonly ApplicationContext _appCtx;
|
||||
|
||||
[Obsolete("Use the overload specifying all dependencies instead")]
|
||||
public BackOfficeClaimsIdentityFactory()
|
||||
:this(ApplicationContext.Current)
|
||||
{
|
||||
}
|
||||
|
||||
public BackOfficeClaimsIdentityFactory(ApplicationContext appCtx)
|
||||
{
|
||||
if (appCtx == null) throw new ArgumentNullException("appCtx");
|
||||
_appCtx = appCtx;
|
||||
|
||||
SecurityStampClaimType = Constants.Security.SessionIdClaimType;
|
||||
UserNameClaimType = ClaimTypes.Name;
|
||||
}
|
||||
@@ -24,9 +37,9 @@ namespace Umbraco.Core.Security
|
||||
public override async Task<ClaimsIdentity> CreateAsync(UserManager<T, int> manager, T user, string authenticationType)
|
||||
{
|
||||
var baseIdentity = await base.CreateAsync(manager, user, authenticationType);
|
||||
|
||||
|
||||
var umbracoIdentity = new UmbracoBackOfficeIdentity(baseIdentity,
|
||||
//set a new session id
|
||||
//NOTE - there is no session id assigned here, this is just creating the identity, a session id will be generated when the cookie is written
|
||||
new UserData
|
||||
{
|
||||
Id = user.Id,
|
||||
@@ -37,15 +50,22 @@ namespace Umbraco.Core.Security
|
||||
Roles = user.Roles.Select(x => x.RoleId).ToArray(),
|
||||
StartContentNodes = user.CalculatedContentStartNodeIds,
|
||||
StartMediaNodes = user.CalculatedMediaStartNodeIds,
|
||||
SessionId = user.SecurityStamp
|
||||
SecurityStamp = user.SecurityStamp
|
||||
});
|
||||
|
||||
return umbracoIdentity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BackOfficeClaimsIdentityFactory : BackOfficeClaimsIdentityFactory<BackOfficeIdentityUser>
|
||||
{
|
||||
{
|
||||
[Obsolete("Use the overload specifying all dependencies instead")]
|
||||
public BackOfficeClaimsIdentityFactory()
|
||||
{
|
||||
}
|
||||
|
||||
public BackOfficeClaimsIdentityFactory(ApplicationContext appCtx) : base(appCtx)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Semver;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
public class BackOfficeCookieAuthenticationProvider : CookieAuthenticationProvider
|
||||
{
|
||||
private readonly ApplicationContext _appCtx;
|
||||
|
||||
[Obsolete("Use the ctor specifying all dependencies")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public BackOfficeCookieAuthenticationProvider()
|
||||
: this(ApplicationContext.Current)
|
||||
{
|
||||
}
|
||||
|
||||
public BackOfficeCookieAuthenticationProvider(ApplicationContext appCtx)
|
||||
{
|
||||
if (appCtx == null) throw new ArgumentNullException("appCtx");
|
||||
_appCtx = appCtx;
|
||||
}
|
||||
|
||||
private static readonly SemVersion MinUmbracoVersionSupportingLoginSessions = new SemVersion(7, 8);
|
||||
|
||||
public override void ResponseSignIn(CookieResponseSignInContext context)
|
||||
{
|
||||
var backOfficeIdentity = context.Identity as UmbracoBackOfficeIdentity;
|
||||
if (backOfficeIdentity != null)
|
||||
{
|
||||
//generate a session id and assign it
|
||||
//create a session token - if we are configured and not in an upgrade state then use the db, otherwise just generate one
|
||||
|
||||
//NOTE - special check because when we are upgrading to 7.8 we cannot create a session since the db isn't ready and we'll get exceptions
|
||||
var canAcquireSession = _appCtx.IsUpgrading == false || _appCtx.CurrentVersion() >= MinUmbracoVersionSupportingLoginSessions;
|
||||
|
||||
var session = canAcquireSession
|
||||
? _appCtx.Services.UserService.CreateLoginSession((int)backOfficeIdentity.Id, context.OwinContext.GetCurrentRequestIpAddress())
|
||||
: Guid.NewGuid();
|
||||
|
||||
backOfficeIdentity.UserData.SessionId = session.ToString();
|
||||
}
|
||||
|
||||
base.ResponseSignIn(context);
|
||||
}
|
||||
|
||||
public override void ResponseSignOut(CookieResponseSignOutContext context)
|
||||
{
|
||||
//Clear the user's session on sign out
|
||||
if (context != null && context.OwinContext != null && context.OwinContext.Authentication != null
|
||||
&& context.OwinContext.Authentication.User != null && context.OwinContext.Authentication.User.Identity != null)
|
||||
{
|
||||
var claimsIdentity = context.OwinContext.Authentication.User.Identity as ClaimsIdentity;
|
||||
var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
Guid guidSession;
|
||||
if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out guidSession))
|
||||
{
|
||||
_appCtx.Services.UserService.ClearLoginSession(guidSession);
|
||||
}
|
||||
}
|
||||
|
||||
base.ResponseSignOut(context);
|
||||
|
||||
//Make sure the definitely all of these cookies are cleared when signing out with cookies
|
||||
context.Response.Cookies.Append(SessionIdValidator.CookieName, "", new CookieOptions
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(-1),
|
||||
Path = "/"
|
||||
});
|
||||
context.Response.Cookies.Append(UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName, "", new CookieOptions
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(-1),
|
||||
@@ -34,21 +96,45 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the culture is set correctly for the current back office user
|
||||
/// Ensures that the culture is set correctly for the current back office user and that the user's session token is valid
|
||||
/// </summary>
|
||||
/// <param name="context"/>
|
||||
/// <returns/>
|
||||
public override Task ValidateIdentity(CookieValidateIdentityContext context)
|
||||
public override async Task ValidateIdentity(CookieValidateIdentityContext context)
|
||||
{
|
||||
EnsureCulture(context);
|
||||
|
||||
await EnsureValidSessionId(context);
|
||||
|
||||
await base.ValidateIdentity(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the user has a valid session id
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// So that we are not overloading the database this throttles it's check to every minute
|
||||
/// </remarks>
|
||||
protected virtual async Task EnsureValidSessionId(CookieValidateIdentityContext context)
|
||||
{
|
||||
if (_appCtx.IsConfigured && _appCtx.IsUpgrading == false)
|
||||
await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context);
|
||||
}
|
||||
|
||||
private void EnsureCulture(CookieValidateIdentityContext context)
|
||||
{
|
||||
var umbIdentity = context.Identity as UmbracoBackOfficeIdentity;
|
||||
if (umbIdentity != null && umbIdentity.IsAuthenticated)
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture =
|
||||
Thread.CurrentThread.CurrentUICulture =
|
||||
new CultureInfo(umbIdentity.Culture);
|
||||
UserCultures.GetOrAdd(umbIdentity.Culture, s => new CultureInfo(s));
|
||||
}
|
||||
|
||||
return base.ValidateIdentity(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used so that we aren't creating a new CultureInfo object for every single request
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, CultureInfo> UserCultures = new ConcurrentDictionary<string, CultureInfo>();
|
||||
}
|
||||
}
|
||||
@@ -223,6 +223,9 @@ namespace Umbraco.Core.Security
|
||||
user.AccessFailedCount = 0;
|
||||
await UserManager.UpdateAsync(user);
|
||||
|
||||
//set the current request's principal to the identity just signed in!
|
||||
_request.User = new ClaimsPrincipal(userIdentity);
|
||||
|
||||
_logger.WriteCore(TraceEventType.Information, 0,
|
||||
string.Format(
|
||||
"Login attempt succeeded for username {0} from IP address {1}",
|
||||
|
||||
@@ -152,7 +152,6 @@ namespace Umbraco.Core.Security
|
||||
//NOTE: This method is mostly here for backwards compat
|
||||
base.InitUserManager(manager, membershipProvider, options.DataProtectionProvider, contentSectionConfig);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -169,7 +168,7 @@ namespace Umbraco.Core.Security
|
||||
|
||||
#region What we support do not currently
|
||||
|
||||
//NOTE: Not sure if we really want/need to ever support this
|
||||
//TODO: We could support this - but a user claims will mostly just be what is in the auth cookie
|
||||
public override bool SupportsUserClaim
|
||||
{
|
||||
get { return false; }
|
||||
@@ -271,6 +270,22 @@ namespace Umbraco.Core.Security
|
||||
//manager.SmsService = new SmsService();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to validate a user's session
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<bool> ValidateSessionIdAsync(int userId, string sessionId)
|
||||
{
|
||||
var userSessionStore = Store as IUserSessionStore<BackOfficeIdentityUser, int>;
|
||||
//if this is not set, for backwards compat (which would be super rare), we'll just approve it
|
||||
if (userSessionStore == null)
|
||||
return true;
|
||||
|
||||
return await userSessionStore.ValidateSessionIdAsync(userId, sessionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will determine which password hasher to use based on what is defined in config
|
||||
/// </summary>
|
||||
@@ -674,4 +689,5 @@ namespace Umbraco.Core.Security
|
||||
return httpContext.GetCurrentRequestIpAddress();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,12 +26,13 @@ namespace Umbraco.Core.Security
|
||||
IUserRoleStore<BackOfficeIdentityUser, int>,
|
||||
IUserSecurityStampStore<BackOfficeIdentityUser, int>,
|
||||
IUserLockoutStore<BackOfficeIdentityUser, int>,
|
||||
IUserTwoFactorStore<BackOfficeIdentityUser, int>
|
||||
IUserTwoFactorStore<BackOfficeIdentityUser, int>,
|
||||
IUserSessionStore<BackOfficeIdentityUser, int>
|
||||
|
||||
//TODO: This would require additional columns/tables for now people will need to implement this on their own
|
||||
//IUserPhoneNumberStore<BackOfficeIdentityUser, int>,
|
||||
//TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation
|
||||
//IQueryableUserStore<BackOfficeIdentityUser, int>
|
||||
//TODO: This would require additional columns/tables for now people will need to implement this on their own
|
||||
//IUserPhoneNumberStore<BackOfficeIdentityUser, int>,
|
||||
//TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation
|
||||
//IQueryableUserStore<BackOfficeIdentityUser, int>
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IEntityService _entityService;
|
||||
@@ -753,6 +754,15 @@ namespace Umbraco.Core.Security
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
}
|
||||
|
||||
|
||||
public Task<bool> ValidateSessionIdAsync(int userId, string sessionId)
|
||||
{
|
||||
Guid guidSessionId;
|
||||
if (Guid.TryParse(sessionId, out guidSessionId))
|
||||
{
|
||||
return Task.FromResult(_userService.ValidateLoginSession(userId, guidSessionId));
|
||||
}
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// An IUserStore interface part to implement if the store supports validating user session Ids
|
||||
/// </summary>
|
||||
/// <typeparam name="TUser"></typeparam>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
public interface IUserSessionStore<TUser, in TKey> : IUserStore<TUser, TKey>, IDisposable
|
||||
where TUser : class, IUser<TKey>
|
||||
{
|
||||
Task<bool> ValidateSessionIdAsync(int userId, string sessionId);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
@@ -7,6 +8,37 @@ namespace Umbraco.Core.Security
|
||||
{
|
||||
public static class OwinExtensions
|
||||
{
|
||||
public static string GetCurrentRequestIpAddress(this IOwinContext owinContext)
|
||||
{
|
||||
if (owinContext == null)
|
||||
{
|
||||
return "Unknown, owinContext is null";
|
||||
}
|
||||
if (owinContext.Request == null)
|
||||
{
|
||||
return "Unknown, owinContext.Request is null";
|
||||
}
|
||||
|
||||
var httpContext = owinContext.TryGetHttpContext();
|
||||
if (httpContext == false)
|
||||
{
|
||||
return "Unknown, cannot resolve HttpContext from owinContext";
|
||||
}
|
||||
|
||||
return httpContext.Result.GetCurrentRequestIpAddress();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nasty little hack to get httpcontextbase from an owin context
|
||||
/// </summary>
|
||||
/// <param name="owinContext"></param>
|
||||
/// <returns></returns>
|
||||
internal static Attempt<HttpContextBase> TryGetHttpContext(this IOwinContext owinContext)
|
||||
{
|
||||
var ctx = owinContext.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
|
||||
return ctx == null ? Attempt<HttpContextBase>.Fail() : Attempt.Succeed(ctx);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the back office sign in manager out of OWIN
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Infrastructure;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Static helper class used to configure a CookieAuthenticationProvider to validate a cookie against a user's session id
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This uses another cookie to track the last checked time which is done for a few reasons:
|
||||
/// * We can't use the user's auth ticket to do thsi because we'd be re-issuing the auth ticket all of the time and it would never expire
|
||||
/// plus the auth ticket size is much larger than this small value
|
||||
/// * This will execute quite often (every minute per user) and in some cases there might be several requests that end up re-issuing the cookie so the cookie value should be small
|
||||
/// * We want to avoid the user lookup if it's not required so that will only happen when the time diff is great enough in the cookie
|
||||
/// </remarks>
|
||||
internal static class SessionIdValidator
|
||||
{
|
||||
public const string CookieName = "UMB_UCONTEXT_C";
|
||||
|
||||
public static async Task ValidateSessionAsync(TimeSpan validateInterval, CookieValidateIdentityContext context)
|
||||
{
|
||||
if (context.Request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) == false)
|
||||
return;
|
||||
|
||||
var valid = await ValidateSessionAsync(validateInterval, context.OwinContext, context.Options.CookieManager, context.Options.SystemClock, context.Properties.IssuedUtc, context.Identity);
|
||||
|
||||
if (valid == false)
|
||||
{
|
||||
context.RejectIdentity();
|
||||
context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> ValidateSessionAsync(
|
||||
TimeSpan validateInterval,
|
||||
IOwinContext owinCtx,
|
||||
ICookieManager cookieManager,
|
||||
ISystemClock systemClock,
|
||||
DateTimeOffset? authTicketIssueDate,
|
||||
ClaimsIdentity currentIdentity)
|
||||
{
|
||||
if (owinCtx == null) throw new ArgumentNullException("owinCtx");
|
||||
if (cookieManager == null) throw new ArgumentNullException("cookieManager");
|
||||
if (systemClock == null) throw new ArgumentNullException("systemClock");
|
||||
|
||||
DateTimeOffset? issuedUtc = null;
|
||||
var currentUtc = systemClock.UtcNow;
|
||||
|
||||
//read the last checked time from a custom cookie
|
||||
var lastCheckedCookie = cookieManager.GetRequestCookie(owinCtx, CookieName);
|
||||
|
||||
if (lastCheckedCookie.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
DateTimeOffset parsed;
|
||||
if (DateTimeOffset.TryParse(lastCheckedCookie, out parsed))
|
||||
{
|
||||
issuedUtc = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
//no cookie, use the issue time of the auth ticket
|
||||
if (issuedUtc.HasValue == false)
|
||||
{
|
||||
issuedUtc = authTicketIssueDate;
|
||||
}
|
||||
|
||||
// Only validate if enough time has elapsed
|
||||
var validate = issuedUtc.HasValue == false;
|
||||
if (issuedUtc.HasValue)
|
||||
{
|
||||
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
|
||||
validate = timeElapsed > validateInterval;
|
||||
}
|
||||
|
||||
if (validate == false)
|
||||
return true;
|
||||
|
||||
var manager = owinCtx.GetUserManager<BackOfficeUserManager>();
|
||||
if (manager == null)
|
||||
return false;
|
||||
|
||||
var userId = currentIdentity.GetUserId<int>();
|
||||
var user = await manager.FindByIdAsync(userId);
|
||||
if (user == null)
|
||||
return false;
|
||||
|
||||
var sessionId = currentIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
if (await manager.ValidateSessionIdAsync(userId, sessionId) == false)
|
||||
return false;
|
||||
|
||||
//we will re-issue the cookie last checked cookie
|
||||
cookieManager.AppendResponseCookie(
|
||||
owinCtx,
|
||||
CookieName,
|
||||
DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffffffzzz"),
|
||||
new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = GlobalSettings.UseSSL || owinCtx.Request.IsSecure,
|
||||
Path = "/"
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ namespace Umbraco.Core.Security
|
||||
|
||||
var username = identity.GetUserName();
|
||||
var session = identity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
var securityStamp = identity.FindFirstValue(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType);
|
||||
var startContentId = identity.FindFirstValue(Constants.Security.StartContentNodeIdClaimType);
|
||||
var startMediaId = identity.FindFirstValue(Constants.Security.StartMediaNodeIdClaimType);
|
||||
|
||||
@@ -66,8 +67,9 @@ namespace Umbraco.Core.Security
|
||||
var roles = identity.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToList();
|
||||
var allowedApps = identity.FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToList();
|
||||
|
||||
var userData = new UserData(session)
|
||||
{
|
||||
var userData = new UserData
|
||||
{
|
||||
SecurityStamp = securityStamp,
|
||||
SessionId = session,
|
||||
AllowedApplications = allowedApps.ToArray(),
|
||||
Culture = culture,
|
||||
@@ -189,7 +191,8 @@ namespace Umbraco.Core.Security
|
||||
Constants.Security.StartContentNodeIdClaimType,
|
||||
Constants.Security.StartMediaNodeIdClaimType,
|
||||
ClaimTypes.Locality,
|
||||
Constants.Security.SessionIdClaimType
|
||||
Constants.Security.SessionIdClaimType,
|
||||
Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -226,7 +229,7 @@ namespace Umbraco.Core.Security
|
||||
// by the SecurityStampValidator, see: https://katanaproject.codeplex.com/workitem/444
|
||||
if (HasClaim(x => x.Type == Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType) == false)
|
||||
{
|
||||
AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, SessionId, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, SecurityStamp, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,6 +310,11 @@ namespace Umbraco.Core.Security
|
||||
get { return UserData.SessionId; }
|
||||
}
|
||||
|
||||
public string SecurityStamp
|
||||
{
|
||||
get { return UserData.SecurityStamp; }
|
||||
}
|
||||
|
||||
public string[] Roles
|
||||
{
|
||||
get { return UserData.Roles; }
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Security
|
||||
/// Use this constructor to create/assign new UserData to the ticket
|
||||
/// </summary>
|
||||
/// <param name="sessionId">
|
||||
/// The security stamp for the user
|
||||
/// The current sessionId for the user
|
||||
/// </param>
|
||||
public UserData(string sessionId)
|
||||
{
|
||||
@@ -30,11 +30,17 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the 'security stamp' for validation
|
||||
/// This is the 'sessionId' for validation
|
||||
/// </summary>
|
||||
[DataMember(Name = "sessionId")]
|
||||
public string SessionId { get; set; }
|
||||
public string SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This is the 'security stamp' for validation
|
||||
/// </summary>
|
||||
[DataMember(Name = "securityStamp")]
|
||||
public string SecurityStamp { get; set; }
|
||||
|
||||
[DataMember(Name = "id")]
|
||||
public object Id { get; set; }
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Services
|
||||
@@ -22,5 +26,86 @@ namespace Umbraco.Core.Services
|
||||
uow.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns paged items in the audit trail for a given entity
|
||||
/// </summary>
|
||||
/// <param name="entityId"></param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection">
|
||||
/// By default this will always be ordered descending (newest first)
|
||||
/// </param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// Optional filter to be applied
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IAuditItem> GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords,
|
||||
Direction orderDirection = Direction.Descending,
|
||||
AuditType[] auditTypeFilter = null,
|
||||
IQuery<IAuditItem> customFilter = null)
|
||||
{
|
||||
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
|
||||
Mandate.ParameterCondition(pageSize > 0, "pageSize");
|
||||
|
||||
if (entityId == Constants.System.Root || entityId <= 0)
|
||||
{
|
||||
totalRecords = 0;
|
||||
return Enumerable.Empty<IAuditItem>();
|
||||
}
|
||||
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
{
|
||||
var repository = RepositoryFactory.CreateAuditRepository(uow);
|
||||
|
||||
var query = Query<IAuditItem>.Builder.Where(x => x.Id == entityId);
|
||||
|
||||
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns paged items in the audit trail for a given user
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection">
|
||||
/// By default this will always be ordered descending (newest first)
|
||||
/// </param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// Optional filter to be applied
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IAuditItem> GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords, Direction orderDirection = Direction.Descending, AuditType[] auditTypeFilter = null, IQuery<IAuditItem> customFilter = null)
|
||||
{
|
||||
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
|
||||
Mandate.ParameterCondition(pageSize > 0, "pageSize");
|
||||
|
||||
if (userId < 0)
|
||||
{
|
||||
totalRecords = 0;
|
||||
return Enumerable.Empty<IAuditItem>();
|
||||
}
|
||||
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
{
|
||||
var repository = RepositoryFactory.CreateAuditRepository(uow);
|
||||
|
||||
var query = Query<IAuditItem>.Builder.Where(x => x.UserId == userId);
|
||||
|
||||
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -993,7 +993,8 @@ 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);
|
||||
true, false, ids);
|
||||
Console.WriteLine(sql.SQL);
|
||||
var x = uow.Database.Fetch<int>(sql);
|
||||
return ids.Length == x.Count;
|
||||
}
|
||||
@@ -2008,96 +2009,6 @@ 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))
|
||||
@@ -2542,7 +2453,6 @@ namespace Umbraco.Core.Services
|
||||
|
||||
Audit(uow, AuditType.Publish, "Save and Publish performed by user", userId, content.Id);
|
||||
uow.Commit();
|
||||
|
||||
return Attempt.If(publishStatus.StatusType == PublishStatusType.Success, publishStatus);
|
||||
}
|
||||
}
|
||||
@@ -2838,4 +2748,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, "DeletedMediaTypeContainer");
|
||||
uow.Events.Dispatch(DeletedMediaTypeContainer, this, deleteEventArgs);
|
||||
|
||||
return OperationStatus.Success(evtMsgs);
|
||||
//TODO: Audit trail ?
|
||||
|
||||
@@ -1,9 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
|
||||
namespace Umbraco.Core.Services
|
||||
{
|
||||
public interface IAuditService : IService
|
||||
{
|
||||
void Add(AuditType type, string comment, int userId, int objectId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns paged items in the audit trail for a given entity
|
||||
/// </summary>
|
||||
/// <param name="entityId"></param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection">
|
||||
/// By default this will always be ordered descending (newest first)
|
||||
/// </param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// Optional filter to be applied
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IAuditItem> GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords,
|
||||
Direction orderDirection = Direction.Descending,
|
||||
AuditType[] auditTypeFilter = null,
|
||||
IQuery<IAuditItem> customFilter = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns paged items in the audit trail for a given user
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection">
|
||||
/// By default this will always be ordered descending (newest first)
|
||||
/// </param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// Optional filter to be applied
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IAuditItem> GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords,
|
||||
Direction orderDirection = Direction.Descending,
|
||||
AuditType[] auditTypeFilter = null,
|
||||
IQuery<IAuditItem> customFilter = null);
|
||||
}
|
||||
}
|
||||
@@ -642,21 +642,7 @@ 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);
|
||||
|
||||
/// <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);
|
||||
bool Sort(IEnumerable<IContent> items, int userId = 0, bool raiseEvents = true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of the current content as an <see cref="IContent"/> item.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -11,7 +12,35 @@ namespace Umbraco.Core.Services
|
||||
/// Defines the UserService, which is an easy access to operations involving <see cref="IProfile"/> and eventually Users.
|
||||
/// </summary>
|
||||
public interface IUserService : IMembershipUserService
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a database entry for starting a new login session for a user
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="requestingIpAddress"></param>
|
||||
/// <returns></returns>
|
||||
Guid CreateLoginSession(int userId, string requestingIpAddress);
|
||||
|
||||
/// <summary>
|
||||
/// Validates that a user login session is valid/current and hasn't been closed
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <returns></returns>
|
||||
bool ValidateLoginSession(int userId, Guid sessionId);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the session's validity
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
void ClearLoginSession(Guid sessionId);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all valid sessions for the user
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
int ClearLoginSessions(int userId);
|
||||
|
||||
/// <summary>
|
||||
/// This is basically facets of UserStates key = state, value = count
|
||||
/// </summary>
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -12,7 +11,6 @@ using Umbraco.Core.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
@@ -43,7 +41,7 @@ namespace Umbraco.Core.Services
|
||||
if (userService == null) throw new ArgumentNullException("userService");
|
||||
_dataTypeService = dataTypeService;
|
||||
_userService = userService;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IMedia"/> object using the alias of the <see cref="IMediaType"/>
|
||||
@@ -63,7 +61,7 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
var parent = GetById(parentId);
|
||||
return CreateMedia(name, parent, mediaTypeAlias, userId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IMedia"/> object using the alias of the <see cref="IMediaType"/>
|
||||
@@ -731,65 +729,10 @@ namespace Umbraco.Core.Services
|
||||
/// <returns><see cref="IMedia"/></returns>
|
||||
public IMedia GetMediaByPath(string mediaPath)
|
||||
{
|
||||
var umbracoFileValue = mediaPath;
|
||||
|
||||
const string Pattern = ".*[_][0-9]+[x][0-9]+[.].*";
|
||||
var isResized = Regex.IsMatch(mediaPath, Pattern);
|
||||
|
||||
// If the image has been resized we strip the "_403x328" of the original "/media/1024/koala_403x328.jpg" url.
|
||||
if (isResized)
|
||||
{
|
||||
var underscoreIndex = mediaPath.LastIndexOf('_');
|
||||
var dotIndex = mediaPath.LastIndexOf('.');
|
||||
umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex));
|
||||
}
|
||||
|
||||
Func<string, Sql> createSql = url => new Sql().Select("*")
|
||||
.From<PropertyDataDto>()
|
||||
.InnerJoin<PropertyTypeDto>()
|
||||
.On<PropertyDataDto, PropertyTypeDto>(left => left.PropertyTypeId, right => right.Id)
|
||||
.Where<PropertyTypeDto>(x => x.Alias == "umbracoFile")
|
||||
.Where<PropertyDataDto>(x => x.VarChar == url);
|
||||
|
||||
var sql = createSql(umbracoFileValue);
|
||||
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
{
|
||||
var propertyDataDto = uow.Database.Fetch<PropertyDataDto, PropertyTypeDto>(sql).FirstOrDefault();
|
||||
|
||||
// If the stripped-down url returns null, we try again with the original url.
|
||||
// Previously, the function would fail on e.g. "my_x_image.jpg"
|
||||
if (propertyDataDto == null)
|
||||
{
|
||||
sql = createSql(mediaPath);
|
||||
propertyDataDto = uow.Database.Fetch<PropertyDataDto, PropertyTypeDto>(sql).FirstOrDefault();
|
||||
}
|
||||
|
||||
// If no reults far, try getting from a json value stored in the ntext column query
|
||||
if (propertyDataDto == null)
|
||||
{
|
||||
var ntextQuery = new Sql().Select("*")
|
||||
.From<PropertyDataDto>()
|
||||
.InnerJoin<PropertyTypeDto>()
|
||||
.On<PropertyDataDto, PropertyTypeDto>(left => left.PropertyTypeId, right => right.Id)
|
||||
.Where<PropertyTypeDto>(x => x.Alias == "umbracoFile")
|
||||
.Where("dataNtext LIKE @0", "%" + umbracoFileValue + "%");
|
||||
propertyDataDto = uow.Database.Fetch<PropertyDataDto, PropertyTypeDto>(ntextQuery).FirstOrDefault();
|
||||
}
|
||||
|
||||
// If still no results, try getting from a json value stored in the nvarchar column
|
||||
if (propertyDataDto == null)
|
||||
{
|
||||
var nvarcharQuery = new Sql().Select("*")
|
||||
.From<PropertyDataDto>()
|
||||
.InnerJoin<PropertyTypeDto>()
|
||||
.On<PropertyDataDto, PropertyTypeDto>(left => left.PropertyTypeId, right => right.Id)
|
||||
.Where<PropertyTypeDto>(x => x.Alias == "umbracoFile")
|
||||
.Where("dataNvarchar LIKE @0", "%" + umbracoFileValue + "%");
|
||||
propertyDataDto = uow.Database.Fetch<PropertyDataDto, PropertyTypeDto>(nvarcharQuery).FirstOrDefault();
|
||||
}
|
||||
|
||||
return propertyDataDto == null ? null : GetById(propertyDataDto.NodeId);
|
||||
var repository = RepositoryFactory.CreateMediaRepository(uow);
|
||||
return repository.GetMediaByPath(mediaPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1021,7 +964,7 @@ namespace Umbraco.Core.Services
|
||||
|
||||
if (raiseEvents)
|
||||
{
|
||||
saveEventArgs.CanCancel = false;
|
||||
saveEventArgs.CanCancel = false;
|
||||
uow.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
}
|
||||
|
||||
|
||||
@@ -986,11 +986,7 @@ 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>
|
||||
/// Disables an <see cref="IUser"/>
|
||||
/// Deletes an <see cref="IUser"/>
|
||||
/// </summary>
|
||||
/// <param name="membershipUser"><see cref="IUser"/> to disable</param>
|
||||
/// <param name="membershipUser"><see cref="IUser"/> to Delete</param>
|
||||
public void Delete(IUser membershipUser)
|
||||
{
|
||||
//disable
|
||||
@@ -558,6 +558,47 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public Guid CreateLoginSession(int userId, string requestingIpAddress)
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
var repository = RepositoryFactory.CreateUserRepository(uow);
|
||||
var session = repository.CreateLoginSession(userId, requestingIpAddress);
|
||||
uow.Commit();
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
public int ClearLoginSessions(int userId)
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
var repository = RepositoryFactory.CreateUserRepository(uow);
|
||||
var count = repository.ClearLoginSessions(userId);
|
||||
uow.Commit();
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearLoginSession(Guid sessionId)
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork())
|
||||
{
|
||||
var repository = RepositoryFactory.CreateUserRepository(uow);
|
||||
repository.ClearLoginSession(sessionId);
|
||||
uow.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
public bool ValidateLoginSession(int userId, Guid sessionId)
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
{
|
||||
var repository = RepositoryFactory.CreateUserRepository(uow);
|
||||
return repository.ValidateLoginSession(userId, sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
public IDictionary<UserState, int> GetUserStates()
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
@@ -712,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,7 +3,6 @@ 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
|
||||
@@ -47,20 +46,8 @@ 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 && !newApplicationUrl) return;
|
||||
if (appContext._umbracoApplicationUrl != null) return;
|
||||
|
||||
var logger = appContext.ProfilingLogger.Logger;
|
||||
|
||||
@@ -145,12 +132,6 @@ 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,
|
||||
@@ -166,7 +147,8 @@ 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);
|
||||
|
||||
return url.TrimEnd('/');
|
||||
appContext._umbracoApplicationUrl = url.TrimEnd('/');
|
||||
logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: " + appContext.UmbracoApplicationUrl + " (UmbracoModule request)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,8 @@ namespace Umbraco.Core.Sync
|
||||
{
|
||||
UtcStamp = DateTime.UtcNow,
|
||||
Instructions = JsonConvert.SerializeObject(instructions, Formatting.None),
|
||||
OriginIdentity = LocalIdentity
|
||||
OriginIdentity = LocalIdentity,
|
||||
InstructionCount = instructions.Sum(x => x.JsonIdCount)
|
||||
};
|
||||
|
||||
ApplicationContext.DatabaseContext.Database.Insert(dto);
|
||||
@@ -165,10 +166,10 @@ namespace Umbraco.Core.Sync
|
||||
}
|
||||
else
|
||||
{
|
||||
//check for how many instructions there are to process
|
||||
//TODO: In 7.6 we need to store the count of instructions per row since this is not affective because there can be far more than one (if not thousands)
|
||||
// of instructions in a single row.
|
||||
var count = _appContext.DatabaseContext.Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId});
|
||||
//check for how many instructions there are to process, each row contains a count of the number of instructions contained in each
|
||||
//row so we will sum these numbers to get the actual count.
|
||||
var count = _appContext.DatabaseContext.Database
|
||||
.ExecuteScalar<int>("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId});
|
||||
if (count > Options.MaxProcessingInstructionCount)
|
||||
{
|
||||
//too many instructions, proceed to cold boot
|
||||
@@ -205,7 +206,7 @@ namespace Umbraco.Core.Sync
|
||||
/// <summary>
|
||||
/// Synchronize the server (throttled).
|
||||
/// </summary>
|
||||
protected void Sync()
|
||||
protected internal void Sync()
|
||||
{
|
||||
lock (_locko)
|
||||
{
|
||||
|
||||
@@ -18,7 +18,10 @@ namespace Umbraco.Core.Sync
|
||||
// need this public, parameter-less constructor so the web service messenger
|
||||
// can de-serialize the instructions it receives
|
||||
public RefreshInstruction()
|
||||
{ }
|
||||
{
|
||||
//set default - this value is not used for reading after it's been deserialized, it's only used for persisting the instruction to the db
|
||||
JsonIdCount = 1;
|
||||
}
|
||||
|
||||
// need this public one so it can be de-serialized - used by the Json thing
|
||||
// otherwise, should use GetInstructions(...)
|
||||
@@ -30,12 +33,16 @@ namespace Umbraco.Core.Sync
|
||||
IntId = intId;
|
||||
JsonIds = jsonIds;
|
||||
JsonPayload = jsonPayload;
|
||||
//set default - this value is not used for reading after it's been deserialized, it's only used for persisting the instruction to the db
|
||||
JsonIdCount = 1;
|
||||
}
|
||||
|
||||
private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType)
|
||||
{
|
||||
RefresherId = refresher.UniqueIdentifier;
|
||||
RefreshType = refreshType;
|
||||
//set default - this value is not used for reading after it's been deserialized, it's only used for persisting the instruction to the db
|
||||
JsonIdCount = 1;
|
||||
}
|
||||
|
||||
private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, Guid guidId)
|
||||
@@ -50,9 +57,21 @@ namespace Umbraco.Core.Sync
|
||||
IntId = intId;
|
||||
}
|
||||
|
||||
private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, string json)
|
||||
/// <summary>
|
||||
/// A private constructor to create a new instance
|
||||
/// </summary>
|
||||
/// <param name="refresher"></param>
|
||||
/// <param name="refreshType"></param>
|
||||
/// <param name="json"></param>
|
||||
/// <param name="idCount">
|
||||
/// When the refresh method is <see cref="RefreshMethodType.RefreshByIds"/> we know how many Ids are being refreshed so we know the instruction
|
||||
/// count which will be taken into account when we store this count in the database.
|
||||
/// </param>
|
||||
private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, string json, int idCount = 1)
|
||||
: this(refresher, refreshType)
|
||||
{
|
||||
JsonIdCount = idCount;
|
||||
|
||||
if (refreshType == RefreshMethodType.RefreshByJson)
|
||||
JsonPayload = json;
|
||||
else
|
||||
@@ -77,8 +96,12 @@ namespace Umbraco.Core.Sync
|
||||
case MessageType.RefreshById:
|
||||
if (idType == null)
|
||||
throw new InvalidOperationException("Cannot refresh by id if idType is null.");
|
||||
if (idType == typeof (int)) // bulk of ints is supported
|
||||
return new[] { new RefreshInstruction(refresher, RefreshMethodType.RefreshByIds, JsonConvert.SerializeObject(ids.Cast<int>().ToArray())) };
|
||||
if (idType == typeof(int))
|
||||
{
|
||||
// bulk of ints is supported
|
||||
var intIds = ids.Cast<int>().ToArray();
|
||||
return new[] { new RefreshInstruction(refresher, RefreshMethodType.RefreshByIds, JsonConvert.SerializeObject(intIds), intIds.Length) };
|
||||
}
|
||||
// else must be guids, bulk of guids is not supported, iterate
|
||||
return ids.Select(x => new RefreshInstruction(refresher, RefreshMethodType.RefreshByGuid, (Guid) x));
|
||||
|
||||
@@ -121,6 +144,14 @@ namespace Umbraco.Core.Sync
|
||||
/// </summary>
|
||||
public string JsonIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of Ids contained in the JsonIds json value
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used to determine the instruction count per row
|
||||
/// </remarks>
|
||||
public int JsonIdCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the payload data value.
|
||||
/// </summary>
|
||||
|
||||
@@ -117,8 +117,6 @@ namespace Umbraco.Core
|
||||
{
|
||||
case UmbracoObjectTypes.Document:
|
||||
return Document;
|
||||
case UmbracoObjectTypes.DocumentBlueprint:
|
||||
return DocumentBluePrint;
|
||||
case UmbracoObjectTypes.Media:
|
||||
return Media;
|
||||
case UmbracoObjectTypes.Member:
|
||||
@@ -163,8 +161,6 @@ namespace Umbraco.Core
|
||||
{
|
||||
case Document:
|
||||
return UmbracoObjectTypes.Document;
|
||||
case DocumentBluePrint:
|
||||
return UmbracoObjectTypes.DocumentBlueprint;
|
||||
case Media:
|
||||
return UmbracoObjectTypes.Media;
|
||||
case Member:
|
||||
|
||||
@@ -39,18 +39,18 @@
|
||||
<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>
|
||||
</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>
|
||||
<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,9 +63,11 @@
|
||||
</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>
|
||||
@@ -81,16 +83,17 @@
|
||||
</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>
|
||||
@@ -102,12 +105,16 @@
|
||||
<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" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Linq.Expressions" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Net.Http.WebRequest" />
|
||||
@@ -239,6 +246,7 @@
|
||||
<Compile Include="Configuration\UmbracoConfigurationSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\AppCodeFileExtensionsCollection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\AppCodeFileExtensionsElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\BackOfficeElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\CharCollection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\CharElement.cs" />
|
||||
<Compile Include="Configuration\CommaDelimitedConfigurationElement.cs" />
|
||||
@@ -247,6 +255,7 @@
|
||||
<Compile Include="Configuration\UmbracoSettings\ContentErrorPageElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ContentErrorsElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ContentSectionExtensions.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\IBackOfficeSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ImagingAutoFillPropertiesCollection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ImagingAutoFillUploadFieldElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ContentImagingElement.cs" />
|
||||
@@ -282,6 +291,7 @@
|
||||
<Compile Include="Configuration\UmbracoSettings\ISecuritySection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\IServer.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ITemplatesSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ITourSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\IUmbracoSettingsSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\IViewStateMoverModuleSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\IWebRoutingSection.cs" />
|
||||
@@ -312,6 +322,7 @@
|
||||
<Compile Include="Configuration\UmbracoSettings\ServerCollection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\ServerElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\TemplatesElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\TourConfigElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\UmbracoConfigurationElement.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\UmbracoSettingsSection.cs" />
|
||||
<Compile Include="Configuration\UmbracoSettings\UrlReplacingElement.cs" />
|
||||
@@ -357,9 +368,9 @@
|
||||
<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" />
|
||||
<Compile Include="Models\IUserControl.cs" />
|
||||
<Compile Include="Models\Membership\ContentPermissionSet.cs" />
|
||||
<Compile Include="Models\Membership\EntityPermissionCollection.cs" />
|
||||
@@ -371,6 +382,8 @@
|
||||
<Compile Include="Models\Membership\UserState.cs" />
|
||||
<Compile Include="Models\Membership\UserType.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedContentTypeConverter.cs" />
|
||||
<Compile Include="Models\Rdbms\MediaDto.cs" />
|
||||
<Compile Include="Models\Rdbms\UserLoginDto.cs" />
|
||||
<Compile Include="Models\Rdbms\UserStartNodeDto.cs" />
|
||||
<Compile Include="Models\UserControl.cs" />
|
||||
<Compile Include="OrderedHashSet.cs" />
|
||||
@@ -532,12 +545,18 @@
|
||||
<Compile Include="Persistence\LockedRepository.cs" />
|
||||
<Compile Include="Persistence\LockingRepository.cs" />
|
||||
<Compile Include="Persistence\Mappers\AccessMapper.cs" />
|
||||
<Compile Include="Persistence\Mappers\AuditMapper.cs" />
|
||||
<Compile Include="Persistence\Mappers\DomainMapper.cs" />
|
||||
<Compile Include="Persistence\Mappers\ExternalLoginMapper.cs" />
|
||||
<Compile Include="Persistence\Mappers\MigrationEntryMapper.cs" />
|
||||
<Compile Include="Persistence\Mappers\UserGroupMapper.cs" />
|
||||
<Compile Include="Persistence\Migrations\LocalMigrationContext.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionFourOneZero\AddPreviewXmlTable.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddTourDataUserColumn.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddIndexToPropertyTypeAliasColumn.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddInstructionCountColumn.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddCmsMediaTable.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddUserLoginTable.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\AddIndexToDictionaryKeyColumn.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\EnsureContentTemplatePermissions.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\ReduceDictionaryKeyColumnsSize.cs" />
|
||||
@@ -685,11 +704,13 @@
|
||||
<Compile Include="Security\IBackOfficeUserPasswordChecker.cs" />
|
||||
<Compile Include="Security\IMembershipProviderPasswordHasher.cs" />
|
||||
<Compile Include="Security\IUserAwarePasswordHasher.cs" />
|
||||
<Compile Include="Security\IUserSessionStore.cs" />
|
||||
<Compile Include="Security\MachineKeyGenerator.cs" />
|
||||
<Compile Include="Security\MembershipProviderPasswordHasher.cs" />
|
||||
<Compile Include="Security\EmailService.cs" />
|
||||
<Compile Include="Security\MembershipProviderPasswordValidator.cs" />
|
||||
<Compile Include="Security\OwinExtensions.cs" />
|
||||
<Compile Include="Security\SessionIdValidator.cs" />
|
||||
<Compile Include="Security\UmbracoEmailMessage.cs" />
|
||||
<Compile Include="Security\UserAwareMembershipProviderPasswordHasher.cs" />
|
||||
<Compile Include="SemVersionExtensions.cs" />
|
||||
|
||||
@@ -17,6 +17,5 @@
|
||||
<package id="Newtonsoft.Json" version="10.0.2" targetFramework="net45" />
|
||||
<package id="Owin" version="1.0" targetFramework="net45" />
|
||||
<package id="semver" version="1.1.2" targetFramework="net45" />
|
||||
<package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
|
||||
<package id="SqlServerCE" version="4.0.0.1" targetFramework="net45" />
|
||||
</packages>
|
||||
@@ -40,27 +40,34 @@
|
||||
<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>
|
||||
@@ -68,22 +75,27 @@
|
||||
<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" />
|
||||
@@ -104,7 +116,6 @@
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="packages.config" />
|
||||
<None Include="_TraceEventProgrammersGuide.docx" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj">
|
||||
@@ -121,8 +132,8 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="TraceEvent.ReadMe.txt" />
|
||||
<Content Include="TraceEvent.ReleaseNotes.txt" />
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -121,8 +121,7 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
{
|
||||
AssertDisplayProperty(result, p, ApplicationContext);
|
||||
}
|
||||
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count() - 1);
|
||||
Assert.IsTrue(result.Tabs.Any(x => x.Label == ui.Text("general", "properties")));
|
||||
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count());
|
||||
Assert.IsTrue(result.Tabs.First().IsActive);
|
||||
Assert.IsTrue(result.Tabs.Except(new[] {result.Tabs.First()}).All(x => x.IsActive == false));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Packaging;
|
||||
|
||||
namespace Umbraco.Tests.Packaging
|
||||
{
|
||||
[TestFixture]
|
||||
class PackageExtractionTests
|
||||
{
|
||||
private const string PackageFileName = "Document_Type_Picker_1.1.umb";
|
||||
|
||||
private static string GetTestPackagePath(string packageName)
|
||||
{
|
||||
const string testPackagesDirName = "Packaging\\Packages";
|
||||
string path = Path.Combine(Core.Configuration.GlobalSettings.FullpathToRoot, testPackagesDirName, packageName);
|
||||
return path;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadFilesFromArchive_NumberOfFilesIs1_SearchingForPackageXmlFile()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new PackageExtraction();
|
||||
|
||||
// Act
|
||||
var result = sut.ReadFilesFromArchive(GetTestPackagePath(PackageFileName), new[] { "Package.xml" });
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, result.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FindMissingFiles_1_UnknownFile()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new PackageExtraction();
|
||||
|
||||
// Act
|
||||
var result = sut.FindMissingFiles(GetTestPackagePath(PackageFileName), new[] { "DoesNotExists.XYZ" });
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, result.Count());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,5 +40,86 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.That(dtos.Any(), Is.True);
|
||||
Assert.That(dtos.First().Comment, Is.EqualTo("This is a System audit trail"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Paged_Items()
|
||||
{
|
||||
var provider = new PetaPocoUnitOfWorkProvider(Logger);
|
||||
var unitOfWork = provider.GetUnitOfWork();
|
||||
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
repo.AddOrUpdate(new AuditItem(i, string.Format("Content {0} created", i), AuditType.New, 0));
|
||||
repo.AddOrUpdate(new AuditItem(i, string.Format("Content {0} published", i), AuditType.Publish, 0));
|
||||
}
|
||||
unitOfWork.Commit();
|
||||
}
|
||||
|
||||
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
|
||||
{
|
||||
long total;
|
||||
var page = repo.GetPagedResultsByQuery(Query<IAuditItem>.Builder, 0, 10, out total, Direction.Descending, null, null);
|
||||
|
||||
Assert.AreEqual(10, page.Count());
|
||||
Assert.AreEqual(200, total);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Paged_Items_With_AuditType_Filter()
|
||||
{
|
||||
var provider = new PetaPocoUnitOfWorkProvider(Logger);
|
||||
var unitOfWork = provider.GetUnitOfWork();
|
||||
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
repo.AddOrUpdate(new AuditItem(i, string.Format("Content {0} created", i), AuditType.New, 0));
|
||||
repo.AddOrUpdate(new AuditItem(i, string.Format("Content {0} published", i), AuditType.Publish, 0));
|
||||
}
|
||||
unitOfWork.Commit();
|
||||
}
|
||||
|
||||
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
|
||||
{
|
||||
long total;
|
||||
var page = repo.GetPagedResultsByQuery(Query<IAuditItem>.Builder, 0, 9, out total, Direction.Descending,
|
||||
new[] {AuditType.Publish}, null)
|
||||
.ToArray();
|
||||
|
||||
Assert.AreEqual(9, page.Length);
|
||||
Assert.IsTrue(page.All(x => x.AuditType == AuditType.Publish));
|
||||
Assert.AreEqual(100, total);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Paged_Items_With_Custom_Filter()
|
||||
{
|
||||
var provider = new PetaPocoUnitOfWorkProvider(Logger);
|
||||
var unitOfWork = provider.GetUnitOfWork();
|
||||
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
repo.AddOrUpdate(new AuditItem(i, "Content created", AuditType.New, 0));
|
||||
repo.AddOrUpdate(new AuditItem(i, "Content published", AuditType.Publish, 0));
|
||||
}
|
||||
unitOfWork.Commit();
|
||||
}
|
||||
|
||||
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
|
||||
{
|
||||
long total;
|
||||
var page = repo.GetPagedResultsByQuery(Query<IAuditItem>.Builder, 0, 8, out total, Direction.Descending,
|
||||
null, Query<IAuditItem>.Builder.Where(item => item.Comment == "Content created"))
|
||||
.ToArray();
|
||||
|
||||
Assert.AreEqual(8, page.Length);
|
||||
Assert.IsTrue(page.All(x => x.Comment == "Content created"));
|
||||
Assert.AreEqual(100, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,7 +271,7 @@ AnotherContentFinder
|
||||
public void Resolves_Assigned_Mappers()
|
||||
{
|
||||
var foundTypes1 = _manager.ResolveAssignedMapperTypes();
|
||||
Assert.AreEqual(28, foundTypes1.Count());
|
||||
Assert.AreEqual(29, foundTypes1.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -3,13 +3,10 @@ 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;
|
||||
|
||||
@@ -102,7 +99,7 @@ namespace Umbraco.Tests.Scoping
|
||||
DoDeleteForContent += OnDoThingFail;
|
||||
DoForTestArgs += OnDoThingFail;
|
||||
DoForTestArgs2 += OnDoThingFail;
|
||||
|
||||
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
|
||||
var content1 = MockedContent.CreateBasicContent(contentType);
|
||||
@@ -117,10 +114,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), "DoDeleteForContent");
|
||||
scope.Events.Dispatch(DoDeleteForContent, this, new DeleteEventArgs<IContent>(content1));
|
||||
scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs<IContent>(content2));
|
||||
//this entire event will be filtered
|
||||
scope.Events.Dispatch(DoForTestArgs, this, new TestEventArgs(content1));
|
||||
@@ -144,36 +141,15 @@ 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();
|
||||
@@ -189,7 +165,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));
|
||||
@@ -197,7 +173,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;
|
||||
@@ -236,7 +212,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);
|
||||
@@ -374,9 +350,6 @@ 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)
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Umbraco.Tests.Security
|
||||
public void Create_From_Claims_Identity()
|
||||
{
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var securityStamp = Guid.NewGuid().ToString();
|
||||
var claimsIdentity = new ClaimsIdentity(new[]
|
||||
{
|
||||
//This is the id that 'identity' uses to check for the user id
|
||||
@@ -36,12 +37,14 @@ namespace Umbraco.Tests.Security
|
||||
new Claim(ClaimTypes.Locality, "en-us", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
new Claim(Constants.Security.SessionIdClaimType, sessionId, Constants.Security.SessionIdClaimType, TestIssuer, TestIssuer),
|
||||
new Claim(ClaimsIdentity.DefaultRoleClaimType, "admin", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, securityStamp, ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
});
|
||||
|
||||
var backofficeIdentity = UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity);
|
||||
|
||||
Assert.AreEqual("1234", backofficeIdentity.Id);
|
||||
Assert.AreEqual(sessionId, backofficeIdentity.SessionId);
|
||||
Assert.AreEqual(securityStamp, backofficeIdentity.SecurityStamp);
|
||||
Assert.AreEqual("testing", backofficeIdentity.Username);
|
||||
Assert.AreEqual("hello world", backofficeIdentity.RealName);
|
||||
Assert.AreEqual(1, backofficeIdentity.StartContentNodes.Length);
|
||||
@@ -50,7 +53,7 @@ namespace Umbraco.Tests.Security
|
||||
Assert.AreEqual("en-us", backofficeIdentity.Culture);
|
||||
Assert.IsTrue(new[] { "admin" }.SequenceEqual(backofficeIdentity.Roles));
|
||||
|
||||
Assert.AreEqual(10, backofficeIdentity.Claims.Count());
|
||||
Assert.AreEqual(11, backofficeIdentity.Claims.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -93,6 +96,7 @@ namespace Umbraco.Tests.Security
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var userData = new UserData(sessionId)
|
||||
{
|
||||
SecurityStamp = sessionId,
|
||||
AllowedApplications = new[] {"content", "media"},
|
||||
Culture = "en-us",
|
||||
Id = 1234,
|
||||
@@ -113,6 +117,7 @@ namespace Umbraco.Tests.Security
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var userData = new UserData(sessionId)
|
||||
{
|
||||
SecurityStamp = sessionId,
|
||||
AllowedApplications = new[] { "content", "media" },
|
||||
Culture = "en-us",
|
||||
Id = 1234,
|
||||
@@ -139,6 +144,7 @@ namespace Umbraco.Tests.Security
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var userData = new UserData(sessionId)
|
||||
{
|
||||
SecurityStamp = sessionId,
|
||||
AllowedApplications = new[] { "content", "media" },
|
||||
Culture = "en-us",
|
||||
Id = 1234,
|
||||
@@ -162,6 +168,7 @@ namespace Umbraco.Tests.Security
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var userData = new UserData(sessionId)
|
||||
{
|
||||
SecurityStamp = sessionId,
|
||||
AllowedApplications = new[] { "content", "media" },
|
||||
Culture = "en-us",
|
||||
Id = 1234,
|
||||
|
||||
@@ -24,9 +24,11 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
{
|
||||
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
|
||||
{
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var identity = new UmbracoBackOfficeIdentity(
|
||||
new UserData(Guid.NewGuid().ToString())
|
||||
new UserData(sessionId)
|
||||
{
|
||||
SecurityStamp = sessionId,
|
||||
Id = 0,
|
||||
Roles = new[] { "admin" },
|
||||
AllowedApplications = new[] { "content", "media", "members" },
|
||||
|
||||
@@ -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.89.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Examine.0.1.89\lib\net45\Examine.dll</HintPath>
|
||||
<Reference Include="Examine, Version=0.1.85.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Examine.0.1.85\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>
|
||||
@@ -188,6 +188,7 @@
|
||||
<Compile Include="DateTimeExtensionsTests.cs" />
|
||||
<Compile Include="Cache\CacheRefresherEventHandlerTests.cs" />
|
||||
<Compile Include="Models\Mapping\UserModelMapperTests.cs" />
|
||||
<Compile Include="Packaging\PackageExtractionTests.cs" />
|
||||
<Compile Include="Persistence\Repositories\SimilarNodeNameTests.cs" />
|
||||
<Compile Include="TestHelpers\ControllerTesting\AuthenticateEverythingExtensions.cs" />
|
||||
<Compile Include="TestHelpers\ControllerTesting\AuthenticateEverythingMiddleware.cs" />
|
||||
|
||||
@@ -405,7 +405,7 @@ namespace Umbraco.Tests.Web.Mvc
|
||||
new Mock<IMemberTypeService>().Object,
|
||||
new Mock<IMemberService>().Object,
|
||||
new Mock<IUserService>().Object,
|
||||
new Mock<ISectionService>().Object,
|
||||
new Mock<ISectionService>().Object,
|
||||
new Mock<IApplicationTreeService>().Object,
|
||||
new Mock<ITagService>().Object,
|
||||
new Mock<INotificationService>().Object,
|
||||
|
||||
@@ -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.89" targetFramework="net45" />
|
||||
<package id="Examine" version="0.1.85" 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" />
|
||||
|
||||
@@ -25,13 +25,14 @@
|
||||
"jquery-migrate": "1.4.0",
|
||||
"angular-dynamic-locale": "0.1.28",
|
||||
"ng-file-upload": "~7.3.8",
|
||||
"tinymce": "~4.5.3",
|
||||
"tinymce": "~4.7.1",
|
||||
"codemirror": "~5.3.0",
|
||||
"angular-local-storage": "~0.2.3",
|
||||
"moment": "~2.10.3",
|
||||
"ace-builds": "^1.2.3",
|
||||
"clipboard": "1.7.1",
|
||||
"font-awesome": "~4.2",
|
||||
"clipboard": "1.7.1"
|
||||
"angular-moment": "^1.0.1"
|
||||
},
|
||||
|
||||
"install": {
|
||||
@@ -68,6 +69,10 @@
|
||||
"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"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,605 @@
|
||||
module.exports = function (grunt) {
|
||||
|
||||
|
||||
|
||||
// Default task.
|
||||
grunt.registerTask('default', ['jshint:dev', 'build', 'karma:unit']);
|
||||
grunt.registerTask('dev', ['jshint:dev', 'build-dev', 'webserver', 'open:dev', 'watch']);
|
||||
grunt.registerTask('docserve', ['docs:api', 'connect:docserver', 'open:docs', 'watch:docs']);
|
||||
grunt.registerTask('vs', ['jshint:dev', 'build-dev', 'watch']);
|
||||
|
||||
//TODO: Too much watching, this brings windows to it's knees when in dev mode
|
||||
//run by the watch task
|
||||
grunt.registerTask('watch-js', ['jshint:dev', 'concat', 'copy:app', 'copy:mocks', 'copy:canvasdesigner', 'copy:vs', 'karma:unit']);
|
||||
grunt.registerTask('watch-less', ['recess:build', 'recess:installer', 'recess:nonodes', 'recess:canvasdesigner', 'postcss', 'copy:canvasdesigner', 'copy:assets', 'copy:vs']);
|
||||
grunt.registerTask('watch-html', ['copy:views', 'copy:vs']);
|
||||
grunt.registerTask('watch-installer', ['concat:install', 'concat:installJs', 'copy:installer', 'copy:vs']);
|
||||
grunt.registerTask('watch-canvasdesigner', ['copy:canvasdesigner', 'concat:canvasdesignerJs', 'copy:vs']);
|
||||
grunt.registerTask('watch-test', ['jshint:dev', 'karma:unit']);
|
||||
|
||||
//triggered from grunt
|
||||
grunt.registerTask('build', ['concat', 'recess:build', 'recess:installer', 'recess:nonodes', 'recess:canvasdesigner', 'postcss', 'bower-install-simple', 'bower', 'copy', 'clean:post']);
|
||||
|
||||
//triggered from grunt dev vs or grunt vs
|
||||
grunt.registerTask('build-dev', ['clean:pre', 'concat', 'recess:build', 'recess:installer', 'recess:nonodes', 'postcss', 'bower-install-simple', 'bower', 'copy']);
|
||||
|
||||
//utillity tasks
|
||||
grunt.registerTask('docs', ['ngdocs']);
|
||||
grunt.registerTask('webserver', ['connect:devserver']);
|
||||
|
||||
|
||||
// Print a timestamp (useful for when watching)
|
||||
grunt.registerTask('timestamp', function () {
|
||||
grunt.log.subhead(Date());
|
||||
});
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
buildVersion: grunt.option('buildversion') || '7',
|
||||
connect: {
|
||||
devserver: {
|
||||
options: {
|
||||
port: 9990,
|
||||
hostname: '0.0.0.0',
|
||||
base: './build',
|
||||
middleware: function(connect, options) {
|
||||
return [
|
||||
//uncomment to enable CSP
|
||||
// util.csp(),
|
||||
//util.rewrite(),
|
||||
connect.favicon('images/favicon.ico'),
|
||||
connect.static(options.base),
|
||||
connect.directory(options.base)
|
||||
];
|
||||
}
|
||||
}
|
||||
},
|
||||
testserver: {},
|
||||
docserver: {
|
||||
options: {
|
||||
port: 8880,
|
||||
hostname: '0.0.0.0',
|
||||
base: './docs/api',
|
||||
middleware: function(connect, options) {
|
||||
return [
|
||||
//uncomment to enable CSP
|
||||
// util.csp(),
|
||||
//util.rewrite(),
|
||||
connect.static(options.base),
|
||||
connect.directory(options.base)
|
||||
];
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
open: {
|
||||
dev: {
|
||||
path: 'http://localhost:9990/belle/'
|
||||
},
|
||||
docs: {
|
||||
path: 'http://localhost:8880/index.html'
|
||||
}
|
||||
},
|
||||
|
||||
distdir: 'build/belle',
|
||||
vsdir: '../Umbraco.Web.UI/umbraco',
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
banner:
|
||||
'/*! <%= pkg.title || pkg.name %>\n' +
|
||||
'<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' +
|
||||
' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;\n' +
|
||||
' * Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n */\n',
|
||||
src: {
|
||||
js: ['src/**/*.js', 'src/*.js'],
|
||||
|
||||
common: ['src/common/**/*.js'],
|
||||
controllers: ['src/**/*.controller.js'],
|
||||
|
||||
specs: ['test/**/*.spec.js'],
|
||||
scenarios: ['test/**/*.scenario.js'],
|
||||
samples: ['sample files/*.js'],
|
||||
html: ['src/index.html', 'src/install.html'],
|
||||
|
||||
everything: ['src/**/*.*', 'test/**/*.*', 'docs/**/*.*'],
|
||||
|
||||
tpl: {
|
||||
app: ['src/views/**/*.html'],
|
||||
common: ['src/common/**/*.tpl.html']
|
||||
},
|
||||
less: ['src/less/belle.less'], // recess:build doesn't accept ** in its file patterns
|
||||
prod: ['<%= distdir %>/js/*.js']
|
||||
},
|
||||
|
||||
clean: {
|
||||
pre: ['<%= distdir %>/*'],
|
||||
post: ['<%= distdir %>/js/*.dev.js']
|
||||
},
|
||||
|
||||
copy: {
|
||||
assets: {
|
||||
files: [{ dest: '<%= distdir %>/assets', src: '**', expand: true, cwd: 'src/assets/' }]
|
||||
},
|
||||
|
||||
config: {
|
||||
files: [{ dest: '<%= distdir %>/../config', src: '**', expand: true, cwd: 'src/config/' }]
|
||||
},
|
||||
|
||||
installer: {
|
||||
files: [{ dest: '<%= distdir %>/views/install', src: '**/*.html', expand: true, cwd: 'src/installer/steps' }]
|
||||
},
|
||||
|
||||
canvasdesigner: {
|
||||
files: [
|
||||
{ dest: '<%= distdir %>/preview', src: '**/*.html', expand: true, cwd: 'src/canvasdesigner' },
|
||||
{ dest: '<%= distdir %>/preview/editors', src: '**/*.html', expand: true, cwd: 'src/canvasdesigner/editors' },
|
||||
{ dest: '<%= distdir %>/assets/less', src: '**/*.less', expand: true, cwd: 'src/canvasdesigner/editors' },
|
||||
{ dest: '<%= distdir %>/js', src: 'canvasdesigner.config.js', expand: true, cwd: 'src/canvasdesigner/config' },
|
||||
{ dest: '<%= distdir %>/js', src: 'canvasdesigner.palettes.js', expand: true, cwd: 'src/canvasdesigner/config' },
|
||||
{ dest: '<%= distdir %>/js', src: 'canvasdesigner.front.js', expand: true, cwd: 'src/canvasdesigner' }
|
||||
]
|
||||
},
|
||||
|
||||
vendor: {
|
||||
files: [{ dest: '<%= distdir %>/lib', src: '**', expand: true, cwd: 'lib/' }]
|
||||
},
|
||||
|
||||
views: {
|
||||
files: [{ dest: '<%= distdir %>/views', src: ['**/*.*', '!**/*.controller.js'], expand: true, cwd: 'src/views' }]
|
||||
},
|
||||
|
||||
app: {
|
||||
files: [
|
||||
{ dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/' }
|
||||
]
|
||||
},
|
||||
|
||||
mocks: {
|
||||
files: [{ dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/common/mocks/' }]
|
||||
},
|
||||
|
||||
vs: {
|
||||
files: [
|
||||
//everything except the index.html root file!
|
||||
//then we need to figure out how to not copy all the test stuff either!?
|
||||
{ dest: '<%= vsdir %>/assets', src: '**', expand: true, cwd: '<%= distdir %>/assets' },
|
||||
{ dest: '<%= vsdir %>/js', src: '**', expand: true, cwd: '<%= distdir %>/js' },
|
||||
{ dest: '<%= vsdir %>/views', src: '**', expand: true, cwd: '<%= distdir %>/views' },
|
||||
{ dest: '<%= vsdir %>/preview', src: '**', expand: true, cwd: '<%= distdir %>/preview' },
|
||||
{ dest: '<%= vsdir %>/lib', src: '**', expand: true, cwd: '<%= distdir %>/lib' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
karma: {
|
||||
unit: { configFile: 'test/config/karma.conf.js', keepalive: true },
|
||||
e2e: { configFile: 'test/config/e2e.js', keepalive: true },
|
||||
watch: { configFile: 'test/config/unit.js', singleRun: false, autoWatch: true, keepalive: true }
|
||||
},
|
||||
|
||||
concat: {
|
||||
index: {
|
||||
src: ['src/index.html'],
|
||||
dest: '<%= distdir %>/index.html',
|
||||
options: {
|
||||
process: true
|
||||
}
|
||||
},
|
||||
install: {
|
||||
src: ['src/installer/installer.html'],
|
||||
dest: '<%= distdir %>/installer.html',
|
||||
options: {
|
||||
process: true
|
||||
}
|
||||
},
|
||||
|
||||
installJs: {
|
||||
src: ['src/installer/**/*.js'],
|
||||
dest: '<%= distdir %>/js/umbraco.installer.js',
|
||||
options: {
|
||||
banner: "<%= banner %>\n(function() { \n\n",
|
||||
footer: "\n\n})();"
|
||||
}
|
||||
},
|
||||
|
||||
canvasdesignerJs: {
|
||||
src: ['src/canvasdesigner/canvasdesigner.global.js', 'src/canvasdesigner/canvasdesigner.controller.js', 'src/canvasdesigner/editors/*.js', 'src/canvasdesigner/lib/*.js'],
|
||||
dest: '<%= distdir %>/js/canvasdesigner.panel.js'
|
||||
},
|
||||
|
||||
controllers: {
|
||||
src: ['src/controllers/**/*.controller.js', 'src/views/**/*.controller.js'],
|
||||
dest: '<%= distdir %>/js/umbraco.controllers.js',
|
||||
options: {
|
||||
banner: "<%= banner %>\n(function() { \n\n",
|
||||
footer: "\n\n})();"
|
||||
}
|
||||
},
|
||||
|
||||
services: {
|
||||
src: ['src/common/services/*.js'],
|
||||
dest: '<%= distdir %>/js/umbraco.services.js',
|
||||
options: {
|
||||
banner: "<%= banner %>\n(function() { \n\n",
|
||||
footer: "\n\n})();"
|
||||
}
|
||||
},
|
||||
|
||||
security: {
|
||||
src: ['src/common/security/*.js'],
|
||||
dest: '<%= distdir %>/js/umbraco.security.js',
|
||||
options: {
|
||||
banner: "<%= banner %>\n(function() { \n\n",
|
||||
footer: "\n\n})();"
|
||||
}
|
||||
},
|
||||
|
||||
resources: {
|
||||
src: ['src/common/resources/*.js'],
|
||||
dest: '<%= distdir %>/js/umbraco.resources.js',
|
||||
options: {
|
||||
banner: "<%= banner %>\n(function() { \n\n",
|
||||
footer: "\n\n})();"
|
||||
}
|
||||
},
|
||||
|
||||
testing: {
|
||||
src: ['src/common/mocks/*/*.js'],
|
||||
dest: '<%= distdir %>/js/umbraco.testing.js',
|
||||
options: {
|
||||
banner: "<%= banner %>\n(function() { \n\n",
|
||||
footer: "\n\n})();"
|
||||
}
|
||||
},
|
||||
|
||||
directives: {
|
||||
src: ['src/common/directives/**/*.js'],
|
||||
dest: '<%= distdir %>/js/umbraco.directives.js',
|
||||
options: {
|
||||
banner: "<%= banner %>\n(function() { \n\n",
|
||||
footer: "\n\n})();"
|
||||
}
|
||||
},
|
||||
|
||||
filters: {
|
||||
src: ['src/common/filters/*.js'],
|
||||
dest: '<%= distdir %>/js/umbraco.filters.js',
|
||||
options: {
|
||||
banner: "<%= banner %>\n(function() { \n\n",
|
||||
footer: "\n\n})();"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
uglify: {
|
||||
options: {
|
||||
mangle: true
|
||||
},
|
||||
combine: {
|
||||
files: {
|
||||
'<%= distdir %>/js/umbraco.min.js': ['<%= distdir %>/js/umbraco.*.js']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
recess: {
|
||||
build: {
|
||||
files: {
|
||||
'<%= distdir %>/assets/css/<%= pkg.name %>.css':
|
||||
['<%= src.less %>']
|
||||
},
|
||||
options: {
|
||||
compile: true,
|
||||
compress: true
|
||||
}
|
||||
},
|
||||
nonodes: {
|
||||
files: {
|
||||
'<%= distdir %>/assets/css/nonodes.style.min.css':
|
||||
['src/less/pages/nonodes.less']
|
||||
},
|
||||
options: {
|
||||
compile: true,
|
||||
compress: true
|
||||
}
|
||||
},
|
||||
installer: {
|
||||
files: {
|
||||
'<%= distdir %>/assets/css/installer.css':
|
||||
['src/less/installer.less']
|
||||
},
|
||||
options: {
|
||||
compile: true,
|
||||
compress: true
|
||||
}
|
||||
},
|
||||
canvasdesigner: {
|
||||
files: {
|
||||
'<%= distdir %>/assets/css/canvasdesigner.css':
|
||||
['src/less/canvas-designer.less', 'src/less/helveticons.less']
|
||||
},
|
||||
options: {
|
||||
compile: true,
|
||||
compress: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
postcss: {
|
||||
options: {
|
||||
processors: [
|
||||
// add vendor prefixes
|
||||
require('autoprefixer-core')({
|
||||
browsers: 'last 2 versions'
|
||||
})
|
||||
]
|
||||
},
|
||||
dist: {
|
||||
src: '<%= distdir %>/assets/css/<%= pkg.name %>.css'
|
||||
}
|
||||
},
|
||||
|
||||
ngTemplateCache: {
|
||||
views: {
|
||||
files: {
|
||||
'<%= distdir %>/js/umbraco.views.js': 'src/views/**/*.html'
|
||||
},
|
||||
options: {
|
||||
trim: 'src/',
|
||||
module: 'umbraco.views'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
docs: {
|
||||
files: ['docs/src/**/*.md'],
|
||||
tasks: ['watch-docs', 'timestamp']
|
||||
},
|
||||
css: {
|
||||
files: 'src/**/*.less',
|
||||
tasks: ['watch-less', 'timestamp'],
|
||||
options: {
|
||||
livereload: true,
|
||||
},
|
||||
},
|
||||
js: {
|
||||
files: ['src/**/*.js', 'src/*.js'],
|
||||
tasks: ['watch-js', 'timestamp'],
|
||||
},
|
||||
test: {
|
||||
files: ['test/**/*.js'],
|
||||
tasks: ['watch-test', 'timestamp'],
|
||||
},
|
||||
installer: {
|
||||
files: ['src/installer/**/*.*'],
|
||||
tasks: ['watch-installer', 'timestamp'],
|
||||
},
|
||||
canvasdesigner: {
|
||||
files: ['src/canvasdesigner/**/*.*'],
|
||||
tasks: ['watch-canvasdesigner', 'timestamp'],
|
||||
},
|
||||
html: {
|
||||
files: ['src/views/**/*.html', 'src/*.html'],
|
||||
tasks: ['watch-html', 'timestamp']
|
||||
},
|
||||
options: {
|
||||
interval: 500
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
ngdocs: {
|
||||
options: {
|
||||
dest: 'docs/api',
|
||||
startPage: '/api',
|
||||
title: "Umbraco Backoffice UI API Documentation",
|
||||
html5Mode: false,
|
||||
styles: [
|
||||
'docs/umb-docs.css'
|
||||
],
|
||||
image: "https://our.umbraco.org/assets/images/logo.svg"
|
||||
},
|
||||
api: {
|
||||
src: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'],
|
||||
title: 'API Documentation'
|
||||
},
|
||||
tutorials: {
|
||||
src: [],
|
||||
title: ''
|
||||
}
|
||||
},
|
||||
|
||||
eslint:{
|
||||
src: ['<%= src.common %>','<%= src.controllers %>'],
|
||||
options: {quiet: true}
|
||||
},
|
||||
|
||||
jshint: {
|
||||
dev: {
|
||||
files: {
|
||||
src: ['<%= src.common %>']
|
||||
},
|
||||
options: {
|
||||
curly: true,
|
||||
eqeqeq: true,
|
||||
immed: true,
|
||||
latedef: "nofunc",
|
||||
newcap: true,
|
||||
noarg: true,
|
||||
sub: true,
|
||||
boss: true,
|
||||
//NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete
|
||||
es5: true,
|
||||
eqnull: true,
|
||||
//NOTE: we need to use eval sometimes so ignore it
|
||||
evil: true,
|
||||
//NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those
|
||||
scripturl: true,
|
||||
//NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used
|
||||
smarttabs: true,
|
||||
globals: {}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
files: {
|
||||
src: ['<%= src.prod %>']
|
||||
},
|
||||
options: {
|
||||
curly: true,
|
||||
eqeqeq: true,
|
||||
immed: true,
|
||||
latedef: "nofunc",
|
||||
newcap: true,
|
||||
noarg: true,
|
||||
sub: true,
|
||||
boss: true,
|
||||
//NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete
|
||||
es5: true,
|
||||
eqnull: true,
|
||||
//NOTE: we need to use eval sometimes so ignore it
|
||||
evil: true,
|
||||
//NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those
|
||||
scripturl: true,
|
||||
//NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used
|
||||
smarttabs: true,
|
||||
globalstrict: true,
|
||||
globals: { $: false, jQuery: false, define: false, require: false, window: false }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
bower: {
|
||||
dev: {
|
||||
dest: '<%= distdir %>/lib',
|
||||
options: {
|
||||
expand: true,
|
||||
ignorePackages: ['bootstrap'],
|
||||
packageSpecific: {
|
||||
'moment': {
|
||||
keepExpandedHierarchy: false,
|
||||
files: ['min/moment-with-locales.js']
|
||||
},
|
||||
'typeahead.js': {
|
||||
keepExpandedHierarchy: false,
|
||||
files: ['dist/typeahead.bundle.min.js']
|
||||
},
|
||||
'underscore': {
|
||||
files: ['underscore-min.js', 'underscore-min.map']
|
||||
},
|
||||
'rgrove-lazyload': {
|
||||
files: ['lazyload.js']
|
||||
},
|
||||
'bootstrap-social': {
|
||||
files: ['bootstrap-social.css']
|
||||
},
|
||||
'font-awesome': {
|
||||
files: ['css/font-awesome.min.css', 'fonts/*']
|
||||
},
|
||||
"jquery": {
|
||||
keepExpandedHierarchy: false,
|
||||
files: ['dist/jquery.min.js', 'dist/jquery.min.map']
|
||||
},
|
||||
'jquery-ui': {
|
||||
keepExpandedHierarchy: false,
|
||||
files: ['jquery-ui.min.js']
|
||||
},
|
||||
'jquery-migrate': {
|
||||
keepExpandedHierarchy: false,
|
||||
files: ['jquery-migrate.min.js']
|
||||
},
|
||||
'tinymce': {
|
||||
files: ['plugins/**', 'themes/**', 'tinymce.min.js']
|
||||
},
|
||||
'angular-dynamic-locale': {
|
||||
files: ['tmhDynamicLocale.min.js', 'tmhDynamicLocale.min.js.map']
|
||||
},
|
||||
'ng-file-upload': {
|
||||
keepExpandedHierarchy: false,
|
||||
files: ['ng-file-upload.min.js']
|
||||
},
|
||||
'angular-local-storage': {
|
||||
keepExpandedHierarchy: false,
|
||||
files: ['dist/angular-local-storage.min.js']
|
||||
},
|
||||
'codemirror': {
|
||||
files: [
|
||||
'lib/codemirror.js',
|
||||
'lib/codemirror.css',
|
||||
|
||||
'mode/css/*',
|
||||
'mode/javascript/*',
|
||||
'mode/xml/*',
|
||||
'mode/htmlmixed/*',
|
||||
|
||||
'addon/search/*',
|
||||
'addon/edit/*',
|
||||
'addon/selection/*',
|
||||
'addon/dialog/*'
|
||||
]
|
||||
},
|
||||
'ace-builds': {
|
||||
files: [
|
||||
'src-min-noconflict/ace.js',
|
||||
|
||||
'src-min-noconflict/ext-language_tools.js',
|
||||
'src-min-noconflict/ext-searchbox.js',
|
||||
'src-min-noconflict/ext-settings_menu.js',
|
||||
|
||||
'src-min-noconflict/snippets/text.js',
|
||||
'src-min-noconflict/snippets/javascript.js',
|
||||
|
||||
'src-min-noconflict/theme-chrome.js',
|
||||
|
||||
'src-min-noconflict/mode-razor.js',
|
||||
'src-min-noconflict/mode-javascript.js',
|
||||
|
||||
'src-min-noconflict/worker-javascript.js',
|
||||
]
|
||||
},
|
||||
'clipboard': {
|
||||
keepExpandedHierarchy: false,
|
||||
files: ['dist/clipboard.min.js']
|
||||
},
|
||||
'angular-moment': {
|
||||
keepExpandedHierarchy: false,
|
||||
files: ['angular-moment.min.js']
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {
|
||||
expand: true
|
||||
}
|
||||
},
|
||||
|
||||
"bower-install-simple": {
|
||||
options: {
|
||||
color: true
|
||||
},
|
||||
"dev": {}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-recess');
|
||||
grunt.loadNpmTasks('grunt-postcss');
|
||||
|
||||
grunt.loadNpmTasks('grunt-karma');
|
||||
|
||||
grunt.loadNpmTasks('grunt-open');
|
||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
||||
grunt.loadNpmTasks("grunt-bower-install-simple");
|
||||
grunt.loadNpmTasks('grunt-bower');
|
||||
grunt.loadNpmTasks('grunt-ngdocs');
|
||||
|
||||
grunt.loadNpmTasks('grunt-eslint');
|
||||
grunt.loadNpmTasks('grunt-hustler');
|
||||
};
|
||||
@@ -31,17 +31,15 @@
|
||||
display: block;
|
||||
padding: 4px;
|
||||
line-height: @baseLineHeight;
|
||||
border: 1px solid @gray-8;
|
||||
border: 1px solid #ddd;
|
||||
.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,
|
||||
a div.thumbnail:hover,
|
||||
a div.thumbnail:focus {
|
||||
border-color: @turquoise;
|
||||
// Add a hover/focus state for linked versions only
|
||||
a.thumbnail:hover,
|
||||
a.thumbnail:focus {
|
||||
border-color: @linkColor;
|
||||
.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",
|
||||
|
||||
@@ -11,7 +11,8 @@ var app = angular.module('umbraco', [
|
||||
'ngMobile',
|
||||
'tmh.dynamicLocale',
|
||||
'ngFileUpload',
|
||||
'LocalStorageModule'
|
||||
'LocalStorageModule',
|
||||
'angularMoment'
|
||||
]);
|
||||
|
||||
var packages = angular.module("umbraco.packages", []);
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 310 KiB After Width: | Height: | Size: 440 KiB |
+116
@@ -0,0 +1,116 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function BackdropDirective($timeout, $http) {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
var events = [];
|
||||
|
||||
scope.clickBackdrop = function(event) {
|
||||
if(scope.disableEventsOnClick === true) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
function onInit() {
|
||||
|
||||
if (scope.highlightElement) {
|
||||
setHighlight();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setHighlight () {
|
||||
|
||||
scope.loading = true;
|
||||
|
||||
$timeout(function () {
|
||||
|
||||
// The element to highlight
|
||||
var highlightElement = angular.element(scope.highlightElement);
|
||||
|
||||
if(highlightElement && highlightElement.length > 0) {
|
||||
|
||||
var offset = highlightElement.offset();
|
||||
var width = highlightElement.outerWidth();
|
||||
var height = highlightElement.outerHeight();
|
||||
|
||||
// Rounding numbers
|
||||
var topDistance = offset.top.toFixed();
|
||||
var topAndHeight = (offset.top + height).toFixed();
|
||||
var leftDistance = offset.left.toFixed();
|
||||
var leftAndWidth = (offset.left + width).toFixed();
|
||||
|
||||
// The four rectangles
|
||||
var rectTop = el.find(".umb-backdrop__rect--top");
|
||||
var rectRight = el.find(".umb-backdrop__rect--right");
|
||||
var rectBottom = el.find(".umb-backdrop__rect--bottom");
|
||||
var rectLeft = el.find(".umb-backdrop__rect--left");
|
||||
|
||||
// Add the css
|
||||
scope.rectTopCss = { "height": topDistance, "left": leftDistance + "px", opacity: scope.backdropOpacity };
|
||||
scope.rectRightCss = { "left": leftAndWidth + "px", "top": topDistance + "px", "height": height, opacity: scope.backdropOpacity };
|
||||
scope.rectBottomCss = { "height": "100%", "top": topAndHeight + "px", "left": leftDistance + "px", opacity: scope.backdropOpacity };
|
||||
scope.rectLeftCss = { "width": leftDistance, opacity: scope.backdropOpacity };
|
||||
|
||||
// Prevent interaction in the highlighted area
|
||||
if(scope.highlightPreventClick) {
|
||||
var preventClickElement = el.find(".umb-backdrop__highlight-prevent-click");
|
||||
preventClickElement.css({ "width": width, "height": height, "left": offset.left, "top": offset.top });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
scope.loading = false;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function resize() {
|
||||
setHighlight();
|
||||
}
|
||||
|
||||
events.push(scope.$watch("highlightElement", function (newValue, oldValue) {
|
||||
if(!newValue) {return;}
|
||||
if(newValue === oldValue) {return;}
|
||||
setHighlight();
|
||||
}));
|
||||
|
||||
$(window).on("resize.umbBackdrop", resize);
|
||||
|
||||
scope.$on("$destroy", function () {
|
||||
// unbind watchers
|
||||
for (var e in events) {
|
||||
events[e]();
|
||||
}
|
||||
$(window).off("resize.umbBackdrop");
|
||||
});
|
||||
|
||||
onInit();
|
||||
|
||||
}
|
||||
|
||||
var directive = {
|
||||
transclude: true,
|
||||
restrict: "E",
|
||||
replace: true,
|
||||
templateUrl: "views/components/application/umb-backdrop.html",
|
||||
link: link,
|
||||
scope: {
|
||||
backdropOpacity: "=?",
|
||||
highlightElement: "=?",
|
||||
highlightPreventClick: "=?",
|
||||
disableEventsOnClick: "=?",
|
||||
}
|
||||
};
|
||||
|
||||
return directive;
|
||||
|
||||
}
|
||||
|
||||
angular.module("umbraco.directives").directive("umbBackdrop", BackdropDirective);
|
||||
|
||||
})();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user