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 @@
- + Enter server domain or IP
diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index 8e85661e8f..79f3797197 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -8,7 +8,7 @@ background-size: 30px 30px !important; color: @white; position: absolute; - z-index: 2000; + z-index: 10000; top: 0px; left: 0px; margin: 0 !Important; diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index 0726db90b2..f57feeb2ad 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -81,8 +81,8 @@ ul.sections li.avatar a { } ul.sections li.avatar a img { - border-radius: 16px; - width: 30px + border-radius: 50%; + width: 30px; } .faded ul.sections li { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-avatar.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-avatar.html deleted file mode 100644 index b89bd2065a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-avatar.html +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html index 94d235ee3a..a8ed1749df 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html @@ -3,7 +3,7 @@
  • - +
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 7de6130230..7517795136 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -6,9 +6,9 @@
    -