diff --git a/build/NuSpecs/build/UmbracoCms.targets b/build/NuSpecs/build/UmbracoCms.targets
index 024d8af7ad..fde5b4ea81 100644
--- a/build/NuSpecs/build/UmbracoCms.targets
+++ b/build/NuSpecs/build/UmbracoCms.targets
@@ -1,6 +1,17 @@
+
+
+ $(MSBuildThisFileDirectory)..\UmbracoFiles\
+
+
+
+
+
+
+
+
$(MSBuildThisFileDirectory)..\UmbracoFiles\
@@ -47,4 +58,4 @@
-
\ No newline at end of file
+
diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt
index df13aa3dec..cff81ba2d8 100644
--- a/build/NuSpecs/tools/Web.config.install.xdt
+++ b/build/NuSpecs/tools/Web.config.install.xdt
@@ -48,9 +48,9 @@
-
-
-
+
+
+
@@ -81,6 +81,10 @@
+
+
+
+
diff --git a/build/RevertToCleanInstall.bat b/build/RevertToCleanInstall.bat
index ea42e05e1b..b21b33d8ff 100644
--- a/build/RevertToCleanInstall.bat
+++ b/build/RevertToCleanInstall.bat
@@ -45,9 +45,6 @@ del ..\src\Umbraco.Web.UI\UserControls\*.*
echo Removing masterpage files
del ..\src\Umbraco.Web.UI\masterpages\*.*
-echo Removing view files
-del ..\src\Umbraco.Web.UI\Views\*.cshtml
-
echo Removing razor files
del ..\src\Umbraco.Web.UI\macroScripts\*.*
@@ -104,7 +101,9 @@ echo Removing user control files
FOR %%A IN (..\src\Umbraco.Web.UI\usercontrols\*.*) DO DEL %%A
echo Removing view files
-FOR %%A IN (..\src\Umbraco.Web.UI\Views\*.cshtml) DO DEL %%A
+ATTRIB +H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S
+FOR %%A IN (..\src\Umbraco.Web.UI\Views\) DO DEL /Q /S *.cshtml -H
+ATTRIB -H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S
echo Removing razor files
FOR %%A IN (..\src\Umbraco.Web.UI\macroScripts\*.*) DO DEL %%A
diff --git a/build/RevertToEmptyInstall.bat b/build/RevertToEmptyInstall.bat
index 2cab84a17d..b8abe4e64e 100644
--- a/build/RevertToEmptyInstall.bat
+++ b/build/RevertToEmptyInstall.bat
@@ -54,9 +54,6 @@ del ..\src\Umbraco.Web.UI\UserControls\*.*
echo Removing masterpage files
del ..\src\Umbraco.Web.UI\masterpages\*.*
-echo Removing view files
-del ..\src\Umbraco.Web.UI\Views\*.cshtml
-
echo Removing razor files
del ..\src\Umbraco.Web.UI\macroScripts\*.*
@@ -122,7 +119,9 @@ echo Removing user control files
FOR %%A IN (..\src\Umbraco.Web.UI\usercontrols\*.*) DO DEL %%A
echo Removing view files
-FOR %%A IN (..\src\Umbraco.Web.UI\Views\*.cshtml) DO DEL %%A
+ATTRIB +H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S
+FOR %%A IN (..\src\Umbraco.Web.UI\Views\) DO DEL /Q /S *.cshtml -H
+ATTRIB -H ..\src\Umbraco.Web.UI\Views\Partials\Grid\*.cshtml /S
echo Removing razor files
FOR %%A IN (..\src\Umbraco.Web.UI\macroScripts\*.*) DO DEL %%A
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index 6c766f8e97..b10616ec36 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyFileVersion("7.3.0")]
-[assembly: AssemblyInformationalVersion("7.3.0")]
\ No newline at end of file
+[assembly: AssemblyFileVersion("7.3.1")]
+[assembly: AssemblyInformationalVersion("7.3.1")]
\ No newline at end of file
diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/AsyncLock.cs
index b4e8e64312..b6a3f8534e 100644
--- a/src/Umbraco.Core/AsyncLock.cs
+++ b/src/Umbraco.Core/AsyncLock.cs
@@ -1,5 +1,6 @@
using System;
using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -117,38 +118,55 @@ namespace Umbraco.Core
private class NamedSemaphoreReleaser : CriticalFinalizerObject, IDisposable
{
private readonly Semaphore _semaphore;
+ private GCHandle _handle;
internal NamedSemaphoreReleaser(Semaphore semaphore)
{
_semaphore = semaphore;
+ _handle = GCHandle.Alloc(_semaphore);
}
public void Dispose()
{
Dispose(true);
- GC.SuppressFinalize(this);
+ GC.SuppressFinalize(this); // finalize will not run
}
private void Dispose(bool disposing)
{
// critical
+ _handle.Free();
_semaphore.Release();
+ _semaphore.Dispose();
}
- // we WANT to release the semaphore because it's a system object
- // ie a critical non-managed resource - so we inherit from CriticalFinalizerObject
- // which means that the finalizer "should" run in all situations
+ // we WANT to release the semaphore because it's a system object, ie a critical
+ // non-managed resource - and if it is not released then noone else can acquire
+ // the lock - so we inherit from CriticalFinalizerObject which means that the
+ // finalizer "should" run in all situations - there is always a chance that it
+ // does not run and the semaphore remains "acquired" but then chances are the
+ // whole process (w3wp.exe...) is going down, at which point the semaphore will
+ // be destroyed by Windows.
- // however... that can fail with System.ObjectDisposedException because the
- // underlying handle was closed... because we cannot guarantee that the semaphore
- // is not gone already... unless we get a GCHandle = GCHandle.Alloc(_semaphore);
- // which should keep it around and then we free the handle?
+ // however, the semaphore is a managed object, and so when the finalizer runs it
+ // might have been finalized already, and then we get a, ObjectDisposedException
+ // in the finalizer - which is bad.
- // so... I'm not sure this is safe really...
+ // in order to prevent this we do two things
+ // - use a GCHandler to ensure the semaphore is still there when the finalizer
+ // runs, so we can actually release it
+ // - wrap the finalizer code in a try...catch to make sure it never throws
~NamedSemaphoreReleaser()
{
- Dispose(false);
+ try
+ {
+ Dispose(false);
+ }
+ catch
+ {
+ // we do NOT want the finalizer to throw - never ever
+ }
}
}
diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs
index d416c846ef..7788fadf75 100644
--- a/src/Umbraco.Core/Models/Member.cs
+++ b/src/Umbraco.Core/Models/Member.cs
@@ -280,7 +280,7 @@ namespace Umbraco.Core.Models
//This is the default value if the prop is not found
true);
if (a.Success == false) return a.Result;
-
+ if (Properties[Constants.Conventions.Member.IsApproved].Value == null) return true;
var tryConvert = Properties[Constants.Conventions.Member.IsApproved].Value.TryConvertTo();
if (tryConvert.Success)
{
@@ -313,7 +313,7 @@ namespace Umbraco.Core.Models
{
var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsLockedOut, "IsLockedOut", false);
if (a.Success == false) return a.Result;
-
+ if (Properties[Constants.Conventions.Member.IsLockedOut].Value == null) return false;
var tryConvert = Properties[Constants.Conventions.Member.IsLockedOut].Value.TryConvertTo();
if (tryConvert.Success)
{
@@ -346,7 +346,7 @@ namespace Umbraco.Core.Models
{
var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLoginDate, "LastLoginDate", default(DateTime));
if (a.Success == false) return a.Result;
-
+ if (Properties[Constants.Conventions.Member.LastLoginDate].Value == null) return default(DateTime);
var tryConvert = Properties[Constants.Conventions.Member.LastLoginDate].Value.TryConvertTo();
if (tryConvert.Success)
{
@@ -379,7 +379,7 @@ namespace Umbraco.Core.Models
{
var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastPasswordChangeDate, "LastPasswordChangeDate", default(DateTime));
if (a.Success == false) return a.Result;
-
+ if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value == null) return default(DateTime);
var tryConvert = Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value.TryConvertTo();
if (tryConvert.Success)
{
@@ -412,7 +412,7 @@ namespace Umbraco.Core.Models
{
var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLockoutDate, "LastLockoutDate", default(DateTime));
if (a.Success == false) return a.Result;
-
+ if (Properties[Constants.Conventions.Member.LastLockoutDate].Value == null) return default(DateTime);
var tryConvert = Properties[Constants.Conventions.Member.LastLockoutDate].Value.TryConvertTo();
if (tryConvert.Success)
{
@@ -446,7 +446,7 @@ namespace Umbraco.Core.Models
{
var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.FailedPasswordAttempts, "FailedPasswordAttempts", 0);
if (a.Success == false) return a.Result;
-
+ if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value == null) return default(int);
var tryConvert = Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value.TryConvertTo();
if (tryConvert.Success)
{
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs
new file mode 100644
index 0000000000..50f78ca66d
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs
@@ -0,0 +1,38 @@
+using System.Linq;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Models.Rdbms;
+using Umbraco.Core.Persistence.SqlSyntax;
+
+namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeOne
+{
+ ///
+ /// This fixes the storage of user languages from the old format like en_us to en-US
+ ///
+ [Migration("7.3.1", 0, GlobalSettings.UmbracoMigrationName)]
+ public class UpdateUserLanguagesToIsoCode : MigrationBase
+ {
+ public UpdateUserLanguagesToIsoCode(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger)
+ {
+ }
+
+ public override void Up()
+ {
+ var userData = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax));
+ foreach (var user in userData.Where(x => x.UserLanguage.Contains("_")))
+ {
+ var languageParts = user.UserLanguage.Split('_');
+ if (languageParts.Length == 2)
+ {
+ Update.Table("umbracoUser")
+ .Set(new {userLanguage = languageParts[0] + "-" + languageParts[1].ToUpperInvariant()})
+ .Where(new {id = user.Id});
+ }
+ }
+ }
+
+ public override void Down()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs
index 770ee65aed..c7cbc0f473 100644
--- a/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs
@@ -2,6 +2,7 @@ using System;
namespace Umbraco.Core.PropertyEditors
{
+ ///
/// Maps a property source value to a data object.
///
// todo: drop IPropertyEditorValueConverter support (when?).
diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs
index 65170341c0..dfe59e1783 100644
--- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs
+++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
using System.Web.Security;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
@@ -9,8 +10,6 @@ using Umbraco.Core.Services;
namespace Umbraco.Core.Security
{
-
-
///
/// Default back office user manager
///
@@ -52,8 +51,8 @@ namespace Umbraco.Core.Security
if (externalLoginService == null) throw new ArgumentNullException("externalLoginService");
var manager = new BackOfficeUserManager(new BackOfficeUserStore(userService, externalLoginService, membershipProvider));
-
- return InitUserManager(manager, membershipProvider, options);
+ manager.InitUserManager(manager, membershipProvider, options);
+ return manager;
}
///
@@ -80,7 +79,7 @@ namespace Umbraco.Core.Security
///
///
///
- private static BackOfficeUserManager InitUserManager(BackOfficeUserManager manager, MembershipProviderBase membershipProvider, IdentityFactoryOptions options)
+ protected void InitUserManager(BackOfficeUserManager manager, MembershipProviderBase membershipProvider, IdentityFactoryOptions options)
{
// Configure validation logic for usernames
manager.UserValidator = new UserValidator(manager)
@@ -134,10 +133,10 @@ namespace Umbraco.Core.Security
//});
//manager.EmailService = new EmailService();
- //manager.SmsService = new SmsService();
-
- return manager;
+ //manager.SmsService = new SmsService();
}
+
+
}
///
@@ -180,5 +179,44 @@ namespace Umbraco.Core.Security
}
#endregion
+ ///
+ /// Logic used to validate a username and password
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// By default this uses the standard ASP.Net Identity approach which is:
+ /// * Get password store
+ /// * Call VerifyPasswordAsync with the password store + user + password
+ /// * Uses the PasswordHasher.VerifyHashedPassword to compare the stored password
+ ///
+ /// In some cases people want simple custom control over the username/password check, for simplicity
+ /// sake, developers would like the users to simply validate against an LDAP directory but the user
+ /// data remains stored inside of Umbraco.
+ /// See: http://issues.umbraco.org/issue/U4-7032 for the use cases.
+ ///
+ /// We've allowed this check to be overridden with a simple callback so that developers don't actually
+ /// have to implement/override this class.
+ ///
+ public async override Task CheckPasswordAsync(T user, string password)
+ {
+ if (BackOfficeUserPasswordChecker != null)
+ {
+ var result = await BackOfficeUserPasswordChecker.CheckPasswordAsync(user, password);
+ //if the result indicates to not fallback to the default, then return true if the credentials are valid
+ if (result != BackOfficeUserPasswordCheckerResult.FallbackToDefaultChecker)
+ {
+ return result == BackOfficeUserPasswordCheckerResult.ValidCredentials;
+ }
+ }
+ //use the default behavior
+ return await base.CheckPasswordAsync(user, password);
+ }
+
+ ///
+ /// Gets/sets the default back office user password checker
+ ///
+ public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; }
}
}
diff --git a/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs b/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs
new file mode 100644
index 0000000000..8ce843856a
--- /dev/null
+++ b/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs
@@ -0,0 +1,12 @@
+namespace Umbraco.Core.Security
+{
+ ///
+ /// The result returned from the IBackOfficeUserPasswordChecker
+ ///
+ public enum BackOfficeUserPasswordCheckerResult
+ {
+ ValidCredentials,
+ InvalidCredentials,
+ FallbackToDefaultChecker
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs b/src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs
new file mode 100644
index 0000000000..6ee65e0fef
--- /dev/null
+++ b/src/Umbraco.Core/Security/IBackOfficeUserPasswordChecker.cs
@@ -0,0 +1,14 @@
+using System.Threading.Tasks;
+using Umbraco.Core.Models.Identity;
+
+namespace Umbraco.Core.Security
+{
+ ///
+ /// Used by the BackOfficeUserManager to check the username/password which allows for developers to more easily
+ /// set the logic for this procedure.
+ ///
+ public interface IBackOfficeUserPasswordChecker
+ {
+ Task CheckPasswordAsync(BackOfficeIdentityUser user, string password);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index da25bd0938..ba8b51fa5b 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -445,6 +445,9 @@ namespace Umbraco.Core.Services
/// An Enumerable list of objects
public IEnumerable GetAncestors(IContent content)
{
+ //null check otherwise we get exceptions
+ if (content.Path.IsNullOrWhiteSpace()) return Enumerable.Empty();
+
var ids = content.Path.Split(',').Where(x => x != Constants.System.Root.ToInvariantString() && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray();
if (ids.Any() == false)
return new List();
diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs
index bf94324388..e38464f5df 100644
--- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs
+++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs
@@ -187,7 +187,13 @@ namespace Umbraco.Core.Sync
using (_profilingLogger.DebugDuration("Syncing from database..."))
{
ProcessDatabaseInstructions();
- PruneOldInstructions();
+ switch (_appContext.GetCurrentServerRole())
+ {
+ case ServerRole.Single:
+ case ServerRole.Master:
+ PruneOldInstructions();
+ break;
+ }
}
}
finally
@@ -214,9 +220,9 @@ namespace Umbraco.Core.Sync
// FIXME not true if we're running on a background thread, assuming we can?
var sql = new Sql().Select("*")
- .From()
+ .From(_appContext.DatabaseContext.SqlSyntax)
.Where(dto => dto.Id > _lastId)
- .OrderBy(dto => dto.Id);
+ .OrderBy(dto => dto.Id, _appContext.DatabaseContext.SqlSyntax);
var dtos = _appContext.DatabaseContext.Database.Fetch(sql);
if (dtos.Count <= 0) return;
@@ -288,7 +294,7 @@ namespace Umbraco.Core.Sync
private void EnsureInstructions()
{
var sql = new Sql().Select("*")
- .From()
+ .From(_appContext.DatabaseContext.SqlSyntax)
.Where(dto => dto.Id == _lastId);
var dtos = _appContext.DatabaseContext.Database.Fetch(sql);
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index e6514693f3..d77d5d169d 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -438,6 +438,7 @@
+
@@ -467,7 +468,9 @@
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/assets/css/nonodes.style.min.css b/src/Umbraco.Web.UI.Client/src/assets/css/nonodes.style.min.css
index 68cc761e96..5299a59c7e 100644
--- a/src/Umbraco.Web.UI.Client/src/assets/css/nonodes.style.min.css
+++ b/src/Umbraco.Web.UI.Client/src/assets/css/nonodes.style.min.css
@@ -1 +1,17 @@
+@font-face {
+ font-family: 'Open Sans';
+ font-style: normal;
+ font-weight: 400;
+ src: url('../fonts/opensans/OpenSans-Regular-webfont.eot');
+ src: local('Open Sans'), local('OpenSans'), url('../fonts/opensans/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), url('../fonts/opensans/OpenSans-Regular-webfont.ttf') format('truetype'), url('../fonts/opensans/OpenSans-Regular-webfont.svg#open_sansregular') format('svg');
+}
+
+@font-face {
+ font-family: 'Open Sans';
+ font-style: normal;
+ font-weight: 600;
+ src: url('../fonts/opensans/OpenSans-Semibold-webfont.eot');
+ src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url('../fonts/opensans/OpenSans-Semibold-webfont.eot?#iefix') format('embedded-opentype'), url('../fonts/opensans/OpenSans-Semibold-webfont.ttf') format('truetype'), url('../fonts/opensans/OpenSans-Semibold-webfont.svg#open_sanssemibold') format('svg');
+}
+
abbr,address,article,aside,audio,b,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,p,pre,q,samp,section,small,span,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,ul,var,video{margin:0;padding:0;outline:0;border:0;background:0 0;vertical-align:baseline;font-size:100%}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}nav ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:'';content:none}a{margin:0;padding:0;background:0 0;vertical-align:baseline;font-size:100%}ins{background-color:#ff9;color:#000;text-decoration:none}mark{background-color:#ff9;color:#000;font-weight:700;font-style:italic}del{text-decoration:line-through}abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help}table{border-spacing:0;border-collapse:collapse}hr{display:block;margin:1em 0;padding:0;height:1px;border:0;border-top:1px solid #ccc}input,select{vertical-align:middle}html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{box-sizing:border-box}body,html{height:100%;width:100%;color:#fff;font-family:'Open Sans',sans-serif;font-weight:400;font-size:.9375em;line-height:1.5}h1{font-size:1.7em;margin:40px auto 10px;font-weight:700}h2{font-size:1.35em;margin:0 auto .4em;font-weight:700}h3{font-size:1em;font-weight:400;font-style:italic}p{font-size:1em;line-height:1.6}p+a{margin-top:1rem;display:inline-block}a,a:active,a:visited{text-decoration:none}.cta{margin:4.5em auto 1.5em;padding-bottom:4.5em}.button,.button:visited{padding:.9375em 1.875em;border-radius:.1em;font-weight:600;font-size:1.2em;background:#2e99f4;color:#fff;display:inline-block;border:none;transition:all 200ms ease-in-out}.button:hover,.button:visited:hover{border-bottom:none;background:#0c80e3}section{background:url(../img/nonodesbg.jpg) center center/cover;height:100%;width:100%;display:table;padding:3em 1.75em}section a,section a:focus,section a:visited{color:#46a5f5;font-size:1.1625em;border-bottom:1px solid transparent;transition:border-bottom 100ms ease-in-out}section a:focus:hover,section a:hover,section a:visited:hover{border-bottom:1px solid}section:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:rgba(0,0,0,.17);background:linear-gradient(45deg,rgba(85,98,112,.1) 10%,rgba(255,107,107,.1) 95%);z-index:0}section article{display:table-cell;vertical-align:middle;margin:0 auto;text-align:center;position:relative;z-index:100}section article>div{max-width:60em;margin:0 auto}section .logo{background:url(../img/logo.png) no-repeat;width:91px;height:91px;margin:0 auto}section .row{overflow:hidden}section .row .col{text-align:left;width:100%}section .row .col:nth-child(2){margin-top:3em}@media screen and (min-width:48em){body,html{font-size:1em}h1{font-size:2.5em;margin:70px auto 0;letter-spacing:.5px}h2{font-size:1.4375em;margin:0 auto 1em}h3{font-size:1.2em}a{font-size:1.1rem;font-weight:600}p+a{margin-top:3rem}.cta{margin:7.5em auto 2.5em;border-bottom:1px solid rgba(255,255,255,.5);padding-bottom:7.5em}section{padding:0 15px}section .row .col{float:left;width:50%;padding-right:5%;display:inline-block}section .row .col:nth-child(2){padding-right:0;padding-left:5%;margin-top:0}.button{font-size:1.1625em}}
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.png b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.png
index 2b2ff80a1d..388354e8a5 100644
Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.png and b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.png differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@2x.png b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@2x.png
new file mode 100644
index 0000000000..3e7257a41a
Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@2x.png differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@3x.png b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@3x.png
new file mode 100644
index 0000000000..d4bc08f1bc
Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@3x.png differ
diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js
index 7dce9f0ee0..c7cc3ff6ed 100644
--- a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js
+++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js
@@ -1,16 +1,18 @@
LazyLoad.js([
- '/Umbraco/lib/jquery/jquery.min.js',
- '/Umbraco/lib/jquery-ui/jquery-ui.min.js',
- '/Umbraco/lib/angular/1.1.5/angular.min.js',
- '/Umbraco/lib/underscore/underscore-min.js',
- '/Umbraco/js/app.js',
- '/Umbraco/js/umbraco.resources.js',
- '/Umbraco/js/umbraco.services.js',
- '/Umbraco/js/umbraco.security.js',
- '/Umbraco/ServerVariables',
- '/Umbraco/lib/spectrum/spectrum.js',
- '/Umbraco/js/canvasdesigner.panel.js',
+ '../lib/jquery/jquery.min.js',
+ '../lib/jquery-ui/jquery-ui.min.js',
+ '../lib/angular/1.1.5/angular.min.js',
+ '../lib/underscore/underscore-min.js',
+ '../lib/umbraco/Extensions.js',
+ '../js/app.js',
+ '../js/umbraco.resources.js',
+ '../js/umbraco.services.js',
+ '../js/umbraco.security.js',
+ '../ServerVariables',
+ '../lib/spectrum/spectrum.js',
+
+ '../js/canvasdesigner.panel.js',
], function () {
jQuery(document).ready(function () {
angular.bootstrap(document, ['Umbraco.canvasdesigner']);
diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/config/canvasdesigner.config.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner/config/canvasdesigner.config.js
index 699cd353a7..54a27cc415 100644
--- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/config/canvasdesigner.config.js
+++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/config/canvasdesigner.config.js
@@ -9,11 +9,6 @@ var canvasdesignerConfig = {
schema: "body",
selector: "body",
editors: [
- {
- type: "wide",
- category: "Dimension",
- name: "Layout"
- },
{
type: "background",
category: "Color",
diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.js
index b32311ee7c..ce6bf2c9f7 100644
--- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.js
+++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.js
@@ -104,6 +104,30 @@ angular.module("Umbraco.canvasdesigner")
}
};
+ function loadFont(font, variant) {
+ WebFont.load({
+ google: {
+ families: [font.fontFamily + ":" + variant]
+ },
+ loading: function () {
+ console.log('loading');
+ },
+ active: function () {
+ $scope.selectedFont = font;
+ $scope.selectedFont.fontWeight = googleGetWeight(variant);
+ $scope.selectedFont.fontStyle = googleGetStyle(variant);
+ // If $apply isn't called, the new font family isn't applied until the next user click.
+ $scope.change({
+ fontFamily: $scope.selectedFont.fontFamily,
+ fontType: $scope.selectedFont.fontType,
+ fontWeight: $scope.selectedFont.fontWeight,
+ fontStyle: $scope.selectedFont.fontStyle,
+ });
+ }
+ });
+ }
+
+ var webFontScriptLoaded = false;
$scope.showFontPreview = function (font, variant) {
if (!variant)
@@ -114,27 +138,19 @@ angular.module("Umbraco.canvasdesigner")
// Font needs to be independently loaded in the iframe for live preview to work.
document.getElementById("resultFrame").contentWindow.getFont(font.fontFamily + ":" + variant);
- WebFont.load({
- google: {
- families: [font.fontFamily + ":" + variant]
- },
- loading: function () {
- console.log('loading');
- },
- active: function () {
- $scope.selectedFont = font;
- $scope.selectedFont.fontWeight = googleGetWeight(variant);
- $scope.selectedFont.fontStyle = googleGetStyle(variant);
- // If $apply isn't called, the new font family isn't applied until the next user click.
- $scope.change({
- fontFamily: $scope.selectedFont.fontFamily,
- fontType: $scope.selectedFont.fontType,
- fontWeight: $scope.selectedFont.fontWeight,
- fontStyle: $scope.selectedFont.fontStyle,
+ if (!webFontScriptLoaded) {
+ $.getScript('https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js')
+ .done(function() {
+ webFontScriptLoaded = true;
+ loadFont(font, variant);
+ })
+ .fail(function() {
+ console.log('error loading webfont');
});
- }
- });
-
+ }
+ else {
+ loadFont(font, variant);
+ }
}
else {
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbavatar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbavatar.directive.js
deleted file mode 100644
index d7835df276..0000000000
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbavatar.directive.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
-* @ngdoc directive
-* @name umbraco.directives.directive:umbAvatar
-* @restrict E
-**/
-function avatarDirective() {
- return {
- restrict: "E", // restrict to an element
- replace: true, // replace the html element with the template
- templateUrl: 'views/components/application/umb-avatar.html',
- scope: {
- name: '@',
- email: '@',
- hash: '@'
- },
- link: function(scope, element, attr, ctrl) {
-
- scope.$watch("hash", function (val) {
- //set the gravatar url
- scope.gravatar = "https://www.gravatar.com/avatar/" + val + "?s=40";
- });
-
- }
- };
-}
-
-angular.module('umbraco.directives').directive("umbAvatar", avatarDirective);
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js
index 7c3d59dfba..adbc3a96a7 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js
@@ -13,14 +13,16 @@ angular.module("umbraco.directives")
restrict: 'E',
replace: true,
templateUrl: 'views/components/property/umb-property.html',
-
+ link: function(scope) {
+ scope.propertyAlias = Umbraco.Sys.ServerVariables.isDebuggingEnabled === true ? scope.property.alias : null;
+ },
//Define a controller for this directive to expose APIs to other directives
controller: function ($scope, $timeout) {
var self = this;
//set the API properties/methods
-
+
self.property = $scope.property;
self.setPropertyError = function(errorMsg) {
$scope.property.propertyErrorMessage = errorMsg;
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js
index 3bbe1d5679..a9296acc37 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js
@@ -149,6 +149,12 @@ function entityResource($q, $http, umbRequestHelper) {
_.each(ids, function(item) {
query += "ids=" + item + "&";
});
+
+ // if ids array is empty we need a empty variable in the querystring otherwise the service returns a error
+ if (ids.length === 0) {
+ query += "ids=&";
+ }
+
query += "type=" + type;
return umbRequestHelper.resourcePromise(
diff --git a/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js b/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js
index 56e0eb3041..2b757707ba 100644
--- a/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js
+++ b/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js
@@ -1,6 +1,6 @@
angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue'])
// This http interceptor listens for authentication successes and failures
- .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', function ($injector, queue, notifications) {
+ .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', 'requestInterceptorFilter', function ($injector, queue, notifications, requestInterceptorFilter) {
return function(promise) {
return promise.then(
@@ -19,6 +19,19 @@ angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue'])
return promise;
}, function(originalResponse) {
// Intercept failed requests
+
+ //Here we'll check if we should ignore the error, this will be based on an original header set
+ var headers = originalResponse.config ? originalResponse.config.headers : {};
+ if (headers["x-umb-ignore-error"] === "ignore") {
+ //exit/ignore
+ return promise;
+ }
+ var filtered = _.find(requestInterceptorFilter(), function(val) {
+ return originalResponse.config.url.indexOf(val) > 0;
+ });
+ if (filtered) {
+ return promise;
+ }
//A 401 means that the user is not logged in
if (originalResponse.status === 401) {
@@ -72,6 +85,10 @@ angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue'])
};
}])
+ .value('requestInterceptorFilter', function() {
+ return ["www.gravatar.com"];
+ })
+
// We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block.
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.responseInterceptors.push('securityInterceptor');
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js
index 7ad3f0e289..3fb291619d 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js
@@ -1,5 +1,5 @@
angular.module('umbraco.services')
- .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, dialogService, $timeout, angularHelper) {
+ .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, dialogService, $timeout, angularHelper, $http) {
var currentUser = null;
var lastUserId = null;
@@ -250,7 +250,7 @@ angular.module('umbraco.services')
}
setCurrentUser(data);
- currentUser.avatar = 'https://www.gravatar.com/avatar/' + data.emailHash + '?s=40&d=404';
+
deferred.resolve(currentUser);
});
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/xmlhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/xmlhelper.service.js
index 03f1f6cbbe..a8562c833e 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/xmlhelper.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/xmlhelper.service.js
@@ -372,9 +372,6 @@ function xmlhelper($http) {
fromJson: function (json) {
var xml = x2js.json2xml_str(json);
return xml;
- },
- parseFeed: function (url) {
- return $http.jsonp('//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=50&callback=JSON_CALLBACK&q=' + encodeURIComponent(url));
}
};
}
diff --git a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js
index 08185a156a..c953d96865 100644
--- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js
@@ -13,7 +13,11 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $
//the null is important because we do an explicit bool check on this in the view
//the avatar is by default the umbraco logo
$scope.authenticated = null;
- $scope.avatar = "assets/img/application/logo.png";
+ $scope.avatar = [
+ { value: "assets/img/application/logo.png" },
+ { value: "assets/img/application/logo@2x.png" },
+ { value: "assets/img/application/logo@3x.png" }
+ ];
$scope.touchDevice = appState.getGlobalState("touchDevice");
@@ -84,20 +88,32 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $
tmhDynamicLocale.set($scope.user.locale);
}
- if($scope.user.emailHash){
- $timeout(function () {
- //yes this is wrong..
- $("#avatar-img").fadeTo(1000, 0, function () {
- $timeout(function () {
- //this can be null if they time out
- if ($scope.user && $scope.user.emailHash) {
- $scope.avatar = "https://www.gravatar.com/avatar/" + $scope.user.emailHash + ".jpg?s=64&d=mm";
- }
+ if ($scope.user.emailHash) {
+
+ //let's attempt to load the avatar, it might not exist or we might not have
+ // internet access so we'll detect it first
+ $http.get("https://www.gravatar.com/avatar/" + $scope.user.emailHash + ".jpg?s=64&d=404")
+ .then(
+ function successCallback(response) {
+ $("#avatar-img").fadeTo(1000, 0, function () {
+ $scope.$apply(function () {
+ //this can be null if they time out
+ if ($scope.user && $scope.user.emailHash) {
+ var avatarBaseUrl = "https://www.gravatar.com/avatar/",
+ hash = $scope.user.emailHash;
+
+ $scope.avatar = [
+ { value: avatarBaseUrl + hash + ".jpg?s=30&d=mm" },
+ { value: avatarBaseUrl + hash + ".jpg?s=60&d=mm" },
+ { value: avatarBaseUrl + hash + ".jpg?s=90&d=mm" }
+ ];
+ }
+ });
+ $("#avatar-img").fadeTo(1000, 1);
+ });
+ }, function errorCallback(response) {
+ //cannot load it from the server so we cannot do anything
});
- $("#avatar-img").fadeTo(1000, 1);
- });
-
- }, 3000);
}
}));
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html
index 0e9ebc7526..0cc7511f89 100644
--- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html
+++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html
@@ -42,7 +42,7 @@