Compare commits

..

38 Commits

Author SHA1 Message Date
Sebastiaan Jansssen b6b7b9f433 Bump version to 7.7.7 2017-12-11 13:57:31 +01:00
Mikkel Holck Madsen fb99b101d8 Merge pull request #2340 from umbraco/temp-U4-10610
U4-10610 Cannot restore content templates
2017-12-05 13:47:11 +01:00
Claus 0ce7efc978 U4-10610 Cannot restore content templates 2017-12-05 10:19:12 +01:00
Mikkel Holck Madsen 11bdfbba19 Merge pull request #2338 from umbraco/temp-u4-10715
U4-10715 - scheduled publishing and LB issue
2017-11-30 10:57:12 +01:00
Stephan 9694831638 U4-10715 - scheduled publishing and LB issue 2017-11-30 08:54:16 +01:00
bjarnef 8d14a3ef2c added number validation in embed overlay 2017-11-20 12:36:12 +01:00
bjarnef 2f3d9c9509 localize texts 2017-11-20 12:32:09 +01:00
Robert 6e8f3b6e13 Merge pull request #2324 from umbraco/temp-UAASSSCRUM-1254
Added custom RollingFileAppender and changed rollingStyle to "size"
2017-11-17 13:03:48 +01:00
mikkelhm b9cbdbd9a7 Add rename of Appender to csproj 2017-11-17 12:01:32 +01:00
mikkelhm ebf35356a8 Updates the CustomRollingFileAppender to read config settings to control it 2017-11-17 12:00:42 +01:00
Sebastiaan Janssen 7eca99e249 Bump version to 7.7.6 2017-11-15 15:21:25 +01:00
Sebastiaan Janssen 837bb83d48 Merge remote-tracking branch 'origin/dev-v7.6' into dev-v7 2017-11-15 14:40:19 +01:00
Sebastiaan Janssen 054ac78970 Revert "Position changed from "target" to "center""
This reverts commit c1769aa299.
2017-11-15 14:39:09 +01:00
Robert 5982a868ea Added custom RollingFileAppender and changed rollingStyle to "size"
OpenFile method has been overiden, this means that everytime a log will be opened, it will check the last access time of all existing logs, and delete the ones that haven't been accessed in more than 1 month.

Changing the rollingStyle to "size" will make sure that logs will roll once the maximumFileSize reaches its set limit.
2017-11-14 12:01:11 +01:00
Sebastiaan Janssen 69a42e26a9 Merge remote-tracking branch 'origin/dev-v7.6' into dev-v7 2017-11-14 11:42:00 +01:00
Sebastiaan Janssen d9ed420ed4 U4-10492 Users failing to list with MySQL 2017-11-14 11:38:45 +01:00
Sebastiaan Janssen 2b3397120f Disambiguate events by explicitly naming them 2017-11-14 11:23:48 +01:00
Sebastiaan Janssen 1f41aa7d81 Merge branch 'dev-v7.6' into dev-v7
# Conflicts:
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoVersion.cs
#	src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
#	src/Umbraco.Web.UI/packages.config
2017-11-14 07:19:08 +01:00
Sebastiaan Janssen dad77fc506 Bump version to 7.7.5 2017-11-14 07:17:19 +01:00
Sebastiaan Janssen 350f5c88c7 Bump version to 7.6.12 2017-11-14 07:16:14 +01:00
Stephan 928c0dc535 Merge pull request #2314 from umbraco/temp-U4-10646
U4-10646 Sorting nodes in edge cases can lead to blanking out of property data
2017-11-13 17:33:27 +01:00
Sebastiaan Janssen cc7a28db65 Update to latest ImageProcessor 2017-11-13 15:39:29 +01:00
Sebastiaan Janssen 065e764957 Update to latest Examine 2017-11-13 15:39:28 +01:00
Sebastiaan Janssen 000ba73887 Merge branch 'dev-v7.6' into dev-v7
# Conflicts:
#	build/NuSpecs/UmbracoCms.Core.nuspec
#	src/Umbraco.Tests/Umbraco.Tests.csproj
#	src/Umbraco.Tests/packages.config
#	src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
#	src/Umbraco.Web.UI/packages.config
#	src/Umbraco.Web/Umbraco.Web.csproj
#	src/Umbraco.Web/packages.config
#	src/UmbracoExamine/UmbracoExamine.csproj
#	src/UmbracoExamine/packages.config
#	src/umbraco.MacroEngines/packages.config
#	src/umbraco.MacroEngines/umbraco.MacroEngines.csproj
2017-11-13 15:38:59 +01:00
Sebastiaan Janssen 08b81d991b Update to latest ImageProcessor 2017-11-13 15:33:54 +01:00
Sebastiaan Janssen 4937f87c0d Update to latest Examine 2017-11-13 15:27:15 +01:00
Claus dc67d82671 adding trim. 2017-11-08 12:08:30 +01:00
Claus c7d9683fc4 Merge pull request #2277 from umbraco/temp-U4-10589
U4-10589 Login should be sanitized and/or cause validation errors if trying to save with
2017-11-08 12:07:34 +01:00
Mads Rasmussen ed7675bdcd Merge pull request #1584 from readingdancer/U4-9144
Style changes to the developer dashboard area.
2017-11-08 11:49:48 +01:00
Mads Rasmussen 3c7c6b4ad7 Update thumbnails.less
Use new color variables
2017-11-08 11:38:32 +01:00
Mads Rasmussen 838d7975c9 Update healthcheck.less
prevent UI from "jumping" on hover
2017-11-08 11:36:23 +01:00
Mads Rasmussen 5af9712d51 Merge branch 'dev-v7' into U4-9144 2017-11-08 10:43:58 +01:00
Sebastiaan Janssen 579c572dd8 Oops, missed that I was passing a string array into the int array param 2017-11-07 18:36:37 +01:00
Sebastiaan Janssen bbf4c18825 U4-10646 Sorting nodes in edge cases can lead to blanking out of property data 2017-11-07 17:12:04 +01:00
Robert 73b2d31298 Moved username trimming to service level, also added email trimming 2017-11-01 13:08:17 +01:00
Robert 8676a99b3a Trimming login input on the server-side as well 2017-11-01 09:00:05 +01:00
Robert 0b2cfe956d Triming login field to avoid validation errors 2017-10-31 15:19:06 +01:00
Chris Houston a4f83e0125 Style changes to the developer dashboard area. 2016-11-03 14:41:41 +00:00
290 changed files with 1573 additions and 7828 deletions
+3 -2
View File
@@ -27,18 +27,19 @@
<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.85, 1.0.0)" />
<dependency id="Examine" version="[0.1.88, 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>
+2 -2
View File
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.8.0")]
[assembly: AssemblyInformationalVersion("7.8.0-beta")]
[assembly: AssemblyFileVersion("7.7.7")]
[assembly: AssemblyInformationalVersion("7.7.7")]
-47
View File
@@ -1,7 +1,6 @@
using System;
using System.Configuration;
using System.Threading;
using Semver;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.ObjectResolution;
@@ -342,52 +341,6 @@ 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)
@@ -1,18 +0,0 @@
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; }
}
}
}
@@ -1,7 +0,0 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface IBackOfficeSection
{
ITourSection Tours { get; }
}
}
@@ -1,7 +0,0 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface ITourSection
{
bool EnableTours { get; }
}
}
@@ -5,8 +5,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface IUmbracoSettingsSection : IUmbracoConfigurationSection
{
IBackOfficeSection BackOffice { get; }
IContentSection Content { get; }
ISecuritySection Security { get; }
@@ -1,17 +0,0 @@
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,12 +8,6 @@ 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
{
@@ -155,11 +149,6 @@ 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.8.0");
private static readonly Version Version = new Version("7.7.7");
/// <summary>
/// Gets the current version of Umbraco.
+2 -2
View File
@@ -26,13 +26,13 @@ namespace Umbraco.Core
var ipAddress = httpContext.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (string.IsNullOrEmpty(ipAddress))
return httpContext.Request.UserHostAddress;
return httpContext.Request.ServerVariables["REMOTE_ADDR"];
var addresses = ipAddress.Split(',');
if (addresses.Length != 0)
return addresses[0];
return httpContext.Request.UserHostAddress;
return httpContext.Request.ServerVariables["REMOTE_ADDR"];
}
catch (System.Exception ex)
{
@@ -0,0 +1,95 @@
using System;
using System.IO;
using log4net.Appender;
using log4net.Util;
namespace Umbraco.Core.Logging
{
/// <summary>
/// This class will do the exact same thing as the RollingFileAppender that comes from log4net
/// With the extension, that it is able to do automatic cleanup of the logfiles in the directory where logging happens
///
/// By specifying the properties MaxLogFileDays and BaseFilePattern, the files will automaticly get deleted when
/// the logger is configured(typically when the app starts). To utilize this appender swap out the type of the rollingFile appender
/// that ships with Umbraco, to be Umbraco.Core.Logging.RollingFileCleanupAppender, and add the maxLogFileDays and baseFilePattern elements
/// to the configuration i.e.:
///
/// <example>
/// <appender name="rollingFile" type="Log4netAwesomeness.CustomRollingFileAppender, Log4netAwesomeness">
/// <file type="log4net.Util.PatternString" value="App_Data\Logs\UmbracoTraceLog.%property{log4net:HostName}.txt" />
/// <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
/// <appendToFile value="true" />
/// <rollingStyle value="Date" />
/// <maximumFileSize value="5MB" />
/// <maxLogFileDays value="5"/>
/// <basefilePattern value="UmbracoTraceLog.*.txt.*"/>
/// <layout type="log4net.Layout.PatternLayout">
/// <conversionPattern value=" %date [P%property{processId}/D%property{appDomainId}/T%thread] %-5level %logger - %message%newline" />
/// </layout>
/// <layout type="log4net.Layout.PatternLayout">
/// <conversionPattern value=" %date [P%property{processId}/D%property{appDomainId}/T%thread] %-5level %logger - %message%newline" />
/// </layout>
/// <encoding value="utf-8" />
/// </appender>
/// </example>
/// </summary>
public class RollingFileCleanupAppender : RollingFileAppender
{
public int MaxLogFileDays { get; set; }
public string BaseFilePattern { get; set; }
/// <summary>
/// This override will delete logs older than the specified amount of days
/// </summary>
/// <param name="fileName"></param>
/// <param name="append"></param>
protected override void OpenFile(string fileName, bool append)
{
bool cleanup = true;
// Validate settings and input
if (MaxLogFileDays <= 0)
{
LogLog.Warn(typeof(RollingFileCleanupAppender), "Parameter 'MaxLogFileDays' needs to be a positive integer, aborting cleanup");
cleanup = false;
}
if (string.IsNullOrWhiteSpace(BaseFilePattern))
{
LogLog.Warn(typeof(RollingFileCleanupAppender), "Parameter 'BaseFilePattern' is empty, aborting cleanup");
cleanup = false;
}
// grab the directory we are logging to, as this is were we will search for older logfiles
var logFolder = Path.GetDirectoryName(fileName);
if (Directory.Exists(logFolder) == false)
{
LogLog.Warn(typeof(RollingFileCleanupAppender), string.Format("Directory '{0}' for logfiles does not exist, aborting cleanup", logFolder));
cleanup = false;
}
// If everything is validated, we can do the actual cleanup
if (cleanup)
{
Cleanup(logFolder);
}
base.OpenFile(fileName, append);
}
private void Cleanup(string directoryPath)
{
// only take files that matches the pattern we are using i.e. UmbracoTraceLog.*.txt.*
string[] logFiles = Directory.GetFiles(directoryPath, BaseFilePattern);
LogLog.Debug(typeof(RollingFileCleanupAppender), string.Format("Found {0} files that matches the baseFilePattern: '{1}'", logFiles.Length, BaseFilePattern));
foreach (var logFile in logFiles)
{
DateTime lastAccessTime = System.IO.File.GetLastWriteTimeUtc(logFile);
// take the value from the config file
if (lastAccessTime < DateTime.Now.AddDays(-MaxLogFileDays))
{
LogLog.Debug(typeof(RollingFileCleanupAppender), string.Format("Deleting file {0} as its lastAccessTime is older than {1} days speficied by MaxLogFileDays", logFile, MaxLogFileDays));
base.DeleteFile(logFile);
}
}
}
}
}
+1 -13
View File
@@ -2,30 +2,18 @@
namespace Umbraco.Core.Models
{
public sealed class AuditItem : Entity, IAuditItem
public sealed class AuditItem : Entity, IAggregateRoot
{
/// <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; }
}
}
-12
View File
@@ -1,12 +0,0 @@
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,6 +40,19 @@ 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)
+1 -6
View File
@@ -69,11 +69,6 @@ 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; }
/// <summary>
/// A Json blob stored for recording tour data for a user
/// </summary>
string TourData { get; set; }
string Avatar { get; set; }
}
}
+1 -13
View File
@@ -99,7 +99,6 @@ 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;
@@ -133,7 +132,6 @@ 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);
@@ -466,18 +464,8 @@ 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,10 +27,5 @@ 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,5 +24,4 @@ namespace Umbraco.Core.Models.Rdbms
[ResultColumn]
public NodeDto NodeDto { get; set; }
}
}
+1 -1
View File
@@ -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; }
}
}
-29
View File
@@ -1,29 +0,0 @@
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,7 +27,6 @@ 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; }
+3 -11
View File
@@ -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,14 +103,6 @@ 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; }
@@ -1,52 +0,0 @@
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; }
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Models.Rdbms
{
[Obsolete("Table no longer exists as of 7.7 - retained only to support migrations from previous versions")]
[Obsolete("Table no longer exists as of 7.6 - retained only to support migrations from previous versions")]
[TableName("umbracoUserType")]
[PrimaryKey("id")]
[ExplicitColumns]
@@ -188,7 +188,7 @@ namespace Umbraco.Core.Models
Language,
/// <summary>
/// Document
/// Document Blueprint
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.DocumentBlueprint, typeof(IContent))]
[FriendlyName("DocumentBlueprint")]
+2 -1
View File
@@ -30,11 +30,12 @@ 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[] GetUserAvatarUrls(this IUser user, ICacheProvider staticCache)
internal static string[] GetCurrentUserAvatarUrls(this IUser user, IUserService userService, 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")
+38 -28
View File
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.IO.Compression;
using ICSharpCode.SharpZipLib.Zip;
namespace Umbraco.Core.Packaging
{
@@ -14,17 +14,17 @@ namespace Umbraco.Core.Packaging
bool fileFound = false;
string foundDir = null;
ReadZipfileEntries(packageFilePath, entry =>
ReadZipfileEntries(packageFilePath, (entry, stream) =>
{
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 entryStream = entry.Open())
using (var reader = new StreamReader(entryStream))
using (var reader = new StreamReader(stream))
{
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 =>
ReadZipfileEntries(packageFilePath, (entry, stream) =>
{
string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower();
if (fileName == string.Empty) { return true; }
@@ -86,9 +86,8 @@ 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())
{
entryStream.CopyTo(streamWriter);
stream.CopyTo(streamWriter);
}
d.Remove(fileName);
@@ -107,7 +106,7 @@ namespace Umbraco.Core.Packaging
{
var retVal = expectedFiles.ToList();
ReadZipfileEntries(packageFilePath, zipEntry =>
ReadZipfileEntries(packageFilePath, (zipEntry, stream) =>
{
string fileName = Path.GetFileName(zipEntry.Name);
@@ -126,7 +125,7 @@ namespace Umbraco.Core.Packaging
var dictionary = new Dictionary<string, List<string>>();
ReadZipfileEntries(packageFilePath, entry =>
ReadZipfileEntries(packageFilePath, (entry, stream) =>
{
string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower();
@@ -149,42 +148,53 @@ namespace Umbraco.Core.Packaging
{
CheckPackageExists(packageFilePath);
var files = new HashSet<string>(filesToGet.Select(f => f.ToLowerInvariant()));
var files = new HashSet<string>(filesToGet.Select(f => f.ToLower()));
using (var fs = File.OpenRead(packageFilePath))
using (var zipArchive = new ZipArchive(fs))
using (var fs = File.OpenRead(packageFilePath))
{
foreach (var zipEntry in zipArchive.Entries)
using (var zipInputStream = new ZipInputStream(fs))
{
if (zipEntry.Name.IsNullOrWhiteSpace() && zipEntry.FullName.EndsWith("/")) continue;
if (files.Contains(zipEntry.Name.ToLowerInvariant()))
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.GetNextEntry()) != null)
{
using (var memStream = new MemoryStream())
using (var entryStream = zipEntry.Open())
if (zipEntry.IsDirectory) continue;
if (files.Contains(zipEntry.Name))
{
entryStream.CopyTo(memStream);
memStream.Close();
yield return memStream.ToArray();
using (var memStream = new MemoryStream())
{
zipInputStream.CopyTo(memStream);
yield return memStream.ToArray();
memStream.Close();
}
}
}
zipInputStream.Close();
}
fs.Close();
}
}
private void ReadZipfileEntries(string packageFilePath, Func<ZipArchiveEntry, bool> entryFunc, bool skipsDirectories = true)
private void ReadZipfileEntries(string packageFilePath, Func<ZipEntry, ZipInputStream, bool> entryFunc, bool skipsDirectories = true)
{
CheckPackageExists(packageFilePath);
using (var fs = File.OpenRead(packageFilePath))
using (var zipArchive = new ZipArchive(fs))
{
foreach (var zipEntry in zipArchive.Entries)
using (var zipInputStream = new ZipInputStream(fs))
{
if (zipEntry.Name.IsNullOrWhiteSpace() && zipEntry.FullName.EndsWith("/") && skipsDirectories)
continue;
if (entryFunc(zipEntry) == false) break;
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.GetNextEntry()) != null)
{
if (zipEntry.IsDirectory && skipsDirectories) continue;
if (entryFunc(zipEntry, zipInputStream) == false) break;
}
zipInputStream.Close();
}
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,7 +5,6 @@ using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Factories
{
internal class MacroFactory
{
public IMacro BuildEntity(MacroDto dto)
@@ -1,6 +1,5 @@
using System;
using System.Globalization;
using System.Text.RegularExpressions;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
@@ -10,17 +9,20 @@ 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)
public MediaFactory(IMediaType contentType, Guid nodeObjectTypeId, int id)
{
_contentType = contentType;
_nodeObjectTypeId = nodeObjectTypeId;
_id = id;
}
public MediaFactory(Guid nodeObjectTypeId)
public MediaFactory(Guid nodeObjectTypeId, int id)
{
_nodeObjectTypeId = nodeObjectTypeId;
_id = id;
}
#region Implementation of IEntityFactory<IMedia,ContentVersionDto>
@@ -62,27 +64,15 @@ namespace Umbraco.Core.Persistence.Factories
return BuildEntity(dto, _contentType);
}
public MediaDto BuildDto(IMedia entity)
public ContentVersionDto BuildDto(IMedia 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
};
var dto = new ContentVersionDto
{
NodeId = entity.Id,
VersionDate = entity.UpdateDate,
VersionId = entity.Version,
ContentDto = BuildContentDto(entity)
};
return dto;
}
@@ -129,30 +119,5 @@ 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,7 +36,6 @@ 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
@@ -70,8 +69,7 @@ namespace Umbraco.Core.Persistence.Factories
UpdateDate = entity.UpdateDate,
Avatar = entity.Avatar,
EmailConfirmedDate = entity.EmailConfirmedDate,
InvitedDate = entity.InvitedDate,
TourData = entity.TourData
InvitedDate = entity.InvitedDate
};
foreach (var startNodeId in entity.StartContentIds)
@@ -1,47 +0,0 @@
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,28 +43,27 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
{3, typeof (ContentDto)},
{4, typeof (ContentVersionDto)},
{5, typeof (DocumentDto)},
{6, typeof (MediaDto)},
{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)},
{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)},
{28, typeof (TagDto)},
{29, typeof (TagRelationshipDto)},
@@ -92,7 +91,6 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
{52, typeof (UserGroup2NodePermissionDto) },
{53, typeof (UserGroup2AppDto) },
{54, typeof (UserStartNodeDto) },
{55, typeof (UserLoginDto)}
};
#endregion
@@ -148,12 +148,6 @@ 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;
}
@@ -1,101 +0,0 @@
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()
{
}
}
}
@@ -1,48 +0,0 @@
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");
}
}
}
@@ -1,32 +0,0 @@
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()
{
}
}
}
@@ -1,38 +0,0 @@
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()
{
}
}
}
@@ -1,29 +0,0 @@
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,5 +1,6 @@
using System;
using System.Data;
using System.Data.SqlClient;
using Umbraco.Core.Persistence.FaultHandling;
namespace Umbraco.Core.Persistence
@@ -1,12 +1,16 @@
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,12 +38,8 @@ namespace Umbraco.Core.Persistence
_tableDefinition = DefinitionFactory.GetTableDefinition(sqlSyntaxProvider, pd.type);
if (_tableDefinition == null) throw new InvalidOperationException("No table definition found for type " + pd.type);
//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();
_readerColumns = pd.Columns.Select(x => x.Value).ToArray();
_sqlSyntaxProvider = sqlSyntaxProvider;
_enumerator = dataSource.GetEnumerator();
_columnDefinitions = _tableDefinition.Columns.ToArray();
@@ -1,113 +1,29 @@
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, IAuditItem>, IAuditRepository
internal class AuditRepository : PetaPocoRepositoryBase<int, AuditItem>, IAuditRepository
{
public AuditRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
}
/// <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)
protected override void PersistNewItem(AuditItem entity)
{
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;
throw new NotImplementedException();
}
protected override void PersistUpdatedItem(IAuditItem entity)
#region Not Implemented - not needed
protected override void PersistUpdatedItem(AuditItem entity)
{
Database.Insert(new LogDto
{
@@ -119,40 +35,26 @@ 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();
@@ -169,33 +71,6 @@ 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,37 +1,11 @@
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, IAuditItem>
public interface IAuditRepository : IRepository<int, AuditItem>
{
/// <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,8 +9,6 @@ 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,12 +91,7 @@ namespace Umbraco.Core.Persistence.Repositories
IProfile GetProfile(string username);
IProfile GetProfile(int id);
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);
IDictionary<UserState, int> GetUserStates();
}
}
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Text;
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>
private Sql GetBaseQuery(BaseQueryType queryType, bool includeFilePaths)
protected override Sql GetBaseQuery(BaseQueryType queryType)
{
var sql = new Sql();
sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsContentVersion.contentId" : "*"))
@@ -95,26 +95,14 @@ 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);
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);
.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);
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);
@@ -140,7 +128,6 @@ 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",
@@ -380,7 +367,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);
var factory = new MediaFactory(NodeObjectTypeId, entity.Id);
var dto = factory.BuildDto(entity);
//NOTE Should the logic below have some kind of fallback for empty parent ids ?
@@ -393,7 +380,7 @@ namespace Umbraco.Core.Persistence.Repositories
var sortOrder = maxSortOrder + 1;
//Create the (base) node data - umbracoNode
var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto;
var nodeDto = dto.ContentDto.NodeDto;
nodeDto.Path = parent.Path;
nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture));
nodeDto.SortOrder = sortOrder;
@@ -428,21 +415,15 @@ namespace Umbraco.Core.Persistence.Repositories
entity.Level = level;
//Create the Content specific data - cmsContent
var contentDto = dto.ContentVersionDto.ContentDto;
var contentDto = dto.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);
@@ -490,14 +471,14 @@ namespace Umbraco.Core.Persistence.Repositories
entity.SortOrder = maxSortOrder + 1;
}
var factory = new MediaFactory(NodeObjectTypeId);
var factory = new MediaFactory(NodeObjectTypeId, entity.Id);
//Look up Content entry to get Primary for updating the DTO
var contentDto = Database.First<ContentDto>("WHERE nodeId = @Id", new { Id = entity.Id });
var contentDto = Database.SingleOrDefault<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.ContentVersionDto.ContentDto.NodeDto;
var nodeDto = dto.ContentDto.NodeDto;
nodeDto.ValidatePathWithException();
var o = Database.Update(nodeDto);
@@ -505,18 +486,15 @@ namespace Umbraco.Core.Persistence.Repositories
if (contentDto.ContentTypeId != entity.ContentTypeId)
{
//Create the Content specific data - cmsContent
var newContentDto = dto.ContentVersionDto.ContentDto;
var newContentDto = dto.ContentDto;
Database.Update(newContentDto);
}
//In order to update the ContentVersion we need to retrieve its primary key id
var contentVerDto = Database.First<ContentVersionDto>("WHERE VersionId = @Version", new { Version = entity.Version });
dto.ContentVersionDto.Id = contentVerDto.Id;
var contentVerDto = Database.SingleOrDefault<ContentVersionDto>("WHERE VersionId = @Version", new { Version = entity.Version });
dto.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
@@ -661,39 +639,7 @@ namespace Umbraco.Core.Persistence.Repositories
}
#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;
}
#endregion
/// <summary>
/// Gets paged media results
@@ -757,8 +703,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);
}
@@ -1,9 +1,14 @@
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;
@@ -48,16 +53,15 @@ 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,6 +4,7 @@ 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,7 +6,6 @@ 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;
@@ -188,95 +187,18 @@ 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, result[0].num},
{UserState.Active, result[1].num},
{UserState.Disabled, result[2].num},
{UserState.LockedOut, result[3].num},
{UserState.Invited, result[4].num}
{UserState.All, (int)result[0].num},
{UserState.Active, (int)result[1].num},
{UserState.Disabled, (int)result[2].num},
{UserState.LockedOut, (int)result[3].num},
{UserState.Invited, (int)result[4].num}
};
}
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();
@@ -479,8 +401,7 @@ ORDER BY colName";
{"updateDate", "UpdateDate"},
{"avatar", "Avatar"},
{"emailConfirmedDate", "EmailConfirmedDate"},
{"invitedDate", "InvitedDate"},
{"tourData", "TourData"}
{"invitedDate", "InvitedDate"}
};
//create list of properties that have changed
@@ -913,6 +834,14 @@ 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,14 +11,12 @@ 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
{
@@ -243,11 +241,8 @@ 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,
@@ -259,7 +254,14 @@ 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
@@ -329,23 +331,7 @@ 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,29 +2,16 @@
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;
}
@@ -37,9 +24,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,
//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
//set a new session id
new UserData
{
Id = user.Id,
@@ -50,22 +37,15 @@ namespace Umbraco.Core.Security
Roles = user.Roles.Select(x => x.RoleId).ToArray(),
StartContentNodes = user.CalculatedContentStartNodeIds,
StartMediaNodes = user.CalculatedMediaStartNodeIds,
SecurityStamp = user.SecurityStamp
SessionId = 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,83 +1,21 @@
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),
@@ -96,45 +34,21 @@ namespace Umbraco.Core.Security
}
/// <summary>
/// Ensures that the culture is set correctly for the current back office user and that the user's session token is valid
/// Ensures that the culture is set correctly for the current back office user
/// </summary>
/// <param name="context"/>
/// <returns/>
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)
public override Task ValidateIdentity(CookieValidateIdentityContext context)
{
var umbIdentity = context.Identity as UmbracoBackOfficeIdentity;
if (umbIdentity != null && umbIdentity.IsAuthenticated)
{
Thread.CurrentThread.CurrentCulture =
Thread.CurrentThread.CurrentUICulture =
UserCultures.GetOrAdd(umbIdentity.Culture, s => new CultureInfo(s));
new CultureInfo(umbIdentity.Culture);
}
}
/// <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>();
return base.ValidateIdentity(context);
}
}
}
@@ -223,9 +223,6 @@ 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,6 +152,7 @@ namespace Umbraco.Core.Security
//NOTE: This method is mostly here for backwards compat
base.InitUserManager(manager, membershipProvider, options.DataProtectionProvider, contentSectionConfig);
}
}
/// <summary>
@@ -168,7 +169,7 @@ namespace Umbraco.Core.Security
#region What we support do not currently
//TODO: We could support this - but a user claims will mostly just be what is in the auth cookie
//NOTE: Not sure if we really want/need to ever support this
public override bool SupportsUserClaim
{
get { return false; }
@@ -270,22 +271,6 @@ 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>
@@ -689,5 +674,4 @@ namespace Umbraco.Core.Security
return httpContext.GetCurrentRequestIpAddress();
}
}
}
@@ -26,13 +26,12 @@ namespace Umbraco.Core.Security
IUserRoleStore<BackOfficeIdentityUser, int>,
IUserSecurityStampStore<BackOfficeIdentityUser, int>,
IUserLockoutStore<BackOfficeIdentityUser, int>,
IUserTwoFactorStore<BackOfficeIdentityUser, int>,
IUserSessionStore<BackOfficeIdentityUser, int>
IUserTwoFactorStore<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;
@@ -754,15 +753,6 @@ 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);
}
}
}
@@ -1,17 +0,0 @@
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,5 +1,4 @@
using System;
using System.Web;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Umbraco.Core.Models.Identity;
@@ -8,37 +7,6 @@ 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>
@@ -1,118 +0,0 @@
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,7 +36,6 @@ 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);
@@ -67,9 +66,8 @@ 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
{
SecurityStamp = securityStamp,
var userData = new UserData(session)
{
SessionId = session,
AllowedApplications = allowedApps.ToArray(),
Culture = culture,
@@ -191,8 +189,7 @@ namespace Umbraco.Core.Security
Constants.Security.StartContentNodeIdClaimType,
Constants.Security.StartMediaNodeIdClaimType,
ClaimTypes.Locality,
Constants.Security.SessionIdClaimType,
Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType
Constants.Security.SessionIdClaimType
};
}
}
@@ -229,7 +226,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, SecurityStamp, ClaimValueTypes.String, Issuer, Issuer, this));
AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, SessionId, ClaimValueTypes.String, Issuer, Issuer, this));
}
}
@@ -310,11 +307,6 @@ namespace Umbraco.Core.Security
get { return UserData.SessionId; }
}
public string SecurityStamp
{
get { return UserData.SecurityStamp; }
}
public string[] Roles
{
get { return UserData.Roles; }
+4 -10
View File
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Security
/// Use this constructor to create/assign new UserData to the ticket
/// </summary>
/// <param name="sessionId">
/// The current sessionId for the user
/// The security stamp for the user
/// </param>
public UserData(string sessionId)
{
@@ -29,18 +29,12 @@ namespace Umbraco.Core.Security
Roles = new string[] { };
}
/// <summary>
/// This is the 'sessionId' for validation
/// </summary>
[DataMember(Name = "sessionId")]
public string SessionId { get; set; }
/// <summary>
/// This is the 'security stamp' for validation
/// </summary>
[DataMember(Name = "securityStamp")]
public string SecurityStamp { get; set; }
[DataMember(Name = "sessionId")]
public string SessionId { get; set; }
[DataMember(Name = "id")]
public object Id { get; set; }
-85
View File
@@ -1,12 +1,8 @@
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
@@ -26,86 +22,5 @@ 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);
}
}
}
}
@@ -2006,6 +2006,96 @@ namespace Umbraco.Core.Services
}
}
return true;
}
/// <summary>
/// Sorts a collection of <see cref="IContent"/> objects by updating the SortOrder according
/// to the ordering of node Ids passed in.
/// </summary>
/// <remarks>
/// Using this method will ensure that the Published-state is maintained upon sorting
/// so the cache is updated accordingly - as needed.
/// </remarks>
/// <param name="ids"></param>
/// <param name="userId"></param>
/// <param name="raiseEvents"></param>
/// <returns>True if sorting succeeded, otherwise False</returns>
public bool Sort(int[] ids, int userId = 0, bool raiseEvents = true)
{
var shouldBePublished = new List<IContent>();
var shouldBeSaved = new List<IContent>();
using (new WriteLock(Locker))
{
var allContent = GetByIds(ids).ToDictionary(x => x.Id, x => x);
var items = ids.Select(x => allContent[x]);
using (var uow = UowProvider.GetUnitOfWork())
{
var asArray = items.ToArray();
var saveEventArgs = new SaveEventArgs<IContent>(asArray);
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
{
uow.Commit();
return false;
}
var repository = RepositoryFactory.CreateContentRepository(uow);
var i = 0;
foreach (var content in asArray)
{
//If the current sort order equals that of the content
//we don't need to update it, so just increment the sort order
//and continue.
if (content.SortOrder == i)
{
i++;
continue;
}
content.SortOrder = i;
content.WriterId = userId;
i++;
if (content.Published)
{
//TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable!
var published = _publishingStrategy.Publish(uow, content, userId).Success;
shouldBePublished.Add(content);
}
else
shouldBeSaved.Add(content);
repository.AddOrUpdate(content);
//add or update a preview
repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
}
foreach (var content in shouldBePublished)
{
//Create and Save ContentXml DTO
repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
}
if (raiseEvents)
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
}
if (shouldBePublished.Any())
{
//TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable!
_publishingStrategy.PublishingFinalized(uow, shouldBePublished, false);
}
Audit(uow, AuditType.Sort, "Sorting content performed by user", userId, 0);
uow.Commit();
}
}
return true;
}
@@ -2453,6 +2543,7 @@ 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);
}
}
@@ -1,58 +1,9 @@
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);
}
}
+15 -1
View File
@@ -642,7 +642,21 @@ namespace Umbraco.Core.Services
/// <param name="userId"></param>
/// <param name="raiseEvents"></param>
/// <returns>True if sorting succeeded, otherwise False</returns>
bool Sort(IEnumerable<IContent> items, int userId = 0, bool raiseEvents = true);
bool Sort(IEnumerable<IContent> items, int userId = 0, bool raiseEvents = true);
/// <summary>
/// Sorts a collection of <see cref="IContent"/> objects by updating the SortOrder according
/// to the ordering of node Ids passed in.
/// </summary>
/// <remarks>
/// Using this method will ensure that the Published-state is maintained upon sorting
/// so the cache is updated accordingly - as needed.
/// </remarks>
/// <param name="ids"></param>
/// <param name="userId"></param>
/// <param name="raiseEvents"></param>
/// <returns>True if sorting succeeded, otherwise False</returns>
bool Sort(int[] ids, int userId = 0, bool raiseEvents = true);
/// <summary>
/// Gets the parent of the current content as an <see cref="IContent"/> item.
+1 -30
View File
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models;
@@ -12,35 +11,7 @@ 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>
+62 -5
View File
@@ -4,6 +4,7 @@ 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;
@@ -11,6 +12,7 @@ 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;
@@ -41,7 +43,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"/>
@@ -61,7 +63,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"/>
@@ -729,10 +731,65 @@ 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 repository = RepositoryFactory.CreateMediaRepository(uow);
return repository.GetMediaByPath(mediaPath);
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);
}
}
@@ -964,7 +1021,7 @@ namespace Umbraco.Core.Services
if (raiseEvents)
{
saveEventArgs.CanCancel = false;
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
}
+5 -1
View File
@@ -986,7 +986,11 @@ namespace Umbraco.Core.Services
/// <param name="raiseEvents">Optional parameter to raise events.
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
public void Save(IMember entity, bool raiseEvents = true)
{
{
//trimming username and email to make sure we have no trailing space
entity.Username = entity.Username.Trim();
entity.Email = entity.Email.Trim();
using (var uow = UowProvider.GetUnitOfWork())
{
var saveEventArgs = new SaveEventArgs<IMember>(entity);
-41
View File
@@ -558,47 +558,6 @@ 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))
@@ -87,8 +87,7 @@ namespace Umbraco.Core.Sync
{
UtcStamp = DateTime.UtcNow,
Instructions = JsonConvert.SerializeObject(instructions, Formatting.None),
OriginIdentity = LocalIdentity,
InstructionCount = instructions.Sum(x => x.JsonIdCount)
OriginIdentity = LocalIdentity
};
ApplicationContext.DatabaseContext.Database.Insert(dto);
@@ -166,10 +165,10 @@ namespace Umbraco.Core.Sync
}
else
{
//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});
//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});
if (count > Options.MaxProcessingInstructionCount)
{
//too many instructions, proceed to cold boot
@@ -206,7 +205,7 @@ namespace Umbraco.Core.Sync
/// <summary>
/// Synchronize the server (throttled).
/// </summary>
protected internal void Sync()
protected void Sync()
{
lock (_locko)
{
+4 -35
View File
@@ -18,10 +18,7 @@ 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(...)
@@ -33,16 +30,12 @@ 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)
@@ -57,21 +50,9 @@ namespace Umbraco.Core.Sync
IntId = intId;
}
/// <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)
private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, string json)
: this(refresher, refreshType)
{
JsonIdCount = idCount;
if (refreshType == RefreshMethodType.RefreshByJson)
JsonPayload = json;
else
@@ -96,12 +77,8 @@ 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
var intIds = ids.Cast<int>().ToArray();
return new[] { new RefreshInstruction(refresher, RefreshMethodType.RefreshByIds, JsonConvert.SerializeObject(intIds), intIds.Length) };
}
if (idType == typeof (int)) // bulk of ints is supported
return new[] { new RefreshInstruction(refresher, RefreshMethodType.RefreshByIds, JsonConvert.SerializeObject(ids.Cast<int>().ToArray())) };
// else must be guids, bulk of guids is not supported, iterate
return ids.Select(x => new RefreshInstruction(refresher, RefreshMethodType.RefreshByGuid, (Guid) x));
@@ -144,14 +121,6 @@ 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>
+4
View File
@@ -117,6 +117,8 @@ namespace Umbraco.Core
{
case UmbracoObjectTypes.Document:
return Document;
case UmbracoObjectTypes.DocumentBlueprint:
return DocumentBluePrint;
case UmbracoObjectTypes.Media:
return Media;
case UmbracoObjectTypes.Member:
@@ -161,6 +163,8 @@ namespace Umbraco.Core
{
case Document:
return UmbracoObjectTypes.Document;
case DocumentBluePrint:
return UmbracoObjectTypes.DocumentBlueprint;
case Media:
return UmbracoObjectTypes.Media;
case Member:
+5 -17
View File
@@ -52,6 +52,10 @@
<HintPath>..\packages\HtmlAgilityPack.1.4.9.5\lib\Net45\HtmlAgilityPack.dll</HintPath>
<Private>True</Private>
</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>
</Reference>
@@ -113,8 +117,6 @@
</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" />
@@ -246,7 +248,6 @@
<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" />
@@ -255,7 +256,6 @@
<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" />
@@ -291,7 +291,6 @@
<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" />
@@ -322,7 +321,6 @@
<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" />
@@ -368,9 +366,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" />
@@ -382,8 +380,6 @@
<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" />
@@ -545,18 +541,12 @@
<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" />
@@ -704,13 +694,11 @@
<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" />
+1
View File
@@ -17,5 +17,6 @@
<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>
@@ -121,7 +121,8 @@ namespace Umbraco.Tests.Models.Mapping
{
AssertDisplayProperty(result, p, ApplicationContext);
}
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count());
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count() - 1);
Assert.IsTrue(result.Tabs.Any(x => x.Label == ui.Text("general", "properties")));
Assert.IsTrue(result.Tabs.First().IsActive);
Assert.IsTrue(result.Tabs.Except(new[] {result.Tabs.First()}).All(x => x.IsActive == false));
}
@@ -1,47 +0,0 @@
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,86 +40,5 @@ 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(29, foundTypes1.Count());
Assert.AreEqual(28, foundTypes1.Count());
}
[Test]
@@ -22,7 +22,6 @@ 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
@@ -37,14 +36,12 @@ 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);
@@ -53,7 +50,7 @@ namespace Umbraco.Tests.Security
Assert.AreEqual("en-us", backofficeIdentity.Culture);
Assert.IsTrue(new[] { "admin" }.SequenceEqual(backofficeIdentity.Roles));
Assert.AreEqual(11, backofficeIdentity.Claims.Count());
Assert.AreEqual(10, backofficeIdentity.Claims.Count());
}
[Test]
@@ -96,7 +93,6 @@ 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,
@@ -117,7 +113,6 @@ 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,
@@ -144,7 +139,6 @@ 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,
@@ -168,7 +162,6 @@ 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,11 +24,9 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
{
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
var sessionId = Guid.NewGuid().ToString();
var identity = new UmbracoBackOfficeIdentity(
new UserData(sessionId)
new UserData(Guid.NewGuid().ToString())
{
SecurityStamp = sessionId,
Id = 0,
Roles = new[] { "admin" },
AllowedApplications = new[] { "content", "media", "members" },
+2 -3
View File
@@ -62,8 +62,8 @@
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="Examine, Version=0.1.85.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Examine.0.1.85\lib\net45\Examine.dll</HintPath>
<Reference Include="Examine, Version=0.1.88.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Examine.0.1.88\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,7 +188,6 @@
<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,
+1 -1
View File
@@ -2,7 +2,7 @@
<packages>
<package id="AutoMapper" version="3.3.1" targetFramework="net45" />
<package id="Castle.Core" version="4.0.0" targetFramework="net45" />
<package id="Examine" version="0.1.85" targetFramework="net45" />
<package id="Examine" version="0.1.88" 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" />
+2 -7
View File
@@ -25,14 +25,13 @@
"jquery-migrate": "1.4.0",
"angular-dynamic-locale": "0.1.28",
"ng-file-upload": "~7.3.8",
"tinymce": "~4.7.1",
"tinymce": "~4.5.3",
"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",
"angular-moment": "^1.0.1"
"clipboard": "1.7.1"
},
"install": {
@@ -69,10 +68,6 @@
"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"
],
-605
View File
@@ -1,605 +0,0 @@
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,15 +31,17 @@
display: block;
padding: 4px;
line-height: @baseLineHeight;
border: 1px solid #ddd;
border: 1px solid @gray-8;
.border-radius(@baseBorderRadius);
.box-shadow(0 1px 3px rgba(0,0,0,.055));
.transition(all .2s ease-in-out);
}
// Add a hover/focus state for linked versions only
a.thumbnail:hover,
a.thumbnail:focus {
border-color: @linkColor;
// Add a hover/focus state for linked versions only.
a.thumbnail:hover,
a.thumbnail:focus,
a div.thumbnail:hover,
a div.thumbnail:focus {
border-color: @turquoise;
.box-shadow(0 1px 4px rgba(0,105,214,.25));
}
+1 -2
View File
@@ -11,8 +11,7 @@ var app = angular.module('umbraco', [
'ngMobile',
'tmh.dynamicLocale',
'ngFileUpload',
'LocalStorageModule',
'angularMoment'
'LocalStorageModule'
]);
var packages = angular.module("umbraco.packages", []);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 KiB

After

Width:  |  Height:  |  Size: 310 KiB

@@ -1,116 +0,0 @@
(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);
})();
@@ -1,109 +0,0 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbDrawer
@restrict E
@scope
@description
The drawer component is a global component and is already added to the umbraco markup. It is registered in globalState and can be opened and configured by raising events.
<h3>Markup example - how to open the drawer</h3>
<pre>
<div ng-controller="My.DrawerController as vm">
<umb-button
type="button"
label="Toggle drawer"
action="vm.toggleDrawer()">
</umb-button>
</div>
</pre>
<h3>Controller example - how to open the drawer</h3>
<pre>
(function () {
"use strict";
function DrawerController(appState) {
var vm = this;
vm.toggleDrawer = toggleDrawer;
function toggleDrawer() {
var showDrawer = appState.getDrawerState("showDrawer");
var model = {
firstName: "Super",
lastName: "Man"
};
appState.setDrawerState("view", "/App_Plugins/path/to/drawer.html");
appState.setDrawerState("model", model);
appState.setDrawerState("showDrawer", !showDrawer);
}
}
angular.module("umbraco").controller("My.DrawerController", DrawerController);
})();
</pre>
<h3>Use the following components in the custom drawer to render the content</h3>
<ul>
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerView}</li>
<li>{@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}</li>
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerContent}</li>
<li>{@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}</li>
</ul>
@param {string} view (<code>binding</code>): Set the drawer view
@param {string} model (<code>binding</code>): Pass in custom data to the drawer
**/
function Drawer($location, $routeParams, helpService, userService, localizationService, dashboardResource) {
return {
restrict: "E", // restrict to an element
replace: true, // replace the html element with the template
templateUrl: 'views/components/application/umbdrawer/umb-drawer.html',
transclude: true,
scope: {
view: "=?",
model: "=?"
},
link: function (scope, element, attr, ctrl) {
function onInit() {
setView();
}
function setView() {
if (scope.view) {
//we do this to avoid a hidden dialog to start loading unconfigured views before the first activation
var configuredView = scope.view;
if (scope.view.indexOf(".html") === -1) {
var viewAlias = scope.view.toLowerCase();
configuredView = "views/common/drawers/" + viewAlias + "/" + viewAlias + ".html";
}
if (configuredView !== scope.configuredView) {
scope.configuredView = configuredView;
}
}
}
onInit();
}
};
}
angular.module('umbraco.directives').directive("umbDrawer", Drawer);
@@ -1,59 +0,0 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbDrawerContent
@restrict E
@scope
@description
Use this directive to render drawer content
<h3>Markup example</h3>
<pre>
<umb-drawer-view>
<umb-drawer-header
title="Drawer Title"
description="Drawer description">
</umb-drawer-header>
<umb-drawer-content>
<!-- Your content here -->
<pre>{{ model | json }}</pre>
</umb-drawer-content>
<umb-drawer-footer>
<!-- Your content here -->
</umb-drawer-footer>
</umb-drawer-view>
</pre>
<h3>Use in combination with</h3>
<ul>
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerView}</li>
<li>{@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}</li>
<li>{@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}</li>
</ul>
**/
(function() {
'use strict';
function DrawerContentDirective() {
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/application/umbdrawer/umb-drawer-content.html'
};
return directive;
}
angular.module('umbraco.directives').directive('umbDrawerContent', DrawerContentDirective);
})();
@@ -1,58 +0,0 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbDrawerFooter
@restrict E
@scope
@description
Use this directive to render a drawer footer
<h3>Markup example</h3>
<pre>
<umb-drawer-view>
<umb-drawer-header
title="Drawer Title"
description="Drawer description">
</umb-drawer-header>
<umb-drawer-content>
<!-- Your content here -->
<pre>{{ model | json }}</pre>
</umb-drawer-content>
<umb-drawer-footer>
<!-- Your content here -->
</umb-drawer-footer>
</umb-drawer-view>
</pre>
<h3>Use in combination with</h3>
<ul>
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerView}</li>
<li>{@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}</li>
<li>{@link umbraco.directives.directive:umbDrawerContent umbDrawerContent}</li>
</ul>
**/
(function() {
'use strict';
function DrawerFooterDirective() {
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/application/umbdrawer/umb-drawer-footer.html'
};
return directive;
}
angular.module('umbraco.directives').directive('umbDrawerFooter', DrawerFooterDirective);
})();
@@ -1,63 +0,0 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbDrawerHeader
@restrict E
@scope
@description
Use this directive to render a drawer header
<h3>Markup example</h3>
<pre>
<umb-drawer-view>
<umb-drawer-header
title="Drawer Title"
description="Drawer description">
</umb-drawer-header>
<umb-drawer-content>
<!-- Your content here -->
<pre>{{ model | json }}</pre>
</umb-drawer-content>
<umb-drawer-footer>
<!-- Your content here -->
</umb-drawer-footer>
</umb-drawer-view>
</pre>
<h3>Use in combination with</h3>
<ul>
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerView}</li>
<li>{@link umbraco.directives.directive:umbDrawerContent umbDrawerContent}</li>
<li>{@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}</li>
</ul>
@param {string} title (<code>attribute</code>): Set a drawer title.
@param {string} description (<code>attribute</code>): Set a drawer description.
**/
(function() {
'use strict';
function DrawerHeaderDirective() {
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/application/umbdrawer/umb-drawer-header.html',
scope: {
"title": "@?",
"description": "@?"
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbDrawerHeader', DrawerHeaderDirective);
})();
@@ -1,58 +0,0 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbDrawerView
@restrict E
@scope
@description
Use this directive to render drawer view
<h3>Markup example</h3>
<pre>
<umb-drawer-view>
<umb-drawer-header
title="Drawer Title"
description="Drawer description">
</umb-drawer-header>
<umb-drawer-content>
<!-- Your content here -->
<pre>{{ model | json }}</pre>
</umb-drawer-content>
<umb-drawer-footer>
<!-- Your content here -->
</umb-drawer-footer>
</umb-drawer-view>
</pre>
<h3>Use in combination with</h3>
<ul>
<li>{@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}</li>
<li>{@link umbraco.directives.directive:umbDrawerContent umbDrawerContent}</li>
<li>{@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}</li>
</ul>
**/
(function() {
'use strict';
function DrawerViewDirective() {
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/application/umbdrawer/umb-drawer-view.html'
};
return directive;
}
angular.module('umbraco.directives').directive('umbDrawerView', DrawerViewDirective);
})();
@@ -111,13 +111,30 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
scope.userDialog = null;
}
//toggle the help dialog by raising the global app state to toggle the help drawer
scope.helpClick = function () {
var showDrawer = appState.getDrawerState("showDrawer");
var drawer = { view: "help", show: !showDrawer };
appState.setDrawerState("view", drawer.view);
appState.setDrawerState("showDrawer", drawer.show);
};
scope.helpClick = function(){
if(scope.userDialog) {
closeUserDialog();
}
if(!scope.helpDialog) {
scope.helpDialog = {
view: "help",
show: true,
close: function(oldModel) {
closeHelpDialog();
}
};
} else {
closeHelpDialog();
}
};
function closeHelpDialog() {
scope.helpDialog.show = false;
scope.helpDialog = null;
}
scope.sectionClick = function (event, section) {
@@ -132,10 +149,12 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
if (scope.userDialog) {
closeUserDialog();
}
if (scope.helpDialog) {
closeHelpDialog();
}
navigationService.hideSearch();
navigationService.showTree(section.alias);
navigationService.showTree(section.alias);
//in some cases the section will have a custom route path specified, if there is one we'll use it
if (section.routePath) {
@@ -1,366 +0,0 @@
(function () {
'use strict';
function TourDirective($timeout, $http, $q, tourService, backdropService) {
function link(scope, el, attr, ctrl) {
var popover;
var pulseElement;
var pulseTimer;
scope.loadingStep = false;
scope.elementNotFound = false;
scope.model.nextStep = function() {
nextStep();
};
scope.model.endTour = function() {
unbindEvent();
tourService.endTour(scope.model);
backdropService.close();
};
scope.model.completeTour = function() {
unbindEvent();
tourService.completeTour(scope.model).then(function() {
backdropService.close();
});
};
scope.model.disableTour = function() {
unbindEvent();
tourService.disableTour(scope.model).then(function() {
backdropService.close();
});
}
function onInit() {
popover = el.find(".umb-tour__popover");
pulseElement = el.find(".umb-tour__pulse");
popover.hide();
scope.model.currentStepIndex = 0;
backdropService.open({disableEventsOnClick: true});
startStep();
}
function setView() {
if (scope.model.currentStep.view && scope.model.alias) {
//we do this to avoid a hidden dialog to start loading unconfigured views before the first activation
var configuredView = scope.model.currentStep.view;
if (scope.model.currentStep.view.indexOf(".html") === -1) {
var viewAlias = scope.model.currentStep.view.toLowerCase();
var tourAlias = scope.model.alias.toLowerCase();
configuredView = "views/common/tours/" + tourAlias + "/" + viewAlias + "/" + viewAlias + ".html";
}
if (configuredView !== scope.configuredView) {
scope.configuredView = configuredView;
}
} else {
scope.configuredView = null;
}
}
function nextStep() {
popover.hide();
pulseElement.hide();
$timeout.cancel(pulseTimer);
scope.model.currentStepIndex++;
// make sure we don't go too far
if(scope.model.currentStepIndex !== scope.model.steps.length) {
startStep();
// tour completed - final step
} else {
scope.loadingStep = true;
waitForPendingRerequests().then(function(){
scope.loadingStep = false;
// clear current step
scope.model.currentStep = {};
// set popover position to center
setPopoverPosition(null);
// remove backdrop hightlight and custom opacity
backdropService.setHighlight(null);
backdropService.setOpacity(null);
});
}
}
function startStep() {
scope.loadingStep = true;
backdropService.setOpacity(scope.model.steps[scope.model.currentStepIndex].backdropOpacity);
backdropService.setHighlight(null);
waitForPendingRerequests().then(function() {
scope.model.currentStep = scope.model.steps[scope.model.currentStepIndex];
setView();
// if highlight element is set - find it
findHighlightElement();
// if a custom event needs to be bound we do it now
if(scope.model.currentStep.event) {
bindEvent();
}
scope.loadingStep = false;
});
}
function findHighlightElement() {
scope.elementNotFound = false;
$timeout(function () {
// if an element isn't set - show the popover in the center
if(scope.model.currentStep && !scope.model.currentStep.element) {
setPopoverPosition(null);
return;
}
var element = angular.element(scope.model.currentStep.element);
// we couldn't find the element in the dom - abort and show error
if(element.length === 0) {
scope.elementNotFound = true;
setPopoverPosition(null);
return;
}
var scrollParent = element.scrollParent();
// Detect if scroll is needed
if (element[0].offsetTop > scrollParent[0].clientHeight) {
scrollParent.animate({
scrollTop: element[0].offsetTop
}, function () {
// Animation complete.
setPopoverPosition(element);
setPulsePosition();
backdropService.setHighlight(scope.model.currentStep.element, scope.model.currentStep.elementPreventClick);
});
} else {
setPopoverPosition(element);
setPulsePosition();
backdropService.setHighlight(scope.model.currentStep.element, scope.model.currentStep.elementPreventClick);
}
});
}
function setPopoverPosition(element) {
$timeout(function () {
var position = "center";
var margin = 20;
var css = {};
var popoverWidth = popover.outerWidth();
var popoverHeight = popover.outerHeight();
var popoverOffset = popover.offset();
var documentWidth = angular.element(document).width();
var documentHeight = angular.element(document).height();
if(element) {
var offset = element.offset();
var width = element.outerWidth();
var height = element.outerHeight();
// messure available space on each side of the target element
var space = {
"top": offset.top,
"right": documentWidth - (offset.left + width),
"bottom": documentHeight - (offset.top + height),
"left": offset.left
};
// get the posistion with most available space
position = findMax(space);
if (position === "top") {
if (offset.left < documentWidth / 2) {
css.top = offset.top - popoverHeight - margin;
css.left = offset.left;
} else {
css.top = offset.top - popoverHeight - margin;
css.left = offset.left - popoverWidth + width;
}
}
if (position === "right") {
if (offset.top < documentHeight / 2) {
css.top = offset.top;
css.left = offset.left + width + margin;
} else {
css.top = offset.top + height - popoverHeight;
css.left = offset.left + width + margin;
}
}
if (position === "bottom") {
if (offset.left < documentWidth / 2) {
css.top = offset.top + height + margin;
css.left = offset.left;
} else {
css.top = offset.top + height + margin;
css.left = offset.left - popoverWidth + width;
}
}
if (position === "left") {
if (offset.top < documentHeight / 2) {
css.top = offset.top;
css.left = offset.left - popoverWidth - margin;
} else {
css.top = offset.top + height - popoverHeight;
css.left = offset.left - popoverWidth - margin;
}
}
} else {
// if there is no dom element center the popover
css.top = "calc(50% - " + popoverHeight/2 + "px)";
css.left = "calc(50% - " + popoverWidth/2 + "px)";
}
popover.css(css).fadeIn("fast");
});
}
function setPulsePosition() {
if(scope.model.currentStep.event) {
pulseTimer = $timeout(function(){
var clickElementSelector = scope.model.currentStep.eventElement ? scope.model.currentStep.eventElement : scope.model.currentStep.element;
var clickElement = $(clickElementSelector);
var offset = clickElement.offset();
var width = clickElement.outerWidth();
var height = clickElement.outerHeight();
pulseElement.css({ "width": width, "height": height, "left": offset.left, "top": offset.top });
pulseElement.fadeIn();
}, 1000);
}
}
function waitForPendingRerequests() {
var deferred = $q.defer();
var timer = window.setInterval(function(){
// check for pending requests both in angular and on the document
if($http.pendingRequests.length === 0 && document.readyState === "complete") {
$timeout(function(){
deferred.resolve();
clearInterval(timer);
});
}
}, 50);
return deferred.promise;
}
function findMax(obj) {
var keys = Object.keys(obj);
var max = keys[0];
for (var i = 1, n = keys.length; i < n; ++i) {
var k = keys[i];
if (obj[k] > obj[max]) {
max = k;
}
}
return max;
}
function bindEvent() {
var bindToElement = scope.model.currentStep.element;
var eventName = scope.model.currentStep.event + ".step-" + scope.model.currentStepIndex;
var removeEventName = "remove.step-" + scope.model.currentStepIndex;
var handled = false;
if(scope.model.currentStep.eventElement) {
bindToElement = scope.model.currentStep.eventElement;
}
$(bindToElement).on(eventName, function(){
if(!handled) {
unbindEvent();
nextStep();
handled = true;
}
});
// Hack: we do this to handle cases where ng-if is used and removes the element we need to click.
// for some reason it seems the elements gets removed before the event is raised. This is a temp solution which assumes:
// "if you ask me to click on an element, and it suddenly gets removed from the dom, let's go on to the next step".
$(bindToElement).on(removeEventName, function () {
if(!handled) {
unbindEvent();
nextStep();
handled = true;
}
});
}
function unbindEvent() {
var eventName = scope.model.currentStep.event + ".step-" + scope.model.currentStepIndex;
var removeEventName = "remove.step-" + scope.model.currentStepIndex;
if(scope.model.currentStep.eventElement) {
angular.element(scope.model.currentStep.eventElement).off(eventName);
angular.element(scope.model.currentStep.eventElement).off(removeEventName);
} else {
angular.element(scope.model.currentStep.element).off(eventName);
angular.element(scope.model.currentStep.element).off(removeEventName);
}
}
function resize() {
findHighlightElement();
}
onInit();
$(window).on('resize.umbTour', resize);
scope.$on('$destroy', function () {
$(window).off('resize.umbTour');
unbindEvent();
$timeout.cancel(pulseTimer);
});
}
var directive = {
transclude: true,
restrict: 'E',
replace: true,
templateUrl: 'views/components/application/umb-tour.html',
link: link,
scope: {
model: "="
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbTour', TourDirective);
})();
@@ -1,35 +0,0 @@
(function() {
'use strict';
function TourStepDirective() {
function link(scope, element, attrs, ctrl) {
scope.close = function() {
if(scope.onClose) {
scope.onClose();
}
}
}
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/application/umbtour/umb-tour-step.html',
scope: {
size: "@?",
onClose: "&?",
hideClose: "=?"
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbTourStep', TourStepDirective);
})();
@@ -1,22 +0,0 @@
(function() {
'use strict';
function TourStepContentDirective() {
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/application/umbtour/umb-tour-step-content.html',
scope: {
content: "="
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbTourStepContent', TourStepContentDirective);
})();

Some files were not shown because too many files have changed in this diff Show More