diff --git a/.gitignore b/.gitignore index 04e39e37c9..32e7c297db 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ src/*.boltdata/ src/umbraco.sln.ide/* build/UmbracoCms.*/ src/.vs/ +src/Umbraco.Web.UI/umbraco/js/install.loader.js diff --git a/build/NuSpecs/tools/Dashboard.config.install.xdt b/build/NuSpecs/tools/Dashboard.config.install.xdt index 605bbb825e..197f9c1b6f 100644 --- a/build/NuSpecs/tools/Dashboard.config.install.xdt +++ b/build/NuSpecs/tools/Dashboard.config.install.xdt @@ -62,13 +62,6 @@ views/dashboard/default/startupdashboardintro.html - - - - - views/dashboard/ChangePassword.html - - diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index 4aef011d05..a05abe2a50 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -283,7 +283,12 @@ namespace Umbraco.Core { var configStatus = ConfigurationStatus; var currentVersion = UmbracoVersion.GetSemanticVersion(); - var ok = configStatus == currentVersion; + + var ok = + //we are not configured if this is null + string.IsNullOrWhiteSpace(configStatus) == false + //they must match + && configStatus == currentVersion; if (ok) { @@ -308,8 +313,9 @@ namespace Umbraco.Core return ok; } - catch + catch (Exception ex) { + LogHelper.Error("Error determining if application is configured, returning false", ex); return false; } diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index 13dee96b97..60fba0ae40 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Core +using System; +using System.ComponentModel; + +namespace Umbraco.Core { public static partial class Constants { @@ -15,6 +18,8 @@ /// /// The auth cookie name /// + [Obsolete("DO NOT USE THIS, USE ISecuritySection.AuthCookieName, this will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string AuthCookieName = "UMB_UCONTEXT"; } diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index 88b6308c29..e91996e32a 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -560,8 +560,7 @@ namespace Umbraco.Core.Models //Additional thumbnails configured as prevalues on the DataType if (thumbnailSizes != null) { - var sep = (thumbnailSizes.Contains("") == false && thumbnailSizes.Contains(",")) ? ',' : ';'; - foreach (var thumb in thumbnailSizes.Split(sep)) + foreach (var thumb in thumbnailSizes.Split(new[] { ";", "," }, StringSplitOptions.RemoveEmptyEntries)) { int thumbSize; if (thumb != "" && int.TryParse(thumb, out thumbSize)) diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs index f57d6683a2..0dc95a8987 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs @@ -4,6 +4,7 @@ using AutoMapper; using Umbraco.Core.Models.Mapping; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; namespace Umbraco.Core.Models.Identity { @@ -24,6 +25,18 @@ namespace Umbraco.Core.Models.Identity .ForMember(user => user.UserTypeAlias, expression => expression.MapFrom(user => user.UserType.Alias)) .ForMember(user => user.AccessFailedCount, expression => expression.MapFrom(user => user.FailedPasswordAttempts)) .ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray())); + + config.CreateMap() + .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.RealName, opt => opt.MapFrom(user => user.Name)) + .ForMember(detail => detail.Roles, opt => opt.MapFrom(user => new[] { user.UserTypeAlias })) + .ForMember(detail => detail.StartContentNode, opt => opt.MapFrom(user => user.StartContentId)) + .ForMember(detail => detail.StartMediaNode, opt => opt.MapFrom(user => user.StartMediaId)) + .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) diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index bd670f3836..5b9f63cf48 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Linq; using System.Threading; +using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs index 6598719454..c0d2d79446 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs @@ -28,37 +28,38 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne { if (database != null) { - //Fetch all PropertyTypes that belongs to a PropertyTypeGroup + //Fetch all PropertyTypes that belongs to a PropertyTypeGroup //NOTE: We are writing the full query because we've added a column to the PropertyTypeDto in later versions so one of the columns // won't exist yet - var propertyTypes = database.Fetch("SELECT * FROM cmsPropertyType WHERE propertyTypeGroupId > 0"); + //NOTE: We're using dynamic to avoid having this migration fail due to the UniqueId column added in 7.3 (this column is not added + // in the table yet and will make the mapping of done by Fetch fail when the actual type is used here). + var propertyTypes = database.Fetch("SELECT * FROM cmsPropertyType WHERE propertyTypeGroupId > 0"); var propertyGroups = database.Fetch("WHERE id > 0"); foreach (var propertyType in propertyTypes) { //Get the PropertyTypeGroup that the current PropertyType references - var parentPropertyTypeGroup = propertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyTypeGroupId); + var parentPropertyTypeGroup = propertyGroups.FirstOrDefault(x => x.Id == propertyType.propertyTypeGroupId); if (parentPropertyTypeGroup != null) { //If the ContentType is the same on the PropertyType and the PropertyTypeGroup the group is valid and we skip to the next - if (parentPropertyTypeGroup.ContentTypeNodeId == propertyType.ContentTypeId) continue; + if (parentPropertyTypeGroup.ContentTypeNodeId == propertyType.contentTypeId) continue; //Check if the 'new' PropertyTypeGroup has already been created var existingPropertyTypeGroup = propertyGroups.FirstOrDefault( x => x.ParentGroupId == parentPropertyTypeGroup.Id && x.Text == parentPropertyTypeGroup.Text && - x.ContentTypeNodeId == propertyType.ContentTypeId); + x.ContentTypeNodeId == propertyType.contentTypeId); //This should ensure that we don't create duplicate groups for a single ContentType if (existingPropertyTypeGroup == null) { - //Create a new PropertyTypeGroup that references the parent group that the PropertyType was referencing pre-6.0.1 var propertyGroup = new PropertyTypeGroupDto { - ContentTypeNodeId = propertyType.ContentTypeId, + ContentTypeNodeId = propertyType.contentTypeId, ParentGroupId = parentPropertyTypeGroup.Id, Text = parentPropertyTypeGroup.Text, SortOrder = parentPropertyTypeGroup.SortOrder @@ -69,19 +70,18 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne propertyGroup.Id = id; propertyGroups.Add(propertyGroup); //Update the reference to the new PropertyTypeGroup on the current PropertyType - propertyType.PropertyTypeGroupId = id; - database.Update(propertyType); + propertyType.propertyTypeGroupId = id; + database.Update("cmsPropertyType", "id", propertyType); } else { - //Update the reference to the existing PropertyTypeGroup on the current PropertyType - propertyType.PropertyTypeGroupId = existingPropertyTypeGroup.Id; - database.Update(propertyType); + //Update the reference to the existing PropertyTypeGroup on the current PropertyType + propertyType.propertyTypeGroupId = existingPropertyTypeGroup.Id; + database.Update("cmsPropertyType", "id", propertyType); } } } } - return string.Empty; } } diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index e332cb6dca..1c7c544ed8 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; @@ -15,7 +16,6 @@ using Microsoft.Owin; using Newtonsoft.Json; using Umbraco.Core.Configuration; using Umbraco.Core.Models.Membership; -using Microsoft.Owin; using Umbraco.Core.Logging; namespace Umbraco.Core.Security @@ -157,9 +157,6 @@ namespace Umbraco.Core.Security return new HttpContextWrapper(http).GetCurrentIdentity(authenticateRequestIfNotFound); } - /// - /// This clears the forms authentication cookie - /// public static void UmbracoLogout(this HttpContextBase http) { if (http == null) throw new ArgumentNullException("http"); @@ -170,6 +167,8 @@ namespace Umbraco.Core.Security /// This clears the forms authentication cookie for webapi since cookies are handled differently /// /// + [Obsolete("Use OWIN IAuthenticationManager.SignOut instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static void UmbracoLogoutWebApi(this HttpResponseMessage response) { if (response == null) throw new ArgumentNullException("response"); @@ -195,11 +194,8 @@ namespace Umbraco.Core.Security response.Headers.AddCookies(new[] { authCookie, prevCookie, extLoginCookie }); } - /// - /// This adds the forms authentication cookie for webapi since cookies are handled differently - /// - /// - /// + [Obsolete("Use WebSecurity.SetPrincipalForRequest")] + [EditorBrowsable(EditorBrowsableState.Never)] public static FormsAuthenticationTicket UmbracoLoginWebApi(this HttpResponseMessage response, IUser user) { if (response == null) throw new ArgumentNullException("response"); @@ -250,26 +246,29 @@ namespace Umbraco.Core.Security if (http == null) throw new ArgumentNullException("http"); new HttpContextWrapper(http).UmbracoLogout(); } - + /// - /// Renews the Umbraco authentication ticket + /// This will force ticket renewal in the OWIN pipeline /// /// /// public static bool RenewUmbracoAuthTicket(this HttpContextBase http) { if (http == null) throw new ArgumentNullException("http"); - return RenewAuthTicket(http, - UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName, - UmbracoConfig.For.UmbracoSettings().Security.AuthCookieDomain, - //Umbraco has always persisted it's original cookie for 1 day so we'll keep it that way - 1440); + http.Items["umbraco-force-auth"] = true; + return true; } + /// + /// This will force ticket renewal in the OWIN pipeline + /// + /// + /// internal static bool RenewUmbracoAuthTicket(this HttpContext http) { if (http == null) throw new ArgumentNullException("http"); - return new HttpContextWrapper(http).RenewUmbracoAuthTicket(); + http.Items["umbraco-force-auth"] = true; + return true; } /// @@ -390,8 +389,7 @@ namespace Umbraco.Core.Security //ensure there's def an expired cookie http.Response.Cookies.Add(new HttpCookie(c) { Expires = DateTime.Now.AddYears(-1) }); } - } - + } } private static FormsAuthenticationTicket GetAuthTicket(this HttpContextBase http, string cookieName) @@ -432,51 +430,6 @@ namespace Umbraco.Core.Security return FormsAuthentication.Decrypt(formsCookie); } - /// - /// Renews the forms authentication ticket & cookie - /// - /// - /// - /// - /// - /// true if there was a ticket to renew otherwise false if there was no ticket - private static bool RenewAuthTicket(this HttpContextBase http, string cookieName, string cookieDomain, int minutesPersisted) - { - if (http == null) throw new ArgumentNullException("http"); - //get the ticket - var ticket = GetAuthTicket(http, cookieName); - //renew the ticket - var renewed = FormsAuthentication.RenewTicketIfOld(ticket); - if (renewed == null) - { - return false; - } - - //get the request cookie to get it's expiry date, - //NOTE: this will never be null becaues we already do this - // check in teh GetAuthTicket. - var formsCookie = http.Request.Cookies[cookieName]; - - //encrypt it - var hash = FormsAuthentication.Encrypt(renewed); - //write it to the response - var cookie = new HttpCookie(cookieName, hash) - { - Expires = DateTime.Now.AddMinutes(minutesPersisted), - Domain = cookieDomain - }; - - if (GlobalSettings.UseSSL) - cookie.Secure = true; - - //ensure http only, this should only be able to be accessed via the server - cookie.HttpOnly = true; - - //rewrite the cooke - http.Response.Cookies.Set(cookie); - return true; - } - /// /// Creates a custom FormsAuthentication ticket with the data specified /// diff --git a/src/Umbraco.Core/Security/BackOfficeCookieAuthenticationProvider.cs b/src/Umbraco.Core/Security/BackOfficeCookieAuthenticationProvider.cs index 5247ea0af4..086d1a77bf 100644 --- a/src/Umbraco.Core/Security/BackOfficeCookieAuthenticationProvider.cs +++ b/src/Umbraco.Core/Security/BackOfficeCookieAuthenticationProvider.cs @@ -1,12 +1,38 @@ +using System; using System.Globalization; +using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; +using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; +using Umbraco.Core.Configuration; namespace Umbraco.Core.Security { public class BackOfficeCookieAuthenticationProvider : CookieAuthenticationProvider { + public override void ResponseSignOut(CookieResponseSignOutContext context) + { + base.ResponseSignOut(context); + + //Make sure the definitely all of these cookies are cleared when signing out with cookies + context.Response.Cookies.Append(UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName, "", new CookieOptions + { + Expires = DateTime.Now.AddYears(-1), + Path = "/" + }); + context.Response.Cookies.Append(Constants.Web.PreviewCookieName, "", new CookieOptions + { + Expires = DateTime.Now.AddYears(-1), + Path = "/" + }); + context.Response.Cookies.Append(Constants.Security.BackOfficeExternalCookieName, "", new CookieOptions + { + Expires = DateTime.Now.AddYears(-1), + Path = "/" + }); + } + /// /// Ensures that the culture is set correctly for the current back office user /// diff --git a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs index 85d6a0c715..f1d18b9d0f 100644 --- a/src/Umbraco.Core/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeSignInManager.cs @@ -53,6 +53,11 @@ namespace Umbraco.Core.Security switch (result) { case SignInStatus.Success: + _logger.WriteCore(TraceEventType.Information, 0, + string.Format( + "User: {0} logged in from IP address {1}", + userName, + _request.RemoteIpAddress), null, null); break; case SignInStatus.LockedOut: _logger.WriteCore(TraceEventType.Information, 0, diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index 772009e89a..712f98aeb2 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -100,6 +100,8 @@ namespace Umbraco.Core.Services xml.Add(new XAttribute("loginName", member.Username)); xml.Add(new XAttribute("email", member.Email)); + + xml.Add(new XAttribute("icon", member.ContentType.Icon)); return xml; } diff --git a/src/Umbraco.Tests/Mvc/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests/Mvc/RenderIndexActionSelectorAttributeTests.cs new file mode 100644 index 0000000000..3f327d3155 --- /dev/null +++ b/src/Umbraco.Tests/Mvc/RenderIndexActionSelectorAttributeTests.cs @@ -0,0 +1,173 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Logging; +using Umbraco.Core.Profiling; +using Umbraco.Web; +using Umbraco.Web.Models; +using Umbraco.Web.Mvc; +using Umbraco.Web.Routing; +using Umbraco.Web.Security; + +namespace Umbraco.Tests.Mvc +{ + [TestFixture] + public class RenderIndexActionSelectorAttributeTests + { + private MethodInfo GetRenderMvcControllerIndexMethodFromCurrentType(Type currType) + { + return currType.GetMethods().Single(x => + { + if (x.Name != "Index") return false; + if (x.ReturnParameter == null || x.ReturnParameter.ParameterType != typeof (ActionResult)) return false; + var p = x.GetParameters(); + if (p.Length != 1) return false; + if (p[0].ParameterType != typeof (RenderModel)) return false; + return true; + }); + } + + [Test] + public void Matches_Default_Index() + { + var attr = new RenderIndexActionSelectorAttribute(); + var req = new RequestContext(); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); + var umbCtx = UmbracoContext.EnsureContext( + Mock.Of(), + appCtx, + new Mock(null, null).Object, + Mock.Of(), + Enumerable.Empty(), + true); + var ctrl = new MatchesDefaultIndexController(umbCtx); + var controllerCtx = new ControllerContext(req, ctrl); + var result = attr.IsValidForRequest(controllerCtx, + GetRenderMvcControllerIndexMethodFromCurrentType(ctrl.GetType())); + + Assert.IsTrue(result); + } + + [Test] + public void Matches_Overriden_Index() + { + var attr = new RenderIndexActionSelectorAttribute(); + var req = new RequestContext(); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); + var umbCtx = UmbracoContext.EnsureContext( + Mock.Of(), + appCtx, + new Mock(null, null).Object, + Mock.Of(), + Enumerable.Empty(), + true); + var ctrl = new MatchesOverriddenIndexController(umbCtx); + var controllerCtx = new ControllerContext(req, ctrl); + var result = attr.IsValidForRequest(controllerCtx, + GetRenderMvcControllerIndexMethodFromCurrentType(ctrl.GetType())); + + Assert.IsTrue(result); + } + + [Test] + public void Matches_Custom_Index() + { + var attr = new RenderIndexActionSelectorAttribute(); + var req = new RequestContext(); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); + var umbCtx = UmbracoContext.EnsureContext( + Mock.Of(), + appCtx, + new Mock(null, null).Object, + Mock.Of(), + Enumerable.Empty(), + true); + var ctrl = new MatchesCustomIndexController(umbCtx); + var controllerCtx = new ControllerContext(req, ctrl); + var result = attr.IsValidForRequest(controllerCtx, + GetRenderMvcControllerIndexMethodFromCurrentType(ctrl.GetType())); + + Assert.IsFalse(result); + } + + [Test] + public void Matches_Async_Index_Same_Signature() + { + var attr = new RenderIndexActionSelectorAttribute(); + var req = new RequestContext(); + var appCtx = new ApplicationContext( + CacheHelper.CreateDisabledCacheHelper(), + new ProfilingLogger(Mock.Of(), Mock.Of())); + var umbCtx = UmbracoContext.EnsureContext( + Mock.Of(), + appCtx, + new Mock(null, null).Object, + Mock.Of(), + Enumerable.Empty(), + true); + var ctrl = new MatchesAsyncIndexController(umbCtx); + var controllerCtx = new ControllerContext(req, ctrl); + var result = attr.IsValidForRequest(controllerCtx, + GetRenderMvcControllerIndexMethodFromCurrentType(ctrl.GetType())); + + Assert.IsFalse(result); + } + + public class MatchesDefaultIndexController : RenderMvcController + { + public MatchesDefaultIndexController(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + } + + public class MatchesOverriddenIndexController : RenderMvcController + { + public MatchesOverriddenIndexController(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + + public override ActionResult Index(RenderModel model) + { + return base.Index(model); + } + } + + public class MatchesCustomIndexController : RenderMvcController + { + public MatchesCustomIndexController(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + + public ActionResult Index(RenderModel model, int page) + { + return base.Index(model); + } + } + + public class MatchesAsyncIndexController : RenderMvcController + { + public MatchesAsyncIndexController(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + + public new async Task Index(RenderModel model) + { + return await Task.FromResult(base.Index(model)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Mvc/ViewDataDictionaryExtensionTests.cs b/src/Umbraco.Tests/Mvc/ViewDataDictionaryExtensionTests.cs index c13818a03c..4c06d30796 100644 --- a/src/Umbraco.Tests/Mvc/ViewDataDictionaryExtensionTests.cs +++ b/src/Umbraco.Tests/Mvc/ViewDataDictionaryExtensionTests.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using System.Text; using System.Web.Mvc; using NUnit.Framework; diff --git a/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs b/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs index 31bdb616b0..7f38bf61a1 100644 --- a/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs @@ -8,6 +8,7 @@ using System.Web; using System.Web.Routing; using Moq; using Umbraco.Core; +using Umbraco.Core.Configuration; namespace Umbraco.Tests.TestHelpers { @@ -60,7 +61,7 @@ namespace Umbraco.Tests.TestHelpers //Cookie collection var cookieCollection = new HttpCookieCollection(); - cookieCollection.Add(new HttpCookie(Constants.Web.AuthCookieName, "FBA996E7-D6BE-489B-B199-2B0F3D2DD826")); + cookieCollection.Add(new HttpCookie("UMB_UCONTEXT", "FBA996E7-D6BE-489B-B199-2B0F3D2DD826")); //Request var requestMock = new Mock(); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index ef142d0d52..f3ed7d225b 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -181,6 +181,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js index 9ba64838c9..06b5f9496c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/macro.resource.js @@ -36,7 +36,7 @@ function macroResource($q, $http, umbRequestHelper) { * @methodOf umbraco.resources.macroResource * * @description - * Gets the result of a macro as html to display in the rich text editor + * Gets the result of a macro as html to display in the rich text editor or in the Grid * * @param {int} macroId The macro id to get parameters for * @param {int} pageId The current page id @@ -45,39 +45,17 @@ function macroResource($q, $http, umbRequestHelper) { */ getMacroResultAsHtmlForEditor: function (macroAlias, pageId, macroParamDictionary) { - //need to format the query string for the custom dictionary - var query = "macroAlias=" + macroAlias + "&pageId=" + pageId; - if (macroParamDictionary) { - var counter = 0; - _.each(macroParamDictionary, function (val, key) { - //check for null - val = val ? val : ""; - //need to detect if the val is a string or an object - if (!angular.isString(val)) { - //if it's not a string we'll send it through the json serializer - var json = angular.toJson(val); - //then we need to url encode it so that it's safe - val = encodeURIComponent(json); - } - else { - //we still need to encode the string, it could contain line breaks, etc... - val = encodeURIComponent(val); - } - - query += "¯oParams[" + counter + "].key=" + key + "¯oParams[" + counter + "].value=" + val; - counter++; - }); - } - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "macroApiBaseUrl", - "GetMacroResultAsHtmlForEditor", - query)), - 'Failed to retrieve macro result for macro with alias ' + macroAlias); + $http.post( + umbRequestHelper.getApiUrl( + "macroApiBaseUrl", + "GetMacroResultAsHtmlForEditor"), { + macroAlias: macroAlias, + pageId: pageId, + macroParams: macroParamDictionary + }), + 'Failed to retrieve macro result for macro with alias ' + macroAlias); } - }; } diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 56e77342b0..f2a5082231 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -521,3 +521,58 @@ height:1px; .umb-loader-wrapper.-bottom { bottom: 0; } + +// Helpers + +.strong { + font-weight: bold; +} + +.inline { + display: inline; +} + + +// Input label styles +// @Simon: not sure where to put this part yet +// --- TODO Needs to be divided into the right .less directories + + +// Titles for input fields +.input-label--title { + font-weight: bold; + color: @black; + + margin-bottom: 3px; +} + + +// Used for input checkmark fields +.input-label--small { + display: inline; + + font-size: 12px; + font-weight: bold; + color: fade(@black, 70); + + &:hover { + color: @black; + } +} + +input[type=checkbox]:checked + .input-label--small { + color: @blue; +} + + +// Use this for headers in the panels +.panel-dialog--header { + border-bottom: 1px solid @gray; + + margin: 10px 0; + padding-bottom: 10px; + + font-size: @fontSizeLarge; + font-weight: bold; + line-height: 20px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index f3025074cb..36c2812cc7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -42,6 +42,23 @@ bottom: 90px; } +.umb-mediapicker-upload { + display: -ms-flexbox; + display: -webkit-box; + display: -webkit-flex; + display: flex; + + .form-search { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + } + + .upload-button { + margin-left: 16px; + } +} + .umb-panel.editor-breadcrumb .umb-panel-body, .umb-panel.editor-breadcrumb .umb-bottom-bar { bottom: 31px !important; } diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index acb9644a97..082e8d079d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -40,8 +40,16 @@ padding: 10px; } -.umb-contentpicker small a { +.umb-contentpicker small { + + &:not(:last-child) { + padding-right: 3px; + border-right: 1px solid @grayMed; + } + + a { color: @gray; + } } /* CODEMIRROR DATATYPE */ diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index ec73e62c0c..f002566eed 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -14,6 +14,7 @@ @grayDarker: #222; @grayDark: #343434; @gray: #555; +@grayMed: #999; @grayLight: #d9d9d9; @grayLighter: #f8f8f8; @white: #fff; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html index cf9b5aba2d..8183189487 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html @@ -1,5 +1,7 @@ -
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 06b790b777..667dfd0f22 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -1,7 +1,7 @@ //this controller simply tells the dialogs service to open a mediaPicker window //with a specified callback, this callback will receive an object with a selection on it -function contentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper) { +function contentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location) { function trim(str, chr) { var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); @@ -49,6 +49,7 @@ function contentPickerController($scope, dialogService, entityResource, editorSt //the default pre-values var defaultConfig = { multiPicker: false, + showOpenButton: false, showEditButton: false, showPathOnHover: false, startNode: { @@ -65,14 +66,17 @@ function contentPickerController($scope, dialogService, entityResource, editorSt //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that! $scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false); + $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false); $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false); $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false); - + var entityType = $scope.model.config.startNode.type === "member" ? "Member" : $scope.model.config.startNode.type === "media" ? "Media" : "Document"; + $scope.allowOpenButton = entityType === "Document" || entityType === "Media"; + $scope.allowEditButton = entityType === "Document"; //the dialog options for the picker var dialogOptions = { @@ -167,6 +171,21 @@ function contentPickerController($scope, dialogService, entityResource, editorSt angularHelper.getCurrentForm($scope).$setDirty(); }; + $scope.showNode = function (index) { + var item = $scope.renderModel[index]; + var id = item.id; + var section = $scope.model.config.startNode.type.toLowerCase(); + + entityResource.getPath(id, entityType).then(function (path) { + navigationService.changeSection(section); + navigationService.showTree(section, { + tree: section, path: path, forceReload: false, activate: true + }); + var routePath = section + "/" + section + "/edit/" + id.toString(); + $location.path(routePath).search(""); + }); + } + $scope.add = function (item) { var currIds = _.map($scope.renderModel, function (i) { return i.id; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html index f2b741d8dc..9f5f3fb60f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html @@ -12,8 +12,10 @@ {{node.name}} -
- Edit + +
+ Open + Edit
@@ -38,7 +40,7 @@
You can only have {{model.config.maxNumber}} items selected
- + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index fbbf6b7bdd..9ded7dc8df 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -212,11 +212,23 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific $scope.reloadView($scope.contentId); } }); - }, 200)); + }, 1000)); - $scope.enterSearch = function($event) { + $scope.filterResults = function (ev) { + //13: enter + + switch (ev.keyCode) { + case 13: + $scope.options.pageNumber = 1; + $scope.actionInProgress = true; + $scope.reloadView($scope.contentId); + break; + } + }; + + $scope.enterSearch = function ($event) { $($event.target).next().focus(); - } + }; $scope.isAnythingSelected = function() { if ($scope.selection.length === 0) { @@ -461,4 +473,4 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific } -angular.module("umbraco").controller("Umbraco.PropertyEditors.ListViewController", listViewController); \ No newline at end of file +angular.module("umbraco").controller("Umbraco.PropertyEditors.ListViewController", listViewController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 4eabec7436..487ba77b97 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -62,7 +62,7 @@ diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 85ceffbc46..44b9f978ef 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -40,7 +40,8 @@ v4.5 true - + + @@ -1856,6 +1857,7 @@ + diff --git a/src/Umbraco.Web.UI/Umbraco/Images/editor/renderbody.gif b/src/Umbraco.Web.UI/Umbraco/Images/editor/renderbody.gif new file mode 100644 index 0000000000..1a84493fe9 Binary files /dev/null and b/src/Umbraco.Web.UI/Umbraco/Images/editor/renderbody.gif differ diff --git a/src/Umbraco.Web.UI/Umbraco/js/install.loader.js b/src/Umbraco.Web.UI/Umbraco/js/install.loader.js deleted file mode 100644 index 869521ec7d..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/js/install.loader.js +++ /dev/null @@ -1,17 +0,0 @@ -LazyLoad.js( [ - 'lib/jquery/jquery.min.js', - /* 1.1.5 */ - 'lib/angular/1.1.5/angular.min.js', - 'lib/angular/1.1.5/angular-cookies.min.js', - 'lib/angular/1.1.5/angular-mobile.min.js', - 'lib/angular/1.1.5/angular-mocks.js', - 'lib/angular/1.1.5/angular-sanitize.min.js', - 'lib/underscore/underscore-min.js', - 'js/umbraco.installer.js', - 'js/umbraco.directives.js' - ], function () { - jQuery(document).ready(function () { - angular.bootstrap(document, ['ngSanitize', 'umbraco.install', 'umbraco.directives.validation']); - }); - } -); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/Dashboard.Release.config b/src/Umbraco.Web.UI/config/Dashboard.Release.config index a1607fc88f..4a998b0b80 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.Release.config +++ b/src/Umbraco.Web.UI/config/Dashboard.Release.config @@ -72,7 +72,7 @@ views/dashboard/default/startupdashboardintro.html - +
diff --git a/src/Umbraco.Web.UI/umbraco/Install/Views/Index.cshtml b/src/Umbraco.Web.UI/umbraco/Install/Views/Index.cshtml index 8171aad9e2..666f6a5b5a 100644 --- a/src/Umbraco.Web.UI/umbraco/Install/Views/Index.cshtml +++ b/src/Umbraco.Web.UI/umbraco/Install/Views/Index.cshtml @@ -45,7 +45,20 @@

-

{{installer.feedback}}

+

{{installer.feedback}}

+ + +