Merge remote-tracking branch 'origin/7.4.0' into dev-v7-deploy

This commit is contained in:
Stephan
2015-12-03 12:21:01 +01:00
209 changed files with 4260 additions and 1432 deletions

1
.gitignore vendored
View File

@@ -131,3 +131,4 @@ src/*.boltdata/
src/umbraco.sln.ide/*
build/UmbracoCms.*/
src/.vs/
src/Umbraco.Web.UI/umbraco/js/install.loader.js

View File

@@ -26,7 +26,7 @@
<dependency id="HtmlAgilityPack" version="[1.4.9, 2.0.0)" />
<dependency id="Lucene.Net" version="[2.9.4.1, 3.0.0.0)" />
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
<dependency id="MySql.Data" version="[6.9.7, 7.0.0)" />
<dependency id="MySql.Data" version="[6.9.8, 7.0.0)" />
<dependency id="xmlrpcnet" version="[2.5.0, 3.0.0)" />
<dependency id="ClientDependency" version="[1.8.4, 2.0.0)" />
<dependency id="ClientDependency-Mvc5" version="[1.8.0, 2.0.0)" />

View File

@@ -62,13 +62,6 @@
views/dashboard/default/startupdashboardintro.html
</control>
</tab>
<tab caption="Change password" xdt:Locator="Match(caption)" xdt:Transform="Remove" />
<tab caption="Change Password" xdt:Locator="Match(caption)" xdt:Transform="Remove" />
<tab caption="Change Password" xdt:Transform="Insert">
<control showOnce="false" addPanel="false" panelCaption="">
views/dashboard/ChangePassword.html
</control>
</tab>
<tab caption="Last Edits" xdt:Locator="Match(caption)" xdt:Transform="Remove" />
</section>

View File

@@ -63,6 +63,7 @@ if ($project) {
if(Test-Path $umbracoBinFolder\MiniProfiler.dll) { Remove-Item $umbracoBinFolder\MiniProfiler.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\MySql.Data.dll) { Remove-Item $umbracoBinFolder\MySql.Data.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Newtonsoft.Json.dll) { Remove-Item $umbracoBinFolder\Newtonsoft.Json.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.Helpers.dll) { Remove-Item $umbracoBinFolder\System.Web.Helpers.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Net.Http.Formatting.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Formatting.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.Http.dll) { Remove-Item $umbracoBinFolder\System.Web.Http.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.Http.WebHost.dll) { Remove-Item $umbracoBinFolder\System.Web.Http.WebHost.dll -Force -Confirm:$false }

View File

@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.3.1")]
[assembly: AssemblyInformationalVersion("7.3.1")]
[assembly: AssemblyFileVersion("7.4.0")]
[assembly: AssemblyInformationalVersion("7.4.0")]

View File

@@ -11,7 +11,7 @@ namespace Umbraco.Core
/// <summary>
/// A resolver to return all IAction objects
/// </summary>
internal sealed class ActionsResolver : LazyManyObjectsResolverBase<ActionsResolver, IAction>
public sealed class ActionsResolver : LazyManyObjectsResolverBase<ActionsResolver, IAction>
{
/// <summary>
/// Constructor

View File

@@ -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<ApplicationContext>("Error determining if application is configured, returning false", ex);
return false;
}

View File

@@ -43,7 +43,7 @@
public const string Users = "users";
/// <summary>
/// Application alias for the users section.
/// Application alias for the forms section.
/// </summary>
public const string Forms = "forms";
}

View File

@@ -315,13 +315,13 @@ namespace Umbraco.Core
public const string TextboxAlias = "Umbraco.Textbox";
/// <summary>
/// Guid for the Textbox multiple datatype.
/// Guid for the Textarea datatype.
/// </summary>
[Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")]
public const string TextboxMultiple = "67DB8357-EF57-493E-91AC-936D305E0F2A";
/// <summary>
/// Alias for the Textbox multiple datatype.
/// Alias for the Textarea datatype.
/// </summary>
public const string TextboxMultipleAlias = "Umbraco.TextboxMultiple";

View File

@@ -0,0 +1,31 @@
namespace Umbraco.Core
{
public static partial class Constants
{
/// <summary>
/// Defines the identifiers for property-type groups conventions that are used within the Umbraco core.
/// </summary>
public static class PropertyTypeGroups
{
/// <summary>
/// Guid for a Image PropertyTypeGroup object.
/// </summary>
public const string Image = "79ED4D07-254A-42CF-8FA9-EBE1C116A596";
/// <summary>
/// Guid for a File PropertyTypeGroup object.
/// </summary>
public const string File = "50899F9C-023A-4466-B623-ABA9049885FE";
/// <summary>
/// Guid for a Image PropertyTypeGroup object.
/// </summary>
public const string Contents = "79995FA2-63EE-453C-A29B-2E66F324CDBE";
/// <summary>
/// Guid for a Image PropertyTypeGroup object.
/// </summary>
public const string Membership = "0756729D-D665-46E3-B84A-37ACEAA614F8";
}
}
}

View File

@@ -1,4 +1,7 @@
namespace Umbraco.Core
using System;
using System.ComponentModel;
namespace Umbraco.Core
{
public static partial class Constants
{
@@ -15,6 +18,8 @@
/// <summary>
/// The auth cookie name
/// </summary>
[Obsolete("DO NOT USE THIS, USE ISecuritySection.AuthCookieName, this will be removed in future versions")]
[EditorBrowsable(EditorBrowsableState.Never)]
public const string AuthCookieName = "UMB_UCONTEXT";
}

View File

@@ -154,7 +154,7 @@ namespace Umbraco.Core.IO
{
get
{
return IOHelper.ReturnPath("umbracoWebservicesPath", "~/umbraco/webservices");
return IOHelper.ReturnPath("umbracoWebservicesPath", Umbraco.EnsureEndsWith("/") + "webservices");
}
}

View File

@@ -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))

View File

@@ -538,6 +538,18 @@ namespace Umbraco.Core.Models
/// <param name="propertyGroupName">Name of the <see cref="PropertyGroup"/> to remove</param>
public void RemovePropertyGroup(string propertyGroupName)
{
// if no group exists with that name, do nothing
var group = PropertyGroups[propertyGroupName];
if (group == null) return;
// re-assign the group's properties to no group
foreach (var property in group.PropertyTypes)
{
property.PropertyGroupId = new Lazy<int>(() => 0);
_propertyTypes.Add(property);
}
// actually remove the group
PropertyGroups.RemoveItem(propertyGroupName);
OnPropertyChanged(PropertyGroupCollectionSelector);
}

View File

@@ -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<BackOfficeIdentityUser, UserData>()
.ConstructUsing((BackOfficeIdentityUser user) => new UserData(Guid.NewGuid().ToString("N"))) //this is the 'session id'
.ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id))
.ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections))
.ForMember(detail => detail.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)

View File

@@ -64,7 +64,7 @@ namespace Umbraco.Core.Models
[IgnoreDataMember]
public CultureInfo CultureInfo
{
get { return CultureInfo.CreateSpecificCulture(IsoCode); }
get { return CultureInfo.GetCultureInfo(IsoCode); }
}
}
}

View File

@@ -11,7 +11,7 @@ namespace Umbraco.Core.Models.Rdbms
internal class MigrationDto
{
[Column("id")]
[PrimaryKeyColumn(AutoIncrement = true)]
[PrimaryKeyColumn(AutoIncrement = true, IdentitySeed = 100)]
public int Id { get; set; }
[Column("name")]

View File

@@ -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;

View File

@@ -108,7 +108,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.MediaRecycleBin), CreateDate = DateTime.Now });
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -92, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-92", SortOrder = 35, UniqueId = new Guid("f0bc4bfb-b499-40d6-ba86-058885a5178c"), Text = "Label", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = new Guid("84c6b441-31df-4ffe-b67e-67d5bc3ae65a"), Text = "Upload", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textbox multiple", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textarea", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = new Guid("0cc0eba1-9960-42c9-bf9b-60e150b429ae"), Text = "Textstring", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = new Guid("ca90c950-0aff-4e72-b976-a30b1ac57dad"), Text = "Richtext editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
@@ -178,11 +178,11 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
private void CreateCmsPropertyTypeGroupData()
{
_database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid("79ED4D07-254A-42CF-8FA9-EBE1C116A596") });
_database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid("50899F9C-023A-4466-B623-ABA9049885FE") });
_database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid("79995FA2-63EE-453C-A29B-2E66F324CDBE") });
_database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) });
_database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) });
_database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) });
//membership property group
_database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid("0756729D-D665-46E3-B84A-37ACEAA614F8") });
_database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) });
}
private void CreateCmsPropertyTypeData()

View File

@@ -46,19 +46,19 @@ ON cmsContentType.nodeId = umbracoNode.id"))
// see BaseDataCreation... built-in groups have their own guids
if (data.grId == 3)
{
guid = new Guid("79ED4D07-254A-42CF-8FA9-EBE1C116A596");
guid = new Guid(Constants.PropertyTypeGroups.Image);
}
else if (data.grId == 4)
{
guid = new Guid("50899F9C-023A-4466-B623-ABA9049885FE");
guid = new Guid(Constants.PropertyTypeGroups.File);
}
else if (data.grId == 5)
{
guid = new Guid("79995FA2-63EE-453C-A29B-2E66F324CDBE");
guid = new Guid(Constants.PropertyTypeGroups.Contents);
}
else if (data.grId == 11)
{
guid = new Guid("0756729D-D665-46E3-B84A-37ACEAA614F8");
guid = new Guid(Constants.PropertyTypeGroups.Membership);
}
else
{
@@ -69,7 +69,7 @@ ON cmsContentType.nodeId = umbracoNode.id"))
}
// set the Unique Id to the one we've generated
Update.Table("cmsPropertyTypeGroup").Set(new { uniqueID = guid }).Where(new { id = data.ptId });
Update.Table("cmsPropertyTypeGroup").Set(new { uniqueID = guid }).Where(new { id = data.grId });
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Rdbms;
@@ -18,13 +19,28 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeTw
public override void Up()
{
Delete.FromTable("umbracoMigration").AllRows();
var migrations = Context.Database.Fetch<MigrationDto>(new Sql().Select("*").From<MigrationDto>(SqlSyntax));
foreach (var migration in migrations)
// Due to the delayed execution of migrations, we have to wrap this code in Execute.Code to ensure the previous
// migration steps (esp. creating the migrations table) have completed before trying to fetch migrations from
// this table.
List<MigrationDto> migrations = null;
Execute.Code(db =>
{
Insert.IntoTable("umbracoMigration")
.Row(new {name = migration.Name, createDate = migration.CreateDate, version = migration.Version});
}
migrations = Context.Database.Fetch<MigrationDto>(new Sql().Select("*").From<MigrationDto>(SqlSyntax));
return string.Empty;
});
Delete.FromTable("umbracoMigration").AllRows();
Execute.Code(database =>
{
if (migrations != null)
{
foreach (var migration in migrations)
{
database.Insert("umbracoMigration", "id", true,
new {name = migration.Name, createDate = migration.CreateDate, version = migration.Version});
}
}
return string.Empty;
});
}
public override void Down()

View File

@@ -31,31 +31,31 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne
//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<PropertyTypeDto>("SELECT * FROM cmsPropertyType WHERE propertyTypeGroupId > 0");
var propertyTypes = database.Fetch<dynamic>("SELECT * FROM cmsPropertyType WHERE propertyTypeGroupId > 0");
var propertyGroups = database.Fetch<PropertyTypeGroupDto>("WHERE id > 0");
foreach (var propertyType in propertyTypes)
{
// get the PropertyTypeGroup of the current PropertyType, skip if not found
var propertyTypeGroup = propertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyTypeGroupId);
var propertyTypeGroup = propertyGroups.FirstOrDefault(x => x.Id == propertyType.propertyTypeGroupId);
if (propertyTypeGroup == null) continue;
// if the PropretyTypeGroup belongs to the same content type as the PropertyType, then fine
if (propertyTypeGroup.ContentTypeNodeId == propertyType.ContentTypeId) continue;
if (propertyTypeGroup.ContentTypeNodeId == propertyType.contentTypeId) continue;
// else we want to assign the PropertyType to a proper PropertyTypeGroup
// ie one that does belong to the same content - look for it
var okPropertyTypeGroup = propertyGroups.FirstOrDefault(x =>
x.Text == propertyTypeGroup.Text && // same name
x.ContentTypeNodeId == propertyType.ContentTypeId); // but for proper content type
x.ContentTypeNodeId == propertyType.contentTypeId); // but for proper content type
if (okPropertyTypeGroup == null)
{
// does not exist, create a new PropertyTypeGroup,
var propertyGroup = new PropertyTypeGroupDto
{
ContentTypeNodeId = propertyType.ContentTypeId,
ContentTypeNodeId = propertyType.contentTypeId,
Text = propertyTypeGroup.Text,
SortOrder = propertyTypeGroup.SortOrder
};
@@ -66,14 +66,14 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne
propertyGroups.Add(propertyGroup);
// update the PropertyType to use the new PropertyTypeGroup
propertyType.PropertyTypeGroupId = id;
propertyType.propertyTypeGroupId = id;
}
else
{
// exists, update PropertyType to use the PropertyTypeGroup
propertyType.PropertyTypeGroupId = okPropertyTypeGroup.Id;
propertyType.propertyTypeGroupId = okPropertyTypeGroup.Id;
}
database.Update(propertyType);
database.Update("cmsPropertyType", "id", propertyType);
}
}

View File

@@ -172,12 +172,12 @@ namespace Umbraco.Core.Persistence.Repositories
((ContentType)entity).AddingEntity();
PersistNewBaseContentType(entity);
PersisteTemplates(entity, false);
PersistTemplates(entity, false);
entity.ResetDirtyProperties();
}
protected void PersisteTemplates(IContentType entity, bool clearAll)
protected void PersistTemplates(IContentType entity, bool clearAll)
{
// remove and insert, if required
Database.Delete<ContentTypeTemplateDto>("WHERE contentTypeNodeId = @Id", new { Id = entity.Id });
@@ -225,7 +225,7 @@ namespace Umbraco.Core.Persistence.Repositories
}
PersistUpdatedBaseContentType(entity);
PersisteTemplates(entity, true);
PersistTemplates(entity, true);
entity.ResetDirtyProperties();
}

View File

@@ -701,14 +701,16 @@ namespace Umbraco.Core.Persistence.Repositories
public RenderingEngine DetermineTemplateRenderingEngine(ITemplate template)
{
var engine = _templateConfig.DefaultRenderingEngine;
if (template.Content.IsNullOrWhiteSpace() == false && MasterPageHelper.IsMasterPageSyntax(template.Content))
var viewHelper = new ViewHelper(_viewsFileSystem);
if (!viewHelper.ViewExists(template))
{
//there is a design but its definitely a webforms design
return RenderingEngine.WebForms;
if (template.Content.IsNullOrWhiteSpace() == false && MasterPageHelper.IsMasterPageSyntax(template.Content))
{
//there is a design but its definitely a webforms design and we haven't got a MVC view already for it
return RenderingEngine.WebForms;
}
}
var viewHelper = new ViewHelper(_viewsFileSystem);
var masterPageHelper = new MasterPageHelper(_masterpagesFileSystem);
switch (engine)

View File

@@ -117,7 +117,7 @@ namespace Umbraco.Core.Persistence
public override void OnException(Exception x)
{
_logger.Info<UmbracoDatabase>(x.StackTrace);
_logger.Error<UmbracoDatabase>("Database exception occurred", x);
base.OnException(x);
}

View File

@@ -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);
}
/// <summary>
/// This clears the forms authentication cookie
/// </summary>
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
/// </summary>
/// <param name="response"></param>
[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 });
}
/// <summary>
/// This adds the forms authentication cookie for webapi since cookies are handled differently
/// </summary>
/// <param name="response"></param>
/// <param name="user"></param>
[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();
}
/// <summary>
/// Renews the Umbraco authentication ticket
/// This will force ticket renewal in the OWIN pipeline
/// </summary>
/// <param name="http"></param>
/// <returns></returns>
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;
}
/// <summary>
/// This will force ticket renewal in the OWIN pipeline
/// </summary>
/// <param name="http"></param>
/// <returns></returns>
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;
}
/// <summary>
@@ -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);
}
/// <summary>
/// Renews the forms authentication ticket & cookie
/// </summary>
/// <param name="http"></param>
/// <param name="cookieName"></param>
/// <param name="cookieDomain"></param>
/// <param name="minutesPersisted"></param>
/// <returns>true if there was a ticket to renew otherwise false if there was no ticket</returns>
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;
}
/// <summary>
/// Creates a custom FormsAuthentication ticket with the data specified
/// </summary>

View File

@@ -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 = "/"
});
}
/// <summary>
/// Ensures that the culture is set correctly for the current back office user
/// </summary>

View File

@@ -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,

View File

@@ -152,6 +152,9 @@ namespace Umbraco.Core.Services
{
var contentType = FindContentTypeByAlias(contentTypeAlias);
var content = new Content(name, parentId, contentType);
var parent = GetById(content.ParentId);
content.Path = string.Concat(parent.IfNotNull(x => x.Path, content.ParentId.ToString()), ",", content.Id);
if (Creating.IsRaisedEventCancelled(new NewEventArgs<IContent>(content, contentTypeAlias, parentId), this))
{
@@ -190,8 +193,11 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IContent"/></returns>
public IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0)
{
if (parent == null) throw new ArgumentNullException("parent");
var contentType = FindContentTypeByAlias(contentTypeAlias);
var content = new Content(name, parent, contentType);
content.Path = string.Concat(parent.Path, ",", content.Id);
if (Creating.IsRaisedEventCancelled(new NewEventArgs<IContent>(content, contentTypeAlias, parent), this))
{
@@ -225,7 +231,7 @@ namespace Umbraco.Core.Services
public IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0)
{
var contentType = FindContentTypeByAlias(contentTypeAlias);
var content = new Content(name, parentId, contentType);
var content = new Content(name, parentId, contentType);
//NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found
// out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now.
@@ -276,6 +282,8 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IContent"/></returns>
public IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0)
{
if (parent == null) throw new ArgumentNullException("parent");
var contentType = FindContentTypeByAlias(contentTypeAlias);
var content = new Content(name, parent, contentType);

View File

@@ -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;
}

View File

@@ -65,6 +65,8 @@ namespace Umbraco.Core.Services
{
var mediaType = FindMediaTypeByAlias(mediaTypeAlias);
var media = new Models.Media(name, parentId, mediaType);
var parent = GetById(media.ParentId);
media.Path = string.Concat(parent.IfNotNull(x => x.Path, media.ParentId.ToString()), ",", media.Id);
if (Creating.IsRaisedEventCancelled(new NewEventArgs<IMedia>(media, mediaTypeAlias, parentId), this))
{
@@ -97,8 +99,12 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IMedia"/></returns>
public IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0)
{
if (parent == null) throw new ArgumentNullException("parent");
var mediaType = FindMediaTypeByAlias(mediaTypeAlias);
var media = new Models.Media(name, parent, mediaType);
media.Path = string.Concat(parent.Path, ",", media.Id);
if (Creating.IsRaisedEventCancelled(new NewEventArgs<IMedia>(media, mediaTypeAlias, parent), this))
{
media.WasCancelled = true;
@@ -186,6 +192,8 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IMedia"/></returns>
public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0)
{
if (parent == null) throw new ArgumentNullException("parent");
var mediaType = FindMediaTypeByAlias(mediaTypeAlias);
var media = new Models.Media(name, parent, mediaType);

View File

@@ -0,0 +1,51 @@
using System.Globalization;
using System.Threading;
namespace Umbraco.Core
{
static class ThreadExtensions
{
public static void SanitizeThreadCulture(this Thread thread)
{
// get the current culture
var currentCulture = CultureInfo.CurrentCulture;
// at the top of any culture should be the invariant culture - find it
// doing an .Equals comparison ensure that we *will* find it and not loop
// endlessly
var invariantCulture = currentCulture;
while (invariantCulture.Equals(CultureInfo.InvariantCulture) == false)
invariantCulture = invariantCulture.Parent;
// now that invariant culture should be the same object as CultureInfo.InvariantCulture
// yet for some reasons, sometimes it is not - and this breaks anything that loops on
// culture.Parent until a reference equality to CultureInfo.InvariantCulture. See, for
// example, the following code in PerformanceCounterLib.IsCustomCategory:
//
// CultureInfo culture = CultureInfo.CurrentCulture;
// while (culture != CultureInfo.InvariantCulture)
// {
// library = GetPerformanceCounterLib(machine, culture);
// if (library.IsCustomCategory(category))
// return true;
// culture = culture.Parent;
// }
//
// The reference comparisons never succeeds, hence the loop never ends, and the
// application hangs.
//
// granted, that comparison should probably be a .Equals comparison, but who knows
// how many times the framework assumes that it can do a reference comparison? So,
// better fix the cultures.
if (ReferenceEquals(invariantCulture, CultureInfo.InvariantCulture))
return;
// if we do not have equality, fix cultures by replacing them with a culture with
// the same name, but obtained here and now, with a proper invariant top culture
thread.CurrentCulture = CultureInfo.GetCultureInfo(thread.CurrentCulture.Name);
thread.CurrentUICulture = CultureInfo.GetCultureInfo(thread.CurrentUICulture.Name);
}
}
}

View File

@@ -86,7 +86,7 @@
</Reference>
<Reference Include="MySql.Data">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\MySql.Data.6.9.7\lib\net45\MySql.Data.dll</HintPath>
<HintPath>..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll</HintPath>
@@ -1290,6 +1290,7 @@
<Compile Include="Sync\ApplicationUrlHelper.cs" />
<Compile Include="Sync\RefreshMethodType.cs" />
<Compile Include="Sync\ServerMessengerBase.cs" />
<Compile Include="ThreadExtensions.cs" />
<Compile Include="TopologicalSorter.cs" />
<Compile Include="Strings\DefaultUrlSegmentProvider.cs" />
<Compile Include="Strings\IUrlSegmentProvider.cs" />
@@ -1332,6 +1333,9 @@
<Compile Include="Constants-PropertyEditors.cs">
<DependentUpon>Constants.cs</DependentUpon>
</Compile>
<Compile Include="Constants-PropertyTypeGroups.cs">
<DependentUpon>Constants.cs</DependentUpon>
</Compile>
<Compile Include="Constants-Packaging.cs">
<DependentUpon>Constants.cs</DependentUpon>
</Compile>

View File

@@ -2,6 +2,7 @@
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Web;
using System.Web.Hosting;
using log4net;
@@ -64,6 +65,7 @@ namespace Umbraco.Core
/// <param name="e"></param>
protected void Application_Start(object sender, EventArgs e)
{
Thread.CurrentThread.SanitizeThreadCulture();
StartApplication(sender, e);
}

View File

@@ -14,7 +14,7 @@
<package id="Microsoft.Owin.Security.Cookies" version="3.0.1" targetFramework="net45" />
<package id="Microsoft.Owin.Security.OAuth" version="3.0.1" targetFramework="net45" />
<package id="MiniProfiler" version="2.1.0" targetFramework="net45" />
<package id="MySql.Data" version="6.9.7" targetFramework="net45" />
<package id="MySql.Data" version="6.9.8" targetFramework="net45" />
<package id="Newtonsoft.Json" version="6.0.8" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" />
<package id="semver" version="1.1.2" targetFramework="net45" />

View File

@@ -56,7 +56,7 @@
<appSettings>
<add key="umbracoConfigurationStatus" value="6.0.0" />
<add key="umbracoReservedUrls" value="~/config/splashes/booting.aspx,~/install/default.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd" />
<add key="umbracoReservedPaths" value="~/umbraco,~/install/" />
<add key="umbracoReservedPaths" value="~/install/" />
<add key="umbracoPath" value="~/umbraco" />
<add key="umbracoHideTopLevelNodeFromPath" value="true" />
<add key="umbracoUseDirectoryUrls" value="false" />
@@ -203,4 +203,4 @@
</assemblyBinding>
</runtime>
</configuration>
</configuration>

View File

@@ -28,7 +28,7 @@
</control>
</tab>
<tab caption="Examine Management">
<control>/umbraco/dashboard/ExamineManagement.ascx</control>
<control>dashboard/ExamineManagement.ascx</control>
</tab>
</section>
@@ -85,7 +85,7 @@
</control>
</tab>
<tab caption="Last Edits">
<control addPanel="true" MaxRecords="30">/umbraco/dashboard/latestEdits.ascx</control>
<control addPanel="true" MaxRecords="30">dashboard/latestEdits.ascx</control>
</tab>
<tab caption="Change Password">
<control addPanel="true">
@@ -104,11 +104,11 @@
views/dashboard/members/membersdashboardintro.html
</control>
<control showOnce="true" addPanel="true" panelCaption="">
/umbraco/members/membersearch.ascx
members/membersearch.ascx
</control>
<control showOnce="true" addPanel="true" panelCaption="">
views/dashboard/members/membersdashboardvideos.html
</control>
</tab>
</section>
</dashBoard>
</dashBoard>

View File

@@ -173,7 +173,8 @@ namespace Umbraco.Tests.Logging
Console.WriteLine("Flushed {0} events during shutdown", numberLoggedAfterClose - numberLoggedBeforeClose);
}
[Test, Explicit("Long-running")]
[NUnit.Framework.Ignore("Keeps failing very randomly")]
[Test]
public void WillShutdownIfBufferCannotBeFlushedFastEnough()
{
const int testSize = 250;

View File

@@ -482,7 +482,7 @@ ALTER TABLE umbracoUser2NodePermission ADD CONSTRAINT PK_umbracoUser2NodePermiss
INSERT INTO umbracoNode (id, trashed, parentID, nodeUser, level, path, sortOrder, uniqueID, text, nodeObjectType, createDate) VALUES
(-92, 0, -1, 0, 11, '-1,-92', 37, 'f0bc4bfb-b499-40d6-ba86-058885a5178c', 'Label', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'),
(-90, 0, -1, 0, 11, '-1,-90', 35, '84c6b441-31df-4ffe-b67e-67d5bc3ae65a', 'Upload', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'),
(-89, 0, -1, 0, 11, '-1,-89', 34, 'c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3', 'Textbox multiple', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'),
(-89, 0, -1, 0, 11, '-1,-89', 34, 'c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3', 'Textarea', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'),
(-88, 0, -1, 0, 11, '-1,-88', 33, '0cc0eba1-9960-42c9-bf9b-60e150b429ae', 'Textstring', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'),
(-87, 0, -1, 0, 11, '-1,-87', 32, 'ca90c950-0aff-4e72-b976-a30b1ac57dad', 'Richtext editor', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'),
(-51, 0, -1, 0, 11, '-1,-51', 4, '2e6d3631-066e-44b8-aec4-96f09099b2b5', 'Numeric', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '2004/09/30 14:01:49.920'),

View File

@@ -624,7 +624,7 @@ ALTER TABLE [umbracoNode] DROP CONSTRAINT [FK_umbracoNode_umbracoNode]
SET IDENTITY_INSERT [umbracoNode] ON
INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-92, 0, -1, 0, 11, N'-1,-92', 37, 'f0bc4bfb-b499-40d6-ba86-058885a5178c', N'Label', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920')
INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-90, 0, -1, 0, 11, N'-1,-90', 35, '84c6b441-31df-4ffe-b67e-67d5bc3ae65a', N'Upload', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920')
INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-89, 0, -1, 0, 11, N'-1,-89', 34, 'c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3', N'Textbox multiple', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920')
INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-89, 0, -1, 0, 11, N'-1,-89', 34, 'c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3', N'Textarea', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920')
INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-88, 0, -1, 0, 11, N'-1,-88', 33, '0cc0eba1-9960-42c9-bf9b-60e150b429ae', N'Textstring', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920')
INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-87, 0, -1, 0, 11, N'-1,-87', 32, 'ca90c950-0aff-4e72-b976-a30b1ac57dad', N'Richtext editor', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920')
INSERT INTO [umbracoNode] ([id], [trashed], [parentID], [nodeUser], [level], [path], [sortOrder], [uniqueID], [text], [nodeObjectType], [createDate]) VALUES (-51, 0, -1, 0, 11, N'-1,-51', 4, '2e6d3631-066e-44b8-aec4-96f09099b2b5', N'Numeric', '30a2a501-1978-4ddb-a57b-f7efed43ba3c', '20040930 14:01:49.920')

View File

@@ -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<ILogger>(), Mock.Of<IProfiler>()));
var umbCtx = UmbracoContext.EnsureContext(
Mock.Of<HttpContextBase>(),
appCtx,
new Mock<WebSecurity>(null, null).Object,
Mock.Of<IUmbracoSettingsSection>(),
Enumerable.Empty<IUrlProvider>(),
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<ILogger>(), Mock.Of<IProfiler>()));
var umbCtx = UmbracoContext.EnsureContext(
Mock.Of<HttpContextBase>(),
appCtx,
new Mock<WebSecurity>(null, null).Object,
Mock.Of<IUmbracoSettingsSection>(),
Enumerable.Empty<IUrlProvider>(),
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<ILogger>(), Mock.Of<IProfiler>()));
var umbCtx = UmbracoContext.EnsureContext(
Mock.Of<HttpContextBase>(),
appCtx,
new Mock<WebSecurity>(null, null).Object,
Mock.Of<IUmbracoSettingsSection>(),
Enumerable.Empty<IUrlProvider>(),
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<ILogger>(), Mock.Of<IProfiler>()));
var umbCtx = UmbracoContext.EnsureContext(
Mock.Of<HttpContextBase>(),
appCtx,
new Mock<WebSecurity>(null, null).Object,
Mock.Of<IUmbracoSettingsSection>(),
Enumerable.Empty<IUrlProvider>(),
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<ActionResult> Index(RenderModel model)
{
return await Task.FromResult(base.Index(model));
}
}
}
}

View File

@@ -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;

View File

@@ -141,6 +141,20 @@ namespace Umbraco.Tests.Services
Assert.AreEqual(20, contentService.CountDescendants(parent.Id));
}
[Test]
public void GetAncestors_Returns_Empty_List_When_Path_Is_Null()
{
// Arrange
var contentService = ServiceContext.ContentService;
// Act
var current = new Mock<IContent>();
var res = contentService.GetAncestors(current.Object);
// Assert
Assert.IsEmpty(res);
}
[Test]
public void Tags_For_Entity_Are_Not_Exposed_Via_Tag_Api_When_Content_Is_Recycled()
{

View File

@@ -1173,6 +1173,8 @@ namespace Umbraco.Tests.Services
{
var service = ServiceContext.ContentTypeService;
var basePage = (IContentType)MockedContentTypes.CreateBasicContentType();
basePage.AddPropertyGroup("Content");
basePage.AddPropertyGroup("Meta");
service.Save(basePage);
var authorPropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext, "author")
@@ -1184,6 +1186,16 @@ namespace Umbraco.Tests.Services
DataTypeDefinitionId = -88
};
var authorAdded = basePage.AddPropertyType(authorPropertyType, "Content");
var titlePropertyType = new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext, "title")
{
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeDefinitionId = -88
};
var titleAdded = basePage.AddPropertyType(authorPropertyType, "Meta");
service.Save(basePage);
basePage = service.GetContentType(basePage.Id);

View File

@@ -0,0 +1,109 @@
using Moq;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Tests.Templates
{
[TestFixture]
public class TemplateRepositoryTests
{
private readonly Mock<IDatabaseUnitOfWork> _unitOfWorkMock = new Mock<IDatabaseUnitOfWork>();
private readonly Mock<CacheHelper> _cacheMock = new Mock<CacheHelper>();
private TemplateRepository _templateRepository;
private readonly Mock<IFileSystem> _viewFileSystemMock = new Mock<IFileSystem>();
private readonly Mock<IFileSystem> _masterpageFileSystemMock = new Mock<IFileSystem>();
private readonly Mock<ITemplatesSection> _templateConfigMock = new Mock<Core.Configuration.UmbracoSettings.ITemplatesSection>();
[SetUp]
public void Setup()
{
var loggerMock = new Mock<ILogger>();
var sqlSyntaxMock = new Mock<ISqlSyntaxProvider>();
_templateRepository = new TemplateRepository(_unitOfWorkMock.Object, _cacheMock.Object, loggerMock.Object, sqlSyntaxMock.Object, _masterpageFileSystemMock.Object, _viewFileSystemMock.Object, _templateConfigMock.Object);
}
[Test]
public void DetermineTemplateRenderingEngine_Returns_MVC_When_ViewFile_Exists_And_Content_Has_Webform_Markup()
{
// Project in MVC mode
_templateConfigMock.Setup(x => x.DefaultRenderingEngine).Returns(RenderingEngine.Mvc);
// Template has masterpage content
var templateMock = new Mock<ITemplate>();
templateMock.Setup(x => x.Alias).Returns("Something");
templateMock.Setup(x => x.Content).Returns("<asp:Content />");
// but MVC View already exists
_viewFileSystemMock.Setup(x => x.FileExists(It.IsAny<string>())).Returns(true);
var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object);
Assert.AreEqual(RenderingEngine.Mvc, res);
}
[Test]
public void DetermineTemplateRenderingEngine_Returns_WebForms_When_ViewFile_Doesnt_Exist_And_Content_Has_Webform_Markup()
{
// Project in MVC mode
_templateConfigMock.Setup(x => x.DefaultRenderingEngine).Returns(RenderingEngine.Mvc);
// Template has masterpage content
var templateMock = new Mock<ITemplate>();
templateMock.Setup(x => x.Alias).Returns("Something");
templateMock.Setup(x => x.Content).Returns("<asp:Content />");
// MVC View doesn't exist
_viewFileSystemMock.Setup(x => x.FileExists(It.IsAny<string>())).Returns(false);
var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object);
Assert.AreEqual(RenderingEngine.WebForms, res);
}
[Test]
public void DetermineTemplateRenderingEngine_Returns_WebForms_When_MasterPage_Exists_And_In_Mvc_Mode()
{
// Project in MVC mode
_templateConfigMock.Setup(x => x.DefaultRenderingEngine).Returns(RenderingEngine.Mvc);
var templateMock = new Mock<ITemplate>();
templateMock.Setup(x => x.Alias).Returns("Something");
// but masterpage already exists
_viewFileSystemMock.Setup(x => x.FileExists(It.IsAny<string>())).Returns(false);
_masterpageFileSystemMock.Setup(x => x.FileExists(It.IsAny<string>())).Returns(true);
var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object);
Assert.AreEqual(RenderingEngine.WebForms, res);
}
[Test]
public void DetermineTemplateRenderingEngine_Returns_Mvc_When_ViewPage_Exists_And_In_Webforms_Mode()
{
// Project in WebForms mode
_templateConfigMock.Setup(x => x.DefaultRenderingEngine).Returns(RenderingEngine.WebForms);
var templateMock = new Mock<ITemplate>();
templateMock.Setup(x => x.Alias).Returns("Something");
// but MVC View already exists
_viewFileSystemMock.Setup(x => x.FileExists(It.IsAny<string>())).Returns(true);
_masterpageFileSystemMock.Setup(x => x.FileExists(It.IsAny<string>())).Returns(false);
var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object);
Assert.AreEqual(RenderingEngine.Mvc, res);
}
}
}

View File

@@ -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<HttpRequestBase>();

View File

@@ -181,6 +181,7 @@
<Compile Include="Logging\AsyncRollingFileAppenderTest.cs" />
<Compile Include="Logging\DebugAppender.cs" />
<Compile Include="Logging\ParallelForwarderTest.cs" />
<Compile Include="Mvc\RenderIndexActionSelectorAttributeTests.cs" />
<Compile Include="Persistence\Repositories\AuditRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\DomainRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\PartialViewRepositoryTests.cs" />
@@ -362,6 +363,7 @@
<Compile Include="Services\MacroServiceTests.cs" />
<Compile Include="Services\UserServiceTests.cs" />
<Compile Include="Manifest\ManifestParserTests.cs" />
<Compile Include="Templates\TemplateRepositoryTests.cs" />
<Compile Include="TestHelpers\BaseSeleniumTest.cs" />
<Compile Include="Integration\InstallPackage.cs" />
<Compile Include="CoreXml\NavigableNavigatorTests.cs" />

View File

@@ -22,7 +22,7 @@
"rgrove-lazyload": "*",
"bootstrap-social": "~4.8.0",
"jquery": "2.0.3",
"jquery-ui": "1.10.3",
"jquery-ui": "1.11.4",
"angular-dynamic-locale": "~0.1.27",
"ng-file-upload": "~7.3.8",
"tinymce": "~4.1.10",

View File

@@ -476,12 +476,11 @@ module.exports = function (grunt) {
files: ['css/font-awesome.min.css', 'fonts/*']
},
"jquery": {
keepExpandedHierarchy: false,
files: ['dist/jquery.min.js', 'dist/jquery.min.map']
},
files: ['jquery.min.js', 'jquery.min.map']
},
'jquery-ui': {
keepExpandedHierarchy: false,
files: ['ui/minified/jquery-ui.min.js']
files: ['jquery-ui.min.js']
},
'tinymce': {
files: ['plugins/**', 'themes/**', 'tinymce.min.js']

View File

@@ -0,0 +1,180 @@
/*!
* jQuery UI Touch Punch 0.2.3
*
* Copyright 20112014, Dave Furfero
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Depends:
* jquery.ui.widget.js
* jquery.ui.mouse.js
*/
(function ($) {
// Detect touch support
$.support.touch = 'ontouchend' in document;
// Ignore browsers without touch support
if (!$.support.touch) {
return;
}
var mouseProto = $.ui.mouse.prototype,
_mouseInit = mouseProto._mouseInit,
_mouseDestroy = mouseProto._mouseDestroy,
touchHandled;
/**
* Simulate a mouse event based on a corresponding touch event
* @param {Object} event A touch event
* @param {String} simulatedType The corresponding mouse event
*/
function simulateMouseEvent (event, simulatedType) {
// Ignore multi-touch events
if (event.originalEvent.touches.length > 1) {
return;
}
event.preventDefault();
var touch = event.originalEvent.changedTouches[0],
simulatedEvent = document.createEvent('MouseEvents');
// Initialize the simulated mouse event using the touch event's coordinates
simulatedEvent.initMouseEvent(
simulatedType, // type
true, // bubbles
true, // cancelable
window, // view
1, // detail
touch.screenX, // screenX
touch.screenY, // screenY
touch.clientX, // clientX
touch.clientY, // clientY
false, // ctrlKey
false, // altKey
false, // shiftKey
false, // metaKey
0, // button
null // relatedTarget
);
// Dispatch the simulated event to the target element
event.target.dispatchEvent(simulatedEvent);
}
/**
* Handle the jQuery UI widget's touchstart events
* @param {Object} event The widget element's touchstart event
*/
mouseProto._touchStart = function (event) {
var self = this;
// Ignore the event if another widget is already being handled
if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) {
return;
}
// Set the flag to prevent other widgets from inheriting the touch event
touchHandled = true;
// Track movement to determine if interaction was a click
self._touchMoved = false;
// Simulate the mouseover event
simulateMouseEvent(event, 'mouseover');
// Simulate the mousemove event
simulateMouseEvent(event, 'mousemove');
// Simulate the mousedown event
simulateMouseEvent(event, 'mousedown');
};
/**
* Handle the jQuery UI widget's touchmove events
* @param {Object} event The document's touchmove event
*/
mouseProto._touchMove = function (event) {
// Ignore event if not handled
if (!touchHandled) {
return;
}
// Interaction was not a click
this._touchMoved = true;
// Simulate the mousemove event
simulateMouseEvent(event, 'mousemove');
};
/**
* Handle the jQuery UI widget's touchend events
* @param {Object} event The document's touchend event
*/
mouseProto._touchEnd = function (event) {
// Ignore event if not handled
if (!touchHandled) {
return;
}
// Simulate the mouseup event
simulateMouseEvent(event, 'mouseup');
// Simulate the mouseout event
simulateMouseEvent(event, 'mouseout');
// If the touch interaction did not move, it should trigger a click
if (!this._touchMoved) {
// Simulate the click event
simulateMouseEvent(event, 'click');
}
// Unset the flag to allow other widgets to inherit the touch event
touchHandled = false;
};
/**
* A duck punch of the $.ui.mouse _mouseInit method to support touch events.
* This method extends the widget with bound touch event handlers that
* translate touch events to mouse events and pass them to the widget's
* original mouse event handling methods.
*/
mouseProto._mouseInit = function () {
var self = this;
// Delegate the touch handlers to the widget's element
self.element.bind({
touchstart: $.proxy(self, '_touchStart'),
touchmove: $.proxy(self, '_touchMove'),
touchend: $.proxy(self, '_touchEnd')
});
// Call the original $.ui.mouse init method
_mouseInit.call(self);
};
/**
* Remove the touch event handlers
*/
mouseProto._mouseDestroy = function () {
var self = this;
// Delegate the touch handlers to the widget's element
self.element.unbind({
touchstart: $.proxy(self, '_touchStart'),
touchmove: $.proxy(self, '_touchMove'),
touchend: $.proxy(self, '_touchEnd')
});
// Call the original $.ui.mouse destroy method
_mouseDestroy.call(self);
};
})(jQuery);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,22 @@
<svg class="illustration" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 140 75" enable-background="new 0 0 140 75" xml:space="preserve">
<path fill="#FFFFFF" stroke="#D9D9D9" d="M132.4,68.6H43.1c-0.5,0-1-0.4-1-1V7.1c0-0.6,0.5-1,1-1h89.4c0.6,0,1,0.4,1,1v60.5
C133.4,68.2,133,68.6,132.4,68.6z"/>
<line fill="none" stroke="#D9D9D9" x1="42.1" y1="16.4" x2="133.4" y2="16.4"/>
<circle fill="#df4949" cx="47" cy="11.6" r="1.5"/>
<circle fill="#F4DD33" cx="52.5" cy="11.6" r="1.5"/>
<circle fill="#49df5c" cx="58" cy="11.6" r="1.5"/>
<g>
<g>
<path fill="none" stroke="#D9D9D9" stroke-linecap="round" stroke-linejoin="round" d="M64.2,44.1c-12.7-11.9-32.7-11.7-45.1,0.8"
/>
<g>
<polygon fill="#D9D9D9" points="64.4,38.9 63.4,39 63.9,43.9 58.9,44 59,45 65,44.9 "/>
</g>
</g>
</g>
<rect x="4.8" y="23.9" fill="#FFFFFF" stroke="#D9D9D9" stroke-linecap="round" stroke-linejoin="round" width="30" height="25"/>
<path fill="#FFFFFF" stroke="#D9D9D9" stroke-linecap="round" stroke-linejoin="round" d="M4.8,48.9c0,0,0-12.5,15-12.5
s15,12.5,15,12.5H4.8z"/>
<circle fill="#FFFFFF" stroke="#D9D9D9" stroke-linecap="round" stroke-linejoin="round" cx="28.7" cy="30.8" r="2.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -24,7 +24,7 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider',
];
$scope.previewDevice = $scope.devices[0];
var apiController = "/Umbraco/Api/Canvasdesigner/";
var apiController = "../Api/Canvasdesigner/";
/*****************************************************************************/
/* Preview devices */
@@ -40,7 +40,7 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider',
/*****************************************************************************/
$scope.exitPreview = function () {
window.top.location.href = "/umbraco/endPreview.aspx?redir=%2f" + $scope.pageId;
window.top.location.href = "../endPreview.aspx?redir=%2f" + $scope.pageId;
};
/*****************************************************************************/

View File

@@ -2,9 +2,9 @@
<html lang="en">
<head>
<title>Umbraco Canvas Designer</title>
<link href="/Umbraco/assets/css/canvasdesigner.css" type="text/css" rel="stylesheet" />
<link href="/Umbraco/lib/spectrum/spectrum.css" type="text/css" rel="stylesheet" />
<link href="/Umbraco/lib/jquery-ui/jquery-ui-1.10.4.custom.min.css" type="text/css" rel="stylesheet" />
<link href="../assets/css/canvasdesigner.css" type="text/css" rel="stylesheet" />
<link href="../lib/spectrum/spectrum.css" type="text/css" rel="stylesheet" />
<link href="../lib/jquery-ui/jquery-ui-1.10.4.custom.min.css" type="text/css" rel="stylesheet" />
</head>
<body id="canvasdesignerPanel" ng-mouseover="outlinePositionHide()" ng-class="{ leftOpen: (showStyleEditor || showPalettePicker) && !showDevicesPreview }" ng-controller="Umbraco.canvasdesignerController">
@@ -20,7 +20,7 @@
<div class="fix-left-menu selected">
<div class="avatar">
<img ng-src="/umbraco/assets/img/application/logo.png">
<img ng-src="../assets/img/application/logo.png">
</div>
<ul class="sections" ng-class="{selected: showDevicesPreview}">
@@ -125,7 +125,7 @@
<div class="canvasdesigner-panel-container" ng-show="categoriesVisibility[category] === true">
<div class="canvasdesigner-panel-property" ng-repeat="item in configItem.editors" ng-show="item.category == category">
<h5>{{item.name}} <i class="icon icon-help-alt"></i></h5>
<div ng-include="'/Umbraco/preview/editors/' + item.type + '.html'"></div>
<div ng-include="'../preview/editors/' + item.type + '.html'"></div>
</div>
</div>
</div>
@@ -154,8 +154,8 @@
<p>Styles saved and published</p>
</div>
<script src="/umbraco/lib/rgrove-lazyload/lazyload.js"></script>
<script src="/umbraco/js/canvasdesigner.loader.js"></script>
<script src="../lib/rgrove-lazyload/lazyload.js"></script>
<script src="../js/canvasdesigner.loader.js"></script>
</body>

View File

@@ -146,6 +146,8 @@ angular.module('umbraco.directives')
.directive('onOutsideClick', function ($timeout) {
return function (scope, element, attrs) {
var eventBindings = [];
function oneTimeClick(event) {
var el = event.target.nodeName;
//ignore link and button clicks
@@ -173,11 +175,33 @@ angular.module('umbraco.directives')
scope.$apply(attrs.onOutsideClick);
}
$timeout(function(){
$(document).on("click", oneTimeClick);
if ("bindClickOn" in attrs) {
eventBindings.push(scope.$watch(function() {
return attrs.bindClickOn;
}, function(newValue) {
if (newValue === "true") {
$(document).on("click", oneTimeClick);
} else {
$(document).off("click", oneTimeClick);
}
}));
} else {
$(document).on("click", oneTimeClick);
}
scope.$on("$destroy", function() {
$(document).off("click", oneTimeClick);
// unbind watchers
for (var e in eventBindings) {
eventBindings[e]();
}
});
}); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution.

View File

@@ -101,7 +101,8 @@ angular.module("umbraco.directives")
relative_urls: false,
toolbar: toolbar,
content_css: stylesheets.join(','),
style_formats: styleFormats
style_formats: styleFormats,
autoresize_bottom_margin: 0
};
@@ -122,11 +123,6 @@ angular.module("umbraco.directives")
editor.getBody().setAttribute('spellcheck', true);
//hide toolbar by default
$(editor.editorContainer)
.find(".mce-toolbar")
.css("visibility", "hidden");
//force overflow to hidden to prevent no needed scroll
editor.getBody().style.overflow = "hidden";
@@ -137,32 +133,6 @@ angular.module("umbraco.directives")
}, 400);
});
// pin toolbar to top of screen if we have focus and it scrolls off the screen
var pinToolbar = function () {
var _toolbar = $(editor.editorContainer).find(".mce-toolbar");
var toolbarHeight = _toolbar.height();
var _tinyMce = $(editor.editorContainer);
var tinyMceRect = _tinyMce[0].getBoundingClientRect();
var tinyMceTop = tinyMceRect.top;
var tinyMceBottom = tinyMceRect.bottom;
if (tinyMceTop < 100 && (tinyMceBottom > (100 + toolbarHeight))) {
_toolbar
.css("visibility", "visible")
.css("position", "fixed")
.css("top", "100px")
.css("margin-top", "0");
} else {
_toolbar
.css("visibility", "visible")
.css("position", "absolute")
.css("top", "auto")
.css("margin-top", (-toolbarHeight - 2) + "px");
}
};
//when we leave the editor (maybe)
editor.on('blur', function (e) {
@@ -177,8 +147,6 @@ angular.module("umbraco.directives")
scope.onBlur();
}
_toolbar.css("visibility", "hidden");
$('.umb-panel-body').off('scroll', pinToolbar);
});
});
@@ -190,8 +158,6 @@ angular.module("umbraco.directives")
scope.onFocus();
}
pinToolbar();
$('.umb-panel-body').on('scroll', pinToolbar);
});
});
@@ -203,8 +169,6 @@ angular.module("umbraco.directives")
scope.onClick();
}
pinToolbar();
$('.umb-panel-body').on('scroll', pinToolbar);
});
});

View File

@@ -144,8 +144,12 @@
function setTargetPosition() {
var viewportWidth = null;
var viewportHeight = null;
var container = $("#contentwrapper");
var containerLeft = container[0].offsetLeft;
var containerRight = containerLeft + container[0].offsetWidth;
var containerTop = container[0].offsetTop;
var containerBottom = containerTop + container[0].offsetHeight;
var mousePositionClickX = null;
var mousePositionClickY = null;
var elementHeight = null;
@@ -161,10 +165,6 @@
// if mouse click position is know place element with mouse in center
if (scope.model.event && scope.model.event) {
// viewport size
viewportWidth = $(window).innerWidth();
viewportHeight = $(window).innerHeight();
// click position
mousePositionClickX = scope.model.event.pageX;
mousePositionClickY = scope.model.event.pageY;
@@ -179,17 +179,29 @@
// check to see if element is outside screen
// outside right
if (position.left + elementWidth > viewportWidth) {
position.right = 0;
if (position.left + elementWidth > containerRight) {
position.right = 10;
position.left = "inherit";
}
// outside bottom
if (position.top + elementHeight > viewportHeight) {
position.bottom = 0;
if (position.top + elementHeight > containerBottom) {
position.bottom = 10;
position.top = "inherit";
}
// outside left
if (position.left < containerLeft) {
position.left = containerLeft + 10;
position.right = "inherit";
}
// outside top
if (position.top < containerTop) {
position.top = 10;
position.bottom = "inherit";
}
el.css(position);
}

View File

@@ -38,8 +38,8 @@ angular.module("umbraco.directives")
'<div ng-class="getNodeCssClass(node)" ng-swipe-right="options(node, $event)" >' +
//NOTE: This ins element is used to display the search icon if the node is a container/listview and the tree is currently in dialog
//'<ins ng-if="tree.enablelistviewsearch && node.metaData.isContainer" class="umb-tree-node-search icon-search" ng-click="searchNode(node, $event)" alt="searchAltText"></ins>' +
'<ins ng-class="{\'icon-navigation-right\': !node.expanded, \'icon-navigation-down\': node.expanded}" ng-click="load(node)"></ins>' +
'<i class="icon umb-tree-icon sprTree"></i>' +
'<ins ng-class="{\'icon-navigation-right\': !node.expanded, \'icon-navigation-down\': node.expanded}" ng-click="load(node)">&nbsp;</ins>' +
'<i class="icon umb-tree-icon sprTree" ng-click="select(node, $event)"></i>' +
'<a href="#/{{node.routePath}}" ng-click="select(node, $event)"></a>' +
//NOTE: These are the 'option' elipses
'<a class="umb-options" ng-click="options(node, $event)"><i></i><i></i><i></i></a>' +

View File

@@ -0,0 +1,39 @@
(function() {
'use strict';
function ConfirmAction() {
function link(scope, el, attr, ctrl) {
scope.clickConfirm = function() {
if(scope.onConfirm) {
scope.onConfirm();
}
};
scope.clickCancel = function() {
if(scope.onCancel) {
scope.onCancel();
}
};
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/umb-confirm-action.html',
scope: {
direction: "@",
onConfirm: "&",
onCancel: "&"
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbConfirmAction', ConfirmAction);
})();

View File

@@ -1,35 +0,0 @@
(function() {
'use strict';
function ConfirmDelete() {
function link(scope, el, attr, ctrl) {
scope.confirmOverlayOpen = false;
scope.toggleOverlay = function() {
scope.confirmOverlayOpen = !scope.confirmOverlayOpen;
};
scope.closeOverlay = function() {
scope.confirmOverlayOpen = false;
};
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/umb-confirm-delete.html',
scope: {
confirmAction: "&"
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbConfirmDelete', ConfirmDelete);
})();

View File

@@ -157,6 +157,16 @@
}
/* ---------- DELETE PROMT ---------- */
scope.togglePrompt = function (object) {
object.deletePrompt = !object.deletePrompt;
};
scope.hidePrompt = function (object) {
object.deletePrompt = false;
};
/* ---------- TOOLBAR ---------- */
scope.toggleSortingMode = function(tool) {
@@ -404,13 +414,13 @@
}
};
scope.deleteProperty = function(tab, propertyIndex, group) {
scope.deleteProperty = function(tab, propertyIndex) {
// remove property
tab.properties.splice(propertyIndex, 1);
// if the last property in group is an placeholder - remove add new tab placeholder
if(group.properties.length === 1 && group.properties[0].propertyState === "init") {
if(tab.properties.length === 1 && tab.properties[0].propertyState === "init") {
angular.forEach(scope.model.groups, function(group, index, groups){
if(group.tabState === 'init') {

View File

@@ -46,32 +46,19 @@
function setActiveLayout(layouts) {
for (var i = 0; layouts.length > i; i++) {
var layout = layouts[i];
if(layout.name === scope.activeLayout.name && layout.path === scope.activeLayout.path) {
if(layout.path === scope.activeLayout.path) {
layout.active = true;
}
}
}
scope.pickLayout = function(selectedLayout) {
for (var i = 0; scope.layouts.length > i; i++) {
var layout = scope.layouts[i];
layout.active = false;
}
selectedLayout.active = true;
scope.activeLayout = selectedLayout;
scope.layoutDropDownIsOpen = false;
if(scope.onLayoutSelect) {
scope.onLayoutSelect(selectedLayout);
scope.layoutDropDownIsOpen = false;
}
};
scope.toggleLayoutDropdown = function() {
@@ -92,7 +79,8 @@
templateUrl: 'views/components/umb-layout-selector.html',
scope: {
layouts: '=',
activeLayout: '='
activeLayout: '=',
onLayoutSelect: "="
},
link: link
};

View File

@@ -25,10 +25,7 @@
}
function setItemData(item) {
item.isFolder = !mediaHelper.hasFilePropertyType(item);
item.hidden = item.isFolder;
if(!item.isFolder){
item.thumbnail = mediaHelper.resolveFile(item, true);
item.image = mediaHelper.resolveFile(item, false);

View File

@@ -144,33 +144,60 @@ angular.module("umbraco.directives")
}).success(function (data, status, headers, config) {
// set done status on file
file.uploadStatus = "done";
if(data.notifications && data.notifications.length > 0) {
// set date/time for when done - used for sorting
file.doneDate = new Date();
// set error status on file
file.uploadStatus = "error";
// Throw message back to user with the cause of the error
file.serverErrorMessage = data.notifications[0].message;
// Put the file in the rejected pool
scope.rejected.push(file);
} else {
// set done status on file
file.uploadStatus = "done";
// set date/time for when done - used for sorting
file.doneDate = new Date();
// Put the file in the done pool
scope.done.push(file);
}
scope.done.push(file);
scope.currentFile = undefined;
//after processing, test if everthing is done
_processQueueItem();
}).error( function (evt, status, headers, config) {
// set status done
file.uploadStatus = "error";
//if the service returns a detailed error
if(evt.InnerException){
file.errorMessage = evt.InnerException.ExceptionMessage;
if (evt.InnerException) {
file.serverErrorMessage = evt.InnerException.ExceptionMessage;
//Check if its the common "too large file" exception
if(evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0){
file.errorMessage = "File too large to upload";
}
//Check if its the common "too large file" exception
if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) {
file.serverErrorMessage = "File too large to upload";
}
} else if (evt.Message) {
file.serverErrorMessage = evt.Message;
}
// If file not found, server will return a 404 and display this message
if(status === 404 ) {
file.serverErrorMessage = "File not found";
}
//after processing, test if everthing is done
scope.done.push(file);
scope.rejected.push(file);
scope.currentFile = undefined;
_processQueueItem();

View File

@@ -0,0 +1,62 @@
/**
* Konami Code directive for AngularJS
* @version v0.0.1
* @license MIT License, http://www.opensource.org/licenses/MIT
*/
angular.module('umbraco.directives')
.directive('konamiCode', ['$document', function ($document) {
var konamiKeysDefault = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];
return {
restrict: 'A',
link: function (scope, element, attr) {
if (!attr.konamiCode) {
throw ('Konami directive must receive an expression as value.');
}
// Let user define a custom code.
var konamiKeys = attr.konamiKeys || konamiKeysDefault;
var keyIndex = 0;
/**
* Fired when konami code is type.
*/
function activated() {
if ('konamiOnce' in attr) {
stopListening();
}
// Execute expression.
scope.$eval(attr.konamiCode);
}
/**
* Handle keydown events.
*/
function keydown(e) {
if (e.keyCode === konamiKeys[keyIndex++]) {
if (keyIndex === konamiKeys.length) {
keyIndex = 0;
activated();
}
} else {
keyIndex = 0;
}
}
/**
* Stop to listen typing.
*/
function stopListening() {
$document.off('keydown', keydown);
}
// Start listening to key typing.
$document.on('keydown', keydown);
// Stop listening when scope is destroyed.
scope.$on('$destroy', stopListening);
}
};
}]);

View File

@@ -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 += "&macroParams[" + counter + "].key=" + key + "&macroParams[" + 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);
}
};
}

View File

@@ -1,10 +1,104 @@
(function() {
'use strict';
function listViewHelper() {
function listViewHelper($cookieStore) {
var firstSelectedIndex = 0;
function getLayout(nodeId, availableLayouts) {
var storedLayouts = [];
if ($cookieStore.get("umblistViewLayout")) {
storedLayouts = $cookieStore.get("umblistViewLayout");
}
if (storedLayouts && storedLayouts.length > 0) {
for (var i = 0; storedLayouts.length > i; i++) {
var layout = storedLayouts[i];
if (layout.nodeId === nodeId) {
return setLayout(nodeId, layout, availableLayouts);
}
}
}
return getFirstAllowedLayout(availableLayouts);
}
function setLayout(nodeId, selectedLayout, availableLayouts) {
var activeLayout = {};
var layoutFound = false;
for (var i = 0; availableLayouts.length > i; i++) {
var layout = availableLayouts[i];
if (layout.path === selectedLayout.path) {
activeLayout = layout;
layout.active = true;
layoutFound = true;
} else {
layout.active = false;
}
}
if(!layoutFound) {
activeLayout = getFirstAllowedLayout(availableLayouts);
}
setLayoutCookie(nodeId, activeLayout);
return activeLayout;
}
function setLayoutCookie(nodeId, selectedLayout) {
var layoutFound = false;
var storedLayouts = [];
if($cookieStore.get("umblistViewLayout")) {
storedLayouts = $cookieStore.get("umblistViewLayout");
}
if(storedLayouts.length > 0) {
for(var i = 0; storedLayouts.length > i; i++) {
var layout = storedLayouts[i];
if(layout.nodeId === nodeId) {
layout.path = selectedLayout.path;
layoutFound = true;
}
}
}
if(!layoutFound) {
var cookieObject = {
"nodeId": nodeId,
"path": selectedLayout.path
};
storedLayouts.push(cookieObject);
}
document.cookie="umblistViewLayout=" + JSON.stringify(storedLayouts);
}
function getFirstAllowedLayout(layouts) {
var firstAllowedLayout = {};
for (var i = 0; layouts.length > i; i++) {
var layout = layouts[i];
if (layout.selected === true) {
firstAllowedLayout = layout;
break;
}
}
return firstAllowedLayout;
}
function selectHandler(selectedItem, selectedIndex, items, selection, $event) {
var start = 0;
@@ -57,7 +151,7 @@
isSelected = true;
}
}
if(!isSelected && !item.hidden) {
if(!isSelected) {
selection.push({id: item.id});
item.selected = true;
}
@@ -169,6 +263,10 @@
var service = {
getLayout: getLayout,
getFirstAllowedLayout: getFirstAllowedLayout,
setLayout: setLayout,
setLayoutCookie: setLayoutCookie,
selectHandler: selectHandler,
selectItem: selectItem,
deselectItem: deselectItem,

View File

@@ -17,9 +17,9 @@ angular.module("umbraco.install").factory('installerService', function($rootScop
//add to umbraco installer facts here
var facts = ['Umbraco helped millions of people watch a man jump from the edge of space',
'Over 250.000 websites are currently powered by Umbraco',
'Over 300 000 websites are currently powered by Umbraco',
"At least 2 people have named their cat 'Umbraco'",
'On an average day, more than 1.000 people download Umbraco',
'On an average day, more than 1000 people download Umbraco',
'<a target="_blank" href="http://umbraco.tv">umbraco.tv</a> is the premier source of Umbraco video tutorials to get you started',
'You can find the world\'s friendliest CMS community at <a target="_blank" href="http://our.umbraco.org">our.umbraco.org</a>',
'You can become a certified Umbraco developer by attending one of the official courses',
@@ -31,7 +31,7 @@ angular.module("umbraco.install").factory('installerService', function($rootScop
"At least 4 people have the Umbraco logo tattooed on them",
"'Umbraco' is the danish name for an allen key",
"Umbraco has been around since 2005, that's a looong time in IT",
"More than 400 people from all over the world meet each year in Denmark in June for our annual conference <a target='_blank' href='http://codegarden14.com'>CodeGarden</a>",
"More than 400 people from all over the world meet each year in Denmark in June for our annual conference <a target='_blank' href='http://codegarden15.com'>CodeGarden</a>",
"While you are installing Umbraco someone else on the other side of the planet is probably doing it too",
"You can extend Umbraco without modifying the source code and using either JavaScript or C#"
];

View File

@@ -91,7 +91,7 @@
@import "components/umb-group-builder.less";
@import "components/umb-list-view.less";
@import "components/umb-table.less";
@import "components/umb-confirm-delete.less";
@import "components/umb-confirm-action.less";
@import "components/umb-keyboard-shortcuts-overview.less";
@import "components/umb-checkbox-list.less";
@import "components/umb-locked-field.less";
@@ -105,6 +105,7 @@
@import "components/tooltip/umb-tooltip.less";
@import "components/tooltip/umb-tooltip-list.less";
@import "components/overlays/umb-overlay-backdrop.less";
@import "components/umb-grid.less";
@import "components/buttons/umb-button.less";
@import "components/buttons/umb-button-group.less";

View File

@@ -2,8 +2,8 @@
/******* font-face *******/
@font-face {
src: url('/Umbraco/assets/fonts/helveticons/helveticons.eot') !important;
src: url('/Umbraco/assets/fonts/helveticons/helveticons.eot?#iefix') format('embedded-opentype'), url('/Umbraco/assets/fonts/helveticons/helveticons.ttf') format('truetype'), url('/Umbraco/assets/fonts/helveticons/helveticons.svg#icomoon') format('svg') !important;
src: url('assets/fonts/helveticons/helveticons.eot') !important;
src: url('assets/fonts/helveticons/helveticons.eot?#iefix') format('embedded-opentype'), url('assets/fonts/helveticons/helveticons.ttf') format('truetype'), url('assets/fonts/helveticons/helveticons.svg#icomoon') format('svg') !important;
}
/****************************/

View File

@@ -0,0 +1,130 @@
// OVERLAY
.umb_confirm-action__overlay {
position: absolute;
z-index: 999999;
display: flex;
}
// positions
.umb_confirm-action__overlay.-top {
top: -50px;
right: auto;
bottom: auto;
left: 0;
animation: fadeInUp 0.2s;
flex-direction: column;
.umb_confirm-action__overlay-action {
margin-bottom: 5px;
}
.umb_confirm-action__overlay-action.-confirm {
order: 1;
}
.umb_confirm-action__overlay-action.-cancel {
order: 2;
}
}
.umb_confirm-action__overlay.-right {
top: 0;
right: -50px;
bottom: auto;
left: auto;
animation: fadeInLeft 0.2s;
flex-direction: row;
.umb_confirm-action__overlay-action {
margin-left: 5px;
}
.umb_confirm-action__overlay-action.-confirm {
order: 2;
}
.umb_confirm-action__overlay-action.-cancel {
order: 1;
}
}
.umb_confirm-action__overlay.-bottom {
top: auto;
right: auto;
bottom: -50px;
left: 0;
animation: fadeInDown 0.2s;
flex-direction: column;
.umb_confirm-action__overlay-action {
margin-top: 5px;
}
.umb_confirm-action__overlay-action.-confirm {
order: 2;
}
.umb_confirm-action__overlay-action.-cancel {
order: 1;
}
}
.umb_confirm-action__overlay.-left {
top: 0;
right: auto;
bottom: auto;
left: -50px;
animation: fadeInRight 0.2s;
flex-direction: row;
.umb_confirm-action__overlay-action {
margin-right: 5px;
}
.umb_confirm-action__overlay-action.-confirm {
order: 1;
}
.umb_confirm-action__overlay-action.-cancel {
order: 2;
}
}
// BUTTONS
.umb_confirm-action__overlay-action {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
border-radius: 40px;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
font-size: 18px;
}
.umb_confirm-action__overlay-action:hover {
text-decoration: none;
color: #ffffff;
}
// confirm button
.umb_confirm-action__overlay-action.-confirm {
background: #ffffff;
color: @green !important;
}
.umb_confirm-action__overlay-action.-confirm:hover {
color: @green !important;
}
// cancel button
.umb_confirm-action__overlay-action.-cancel {
background: #ffffff;
color: @red !important;
}
.umb_confirm-action__overlay-action.-cancel:hover {
color: @red !important;
}

View File

@@ -1,34 +0,0 @@
.umb-confirm-delete {
position: relative;
}
.umb_confirm-delete__overlay-action {
position: absolute;
z-index: 999999;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
border-radius: 40px;
animation: fadeInRight 0.2s;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
}
.umb_confirm-delete__overlay-action:hover {
text-decoration: none;
color: #ffffff;
}
.umb_confirm-delete__overlay-action.-confirm {
background: @green;
top: 0;
left: -50px;
}
.umb_confirm-delete__overlay-action.-cancel {
background: @red;
top: 0;
left: -25px;
}

View File

@@ -41,11 +41,15 @@
// file select link
.file-select {
color: @gray;
font-size: 16px;
font-size: 15px;
color: @blue;
cursor: pointer;
margin-top: 10px;
&:hover {
text-decoration: underline;
color: @blueDark;
text-decoration: underline;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -59,10 +59,12 @@
position: absolute;
top: -30px;
right: 20px;
font-size: 18px;
}
.umb-group-builder__group-remove:hover {
cursor: pointer;
color: @blueDark;
}
.umb-group-builder__group-title-wrapper {
@@ -312,6 +314,12 @@ input.umb-group-builder__group-sort-value {
margin: 0 0 10px 0;
display: block;
font-size: 18px;
position: relative;
cursor: pointer;
}
.umb-group-builder__property-action:hover {
color: @blueDark;
}
.umb-group-builder__property-inherited-label {

View File

@@ -51,14 +51,13 @@ input.umb-table__input {
text-decoration: none;
color: fade(@gray, 75%);
}
}
&.sortable {
&:hover {
text-decoration: none;
cursor: pointer;
color: @black;
}
.umb-table-head__link .sortable {
&:hover {
text-decoration: none;
cursor: pointer;
color: @black;
}
}
@@ -119,11 +118,12 @@ input.umb-table__input {
font-size: 16px;
}
.umb-table-body__empty { // Styles of no items in the listview
// Styles of no items in the listview
.umb-table-body__empty {
font-size: 16px;
text-align: center;
color: fade(@grayLight, 85%);
color: @gray;
padding: 20px 0;
@@ -178,7 +178,7 @@ input.umb-table__input {
.umb-table-cell > * {
overflow: hidden;
white-space: nowrap; // Disable/Enable this to keep string on one line
white-space: nowrap; //NOTE Disable/Enable this to keep textstring on one line
text-overflow: ellipsis;
cursor: default;
@@ -192,7 +192,7 @@ input.umb-table__input {
}
//NOTE Gives the title more wieght in width compared to other cells
// Increases the space for the name cell
.umb-table__name {
flex: 1 1 25%;
max-width: 25%;

View File

@@ -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;
}

View File

@@ -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;
}
@@ -140,6 +157,7 @@
border-radius: @tabsBorderRadius;
box-shadow: none;
padding: 0;
z-index: 6020;
}
.umb-btn-toolbar .dropdown-menu small {

View File

@@ -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 */

View File

@@ -14,6 +14,7 @@
@grayDarker: #222;
@grayDark: #343434;
@gray: #555;
@grayMed: #999;
@grayLight: #d9d9d9;
@grayLighter: #f8f8f8;
@white: #fff;

View File

@@ -37,7 +37,7 @@
<li class="span2" ng-repeat="video in videos">
<div class="thumbnail" style="margin-right: 20px">
<a target="_blank" href="{{video.link}}?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv" title="{{video.title}}">
<img ng-src="{{video.thumbnail}}" alt="{{video.title}}">
<img ng-src="{{video.thumbnail}}?width=120" alt="{{video.title}}">
</a>
</div>
</li>

View File

@@ -1,5 +1,5 @@
angular.module("umbraco").controller("Umbraco.Dialogs.LoginController",
function ($scope, localizationService, userService, externalLoginInfo) {
function ($scope, $cookies, localizationService, userService, externalLoginInfo) {
/**
* @ngdoc function
@@ -10,18 +10,39 @@
* @description
* signs the user in
*/
var d = new Date();
//var weekday = new Array("Super Sunday", "Manic Monday", "Tremendous Tuesday", "Wonderful Wednesday", "Thunder Thursday", "Friendly Friday", "Shiny Saturday");
localizationService.localize("login_greeting" + d.getDay()).then(function (label) {
$scope.greeting = label;
}); // weekday[d.getDay()];
var d = new Date();
var konamiGreetings = new Array("Suze Sunday", "Malibu Monday", "Tequila Tuesday", "Whiskey Wednesday", "Negroni Day", "Fernet Friday", "Sancerre Saturday");
var konamiMode = $cookies.konamiLogin;
//var weekday = new Array("Super Sunday", "Manic Monday", "Tremendous Tuesday", "Wonderful Wednesday", "Thunder Thursday", "Friendly Friday", "Shiny Saturday");
if (konamiMode == "1") {
$scope.greeting = "Happy " + konamiGreetings[d.getDay()];
} else {
localizationService.localize("login_greeting" + d.getDay()).then(function (label) {
$scope.greeting = label;
}); // weekday[d.getDay()];
}
$scope.errorMsg = "";
$scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl;
$scope.externalLoginProviders = externalLoginInfo.providers;
$scope.externalLoginInfo = externalLoginInfo;
$scope.activateKonamiMode = function () {
if ($cookies.konamiLogin == "1") {
// somehow I can't update the cookie value using $cookies, so going native
document.cookie = "konamiLogin=; expires=Thu, 01 Jan 1970 00:00:01 GMT;";
document.location.reload();
} else {
document.cookie = "konamiLogin=1; expires=Tue, 01 Jan 2030 00:00:01 GMT;";
$scope.$apply(function () {
$scope.greeting = "Happy " + konamiGreetings[d.getDay()];
});
}
}
$scope.loginSubmit = function (login, password) {
//if the login and password are not empty we need to automatically

View File

@@ -1,5 +1,5 @@
<div ng-controller="Umbraco.Dialogs.LoginController">
<div id="login" class="umb-modalcolumn" ng-class="{'show-validation': loginForm.$invalid}" ng-cloak>
<div id="login" class="umb-modalcolumn" ng-class="{'show-validation': loginForm.$invalid}" ng-cloak konami-code="activateKonamiMode()">
<div class="form">
<h1>{{greeting}}</h1>
<p>

View File

@@ -1,5 +1,7 @@
<div ng-controller="Umbraco.Dialogs.MediaPickerController"
id="fileupload">
<form ng-controller="Umbraco.Dialogs.MediaPickerController" id="fileupload"
method="POST"
enctype="multipart/form-data"
umb-image-upload="options">
<div class="umb-panel umb-dialogs-mediapicker" ng-if="target">
<div class="umb-panel-body no-header with-footer compact">
@@ -67,18 +69,16 @@
<div class="umb-panel-header">
<div class="umb-el-wrap umb-panel-buttons">
<div class="span5">
<div class="form-search">
<i class="icon-search"></i>
<input type="text"
ng-model="searchTerm"
class="umb-search-field search-query"
placeholder="Filter...">
</div>
<div class="umb-el-wrap umb-panel-buttons umb-mediapicker-upload">
<div class="form-search">
<i class="icon-search"></i>
<input type="text"
ng-model="searchTerm"
class="umb-search-field search-query"
placeholder="Filter...">
</div>
<div class="pull-right">
<div class="upload-button">
<button class="btn fileinput-button" ng-click="upload()" ng-class="{disabled: disabled}">
<i class="icon-page-up"></i>
<localize key="general_upload">Upload</localize>
@@ -156,4 +156,4 @@
</div>
</div>
</div>

View File

@@ -0,0 +1,8 @@
angular.module("umbraco").controller('Umbraco.Dialogs.Template.SnippetController',
function($scope) {
$scope.type = $scope.dialogOptions.type;
$scope.section = {
name: "",
required: false
};
});

View File

@@ -0,0 +1,37 @@
<form novalidate name="contentForm"
ng-controller="Umbraco.Dialogs.Template.SnippetController"
ng-submit="close()"
val-form-manager>
<div class="umb-panel">
<div class="umb-panel-header">
<div class="panel-dialog--header" style="border-bottom: 1px solid whitesmoke">Configure the section</div>
</div>
<div class="umb-panel-body with-footer umb-querybuilder">
<label>
<div class="input-label--title">
Section name:
</div>
<input type="text" ng-model="section.name"/>
</label>
<label ng-show="type === 'rendersection'">
<input style="margin: 0;" type="checkbox" ng-checked="section.required" ng-model="section.required"/>
<div class="input-label--small">
<localize key="general_required">Required</localize>
</div>
</label>
</div>
<div class="umb-panel-footer">
<div class="umb-el-wrap umb-panel-buttons">
<div class="btn-toolbar umb-btn-toolbar pull-right">
<a href ng-click="close()" class="btn btn-link">
<localize key="general_close">Close</localize>
</a>
<a href ng-click="submit(section)" class="btn btn-primary">Insert</a>
</div>
</div>
</div>
</div>
</form>

View File

@@ -8,6 +8,7 @@
</div>
</div>
<h1 class="headline">{{user.name}}</h1>
<small class="umb-version">Umbraco version {{version}}</small>
<p class="muted">
<small>
<localize key="user_sessionExpires" />: {{remainingAuthSeconds | timespan}}
@@ -114,6 +115,5 @@
</div>
<small class="umb-version">Umbraco version {{version}}</small>
</div>
</div>

View File

@@ -95,7 +95,10 @@
if(editor.id === null) {
dataTypeResource.getScaffold().then(function(dataType) {
// add scaffold in data type root
var parentId = -1;
dataTypeResource.getScaffold(parentId).then(function(dataType) {
// set alias
dataType.selectedEditor = editor.alias;

View File

@@ -1,4 +1,4 @@
<div class="form-search">
<div class="form-search" ng-hide="model.filter === false">
<i class="icon-search"></i>
<input type="text"
style="width: 100%"

View File

@@ -12,6 +12,7 @@ angular.module("umbraco")
$scope.cropSize = dialogOptions.cropSize;
$scope.lastOpenedNode = $cookieStore.get("umbLastOpenedMediaNodeId");
$scope.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes);
$scope.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB";
$scope.model.selectedImages = [];
@@ -178,7 +179,7 @@ angular.module("umbraco")
//default root item
if (!$scope.target) {
if($scope.lastOpenedNode) {
if($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) {
entityResource.getById($scope.lastOpenedNode, "media")
.then(function(node){
@@ -186,13 +187,15 @@ angular.module("umbraco")
// make sure that las opened node is on the same path as start node
var nodePath = node.path.split(",");
if(nodePath.indexOf($scope.startNodeId) !== -1) {
if(nodePath.indexOf($scope.startNodeId.toString()) !== -1) {
$scope.gotoFolder({id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder"});
} else {
$scope.gotoFolder({id: $scope.startNodeId, name: "Media", icon: "icon-folder"});
}
});
}, function (err) {
$scope.gotoFolder({id: $scope.startNodeId, name: "Media", icon: "icon-folder"});
});
} else {

View File

@@ -47,7 +47,8 @@
parent-id="{{currentFolder.id}}"
files-uploaded="onUploadComplete"
files-queued="onFilesQueue"
accept="{{acceptedFileTypes}}">
accept="{{acceptedFileTypes}}"
max-file-size="{{maxFileSize}}">
</umb-file-dropzone>
<umb-photo-folder

View File

@@ -0,0 +1,16 @@
<div class="umb_confirm-action__overlay"
ng-class="{
'-top': direction === 'top',
'-right': direction === 'right',
'-bottom': direction === 'bottom',
'-left': direction === 'left'}"
on-outside-click="clickCancel()">
<a class="umb_confirm-action__overlay-action -confirm" href="" ng-click="clickConfirm()">
<i class="icon-check"></i>
</a>
<a class="umb_confirm-action__overlay-action -cancel" href="" ng-click="clickCancel()">
<i class="icon-delete"></i>
</a>
</div>

View File

@@ -1,19 +0,0 @@
<div class="umb-confirm-delete">
<a href="" ng-click="toggleOverlay()">
<i class="icon-trash"></i>
</a>
<div ng-if="confirmOverlayOpen" on-outside-click="closeOverlay()">
<a class="umb_confirm-delete__overlay-action -confirm" href="" ng-click="confirmAction()">
<i class="icon-check"></i>
</a>
<a class="umb_confirm-delete__overlay-action -cancel" href="" ng-click="closeOverlay()">
<i class="icon-delete"></i>
</a>
</div>
</div>

View File

@@ -24,7 +24,13 @@
<div class="umb-group-builder__group" ng-if="tab.tabState !== 'init'" ng-class="{'-active':tab.tabState=='active', '-inherited': tab.inherited, 'umb-group-builder__group-handle -sortable': sortingMode && !tab.inherited}" ng-click="activateGroup(tab)">
<div class="umb-group-builder__group-remove" ng-if="!sortingMode && tab.properties.length <= 1 && model.groups.length > 1">
<umb-confirm-delete confirm-action="removeGroup($index)"></umbraco-confirm-delete>
<i class="icon-trash" ng-click="togglePrompt(tab)"></i>
<umb-confirm-action
ng-if="tab.deletePrompt"
direction="left"
on-confirm="removeGroup($index)"
on-cancel="hidePrompt(tab)">
</umb-confirm-action>
</div>
<div class="umb-group-builder__group-title-wrapper">
@@ -97,7 +103,7 @@
<ng-form name="propertyTypeForm">
<div class="control-group -no-margin" ng-if="!sortingMode">
<div class="umb-group-builder__property-meta-alias" ng-if="property.inherited">{{ property.alias }}</div>
<umb-locked-field ng-if="!property.inherited"
locked="locked"
@@ -146,17 +152,23 @@
</div>
<!-- row tools -->
<div tabindex="-1" class="umb-group-builder__property-actions" ng-if="!sortingMode">
<div class="umb-group-builder__property-actions" ng-if="!sortingMode">
<div ng-if="!property.inherited">
<!-- delete property -->
<div class="umb-group-builder__property-action">
<umb-confirm-delete confirm-action="deleteProperty(tab, $index, tab)"></umbraco-confirm-delete>
<i class="icon-trash" ng-click="togglePrompt(property)"></i>
<umb-confirm-action
ng-if="property.deletePrompt"
direction="left"
on-confirm="deleteProperty(tab, $index)"
on-cancel="hidePrompt(property)">
</umb-confirm-action>
</div>
<!-- settings for property -->
<a href="" class="umb-group-builder__property-action" ng-click="editPropertyTypeSettings(property, tab)">
<div class="umb-group-builder__property-action" ng-click="editPropertyTypeSettings(property, tab)">
<i class="icon icon-settings"></i>
</a>

View File

@@ -1,6 +1,6 @@
<div class="umb-media-grid">
<a href="" ng-click="clickItem(item)" ng-repeat="item in items" ng-style="item.flexStyle" class="umb-media-grid__item" ng-hide="item.hidden">
<a href="" ng-click="clickItem(item)" ng-repeat="item in items" ng-style="item.flexStyle" class="umb-media-grid__item">
<div class="umb-media-grid__actions">
<i ng-if="onDetailsHover" class="icon-info umb-media-grid__action -not-clickable" ng-mouseover="hoverItemDetails(item, $event, true)" ng-mouseleave="hoverItemDetails(item, $event, false)"></i>

View File

@@ -1,6 +1,8 @@
<div>
<div class="umb-table" ng-if="items">
<!-- Listviews head section -->
<div class="umb-table-head">
<div class="umb-table-row">
@@ -31,7 +33,7 @@
</div>
</div>
<!-- Listview body section -->
<div class="umb-table-body">
<div class="umb-table-row"
ng-repeat="item in items"
@@ -61,6 +63,7 @@
</div>
<!-- If list is empty, then display -->
<div ng-if="!items" class="umb-table-body__empty">
<localize key="content_listViewNoItems">There are no items show in the list.</localize>
</div>

View File

@@ -17,29 +17,10 @@
<div class="content" >
<!-- Drag and drop illustration -->
<svg class="illustration" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 140 75" enable-background="new 0 0 140 75" xml:space="preserve">
<path fill="#FFFFFF" stroke="#D9D9D9" d="M132.4,68.6H43.1c-0.5,0-1-0.4-1-1V7.1c0-0.6,0.5-1,1-1h89.4c0.6,0,1,0.4,1,1v60.5
C133.4,68.2,133,68.6,132.4,68.6z"/>
<line fill="none" stroke="#D9D9D9" x1="42.1" y1="16.4" x2="133.4" y2="16.4"/>
<circle fill="#DF7F48" cx="47" cy="11.6" r="1.5"/>
<circle fill="#DF7F48" cx="52.5" cy="11.6" r="1.5"/>
<circle fill="#DF7F48" cx="58" cy="11.6" r="1.5"/>
<g>
<g>
<path fill="none" stroke="#D9D9D9" stroke-linecap="round" stroke-linejoin="round" d="M64.2,44.1c-12.7-11.9-32.7-11.7-45.1,0.8"
/>
<g>
<polygon fill="#D9D9D9" points="64.4,38.9 63.4,39 63.9,43.9 58.9,44 59,45 65,44.9 "/>
</g>
</g>
</g>
<rect x="4.8" y="23.9" fill="#FFFFFF" stroke="#D9D9D9" stroke-linecap="round" stroke-linejoin="round" width="30" height="25"/>
<path fill="#FFFFFF" stroke="#D9D9D9" stroke-linecap="round" stroke-linejoin="round" d="M4.8,48.9c0,0,0-12.5,15-12.5
s15,12.5,15,12.5H4.8z"/>
<circle fill="#FFFFFF" stroke="#D9D9D9" stroke-linecap="round" stroke-linejoin="round" cx="28.7" cy="30.8" r="2.5"/>
</svg>
<img src="assets/img/uploader/upload-illustration.svg" alt="" />
<!-- Select files -->
<div class="file-select"
@@ -71,6 +52,7 @@
</li>
<li class="file" ng-if="currentFile">
<!-- file name -->
<div class="file-name">{{ currentFile.name }}</div>
@@ -83,6 +65,7 @@
<!-- make list sort order the same as photo grid. The last uploaded photo in the top -->
<li class="file" ng-repeat="queued in queue">
<!-- file name -->
<div class="file-name">{{ queued.name }}</div>
</li>
@@ -92,11 +75,15 @@
<!-- file name -->
<div class="file-description">
{{ file.name }}
<strong>{{ file.name }}</strong>
<span class="file-error" ng-if="file.$error">
<span ng-if="file.$error === 'pattern'" class="errorMessage color-red">( Only allowed file types are: "{{ accept }}" )</span>
<span ng-if="file.$error === 'maxSize'" class="errorMessage color-red">( Max file size is " {{ maxFileSize }} " )</span>
<span ng-if="file.$error === 'pattern'" class="errorMessage color-red">(Only allowed file types are: "{{ accept }}")</span>
<span ng-if="file.$error === 'maxSize'" class="errorMessage color-red">(Max file size is "{{maxFileSize}}")</span>
</span>
<span class="file-error" ng-if="file.serverErrorMessage">
<span class="errorMessage color-red">({{file.serverErrorMessage}})</span>
</span>
</div>

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