Compare commits

..

48 Commits

Author SHA1 Message Date
Shannon 89c376f77a adds ValueTuple reference to nuspec 2018-03-26 20:08:11 +11:00
Claus 7c264b4da0 adding using statement to preview file to prevent missing htmlhelper error. 2018-03-23 11:57:29 +01:00
Sebastiaan Janssen 81d380a47e Merge branch 'dev-v7' into master-v7 2018-03-22 14:55:52 +01:00
Sebastiaan Janssen 19e5715a84 Whoops, that was not supposed to say beta 2018-03-22 14:55:02 +01:00
Sebastiaan Janssen 2e2dce170a Merge branch 'dev-v7.10' into master-v7 2018-03-22 11:28:48 +01:00
Sebastiaan Janssen 59f9a64e81 Merge branch 'dev-v7' into dev-v7.10
# Conflicts:
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoVersion.cs
2018-03-22 11:27:26 +01:00
Claus c9c9e8352a Merge pull request #2532 from umbraco/temp-U4-11134
redirects preview to login when not authorized
2018-03-22 10:22:32 +01:00
Shannon c4e5ab54d2 redirects preview to login when not authorized 2018-03-22 19:55:55 +11:00
Shannon 53bfec8a04 Merge branch 'dev-v7' into dev-v7.10 2018-03-22 19:22:34 +11:00
Shannon b361cfebb7 updates npm build dependencies 2018-03-21 13:55:01 +11:00
Shannon ebea5e8170 fixes boolean pre-value editor Html Id 2018-03-21 13:53:54 +11:00
Shannon 995f3f38a3 Fixes upgrade issue with copying the preview.old directory and updates the link to the the correct public one 2018-03-21 12:52:17 +11:00
Shannon 2ad79956d9 Re-apply Drop Down Flexible "Merge pull request #2502 from umbraco/temp-U4-11032"" 2018-03-21 12:37:26 +11:00
Shannon 96c6a17d03 Merge branch 'dev-v7' into dev-v7.10 2018-03-21 12:36:55 +11:00
Shannon Deminick 41c4e024de Merge pull request #2505 from umbraco/temp-u4-10756
U4-10756 - id/guid cache
2018-03-21 11:42:04 +11:00
Shannon b46add81f0 Merge branch 'dev-v7.10' into temp-u4-10756 2018-03-21 11:39:28 +11:00
Shannon ff31cd9f72 Changes Set to Populate, changes Fetch to Query and makes GetById public 2018-03-21 11:31:30 +11:00
Shannon 084af43dab Merge remote-tracking branch 'origin/dev-v7' into dev-v7.10 2018-03-21 10:20:20 +11:00
Sebastiaan Janssen e3e425695f U4-8724 tinyMceConfig.config customConfig bool not correct deserialized 2018-03-16 16:34:58 +01:00
Sebastiaan Janssen b539c6ec27 Merge pull request #1445 from KevinJump/dev-v7-ConnectionStringConfigSource
U4-8890 - Honour ConfigSource when setting ConnectionString
2018-03-16 15:45:21 +01:00
Shannon 69f7623fb7 Doh! we had test code checked in .. oops! 2018-03-16 14:40:26 +11:00
Shannon 82820284f3 Merge remote-tracking branch 'origin/dev-v7' into dev-v7.10
# Conflicts:
#	src/Umbraco.Web/Features/DisabledFeatures.cs
#	src/Umbraco.Web/Features/EnabledFeatures.cs
2018-03-16 14:30:55 +11:00
Sebastiaan Janssen f66e239584 PhantomJs doesn't understand fancy ES6 notation 2018-03-15 10:32:25 +01:00
Sebastiaan Janssen a465ed415d Rename to javascriptLibraryService 2018-03-15 10:08:00 +01:00
Sebastiaan Janssen 5161fd0cef Remove trailing space from filename and clean up code a little 2018-03-15 10:07:04 +01:00
Sebastiaan Janssen eac9785c45 Apply PR #2507 2018-03-15 10:02:20 +01:00
Stephan af0a03a3e0 U4-10756 - cleanup 2018-03-15 09:51:12 +01:00
Stephan fb5e6c22ce Merge remote-tracking branch 'origin/dev-v7' into temp-u4-10756 2018-03-15 09:46:52 +01:00
Stephan 6bc68005f4 U4-10756 - id/guid cache, new version 2018-03-15 09:46:39 +01:00
Shannon 05c38db54c updates naming of the extending preview property and fixes the angular rendering on ng-init 2018-03-14 15:24:30 +11:00
Shannon 00fdace570 fixes preview view web.config and makes sure we pass the preview model to the view 2018-03-14 14:38:21 +11:00
Shannon 4e531976e9 missing file 2018-03-14 14:24:11 +11:00
Shannon 0feb1b6cc0 fixes build since we don't have preview files 2018-03-14 14:02:35 +11:00
Shannon 830f460b7e bumps version 2018-03-14 13:58:48 +11:00
Shannon 3e2fa64820 Changes the Preview Index.cshtml to use a real model instead of viewdata, adds the correct razor web.config for these preview views, changes the enabled feature to be able to inject a razor view instead of strings 2018-03-14 13:56:49 +11:00
Shannon Deminick 7db4f841e3 Merge pull request #2485 from umbraco/temp-UAASSCRUM-1405
UAASSCRUM-1405 Clean up preview for Headless
2018-03-14 13:39:28 +11:00
Shannon 509d27f93b Merge branch 'dev-v7.10' into temp-UAASSCRUM-1405
# Conflicts:
#	src/Umbraco.Web/Features/EnabledFeatures.cs
2018-03-14 13:38:25 +11:00
Shannon f42b122e73 adding missing files 2018-03-14 13:35:51 +11:00
Shannon 1b6c070752 Merge remote-tracking branch 'origin/dev-v7' into temp-UAASSCRUM-1405 2018-03-14 12:56:07 +11:00
Shannon bedce100d2 Fixes merge and the preview views path 2018-03-14 12:55:34 +11:00
Shannon 0e08a56ada Merge branch 'dev-v7' into temp-UAASSCRUM-1405
# Conflicts:
#	src/Umbraco.Web/Features/DisabledFeatures.cs
#	src/Umbraco.Web/Features/EnabledFeatures.cs
2018-03-14 12:25:37 +11:00
Stephan cd63806895 U4-10756 - id/guid cache 2018-03-06 16:11:27 +01:00
Claus 23638702c8 adding missing views... 2018-03-01 12:57:43 +01:00
Claus 52684b6189 allowing to extend html used in preview view. 2018-02-28 13:41:49 +01:00
Claus 13c675d718 updating route to use the old url.
removing leftover index.html.
adding migration for renaming preview folder on disk.
2018-02-28 12:04:39 +01:00
Claus 0ee3ebd180 adding the preview controller to project.
removing old static html files from src (they're now cshtml files in Views).
copy task should not copy static canvasdesigner files anymore.
2018-02-28 08:58:16 +01:00
Claus 9c8c9d8a39 UAASSCRUM-1405 Clean up preview for Headless 2018-02-27 15:47:47 +01:00
Kevin Jump ae110c1cae Honour ConfigSource when setting ConnectionString
Lets umbraco honour the configSource attribute on a connectionstrings
section, meaning you can install the connectionstring into a file other
than web.config.
2016-08-23 08:45:30 +01:00
84 changed files with 1494 additions and 703 deletions
+1
View File
@@ -113,6 +113,7 @@ build/ApiDocs/*
build/ApiDocs/Output/*
src/Umbraco.Web.UI.Client/bower_components/*
/src/Umbraco.Web.UI/Umbraco/preview
/src/Umbraco.Web.UI/Umbraco/preview.old
#Ignore Rule for output of generated documentation files from Grunt docserve
src/Umbraco.Web.UI.Client/docs/api
@@ -376,7 +376,7 @@ function Prepare-Packages
Copy-Files "$src\Umbraco.Web.UI\umbraco\js" "*" "$tmp\WebApp\umbraco\js"
Copy-Files "$src\Umbraco.Web.UI\umbraco\lib" "*" "$tmp\WebApp\umbraco\lib"
Copy-Files "$src\Umbraco.Web.UI\umbraco\views" "*" "$tmp\WebApp\umbraco\views"
Copy-Files "$src\Umbraco.Web.UI\umbraco\preview" "*" "$tmp\WebApp\umbraco\preview"
}
#
+1
View File
@@ -39,6 +39,7 @@
<!-- Markdown can not be updated due to: https://github.com/hey-red/markdownsharp/issues/71#issuecomment-233585487 -->
<dependency id="Markdown" version="[1.14.7, 2.0.0)" />
<dependency id="System.Threading.Tasks.Dataflow" version="[4.7.0, 5.0.0)" />
<dependency id="System.ValueTuple" version="[4.4.0, 5.0.0)" />
</dependencies>
</metadata>
<files>
+2 -2
View File
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.9.5")]
[assembly: AssemblyInformationalVersion("7.9.5")]
[assembly: AssemblyFileVersion("7.10.0")]
[assembly: AssemblyInformationalVersion("7.10.0")]
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
private static readonly Version Version = new Version("7.9.5");
private static readonly Version Version = new Version("7.10.0");
/// <summary>
/// Gets the current version of Umbraco.
@@ -122,6 +122,11 @@ namespace Umbraco.Core
/// Alias for the Dropdown list, publishing keys datatype.
/// </summary>
public const string DropdownlistPublishingKeysAlias = "Umbraco.DropdownlistPublishingKeys";
/// <summary>
/// Alias for the "new" Dropdown list, that replaces the old four deprecated ones and works as other list based property editors
/// </summary>
public const string DropDownListFlexibleAlias = "Umbraco.DropDown.Flexible";
/// <summary>
/// Guid for the Folder browser datatype.
@@ -452,4 +457,4 @@ namespace Umbraco.Core
}
}
}
}
}
+16
View File
@@ -459,6 +459,22 @@ namespace Umbraco.Core
var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
var connectionstrings = xml.Root.DescendantsAndSelf("connectionStrings").Single();
// honour configSource, if its set, change the xml file we are saving the configuration
// to the one set in the configSource attribute
if (connectionstrings.Attribute("configSource") != null)
{
var source = connectionstrings.Attribute("configSource").Value;
var configFile = IOHelper.MapPath(string.Format("{0}/{1}", SystemDirectories.Root, source));
LogHelper.Info<DatabaseContext>("storing ConnectionString in {0}", () => configFile);
if (System.IO.File.Exists(configFile))
{
xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
fileName = configFile;
}
connectionstrings = xml.Root.DescendantsAndSelf("connectionStrings").Single();
}
// Update connectionString if it exists, or else create a new appSetting for the given key and value
var setting = connectionstrings.Descendants("add").FirstOrDefault(s => s.Attribute("name").Value == Constants.System.UmbracoConnectionName);
if (setting == null)
@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;
using Umbraco.Core.Configuration;
@@ -26,6 +27,7 @@ namespace Umbraco.Core.IO
private ShadowWrapper _xsltFileSystem;
private ShadowWrapper _masterPagesFileSystem;
private ShadowWrapper _mvcViewsFileSystem;
private ShadowWrapper _javaScriptLibraryFileSystem;
#region Singleton & Constructor
@@ -113,6 +115,7 @@ namespace Umbraco.Core.IO
var xsltFileSystem = new PhysicalFileSystem(SystemDirectories.Xslt);
var masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages);
var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
var javaScriptLibraryFileSystem = new PhysicalFileSystem(Path.Combine(SystemDirectories.Umbraco, "lib"));
_macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", ScopeProvider);
_partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", ScopeProvider);
@@ -123,6 +126,7 @@ namespace Umbraco.Core.IO
_xsltFileSystem = new ShadowWrapper(xsltFileSystem, "xslt", ScopeProvider);
_masterPagesFileSystem = new ShadowWrapper(masterPagesFileSystem, "masterpages", ScopeProvider);
_mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", ScopeProvider);
_javaScriptLibraryFileSystem = new ShadowWrapper(javaScriptLibraryFileSystem, "Lib", ScopeProvider);
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again
MediaFileSystem = GetFileSystemProvider<MediaFileSystem>();
@@ -143,6 +147,7 @@ namespace Umbraco.Core.IO
public IFileSystem2 XsltFileSystem { get { return _xsltFileSystem; } }
public IFileSystem2 MasterPagesFileSystem { get { return _mvcViewsFileSystem; } }
public IFileSystem2 MvcViewsFileSystem { get { return _mvcViewsFileSystem; } }
internal IFileSystem2 JavaScriptLibraryFileSystem { get { return _javaScriptLibraryFileSystem; } }
public MediaFileSystem MediaFileSystem { get; private set; }
#endregion
-1
View File
@@ -203,7 +203,6 @@ namespace Umbraco.Core.IO
{
get
{
//by default the packages folder should exist in the data folder
return IOHelper.ReturnPath("umbracoPreviewPath", Data + IOHelper.DirSepChar + "preview");
}
}
@@ -0,0 +1,44 @@
using System.IO;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.SqlSyntax;
using File = System.IO.File;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTenZero
{
/// <summary>
/// Renames the preview folder containing static html files to ensure it does not interfere with the MVC route
/// that is now supposed to render these views dynamically. We don't want to delete as people may have made
/// customizations to these files that would need to be migrated to the new .cshtml view files.
/// </summary>
[Migration("7.10.0", 1, Constants.System.UmbracoMigrationName)]
public class RenamePreviewFolder : MigrationBase
{
public RenamePreviewFolder(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{
}
public override void Up()
{
var previewFolderPath = IOHelper.MapPath(SystemDirectories.Umbraco + "/preview");
if (Directory.Exists(previewFolderPath))
{
var newPath = previewFolderPath.Replace("preview", "preview.old");
if (Directory.Exists(newPath) == false)
{
Directory.Move(previewFolderPath, newPath);
var readmeText =
$"Static html files used for preview and canvas editing functionality no longer live in this directory.\r\n" +
$"Instead they have been recreated as MVC views and can now be found in '~/Umbraco/Views/Preview'.\r\n" +
$"See issue: http://issues.umbraco.org/issue/U4-11090";
File.WriteAllText(Path.Combine(newPath, "readme.txt"), readmeText);
}
}
}
public override void Down()
{
}
}
}
+8 -7
View File
@@ -402,7 +402,7 @@ namespace Umbraco.Core.Persistence
}
// Helper to handle named parameters from object properties
static readonly Regex rxParams = new Regex(@"(?<!@)@\w+", RegexOptions.Compiled);
static Regex rxParams = new Regex(@"(?<!@)@\w+", RegexOptions.Compiled);
public static string ProcessParams(string _sql, object[] args_src, List<object> args_dest)
{
return rxParams.Replace(_sql, m =>
@@ -545,7 +545,7 @@ namespace Umbraco.Core.Persistence
}
// Create a command
static readonly Regex rxParamsPrefix = new Regex(@"(?<!@)@\w+", RegexOptions.Compiled);
static Regex rxParamsPrefix = new Regex(@"(?<!@)@\w+", RegexOptions.Compiled);
public IDbCommand CreateCommand(IDbConnection connection, string sql, params object[] args)
{
// Perform named argument replacements
@@ -666,8 +666,8 @@ namespace Umbraco.Core.Persistence
return ExecuteScalar<T>(sql.SQL, sql.Arguments);
}
static readonly Regex rxSelect = new Regex(@"\A\s*(SELECT|EXECUTE|CALL)\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline);
static readonly Regex rxFrom = new Regex(@"\A\s*FROM\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline);
Regex rxSelect = new Regex(@"\A\s*(SELECT|EXECUTE|CALL)\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline);
Regex rxFrom = new Regex(@"\A\s*FROM\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline);
string AddSelectClause<T>(string sql)
{
if (sql.StartsWith(";"))
@@ -701,9 +701,9 @@ namespace Umbraco.Core.Persistence
return Fetch<T>(sql.SQL, sql.Arguments);
}
static readonly Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?<!,\s+)\bFROM\b", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
static readonly Regex rxOrderBy = new Regex(@"\bORDER\s+BY\s+(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
static readonly Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
static Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?<!,\s+)\bFROM\b", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
static Regex rxOrderBy = new Regex(@"\bORDER\s+BY\s+(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
static Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
public static bool SplitSqlForPaging(string sql, out string sqlCount, out string sqlSelectRemoved, out string sqlOrderBy)
{
sqlSelectRemoved = null;
@@ -2546,4 +2546,5 @@ namespace Umbraco.Core.Persistence
}
}
}
}
-3
View File
@@ -20,7 +20,6 @@ namespace Umbraco.Core.Scoping
public NoScope(ScopeProvider scopeProvider)
{
_scopeProvider = scopeProvider;
Timestamp = DateTime.Now;
#if DEBUG_SCOPES
_scopeProvider.RegisterScope(this);
#endif
@@ -29,8 +28,6 @@ namespace Umbraco.Core.Scoping
private readonly Guid _instanceId = Guid.NewGuid();
public Guid InstanceId { get { return _instanceId; } }
public DateTime Timestamp { get; }
/// <inheritdoc />
public bool CallContext { get { return false; } }
+1 -1
View File
@@ -374,7 +374,7 @@ namespace Umbraco.Core.Scoping
}
var parent = ParentScope;
_scopeProvider.AmbientScope = parent; // might be null = this is how scopes are removed from context objects
_scopeProvider.AmbientScope = parent;
#if DEBUG_SCOPES
_scopeProvider.Disposed(this);
+6 -51
View File
@@ -2,12 +2,10 @@
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Web;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
#if DEBUG_SCOPES
using System.Linq;
@@ -68,38 +66,22 @@ namespace Umbraco.Core.Scoping
// tests, any other things (see https://msdn.microsoft.com/en-us/library/dn458353(v=vs.110).aspx),
// but we don't want to make all of our objects serializable since they are *not* meant to be
// used in cross-AppDomain scenario anyways.
//
// in addition, whatever goes into the logical call context is serialized back and forth any
// time cross-AppDomain code executes, so if we put an "object" there, we'll get *another*
// "object" instance back - and so we cannot use a random object as a key.
//
// time cross-AppDomain code executes, so if we put an "object" there, we'll can *another*
// "object" instance - and so we cannot use a random object as a key.
// so what we do is: we register a guid in the call context, and we keep a table mapping those
// guids to the actual objects. the guid serializes back and forth without causing any issue,
// and we can retrieve the actual objects from the table.
// only issue: how are we supposed to clear the table? we can't, really. objects should take
// care of de-registering themselves from context.
// everything we use does, except the NoScope scope, which just stays there
//
// so far, the only objects that go into this table are scopes (using ScopeItemKey) and
// scope contexts (using ContextItemKey).
// during tests, NoScope can to into call context... nothing much we can do about it
private static readonly object StaticCallContextObjectsLock = new object();
private static readonly Dictionary<Guid, object> StaticCallContextObjects
= new Dictionary<Guid, object>();
// normal scopes and scope contexts take greate care removing themselves when disposed, so it
// is all safe. OTOH the NoScope *CANNOT* remove itself, this is by design, it *WILL* leak and
// there is little (nothing) we can do about it - NoScope exists for backward compatibility
// reasons and relying on it is greatly discouraged.
//
// however... we can *try* at protecting the app against memory leaks, by collecting NoScope
// instances that are too old. if anything actually *need* to retain a NoScope instance for
// a long time, it will break. but that's probably ok. so: the constants below define how
// long a NoScope instance can stay in the table before being removed, and how often we should
// collect the table - and collecting happens anytime SetCallContextObject is invoked
private static readonly TimeSpan StaticCallContextNoScopeLifeSpan = TimeSpan.FromMinutes(30);
private static readonly TimeSpan StaticCallContextCollectPeriod = TimeSpan.FromMinutes(4);
private static DateTime _staticCallContextLastCollect = DateTime.MinValue;
#if DEBUG_SCOPES
public Dictionary<Guid, object> CallContextObjects
{
@@ -174,7 +156,6 @@ namespace Umbraco.Core.Scoping
//Logging.LogHelper.Debug<ScopeProvider>("At:\r\n" + Head(Environment.StackTrace, 24));
#endif
StaticCallContextObjects.Remove(objectKey);
CollectStaticCallContextObjectsLocked();
}
}
else
@@ -190,37 +171,11 @@ namespace Umbraco.Core.Scoping
//Logging.LogHelper.Debug<ScopeProvider>("At:\r\n" + Head(Environment.StackTrace, 24));
#endif
StaticCallContextObjects.Add(objectKey, value);
CollectStaticCallContextObjectsLocked();
}
CallContext.LogicalSetData(key, objectKey);
}
}
private static void CollectStaticCallContextObjectsLocked()
{
// is it time to collect?
var now = DateTime.Now;
if (now - _staticCallContextLastCollect <= StaticCallContextCollectPeriod)
return;
// disable warning: this method is invoked from within a lock
// ReSharper disable InconsistentlySynchronizedField
var threshold = now.Add(-StaticCallContextNoScopeLifeSpan);
var guids = StaticCallContextObjects
.Where(x => x.Value is NoScope noScope && noScope.Timestamp < threshold)
.Select(x => x.Key)
.ToList();
if (guids.Count > 0)
{
LogHelper.Warn<ScopeProvider>($"Collected {guids.Count} NoScope instances from StaticCallContextObjects.");
foreach (var guid in guids)
StaticCallContextObjects.Remove(guid);
}
// ReSharper restore InconsistentlySynchronizedField
_staticCallContextLastCollect = now;
}
// this is for tests exclusively until we have a proper accessor in v8
internal static Func<IDictionary> HttpContextItemsGetter { get; set; }
+219 -46
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using Umbraco.Core.Models;
@@ -22,13 +23,139 @@ namespace Umbraco.Core.Services
// note - no need for uow, scope would be enough, but a pain to wire
// note - for pure read-only we might want to *not* enforce a transaction?
// notes
//
// - this class assumes that the id/guid map is unique; that is, if an id and a guid map
// to each other, then the id will never map to another guid, and the guid will never map
// to another id
//
// - LeeK's solution in 7.7 was to look for the id/guid in the content cache "on demand" via
// XPath, which is probably fast enough but cannot deal with media ids + it maintains a
// separate, duplicate cache
// see https://github.com/umbraco/Umbraco-CMS/pull/2398
//
// - Andy's solution in a package was to prefetch all by sql; it cannot prefecth reserved ids
// as we don't know the corresponding object type, but that's not a big issue - but then we
// have a full database query on startup
// see https://github.com/AndyButland/UmbracoUdiToIdCache
//
// - the original IdkMap implementation that was used by services, did a database lookup on
// each cache miss, which is fine enough for services, but would be really slow at content
// cache level
//
// - cache is cleared by MediaCacheRefresher, UnpublishedPageCacheRefresher, and other
// refreshers - because id/guid map is unique, we only clear to avoid leaking memory, 'cos
// we don't risk caching obsolete values - and only when actually deleting
//
// so...
//
// - there's a single caching point, and it's idkMap
// - there are no "helper methods" - the published content cache itself knows about Guids
// - when the published content cache is instanciated, it populates the idkMap with what it knows
// and it registers a way for the idkMap to look for id/keys in the published content cache
// - we do NOT prefetch anything from database
// - when a request comes in:
// the published content cache uses the idkMap to map id/key
// if the idkMap already knows about the map, it returns the value
// else it tries the published cache via XPath
// else it hits the database
private readonly ConcurrentDictionary<UmbracoObjectTypes, (Func<int, Guid> id2key, Func<Guid, int> key2id)> _dictionary
= new ConcurrentDictionary<UmbracoObjectTypes, (Func<int, Guid> id2key, Func<Guid, int> key2id)>();
internal void SetMapper(UmbracoObjectTypes umbracoObjectType, Func<int, Guid> id2key, Func<Guid, int> key2id)
{
_dictionary[umbracoObjectType] = (id2key, key2id);
}
internal void Populate(IEnumerable<(int id, Guid key)> pairs, UmbracoObjectTypes umbracoObjectType)
{
try
{
_locker.EnterWriteLock();
foreach (var pair in pairs)
{
_id2Key.Add(pair.id, new TypedId<Guid>(pair.key, umbracoObjectType));
_key2Id.Add(pair.key, new TypedId<int>(pair.id, umbracoObjectType));
}
}
finally
{
if (_locker.IsWriteLockHeld)
_locker.ExitWriteLock();
}
}
#if POPULATE_FROM_DATABASE
private void PopulateLocked()
{
// don't if not empty
if (_key2Id.Count > 0) return;
using (var uow = _uowProvider.GetUnitOfWork())
{
// populate content and media items
var types = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media };
var values = uow.Database.Query<TypedIdDto>("SELECT id, uniqueId, nodeObjectType FROM umbracoNode WHERE nodeObjectType IN @types", new { types });
foreach (var value in values)
{
var umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(value.NodeObjectType);
_id2Key.Add(value.Id, new TypedId<Guid>(value.UniqueId, umbracoObjectType));
_key2Id.Add(value.UniqueId, new TypedId<int>(value.Id, umbracoObjectType));
}
}
}
private Attempt<int> PopulateAndGetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType)
{
try
{
_locker.EnterWriteLock();
PopulateLocked();
return _key2Id.TryGetValue(key, out var id) && id.UmbracoObjectType == umbracoObjectType
? Attempt.Succeed(id.Id)
: Attempt<int>.Fail();
}
finally
{
if (_locker.IsReadLockHeld)
_locker.ExitReadLock();
}
}
private Attempt<Guid> PopulateAndGetKeyForId(int id, UmbracoObjectTypes umbracoObjectType)
{
try
{
_locker.EnterWriteLock();
PopulateLocked();
return _id2Key.TryGetValue(id, out var key) && key.UmbracoObjectType == umbracoObjectType
? Attempt.Succeed(key.Id)
: Attempt<Guid>.Fail();
}
finally
{
if (_locker.IsReadLockHeld)
_locker.ExitReadLock();
}
}
#endif
public Attempt<int> GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType)
{
TypedId<int> id;
bool empty;
try
{
_locker.EnterReadLock();
if (_key2Id.TryGetValue(key, out id) && id.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(id.Id);
if (_key2Id.TryGetValue(key, out var id) && id.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(id.Id);
empty = _key2Id.Count == 0;
}
finally
{
@@ -36,20 +163,37 @@ namespace Umbraco.Core.Services
_locker.ExitReadLock();
}
int? val;
using (var uow = _uowProvider.GetUnitOfWork())
#if POPULATE_FROM_DATABASE
// if cache is empty and looking for a document or a media,
// populate the cache at once and return what we found
if (empty && (umbracoObjectType == UmbracoObjectTypes.Document || umbracoObjectType == UmbracoObjectTypes.Media))
return PopulateAndGetIdForKey(key, umbracoObjectType);
#endif
// optimize for read speed: reading database outside a lock means that we could read
// multiple times, but we don't lock the cache while accessing the database = better
int? val = null;
if (_dictionary.TryGetValue(umbracoObjectType, out var mappers))
if ((val = mappers.key2id(key)) == default(int)) val = null;
if (val == null)
{
//if it's unknown don't include the nodeObjectType in the query
if (umbracoObjectType == UmbracoObjectTypes.Unknown)
using (var uow = _uowProvider.GetUnitOfWork())
{
val = uow.Database.ExecuteScalar<int?>("SELECT id FROM umbracoNode WHERE uniqueId=@id", new { id = key});
//if it's unknown don't include the nodeObjectType in the query
if (umbracoObjectType == UmbracoObjectTypes.Unknown)
{
val = uow.Database.ExecuteScalar<int?>("SELECT id FROM umbracoNode WHERE uniqueId=@id", new { id = key});
}
else
{
val = uow.Database.ExecuteScalar<int?>("SELECT id FROM umbracoNode WHERE uniqueId=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)",
new { id = key, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid });
}
uow.Commit();
}
else
{
val = uow.Database.ExecuteScalar<int?>("SELECT id FROM umbracoNode WHERE uniqueId=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)",
new { id = key, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid });
}
uow.Commit();
}
if (val == null) return Attempt<int>.Fail();
@@ -83,13 +227,23 @@ namespace Umbraco.Core.Services
return GetIdForKey(guidUdi.Guid, umbracoType);
}
public Attempt<Udi> GetUdiForId(int id, UmbracoObjectTypes umbracoObjectType)
{
var keyAttempt = GetKeyForId(id, umbracoObjectType);
return keyAttempt
? Attempt.Succeed<Udi>(new GuidUdi(Constants.UdiEntityType.FromUmbracoObjectType(umbracoObjectType), keyAttempt.Result))
: Attempt<Udi>.Fail();
}
public Attempt<Guid> GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType)
{
TypedId<Guid> key;
bool empty;
try
{
_locker.EnterReadLock();
if (_id2Key.TryGetValue(id, out key) && key.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(key.Id);
if (_id2Key.TryGetValue(id, out var key) && key.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(key.Id);
empty = _id2Key.Count == 0;
}
finally
{
@@ -97,20 +251,37 @@ namespace Umbraco.Core.Services
_locker.ExitReadLock();
}
Guid? val;
using (var uow = _uowProvider.GetUnitOfWork())
#if POPULATE_FROM_DATABASE
// if cache is empty and looking for a document or a media,
// populate the cache at once and return what we found
if (empty && (umbracoObjectType == UmbracoObjectTypes.Document || umbracoObjectType == UmbracoObjectTypes.Media))
return PopulateAndGetKeyForId(id, umbracoObjectType);
#endif
// optimize for read speed: reading database outside a lock means that we could read
// multiple times, but we don't lock the cache while accessing the database = better
Guid? val = null;
if (_dictionary.TryGetValue(umbracoObjectType, out var mappers))
if ((val = mappers.id2key(id)) == default(Guid)) val = null;
if (val == null)
{
//if it's unknown don't include the nodeObjectType in the query
if (umbracoObjectType == UmbracoObjectTypes.Unknown)
using (var uow = _uowProvider.GetUnitOfWork())
{
val = uow.Database.ExecuteScalar<Guid?>("SELECT uniqueId FROM umbracoNode WHERE id=@id", new { id });
//if it's unknown don't include the nodeObjectType in the query
if (umbracoObjectType == UmbracoObjectTypes.Unknown)
{
val = uow.Database.ExecuteScalar<Guid?>("SELECT uniqueId FROM umbracoNode WHERE id=@id", new { id });
}
else
{
val = uow.Database.ExecuteScalar<Guid?>("SELECT uniqueId FROM umbracoNode WHERE id=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)",
new { id, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid });
}
uow.Commit();
}
else
{
val = uow.Database.ExecuteScalar<Guid?>("SELECT uniqueId FROM umbracoNode WHERE id=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)",
new { id, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid });
}
uow.Commit();
}
if (val == null) return Attempt<Guid>.Fail();
@@ -142,6 +313,8 @@ namespace Umbraco.Core.Services
return guid;
}
// invoked on UnpublishedPageCacheRefresher.RefreshAll
// anything else will use the id-specific overloads
public void ClearCache()
{
try
@@ -162,8 +335,7 @@ namespace Umbraco.Core.Services
try
{
_locker.EnterWriteLock();
TypedId<Guid> key;
if (_id2Key.TryGetValue(id, out key) == false) return;
if (_id2Key.TryGetValue(id, out var key) == false) return;
_id2Key.Remove(id);
_key2Id.Remove(key.Id);
}
@@ -179,8 +351,7 @@ namespace Umbraco.Core.Services
try
{
_locker.EnterWriteLock();
TypedId<int> id;
if (_key2Id.TryGetValue(key, out id) == false) return;
if (_key2Id.TryGetValue(key, out var id) == false) return;
_id2Key.Remove(id.Id);
_key2Id.Remove(key);
}
@@ -191,26 +362,28 @@ namespace Umbraco.Core.Services
}
}
// ReSharper disable ClassNeverInstantiated.Local
// ReSharper disable UnusedAutoPropertyAccessor.Local
private class TypedIdDto
{
public int Id { get; set; }
public Guid UniqueId { get; set; }
public Guid NodeObjectType { get; set; }
}
// ReSharper restore ClassNeverInstantiated.Local
// ReSharper restore UnusedAutoPropertyAccessor.Local
private struct TypedId<T>
{
private readonly T _id;
private readonly UmbracoObjectTypes _umbracoObjectType;
public T Id
{
get { return _id; }
}
public UmbracoObjectTypes UmbracoObjectType
{
get { return _umbracoObjectType; }
}
public TypedId(T id, UmbracoObjectTypes umbracoObjectType)
{
_umbracoObjectType = umbracoObjectType;
_id = id;
UmbracoObjectType = umbracoObjectType;
Id = id;
}
public UmbracoObjectTypes UmbracoObjectType { get; }
public T Id { get; }
}
}
}
}
+4
View File
@@ -115,6 +115,9 @@
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Transactions" />
<Reference Include="System.ValueTuple, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System.Web.Extensions" />
@@ -603,6 +606,7 @@
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSixZero\NormalizeTemplateGuids.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSixZero\RemovePropertyDataIdIndex.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSixZero\RemoveUmbracoDeployTables.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenTenZero\RenamePreviewFolder.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeTwo\EnsureMigrationsTableIdentityIsCorrect.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFourZero\EnsureContentTypeUniqueIdsAreConsistent.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddExternalLoginsTable.cs" />
+1
View File
@@ -18,4 +18,5 @@
<package id="Owin" version="1.0" targetFramework="net45" />
<package id="semver" version="1.1.2" targetFramework="net45" />
<package id="SqlServerCE" version="4.0.0.1" targetFramework="net45" />
<package id="System.ValueTuple" version="4.4.0" targetFramework="net45" />
</packages>
@@ -139,8 +139,8 @@
<Reference Include="System.Threading.Thread, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
<Reference Include="System.ValueTuple, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
+4
View File
@@ -62,6 +62,10 @@
<assemblyIdentity name="System.Xml.XPath.XDocument" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
+1 -1
View File
@@ -51,7 +51,7 @@
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net46" />
<package id="System.Threading.Tasks.Parallel" version="4.3.0" targetFramework="net46" />
<package id="System.Threading.Thread" version="4.3.0" targetFramework="net46" />
<package id="System.ValueTuple" version="4.3.0" targetFramework="net46" />
<package id="System.ValueTuple" version="4.4.0" targetFramework="net46" />
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net46" />
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net46" />
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="net46" />
+6 -1
View File
@@ -42,7 +42,12 @@
"codemirror"
],
"sources": {
"moment": "bower_components/moment/min/moment-with-locales.js",
"moment": [
"bower_components/moment/min/moment.min.js",
"bower_components/moment/min/moment-with-locales.js",
"bower_components/moment/min/moment-with-locales.min.js",
"bower_components/moment/locale/*.js"
],
"underscore": [
"bower_components/underscore/underscore-min.js",
"bower_components/underscore/underscore-min.map"
+1 -3
View File
@@ -73,7 +73,6 @@ var sources = {
js: {
preview: { files: ["src/canvasdesigner/**/*.js"], out: "umbraco.canvasdesigner.js" },
installer: { files: ["src/installer/**/*.js"], out: "umbraco.installer.js" },
controllers: { files: ["src/{views,controllers}/**/*.controller.js"], out: "umbraco.controllers.js" },
directives: { files: ["src/common/directives/**/*.js"], out: "umbraco.directives.js" },
filters: { files: ["src/common/filters/**/*.js"], out: "umbraco.filters.js" },
@@ -85,8 +84,7 @@ var sources = {
//selectors for copying all views into the build
//processed in the views task
views:{
umbraco: {files: ["src/views/**/*html"], folder: ""},
preview: { files: ["src/canvasdesigner/**/*.html"], folder: "../preview"},
umbraco: {files: ["src/views/**/*.html"], folder: ""},
installer: {files: ["src/installer/steps/*.html"], folder: "install"}
},
+6 -6
View File
@@ -27,9 +27,9 @@
"gulp": "^3.9.1",
"gulp-concat": "^2.6.0",
"gulp-connect": "5.0.0",
"gulp-less": "^3.1.0",
"gulp-less": "^3.5.0",
"gulp-ngdocs": "^0.3.0",
"gulp-open": "^2.0.0",
"gulp-open": "^2.1.0",
"gulp-postcss": "^6.2.0",
"gulp-rename": "^1.2.2",
"gulp-sort": "^2.0.0",
@@ -38,11 +38,11 @@
"gulp-wrap-js": "^0.4.1",
"jasmine-core": "2.5.2",
"karma": "^1.7.0",
"karma-jasmine": "^1.1.0",
"karma-jasmine": "^1.1.1",
"karma-phantomjs-launcher": "^1.0.4",
"less": "^2.6.1",
"lodash": "^4.16.3",
"less": "^2.7.3",
"lodash": "^4.17.5",
"merge-stream": "^1.0.1",
"run-sequence": "^2.1.0"
"run-sequence": "^2.2.1"
}
}
@@ -1,3 +0,0 @@
<div class="box-slider">
<div colorpicker ng-model="item.values.color"></div>
</div>
@@ -255,11 +255,9 @@
else {
$scope.save().then(function (data) {
previewWindow.location.href = redirect;
});
});
}
}
};
$scope.restore = function (content) {
@@ -8,6 +8,8 @@
var evts = [];
var isInfoTab = false;
scope.publishStatus = {};
scope.disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates;
function onInit() {
@@ -145,6 +145,12 @@ angular.module("umbraco.directives")
}
}
}
if (val === "true") {
tinyMceConfig.customConfig[i] = true;
}
if (val === "false") {
tinyMceConfig.customConfig[i] = false;
}
}
angular.extend(baseLineConfigObj, tinyMceConfig.customConfig);
@@ -0,0 +1,37 @@
(function () {
"use strict";
function javascriptLibraryService($q, $http, umbRequestHelper) {
var existingLocales = [];
function getSupportedLocalesForMoment() {
var deferred = $q.defer();
if (existingLocales.length === 0) {
umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"backOfficeAssetsApiBaseUrl",
"GetSupportedMomentLocales")),
"Failed to get cultures").then(function(locales) {
existingLocales = locales;
deferred.resolve(existingLocales);
});
} else {
deferred.resolve(existingLocales);
}
return deferred.promise;
}
var service = {
getSupportedLocalesForMoment: getSupportedLocalesForMoment
};
return service;
}
angular.module("umbraco.services").factory("javascriptLibraryService", javascriptLibraryService);
})();
@@ -1,285 +1,324 @@
angular.module('umbraco.services')
.factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, dialogService, $timeout, angularHelper, $http) {
.factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, assetsService, dialogService, $timeout, angularHelper, $http, javascriptLibraryService) {
var currentUser = null;
var lastUserId = null;
var loginDialog = null;
var currentUser = null;
var lastUserId = null;
var loginDialog = null;
//this tracks the last date/time that the user's remainingAuthSeconds was updated from the server
// this is used so that we know when to go and get the user's remaining seconds directly.
var lastServerTimeoutSet = null;
//this tracks the last date/time that the user's remainingAuthSeconds was updated from the server
// this is used so that we know when to go and get the user's remaining seconds directly.
var lastServerTimeoutSet = null;
function openLoginDialog(isTimedOut) {
if (!loginDialog) {
loginDialog = dialogService.open({
function openLoginDialog(isTimedOut) {
if (!loginDialog) {
loginDialog = dialogService.open({
//very special flag which means that global events cannot close this dialog
manualClose: true,
//very special flag which means that global events cannot close this dialog
manualClose: true,
template: 'views/common/dialogs/login.html',
modalClass: "login-overlay",
animation: "slide",
show: true,
callback: onLoginDialogClose,
dialogData: {
isTimedOut: isTimedOut
}
});
}
}
function onLoginDialogClose(success) {
loginDialog = null;
if (success) {
securityRetryQueue.retryAll(currentUser.name);
}
else {
securityRetryQueue.cancelAll();
$location.path('/');
}
}
/**
This methods will set the current user when it is resolved and
will then start the counter to count in-memory how many seconds they have
remaining on the auth session
*/
function setCurrentUser(usr) {
if (!usr.remainingAuthSeconds) {
throw "The user object is invalid, the remainingAuthSeconds is required.";
}
currentUser = usr;
lastServerTimeoutSet = new Date();
//start the timer
countdownUserTimeout();
}
/**
Method to count down the current user's timeout seconds,
this will continually count down their current remaining seconds every 5 seconds until
there are no more seconds remaining.
*/
function countdownUserTimeout() {
$timeout(function () {
if (currentUser) {
//countdown by 5 seconds since that is how long our timer is for.
currentUser.remainingAuthSeconds -= 5;
//if there are more than 30 remaining seconds, recurse!
if (currentUser.remainingAuthSeconds > 30) {
//we need to check when the last time the timeout was set from the server, if
// it has been more than 30 seconds then we'll manually go and retrieve it from the
// server - this helps to keep our local countdown in check with the true timeout.
if (lastServerTimeoutSet != null) {
var now = new Date();
var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000;
if (seconds > 30) {
//first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
// wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
lastServerTimeoutSet = null;
//now go get it from the server
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function () {
authResource.getRemainingTimeoutSeconds().then(function (result) {
setUserTimeoutInternal(result);
});
template: 'views/common/dialogs/login.html',
modalClass: "login-overlay",
animation: "slide",
show: true,
callback: onLoginDialogClose,
dialogData: {
isTimedOut: isTimedOut
}
});
}
}
}
//recurse the countdown!
countdownUserTimeout();
}
else {
function onLoginDialogClose(success) {
loginDialog = null;
//we are either timed out or very close to timing out so we need to show the login dialog.
if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) {
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function () {
try {
//NOTE: We are calling this again so that the server can create a log that the timeout has expired, we
// don't actually care about this result.
authResource.getRemainingTimeoutSeconds();
}
finally {
userAuthExpired();
}
});
if (success) {
securityRetryQueue.retryAll(currentUser.name);
}
else {
//we've got less than 30 seconds remaining so let's check the server
if (lastServerTimeoutSet != null) {
//first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
// wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
lastServerTimeoutSet = null;
//now go get it from the server
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function () {
authResource.getRemainingTimeoutSeconds().then(function (result) {
setUserTimeoutInternal(result);
});
});
}
//recurse the countdown!
countdownUserTimeout();
securityRetryQueue.cancelAll();
$location.path('/');
}
}
}
}, 5000, //every 5 seconds
false); //false = do NOT execute a digest for every iteration
}
/** Called to update the current user's timeout */
function setUserTimeoutInternal(newTimeout) {
var asNumber = parseFloat(newTimeout);
if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) {
currentUser.remainingAuthSeconds = newTimeout;
lastServerTimeoutSet = new Date();
}
}
/** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */
function userAuthExpired(isLogout) {
//store the last user id and clear the user
if (currentUser && currentUser.id !== undefined) {
lastUserId = currentUser.id;
}
if (currentUser) {
currentUser.remainingAuthSeconds = 0;
}
lastServerTimeoutSet = null;
currentUser = null;
//broadcast a global event that the user is no longer logged in
eventsService.emit("app.notAuthenticated");
openLoginDialog(isLogout === undefined ? true : !isLogout);
}
// Register a handler for when an item is added to the retry queue
securityRetryQueue.onItemAddedCallbacks.push(function (retryItem) {
if (securityRetryQueue.hasMore()) {
userAuthExpired();
}
});
return {
/** Internal method to display the login dialog */
_showLoginDialog: function () {
openLoginDialog();
},
/** Returns a promise, sends a request to the server to check if the current cookie is authorized */
isAuthenticated: function () {
//if we've got a current user then just return true
if (currentUser) {
var deferred = $q.defer();
deferred.resolve(true);
return deferred.promise;
/**
This methods will set the current user when it is resolved and
will then start the counter to count in-memory how many seconds they have
remaining on the auth session
*/
function setCurrentUser(usr) {
if (!usr.remainingAuthSeconds) {
throw "The user object is invalid, the remainingAuthSeconds is required.";
}
currentUser = usr;
lastServerTimeoutSet = new Date();
//start the timer
countdownUserTimeout();
}
return authResource.isAuthenticated();
},
/** Returns a promise, sends a request to the server to validate the credentials */
authenticate: function (login, password) {
/**
Method to count down the current user's timeout seconds,
this will continually count down their current remaining seconds every 5 seconds until
there are no more seconds remaining.
*/
function countdownUserTimeout() {
return authResource.performLogin(login, password)
.then(this.setAuthenticationSuccessful);
},
setAuthenticationSuccessful: function (data) {
$timeout(function () {
//when it's successful, return the user data
setCurrentUser(data);
if (currentUser) {
//countdown by 5 seconds since that is how long our timer is for.
currentUser.remainingAuthSeconds -= 5;
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "credentials" };
//if there are more than 30 remaining seconds, recurse!
if (currentUser.remainingAuthSeconds > 30) {
//broadcast a global event
eventsService.emit("app.authenticated", result);
return result;
},
//we need to check when the last time the timeout was set from the server, if
// it has been more than 30 seconds then we'll manually go and retrieve it from the
// server - this helps to keep our local countdown in check with the true timeout.
if (lastServerTimeoutSet != null) {
var now = new Date();
var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000;
/** Logs the user out
*/
logout: function () {
if (seconds > 30) {
return authResource.performLogout()
.then(function (data) {
userAuthExpired();
//done!
return null;
});
},
//first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
// wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
lastServerTimeoutSet = null;
/** Refreshes the current user data with the data stored for the user on the server and returns it */
refreshCurrentUser: function() {
var deferred = $q.defer();
//now go get it from the server
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function () {
authResource.getRemainingTimeoutSeconds().then(function (result) {
setUserTimeoutInternal(result);
});
});
}
}
authResource.getCurrentUser()
.then(function (data) {
//recurse the countdown!
countdownUserTimeout();
}
else {
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" };
setCurrentUser(data);
//we are either timed out or very close to timing out so we need to show the login dialog.
if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) {
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function () {
try {
//NOTE: We are calling this again so that the server can create a log that the timeout has expired, we
// don't actually care about this result.
authResource.getRemainingTimeoutSeconds();
}
finally {
userAuthExpired();
}
});
}
else {
//we've got less than 30 seconds remaining so let's check the server
deferred.resolve(currentUser);
}, function () {
//it failed, so they are not logged in
deferred.reject();
});
if (lastServerTimeoutSet != null) {
//first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
// wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
lastServerTimeoutSet = null;
return deferred.promise;
},
//now go get it from the server
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function () {
authResource.getRemainingTimeoutSeconds().then(function (result) {
setUserTimeoutInternal(result);
});
});
}
/** Returns the current user object in a promise */
getCurrentUser: function (args) {
var deferred = $q.defer();
//recurse the countdown!
countdownUserTimeout();
if (!currentUser) {
authResource.getCurrentUser()
.then(function (data) {
}
}
}
}, 5000, //every 5 seconds
false); //false = do NOT execute a digest for every iteration
}
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" };
/** Called to update the current user's timeout */
function setUserTimeoutInternal(newTimeout) {
if (args && args.broadcastEvent) {
//broadcast a global event, will inform listening controllers to load in the user specific data
var asNumber = parseFloat(newTimeout);
if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) {
currentUser.remainingAuthSeconds = newTimeout;
lastServerTimeoutSet = new Date();
}
}
/** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */
function userAuthExpired(isLogout) {
//store the last user id and clear the user
if (currentUser && currentUser.id !== undefined) {
lastUserId = currentUser.id;
}
if (currentUser) {
currentUser.remainingAuthSeconds = 0;
}
lastServerTimeoutSet = null;
currentUser = null;
//broadcast a global event that the user is no longer logged in
eventsService.emit("app.notAuthenticated");
openLoginDialog(isLogout === undefined ? true : !isLogout);
}
// Register a handler for when an item is added to the retry queue
securityRetryQueue.onItemAddedCallbacks.push(function (retryItem) {
if (securityRetryQueue.hasMore()) {
userAuthExpired();
}
});
return {
/** Internal method to display the login dialog */
_showLoginDialog: function () {
openLoginDialog();
},
/** Returns a promise, sends a request to the server to check if the current cookie is authorized */
isAuthenticated: function () {
//if we've got a current user then just return true
if (currentUser) {
var deferred = $q.defer();
deferred.resolve(true);
return deferred.promise;
}
return authResource.isAuthenticated();
},
/** Returns a promise, sends a request to the server to validate the credentials */
authenticate: function (login, password) {
return authResource.performLogin(login, password)
.then(this.setAuthenticationSuccessful);
},
setAuthenticationSuccessful: function (data) {
//when it's successful, return the user data
setCurrentUser(data);
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "credentials" };
//broadcast a global event
eventsService.emit("app.authenticated", result);
}
return result;
},
setCurrentUser(data);
/** Logs the user out
*/
logout: function () {
deferred.resolve(currentUser);
}, function () {
//it failed, so they are not logged in
deferred.reject();
});
return authResource.performLogout()
.then(function (data) {
userAuthExpired();
//done!
return null;
});
},
}
else {
deferred.resolve(currentUser);
}
/** Refreshes the current user data with the data stored for the user on the server and returns it */
refreshCurrentUser: function () {
var deferred = $q.defer();
return deferred.promise;
},
authResource.getCurrentUser()
.then(function (data) {
/** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */
setUserTimeout: function (newTimeout) {
setUserTimeoutInternal(newTimeout);
}
};
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" };
});
setCurrentUser(data);
deferred.resolve(currentUser);
}, function () {
//it failed, so they are not logged in
deferred.reject();
});
return deferred.promise;
},
/** Returns the current user object in a promise */
getCurrentUser: function (args) {
var deferred = $q.defer();
if (!currentUser) {
authResource.getCurrentUser()
.then(function (data) {
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" };
if (args && args.broadcastEvent) {
//broadcast a global event, will inform listening controllers to load in the user specific data
eventsService.emit("app.authenticated", result);
}
setCurrentUser(data);
deferred.resolve(currentUser);
}, function () {
//it failed, so they are not logged in
deferred.reject();
});
}
else {
deferred.resolve(currentUser);
}
return deferred.promise;
},
/** Loads the Moment.js Locale for the current user. */
loadMomentLocaleForCurrentUser: function () {
var deferred = $q.defer();
function loadLocales(currentUser, supportedLocales) {
var locale = currentUser.locale.toLowerCase();
if (locale !== 'en-us') {
var localeUrls = [];
if (supportedLocales.indexOf(locale + '.js') > -1) {
localeUrls.push('lib/moment/' + locale + '.js');
}
if (locale.indexOf('-') > -1) {
var majorLocale = locale.split('-')[0] + '.js';
if (supportedLocales.indexOf(majorLocale) > -1) {
localeUrls.push('lib/moment/' + majorLocale);
}
}
assetsService.load(localeUrls).then(function () {
deferred.resolve(localeUrls);
});
} else {
deferred.resolve(['']);
}
}
var promises = {
currentUser: this.getCurrentUser(),
supportedLocales: javascriptLibraryService.getSupportedLocalesForMoment()
}
$q.all(promises).then(function (values) {
loadLocales(values.currentUser, values.supportedLocales);
});
return deferred.promise;
},
/** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */
setUserTimeout: function (newTimeout) {
setUserTimeoutInternal(newTimeout);
}
};
});
+18 -15
View File
@@ -18,23 +18,26 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi
eventsService.on("app.authenticated", function(evt, data) {
assetsService._loadInitAssets().then(function() {
//Register all of the tours on the server
tourService.registerAllTours().then(function () {
appReady(data);
// Auto start intro tour
tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) {
// start intro tour if it hasn't been completed or disabled
if (introTour && introTour.disabled !== true && introTour.completed !== true) {
tourService.startTour(introTour);
}
// Loads the user's locale settings for Moment.
userService.loadMomentLocaleForCurrentUser().then(function() {
//Register all of the tours on the server
tourService.registerAllTours().then(function () {
appReady(data);
// Auto start intro tour
tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) {
// start intro tour if it hasn't been completed or disabled
if (introTour && introTour.disabled !== true && introTour.completed !== true) {
tourService.startTour(introTour);
}
});
}, function(){
appReady(data);
});
}, function(){
appReady(data);
});
});
});
@@ -171,27 +171,25 @@
{{publishStatus.label}}
</umb-badge>
</umb-control-group>
<umb-control-group data-element="node-info-create-date" label="@template_createdDate">
{{node.createDateFormatted}} <localize key="general_by">by</localize> {{ node.owner.name }}
</umb-control-group>
<umb-control-group data-element="node-info-document-type" label="@content_documentType">
<umb-node-preview
style="max-width: 100%; margin-bottom: 0;"
icon="documentType.icon"
name="documentType.name"
allow-open="allowOpen"
on-open="openDocumentType(documentType)">
<umb-node-preview style="max-width: 100%; margin-bottom: 0;"
icon="documentType.icon"
name="documentType.name"
allow-open="allowOpen"
on-open="openDocumentType(documentType)">
</umb-node-preview>
</umb-control-group>
<umb-control-group data-element="node-info-template" label="@template_template">
<select
class="input-block-level"
ng-model="node.template"
ng-options="key as value for (key, value) in availableTemplates"
ng-change="updateTemplate(node.template)">
<umb-control-group ng-if="disableTemplates == false" data-element="node-info-template" label="@template_template">
<select class="input-block-level"
ng-model="node.template"
ng-options="key as value for (key, value) in availableTemplates"
ng-change="updateTemplate(node.template)">
<option value=""><localize key="general_choose">Choose</localize>...</option>
</select>
</umb-control-group>
@@ -200,7 +198,7 @@
<div>{{ node.id }}</div>
<small>{{ node.key }}</small>
</umb-control-group>
</umb-box-content>
</umb-box>
</div>
@@ -14,18 +14,21 @@ function DocumentTypesCreateController($scope, $location, navigationService, con
creatingFolder: false,
};
var disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates;
$scope.model.disableTemplates = disableTemplates;
var node = $scope.dialogOptions.currentNode,
localizeCreateFolder = localizationService.localize("defaultdialog_createFolder");
$scope.showCreateFolder = function() {
$scope.showCreateFolder = function () {
$scope.model.creatingFolder = true;
};
$scope.createContainer = function() {
$scope.createContainer = function () {
if (formHelper.submitForm({scope: $scope, formCtrl: this.createFolderForm, statusMessage: localizeCreateFolder})) {
if (formHelper.submitForm({ scope: $scope, formCtrl: this.createFolderForm, statusMessage: localizeCreateFolder })) {
contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function(folderId) {
contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
navigationService.hideMenu();
@@ -44,7 +47,7 @@ function DocumentTypesCreateController($scope, $location, navigationService, con
var section = appState.getSectionState("currentSection");
}, function(err) {
}, function (err) {
$scope.error = err;
@@ -58,14 +61,17 @@ function DocumentTypesCreateController($scope, $location, navigationService, con
}
};
$scope.createDocType = function() {
$location.search('create', null);
$location.search('notemplate', null);
$location.path("/settings/documenttypes/edit/" + node.id).search("create", "true");
navigationService.hideMenu();
};
// Disabling logic for creating document type with template if disableTemplates is set to true
if (!disableTemplates) {
$scope.createDocType = function () {
$location.search('create', null);
$location.search('notemplate', null);
$location.path("/settings/documenttypes/edit/" + node.id).search("create", "true");
navigationService.hideMenu();
};
}
$scope.createComponent = function() {
$scope.createComponent = function () {
$location.search('create', null);
$location.search('notemplate', null);
$location.path("/settings/documenttypes/edit/" + node.id).search("create", "true").search("notemplate", "true");
@@ -4,7 +4,7 @@
<h5><localize key="create_createUnder">Create an item under</localize> {{currentNode.name}}</h5>
<ul class="umb-actions umb-actions-child">
<li data-element="action-documentType">
<li data-element="action-documentType" ng-hide="model.disableTemplates">
<a href="" ng-click="createDocType()" umb-auto-focus>
<i class="large icon-item-arrangement"></i>
@@ -15,10 +15,14 @@
</a>
</li>
<li data-element="action-documentTypeWithoutTemplate">
<a href="" ng-click="createComponent()">
<i class="large icon-item-arrangement"></i>
<span class="menu-label"><localize key="create_documentTypeWithoutTemplate"></localize></span>
</a>
<a href="" ng-click="createComponent()">
<i class="large icon-item-arrangement"></i>
<span class="menu-label">
<localize ng-if="model.disableTemplates == false" key="create_documentTypeWithoutTemplate"></localize>
<localize ng-if="model.disableTemplates == true" key="content_documentType">Document type></localize>
</span>
</a>
</li>
<li data-element="action-folder" ng-if="model.allowCreateFolder">
<a href="" ng-click="showCreateFolder()">
@@ -33,7 +37,7 @@
<form novalidate name="createFolderForm"
ng-submit="createContainer()"
val-form-manager>
<div ng-show="error">
<h5 class="text-error">{{error.errorMsg}}</h5>
<p class="text-error">{{error.data.message}}</p>
@@ -15,6 +15,36 @@
var localizeSaving = localizationService.localize("general_saving");
var evts = [];
var disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates;
var buttons = [
{
"name": localizationService.localize("general_design"),
"alias": "design",
"icon": "icon-document-dashed-line",
"view": "views/documenttypes/views/design/design.html",
"active": true
},
{
"name": localizationService.localize("general_listView"),
"alias": "listView",
"icon": "icon-list",
"view": "views/documenttypes/views/listview/listview.html"
},
{
"name": localizationService.localize("general_rights"),
"alias": "permissions",
"icon": "icon-keychain",
"view": "views/documenttypes/views/permissions/permissions.html"
},
{
"name": localizationService.localize("treeHeaders_templates"),
"alias": "templates",
"icon": "icon-layout",
"view": "views/documenttypes/views/templates/templates.html"
}
];
vm.save = save;
vm.currentNode = null;
@@ -23,97 +53,73 @@
vm.page = {};
vm.page.loading = false;
vm.page.saveButtonState = "init";
vm.page.navigation = [
{
"name": localizationService.localize("general_design"),
"alias": "design",
"icon": "icon-document-dashed-line",
"view": "views/documenttypes/views/design/design.html",
"active": true
},
{
"name": localizationService.localize("general_listView"),
"alias": "listView",
"icon": "icon-list",
"view": "views/documenttypes/views/listview/listview.html"
},
{
"name": localizationService.localize("general_rights"),
"alias": "permissions",
"icon": "icon-keychain",
"view": "views/documenttypes/views/permissions/permissions.html"
},
{
"name": localizationService.localize("treeHeaders_templates"),
"alias": "templates",
"icon": "icon-layout",
"view": "views/documenttypes/views/templates/templates.html"
}
];
vm.page.navigation = [];
loadButtons();
vm.page.keyboardShortcutsOverview = [
{
"name": localizationService.localize("main_sections"),
"shortcuts": [
{
"description": localizationService.localize("shortcuts_navigateSections"),
"keys": [{ "key": "1" }, { "key": "4" }],
"keyRange": true
}
]
},
{
"name": localizationService.localize("general_design"),
"shortcuts": [
{
"description": localizationService.localize("shortcuts_addTab"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }]
},
{
"description": localizationService.localize("shortcuts_addProperty"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "p" }]
},
{
"description": localizationService.localize("shortcuts_addEditor"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "e" }]
},
{
"description": localizationService.localize("shortcuts_editDataType"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "d" }]
}
]
},
{
"name": localizationService.localize("general_listView"),
"shortcuts": [
{
"description": localizationService.localize("shortcuts_toggleListView"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "l" }]
}
]
},
{
"name": localizationService.localize("general_rights"),
"shortcuts": [
{
"description": localizationService.localize("shortcuts_toggleAllowAsRoot"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "r" }]
},
{
"description": localizationService.localize("shortcuts_addChildNode"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "c" }]
}
]
},
{
"name": localizationService.localize("treeHeaders_templates"),
"shortcuts": [
{
"description": localizationService.localize("shortcuts_addTemplate"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }]
}
]
}
{
"name": localizationService.localize("main_sections"),
"shortcuts": [
{
"description": localizationService.localize("shortcuts_navigateSections"),
"keys": [{ "key": "1" }, { "key": "4" }],
"keyRange": true
}
]
},
{
"name": localizationService.localize("general_design"),
"shortcuts": [
{
"description": localizationService.localize("shortcuts_addTab"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }]
},
{
"description": localizationService.localize("shortcuts_addProperty"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "p" }]
},
{
"description": localizationService.localize("shortcuts_addEditor"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "e" }]
},
{
"description": localizationService.localize("shortcuts_editDataType"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "d" }]
}
]
},
{
"name": localizationService.localize("general_listView"),
"shortcuts": [
{
"description": localizationService.localize("shortcuts_toggleListView"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "l" }]
}
]
},
{
"name": localizationService.localize("general_rights"),
"shortcuts": [
{
"description": localizationService.localize("shortcuts_toggleAllowAsRoot"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "r" }]
},
{
"description": localizationService.localize("shortcuts_addChildNode"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "c" }]
}
]
},
{
"name": localizationService.localize("treeHeaders_templates"),
"shortcuts": [
{
"description": localizationService.localize("shortcuts_addTemplate"),
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }]
}
]
}
];
contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
@@ -144,7 +150,7 @@
vm.page.saveButtonState = "busy";
localizationService.localize("modelsBuilder_buildingModels").then(function (headerValue) {
localizationService.localize("modelsBuilder_waitingMessage").then(function(msgValue) {
localizationService.localize("modelsBuilder_waitingMessage").then(function (msgValue) {
notificationsService.info(headerValue, msgValue);
});
});
@@ -155,26 +161,26 @@
if (!result.lastError) {
//re-check model status
contentTypeHelper.checkModelsBuilderStatus().then(function(statusResult) {
contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) {
vm.page.modelsBuilder = statusResult;
});
//clear and add success
vm.page.saveButtonState = "init";
localizationService.localize("modelsBuilder_modelsGenerated").then(function(value) {
localizationService.localize("modelsBuilder_modelsGenerated").then(function (value) {
notificationsService.success(value);
});
} else {
vm.page.saveButtonState = "error";
localizationService.localize("modelsBuilder_modelsExceptionInUlog").then(function(value) {
localizationService.localize("modelsBuilder_modelsExceptionInUlog").then(function (value) {
notificationsService.error(value);
});
}
}, function () {
vm.page.saveButtonState = "error";
localizationService.localize("modelsBuilder_modelsGeneratedError").then(function(value) {
localizationService.localize("modelsBuilder_modelsGeneratedError").then(function (value) {
notificationsService.error(value);
});
});
@@ -189,7 +195,7 @@
//we are creating so get an empty data type item
contentTypeResource.getScaffold($routeParams.id)
.then(function(dt) {
.then(function (dt) {
init(dt);
vm.page.loading = false;
});
@@ -207,12 +213,26 @@
});
}
function loadButtons() {
angular.forEach(buttons,
function (val, index) {
if (disableTemplates === true && val.alias === "templates") {
buttons.splice(index, 1);
}
});
vm.page.navigation = buttons;
}
/* ---------- SAVE ---------- */
function save() {
// only save if there is no overlays open
if(overlayHelper.getNumberOfOverlays() === 0) {
if (overlayHelper.getNumberOfOverlays() === 0) {
var deferred = $q.defer();
@@ -233,7 +253,7 @@
// we need to rebind... the IDs that have been created!
rebindCallback: function (origContentType, savedContentType) {
vm.contentType.id = savedContentType.id;
vm.contentType.groups.forEach(function(group) {
vm.contentType.groups.forEach(function (group) {
if (!group.name) return;
var k = 0;
while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name)
@@ -273,7 +293,7 @@
}
else {
localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) {
localizationService.localize("speechBubbles_validationFailedMessage").then(function(msgValue) {
localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) {
notificationsService.error(headerValue, msgValue);
});
});
@@ -331,7 +351,7 @@
function getDataTypeDetails(property) {
if (property.propertyState !== "init") {
dataTypeResource.getById(property.dataTypeId)
.then(function(dataType) {
.then(function (dataType) {
property.dataTypeIcon = dataType.icon;
property.dataTypeName = dataType.name;
});
@@ -345,7 +365,7 @@
});
}
evts.push(eventsService.on("app.refreshEditor", function(name, error) {
evts.push(eventsService.on("app.refreshEditor", function (name, error) {
loadDocumentType();
}));
@@ -0,0 +1,4 @@
angular.module("umbraco").controller("Umbraco.PrevalueEditors.BooleanController",
function ($scope) {
$scope.htmlId = "bool-" + String.CreateGuid();
});
@@ -1 +1,4 @@
<input name="boolean" type="checkbox" ng-model="model.value" ng-true-value="1" ng-false-value="0" />
<label ng-controller="Umbraco.PrevalueEditors.BooleanController" for="{{htmlId}}" class="checkbox">
<input name="boolean" type="checkbox" ng-model="model.value" ng-true-value="1" ng-false-value="0" id="{{htmlId}}" />
<localize key="general_yes">Yes</localize>
</label>
@@ -0,0 +1,81 @@
angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleController",
function($scope) {
//setup the default config
var config = {
items: [],
multiple: false
};
//map the user config
angular.extend(config, $scope.model.config);
//map back to the model
$scope.model.config = config;
function convertArrayToDictionaryArray(model){
//now we need to format the items in the dictionary because we always want to have an array
var newItems = [];
for (var i = 0; i < model.length; i++) {
newItems.push({ id: model[i], sortOrder: 0, value: model[i] });
}
return newItems;
}
function convertObjectToDictionaryArray(model){
//now we need to format the items in the dictionary because we always want to have an array
var newItems = [];
var vals = _.values($scope.model.config.items);
var keys = _.keys($scope.model.config.items);
for (var i = 0; i < vals.length; i++) {
var label = vals[i].value ? vals[i].value : vals[i];
newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: label });
}
return newItems;
}
$scope.updateSingleDropdownValue = function() {
$scope.model.value = [$scope.model.singleDropdownValue];
}
if (angular.isArray($scope.model.config.items)) {
//PP: I dont think this will happen, but we have tests that expect it to happen..
//if array is simple values, convert to array of objects
if(!angular.isObject($scope.model.config.items[0])){
$scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items);
}
}
else if (angular.isObject($scope.model.config.items)) {
$scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items);
}
else {
throw "The items property must be either an array or a dictionary";
}
//sort the values
$scope.model.config.items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
//now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
// to "" gets selected by default
if ($scope.model.value === null || $scope.model.value === undefined) {
if ($scope.model.config.multiple) {
$scope.model.value = [];
}
else {
$scope.model.value = "";
}
}
// if we run in single mode we'll store the value in a local variable
// so we can pass an array as the model as our PropertyValueEditor expects that
$scope.model.singleDropdownValue = "";
if ($scope.model.config.multiple === "0") {
$scope.model.singleDropdownValue = Array.isArray($scope.model.value) ? $scope.model.value[0] : $scope.model.value;
}
});
@@ -0,0 +1,19 @@
<div ng-controller="Umbraco.PropertyEditors.DropdownFlexibleController" ng-switch="model.config.multiple">
<select name="dropDownList"
class="umb-editor umb-dropdown"
ng-switch-default
ng-change="updateSingleDropdownValue()"
ng-model="model.singleDropdownValue"
ng-options="item.id as item.value for item in model.config.items">
<option></option>
</select>
<!--NOTE: This ng-switch is required because ng-multiple doesn't actually support dynamic bindings with multi-select lists -->
<select name="dropDownList"
class="umb-editor umb-dropdown"
ng-switch-when="1"
multiple
ng-model="model.value"
ng-options="item.id as item.value for item in model.config.items"></select>
</div>
@@ -157,26 +157,24 @@ angular.module("umbraco")
.controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController)
.run(function(mediaHelper, umbRequestHelper, assetsService){
if (mediaHelper && mediaHelper.registerFileResolver) {
assetsService.load(["lib/moment/moment-with-locales.js"]).then(
function () {
//NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
// they contain different data structures so if we need to query against it we need to be aware of this.
mediaHelper.registerFileResolver("Umbraco.UploadField", function(property, entity, thumbnail){
if (thumbnail) {
if (mediaHelper.detectIfImageByExtension(property.value)) {
//get default big thumbnail from image processor
var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first";
return thumbnailUrl;
}
else {
return null;
}
}
else {
return property.value;
}
});
//NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
// they contain different data structures so if we need to query against it we need to be aware of this.
mediaHelper.registerFileResolver("Umbraco.UploadField", function(property, entity, thumbnail){
if (thumbnail) {
if (mediaHelper.detectIfImageByExtension(property.value)) {
//get default big thumbnail from image processor
var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first";
return thumbnailUrl;
}
else {
return null;
}
}
);
else {
return property.value;
}
});
}
});
@@ -185,6 +185,12 @@ angular.module("umbraco")
//cannot parse, we'll just leave it
}
}
if (val === "true") {
tinyMceConfig.customConfig[i] = true;
}
if (val === "false") {
tinyMceConfig.customConfig[i] = false;
}
}
}
@@ -99,4 +99,4 @@ module.exports = function (config) {
'karma-phantomjs-launcher'
]
});
};
};
+15 -3
View File
@@ -837,6 +837,19 @@
<Content Include="Views\Partials\Grid\Editors\Base.cshtml" />
<Content Include="Views\Partials\Grid\Bootstrap3-Fluid.cshtml" />
<Content Include="Views\Partials\Grid\Bootstrap2-Fluid.cshtml" />
<Content Include="Umbraco\Views\Preview\Background.cshtml" />
<Content Include="Umbraco\Views\Preview\Border.cshtml" />
<Content Include="Umbraco\Views\Preview\Color.cshtml" />
<Content Include="Umbraco\Views\Preview\Googlefontpicker.cshtml" />
<Content Include="Umbraco\Views\Preview\GridRow.cshtml" />
<Content Include="Umbraco\Views\Preview\Index.cshtml" />
<Content Include="Umbraco\Views\Preview\Layout.cshtml" />
<Content Include="Umbraco\Views\Preview\Margin.cshtml" />
<Content Include="Umbraco\Views\Preview\Padding.cshtml" />
<Content Include="Umbraco\Views\Preview\Radius.cshtml" />
<Content Include="Umbraco\Views\Preview\Shadow.cshtml" />
<Content Include="Umbraco\Views\Preview\Slider.cshtml" />
<Content Include="Umbraco\Views\Preview\web.config" />
<None Include="Web.Debug.config.transformed" />
<None Include="web.Template.Debug.config">
<DependentUpon>Web.Template.config</DependentUpon>
@@ -994,7 +1007,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="App_Data\" />
<Folder Include="Umbraco\preview\" />
<Folder Include="Views\MacroPartials\" />
</ItemGroup>
<ItemGroup>
@@ -1023,9 +1035,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\"
<WebProjectProperties>
<UseIIS>True</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>7950</DevelopmentServerPort>
<DevelopmentServerPort>7100</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:7950</IISUrl>
<IISUrl>http://localhost:7100</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
@@ -1,4 +1,4 @@

@inherits System.Web.Mvc.WebViewPage
<div ng-controller="Umbraco.canvasdesigner.background">
<div class="box-slider">
@@ -1,4 +1,4 @@

@inherits System.Web.Mvc.WebViewPage
<div ng-controller="Umbraco.canvasdesigner.border" class="bordereditor">
<div class="box-slider">
@@ -17,4 +17,4 @@
<div ui-slider min="0" max="40" step="1" ng-model="selectedBorder.size"></div>
</div>
</div>
</div>
@@ -0,0 +1,4 @@
@inherits System.Web.Mvc.WebViewPage
<div class="box-slider">
<div colorpicker ng-model="item.values.color"></div>
</div>
@@ -1,4 +1,4 @@

@inherits System.Web.Mvc.WebViewPage
<div ng-controller="Umbraco.canvasdesigner.googlefontpicker">
<div class="box-slider">
@@ -1,8 +1,8 @@

@inherits System.Web.Mvc.WebViewPage
<div ng-controller="Umbraco.canvasdesigner.gridRow">
<div class="box-slider">
<input type="checkbox" ng-model="item.values.fullsize" /><label>Full size</label>
</div>
</div>
</div>
@@ -1,4 +1,9 @@
<!DOCTYPE html>
@using System.Web.Mvc.Html
@inherits System.Web.Mvc.WebViewPage<Umbraco.Web.Models.ContentEditing.BackOfficePreview>
@{
var disableDevicePreview = Model.DisableDevicePreview.ToString().ToLowerInvariant();
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Umbraco Canvas Designer</title>
@@ -6,27 +11,25 @@
<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">
<div class="wait" ng-show="!frameLoaded"></div>
@if (string.IsNullOrWhiteSpace(Model.PreviewExtendedHeaderView) == false)
{
@Html.Partial(Model.PreviewExtendedHeaderView)
}
<div id="demo-iframe-wrapper" ng-show="frameLoaded" class="{{previewDevice.css}}">
<iframe id="resultFrame" ng-src="{{pageUrl}}" frameborder="0" iframe-is-loaded></iframe>
</div>
<div class="canvasdesigner" ng-init="showDevicesPreview = true; showPalettePicker = true" ng-mouseenter="positionSelectedHide()">
<div class="canvasdesigner" ng-init="showDevicesPreview = true; showDevices = !@(disableDevicePreview); showPalettePicker = true" ng-mouseenter="positionSelectedHide()">
<div class="fix-left-menu selected">
<div class="avatar">
<img
ng-src="../assets/img/application/logo.png"
ng-srcset="../assets/img/application/logo@2x.png 2x,
<img ng-src="../assets/img/application/logo.png"
ng-srcset="../assets/img/application/logo@2x.png 2x,
../assets/img/application/logo@3x.png 3x" />
</div>
<ul class="sections" ng-class="{selected: showDevicesPreview}">
<ul class="sections" ng-class="{selected: showDevicesPreview && showDevices}">
<li ng-repeat="device in devices" ng-class="{ current:previewDevice==device }" ng-click="updatePreviewDevice(device)">
<a href="#"><i class="icon {{device.icon}}" title="{{device.title}}"></i><span></span></a>
</li>
@@ -39,9 +42,8 @@
</li>
<li ng-click="exitPreview()">
<a href="#" title="Exit Preview"><i class="icon icon-wrong"></i><span> </span></a>
</li>
</li>
</ul>
<ul class="sections" ng-class="{selected: !showDevicesPreview}" ng-if="enableCanvasdesigner > 0">
<li ng-click="openPreviewDevice()">
<a href="#"><i class="icon {{previewDevice.icon}}"></i><span>Preview</span></a>
@@ -53,15 +55,11 @@
<a href="#"><i class="icon icon-paint-roller"></i><span>UI Designer</span></a>
</li>
</ul>
</div>
<div class="main-panel" ng-class="{selected: !showDevicesPreview && ( showPalettePicker || showStyleEditor )}">
<div class="header">
<h3>Palette Style</h3>
</div>
<div class="content">
<ul class="samples">
<li ng-repeat="palette in canvasdesignerPalette">
@@ -78,7 +76,6 @@
</li>
</ul>
</div>
<div class="btn-group">
<a class="btn btn-success" ng-click="saveStyle()">Save Style</a>
<a class="btn btn-success dropdown-toggle" ng-click="opendropdown = !opendropdown">
@@ -90,11 +87,8 @@
<li><a ng-click="makePreset();opendropdown = false">Make preset</a></li>
</ul>
</div>
</div>
<div class="main-panel" ng-class="{selected: !showDevicesPreview && showStyleEditor}">
<div ng-show="!currentSelected">
<div class="header">
<h3>Select</h3>
@@ -112,7 +106,6 @@
</ul>
</div>
</div>
<div ng-repeat="configItem in canvasdesignerModel.configs" ng-show="currentSelected && currentSelected.name.toLowerCase() == configItem.name.toLowerCase()
&& currentSelected.schema.toLowerCase() == configItem.schema.toLowerCase()" on-finish-render-filters>
<div class="header">
@@ -134,7 +127,6 @@
</div>
</div>
</div>
<div class="btn-group">
<a class="btn btn-success" ng-click="saveStyle()">Save Style</a>
<a class="btn btn-success dropdown-toggle" ng-click="opendropdown = !opendropdown">
@@ -146,20 +138,13 @@
<li><a ng-click="makePreset();opendropdown = false">Make preset</a></li>
</ul>
</div>
</div>
<div class="float-panel"></div>
</div>
<div id="speechbubble">
<p>Styles saved and published</p>
</div>
<script src="../lib/rgrove-lazyload/lazyload.js"></script>
<script src="../js/canvasdesigner.loader.js"></script>
</body>
</html>
@@ -1,4 +1,4 @@

@inherits System.Web.Mvc.WebViewPage
<div ng-controller="Umbraco.canvasdesigner.layout">
<div class="box-slider">
@@ -7,4 +7,4 @@
<input type="radio" ng-model="item.values.layout" value="full"> Full
</div>
</div>
</div>
@@ -1,7 +1,7 @@

@inherits System.Web.Mvc.WebViewPage
<div ng-controller="Umbraco.canvasdesigner.margin">
<div class="box-slider">
<div class="box-slider">
<ul class="box-preview">
<li ng-repeat="margin in marginList" class="border-{{margin}}" ng-class="{selected: selectedmargin.name == margin}" ng-click="setSelectedmargin(margin)"></li>
</ul>
@@ -11,4 +11,4 @@
<div ui-slider min="0" max="400" step="1" ng-model="selectedmargin.value"></div>
</div>
</div>
</div>
@@ -1,4 +1,4 @@

@inherits System.Web.Mvc.WebViewPage
<div ng-controller="Umbraco.canvasdesigner.padding">
<div class="box-slider">
@@ -11,4 +11,4 @@
<div ui-slider min="0" max="400" step="1" ng-model="selectedpadding.value"></div>
</div>
</div>
</div>
@@ -1,9 +1,9 @@

@inherits System.Web.Mvc.WebViewPage
<div ng-controller="Umbraco.canvasdesigner.radius">
<div class="box-slider">
<ul class="box-preview">
<li ng-repeat="radius in radiusList" ng-class="{selected: selectedradius.name == radius}" ng-click="setSelectedradius(radius)">
<span ng-show="radius == 'topleft' || radius == 'all'" class="radius-top-left"></span>
<span ng-show="radius == 'topright' || radius == 'all'" class="radius-top-right"></span>
@@ -18,4 +18,4 @@
<div ui-slider min="0" max="40" step="1" ng-model="selectedradius.value"></div>
</div>
</div>
</div>
@@ -1,8 +1,8 @@

@inherits System.Web.Mvc.WebViewPage
<div ng-controller="Umbraco.canvasdesigner.shadow">
<div class="box-slider">
<div ui-slider min="0" max="100" step="1" ng-model="item.values.shadow"></div>
</div>
</div>
</div>
@@ -1,8 +1,8 @@

@inherits System.Web.Mvc.WebViewPage
<div ng-controller="Umbraco.canvasdesigner.slider">
<div class="box-slider">
<div ui-slider min="{{item.min}}" max="{{item.max}}" step="1" ng-model="item.values.slider"></div>
</div>
</div>
</div>
@@ -0,0 +1,41 @@
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
</system.web.webPages.razor>
<appSettings>
<add key="webpages:Enabled" value="false" />
</appSettings>
<system.webServer>
<handlers>
<remove name="BlockViewHandler"/>
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
</handlers>
</system.webServer>
<system.web>
<compilation>
<assemblies>
<add assembly="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</assemblies>
</compilation>
</system.web>
</configuration>
@@ -9,7 +9,6 @@ using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Sync;
using Umbraco.Web.Routing;
using Umbraco.Core.Logging;
using Umbraco.Core.Scoping;
using Umbraco.Web.Scheduling;
namespace Umbraco.Web
@@ -22,12 +21,9 @@ namespace Umbraco.Web
/// </remarks>
public class BatchedDatabaseServerMessenger : DatabaseServerMessenger
{
private readonly ApplicationContext _appContext;
public BatchedDatabaseServerMessenger(ApplicationContext appContext, bool enableDistCalls, DatabaseServerMessengerOptions options)
: base(appContext, enableDistCalls, options)
{
_appContext = appContext;
Scheduler.Initializing += Scheduler_Initializing;
}
@@ -46,7 +42,7 @@ namespace Umbraco.Web
//start the background task runner for processing instructions
const int delayMilliseconds = 60000;
var instructionProcessingRunner = new BackgroundTaskRunner<IBackgroundTask>("InstructionProcessing", ApplicationContext.ProfilingLogger.Logger);
var instructionProcessingTask = new InstructionProcessing(instructionProcessingRunner, this, _appContext.ScopeProvider, delayMilliseconds, Options.ThrottleSeconds * 1000);
var instructionProcessingTask = new InstructionProcessing(instructionProcessingRunner, this, delayMilliseconds, Options.ThrottleSeconds * 1000);
instructionProcessingRunner.TryAdd(instructionProcessingTask);
e.Add(instructionProcessingTask);
}
@@ -77,31 +73,18 @@ namespace Umbraco.Web
private class InstructionProcessing : RecurringTaskBase
{
private readonly DatabaseServerMessenger _messenger;
private readonly IScopeProvider _scopeProvider;
public InstructionProcessing(IBackgroundTaskRunner<RecurringTaskBase> runner,
DatabaseServerMessenger messenger,
IScopeProvider scopeProvider,
int delayMilliseconds, int periodMilliseconds)
: base(runner, delayMilliseconds, periodMilliseconds)
{
_messenger = messenger;
_scopeProvider = scopeProvider;
}
public override bool PerformRun()
{
// beware!
// DatabaseServerMessenger uses _appContext.DatabaseContext.Database without creating
// scopes, and since we are running in a background task, there will be no ambient
// scope (as would be the case within a web request), and so we would end up creating
// (and leaking) a NoScope instance, which is bad - better make sure we have a true
// scope here! - see U4-11207
using (var scope = _scopeProvider.CreateScope())
{
_messenger.Sync();
scope.Complete();
}
_messenger.Sync();
//return true to repeat
return true;
}
@@ -138,17 +121,14 @@ namespace Umbraco.Web
batch.Clear();
//Write the instructions but only create JSON blobs with a max instruction count equal to MaxProcessingInstructionCount
using (var scope = _appContext.ScopeProvider.CreateScope())
foreach (var instructionsBatch in instructions.InGroupsOf(Options.MaxProcessingInstructionCount))
{
foreach (var instructionsBatch in instructions.InGroupsOf(Options.MaxProcessingInstructionCount))
{
WriteInstructions(scope, instructionsBatch);
}
scope.Complete();
WriteInstructions(instructionsBatch);
}
}
private void WriteInstructions(IScope scope, IEnumerable<RefreshInstruction> instructions)
private void WriteInstructions(IEnumerable<RefreshInstruction> instructions)
{
var dto = new CacheInstructionDto
{
@@ -157,7 +137,8 @@ namespace Umbraco.Web
OriginIdentity = LocalIdentity,
InstructionCount = instructions.Sum(x => x.JsonIdCount)
};
scope.Database.Insert(dto);
ApplicationContext.DatabaseContext.Database.Insert(dto);
}
protected ICollection<RefreshInstructionEnvelope> GetBatch(bool create)
@@ -198,19 +179,16 @@ namespace Umbraco.Web
if (batch == null)
{
//only write the json blob with a maximum count of the MaxProcessingInstructionCount
using (var scope = _appContext.ScopeProvider.CreateScope())
foreach (var maxBatch in instructions.InGroupsOf(Options.MaxProcessingInstructionCount))
{
foreach (var maxBatch in instructions.InGroupsOf(Options.MaxProcessingInstructionCount))
{
WriteInstructions(scope, maxBatch);
}
scope.Complete();
WriteInstructions(maxBatch);
}
}
else
{
batch.Add(new RefreshInstructionEnvelope(servers, refresher, instructions));
}
}
}
}
}
}
+3 -2
View File
@@ -155,7 +155,8 @@ namespace Umbraco.Web.Cache
foreach (var payload in payloads)
{
ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id);
if (payload.Operation == OperationType.Deleted)
ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id);
var mediaCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache<IMedia>();
@@ -190,4 +191,4 @@ namespace Umbraco.Web.Cache
}
}
}
}
}
@@ -84,7 +84,6 @@ namespace Umbraco.Web.Cache
public override void Refresh(int id)
{
ApplicationContext.Current.Services.IdkMap.ClearCache(id);
ClearRepositoryCacheItemById(id);
ClearAllIsolatedCacheByEntityType<PublicAccessEntry>();
content.Instance.UpdateSortOrder(id);
@@ -104,7 +103,6 @@ namespace Umbraco.Web.Cache
public override void Refresh(IContent instance)
{
ApplicationContext.Current.Services.IdkMap.ClearCache(instance.Id);
ClearRepositoryCacheItemById(instance.Id);
ClearAllIsolatedCacheByEntityType<PublicAccessEntry>();
content.Instance.UpdateSortOrder(instance);
@@ -150,4 +148,4 @@ namespace Umbraco.Web.Cache
}
}
}
}
}
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using Umbraco.Core.IO;
using Umbraco.Web.Mvc;
namespace Umbraco.Web.Editors
{
[PluginController("UmbracoApi")]
public class BackOfficeAssetsController : UmbracoAuthorizedJsonController
{
[HttpGet]
public IEnumerable<string> GetSupportedMomentLocales()
{
const string momentLocaleFolder = "moment";
var fileSystem = FileSystemProviderManager.Current.JavaScriptLibraryFileSystem;
var cultures = fileSystem.GetFiles(momentLocaleFolder, "*.js").ToList();
for (var i = 0; i < cultures.Count; i++)
{
cultures[i] = cultures[i]
.Substring(cultures[i].IndexOf(momentLocaleFolder, StringComparison.Ordinal) + momentLocaleFolder.Length + 1);
}
return cultures;
}
}
}
@@ -13,6 +13,7 @@ using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Web.Features;
using Umbraco.Web.HealthCheck;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
@@ -52,7 +53,8 @@ namespace Umbraco.Web.Editors
{"umbracoUrls", new[] {"authenticationApiBaseUrl", "serverVarsJs", "externalLoginsUrl", "currentUserApiBaseUrl"}},
{"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage"}},
{"application", new[] {"applicationPath", "cacheBuster"}},
{"isDebuggingEnabled", new string[] { }}
{"isDebuggingEnabled", new string[] { }},
{"features", new [] {"disabledFeatures"}}
};
//now do the filtering...
var defaults = GetServerVariables();
@@ -81,7 +83,7 @@ namespace Umbraco.Web.Editors
//TODO: This is ultra confusing! this same key is used for different things, when returning the full app when authenticated it is this URL but when not auth'd it's actually the ServerVariables address
// so based on compat and how things are currently working we need to replace the serverVarsJs one
((Dictionary<string, object>) defaults["umbracoUrls"])["serverVarsJs"] = _urlHelper.Action("ServerVariables", "BackOffice");
((Dictionary<string, object>)defaults["umbracoUrls"])["serverVarsJs"] = _urlHelper.Action("ServerVariables", "BackOffice");
return defaults;
}
@@ -268,6 +270,10 @@ namespace Umbraco.Web.Editors
{
"helpApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<HelpController>(
controller => controller.GetContextHelpForPage("","",""))
},
{
"backOfficeAssetsApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<BackOfficeAssetsController>(
controller => controller.GetSupportedMomentLocales())
}
}
},
@@ -328,6 +334,18 @@ namespace Umbraco.Web.Editors
.ToArray()
}
}
},
{
"features", new Dictionary<string,object>
{
{
"disabledFeatures", new Dictionary<string,object>
{
{ "disableTemplates", FeaturesResolver.Current.Features.Disabled.DisableTemplates}
}
}
}
}
};
return defaultVals;
@@ -352,9 +370,9 @@ namespace Umbraco.Web.Editors
.ToArray();
return (from p in pluginTreesWithAttributes
let treeAttr = p.attributes.OfType<TreeAttribute>().Single()
let pluginAttr = p.attributes.OfType<PluginControllerAttribute>().Single()
select new Dictionary<string, string>
let treeAttr = p.attributes.OfType<TreeAttribute>().Single()
let pluginAttr = p.attributes.OfType<PluginControllerAttribute>().Single()
select new Dictionary<string, string>
{
{"alias", treeAttr.Alias}, {"packageFolder", pluginAttr.AreaName}
}).ToArray();
@@ -0,0 +1,41 @@
using System;
using System.Web.Mvc;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Web.Features;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
namespace Umbraco.Web.Editors
{
[DisableBrowserCache]
public class PreviewController : Controller
{
[UmbracoAuthorize(redirectToUmbracoLogin: true)]
public ActionResult Index()
{
var model = new BackOfficePreview
{
DisableDevicePreview = FeaturesResolver.Current.Features.Disabled.DisableDevicePreview,
PreviewExtendedHeaderView = FeaturesResolver.Current.Features.Enabled.PreviewExtendedView
};
if (model.PreviewExtendedHeaderView.IsNullOrWhiteSpace() == false)
{
var viewEngineResult = ViewEngines.Engines.FindPartialView(ControllerContext, model.PreviewExtendedHeaderView);
if (viewEngineResult.View == null)
{
throw new InvalidOperationException("Could not find the view " + model.PreviewExtendedHeaderView + ", the following locations were searched: " + Environment.NewLine + string.Join(Environment.NewLine, viewEngineResult.SearchedLocations));
}
}
return View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + "Index.cshtml", model);
}
public ActionResult Editors(string editor)
{
if (string.IsNullOrEmpty(editor)) throw new ArgumentNullException("editor");
return View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + editor.Replace(".html", string.Empty) + ".cshtml");
}
}
}
@@ -20,5 +20,16 @@ namespace Umbraco.Web.Features
/// Gets the disabled controllers.
/// </summary>
public TypeList<UmbracoApiControllerBase> Controllers { get; private set; }
/// <summary>
/// Disables the device preview feature of previewing.
/// </summary>
public bool DisableDevicePreview { get; set; }
/// <summary>
/// If true, all references to templates will be removed in the back office and routing
/// </summary>
public bool DisableTemplates { get; set; }
}
}
+5 -3
View File
@@ -6,9 +6,11 @@ namespace Umbraco.Web.Features
internal class EnabledFeatures
{
/// <summary>
/// Specifies if rendering pipeline should ignore HasTemplate check when handling a request.
/// <remarks>This is to allow JSON preview of content with no template set.</remarks>
/// This allows us to inject a razor view into the Umbraco preview view to extend it
/// </summary>
public bool RenderNoTemplate { get; set; }
/// <remarks>
/// This is set to a virtual path of a razor view file
/// </remarks>
public string PreviewExtendedView { get; set; }
}
}
+3 -10
View File
@@ -16,14 +16,7 @@ namespace Umbraco.Web.Features
Disabled = new DisabledFeatures();
Enabled = new EnabledFeatures();
}
// note
// currently, the only thing a FeatureSet does is list disabled controllers,
// but eventually we could enable and disable more parts of Umbraco. and then
// we would need some logic to figure out what's enabled/disabled - hence it's
// better to use IsEnabled, where the logic would go, rather than directly
// accessing the Disabled collection.
/// <summary>
/// Gets the disabled features.
/// </summary>
@@ -35,9 +28,9 @@ namespace Umbraco.Web.Features
public EnabledFeatures Enabled { get; set; }
/// <summary>
/// Determines whether a feature is enabled.
/// Determines whether a controller is enabled.
/// </summary>
public bool IsEnabled(Type feature)
public bool IsControllerEnabled(Type feature)
{
if (typeof(UmbracoApiControllerBase).IsAssignableFrom(feature))
return Disabled.Controllers.Contains(feature) == false;
@@ -6,6 +6,7 @@ using Umbraco.Core.Models;
namespace Umbraco.Web.Models.ContentEditing
{
[DataContract(Name = "auditLog", Namespace = "")]
public class AuditLog
{
@@ -31,4 +32,4 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "comment")]
public string Comment { get; set; }
}
}
}
@@ -0,0 +1,12 @@
namespace Umbraco.Web.Models.ContentEditing
{
/// <summary>
/// The model representing Previewing of a content item from the back office
/// </summary>
public class BackOfficePreview
{
public string PreviewExtendedHeaderView { get; set; }
//TODO: We could potentially have a 'footer' view
public bool DisableDevicePreview { get; set; }
}
}
+8 -6
View File
@@ -1,10 +1,6 @@
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Mvc;
using Umbraco.Core.Configuration;
using Umbraco.Web.Editors;
using Umbraco.Web.Install;
using Umbraco.Web.Install.Controllers;
namespace Umbraco.Web.Mvc
{
@@ -24,6 +20,12 @@ namespace Umbraco.Web.Mvc
/// </remarks>
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Umbraco_preview",
GlobalSettings.UmbracoMvcArea + "/preview/{action}/{editor}",
new {controller = "Preview", action = "Index", editor = UrlParameter.Optional},
new[] { "Umbraco.Web.Editors" });
context.MapRoute(
"Umbraco_back_office",
GlobalSettings.UmbracoMvcArea + "/{action}/{id}",
@@ -51,4 +53,4 @@ namespace Umbraco.Web.Mvc
get { return GlobalSettings.UmbracoMvcArea; }
}
}
}
}
+6 -6
View File
@@ -393,12 +393,12 @@ namespace Umbraco.Web.Mvc
return GetWebFormsHandler();
}
//here we need to check if there is no hijacked route and no template assigned, if this is the case
//we want to return a blank page, but we'll leave that up to the NoTemplateHandler.
//we also check if we're allowed to render even though there's no template (json render in headless).
if (publishedContentRequest.HasTemplate == false &&
routeDef.HasHijackedRoute == false &&
FeaturesResolver.Current.Features.Enabled.RenderNoTemplate == false)
//Here we need to check if there is no hijacked route and no template assigned,
//if this is the case we want to return a blank page, but we'll leave that up to the NoTemplateHandler.
//We also check if templates have been disabled since if they are then we're allowed to render even though there's no template,
//for example for json rendering in headless.
if ((publishedContentRequest.HasTemplate == false && FeaturesResolver.Current.Features.Disabled.DisableTemplates == false)
&& routeDef.HasHijackedRoute == false)
{
publishedContentRequest.UpdateOnMissingTemplate(); // will go 404
@@ -4,6 +4,7 @@ using System.Web.Mvc;
using Umbraco.Core;
using Umbraco.Web.Security;
using umbraco.BasePages;
using Umbraco.Core.Configuration;
namespace Umbraco.Web.Mvc
{
@@ -14,6 +15,7 @@ namespace Umbraco.Web.Mvc
{
private readonly ApplicationContext _applicationContext;
private readonly UmbracoContext _umbracoContext;
private readonly string _redirectUrl;
private ApplicationContext GetApplicationContext()
{
@@ -36,16 +38,40 @@ namespace Umbraco.Web.Mvc
_applicationContext = _umbracoContext.Application;
}
/// <summary>
/// Default constructor
/// </summary>
public UmbracoAuthorizeAttribute()
{
}
/// <summary>
/// Ensures that the user must be in the Administrator or the Install role
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
protected override bool AuthorizeCore(HttpContextBase httpContext)
/// <summary>
/// Constructor specifying to redirect to the specified location if not authorized
/// </summary>
/// <param name="redirectUrl"></param>
public UmbracoAuthorizeAttribute(string redirectUrl)
{
_redirectUrl = redirectUrl ?? throw new ArgumentNullException(nameof(redirectUrl));
}
/// <summary>
/// Constructor specifying to redirect to the umbraco login page if not authorized
/// </summary>
/// <param name="redirectToUmbracoLogin"></param>
public UmbracoAuthorizeAttribute(bool redirectToUmbracoLogin)
{
if (redirectToUmbracoLogin)
{
_redirectUrl = GlobalSettings.Path.EnsureStartsWith("~");
}
}
/// <summary>
/// Ensures that the user must be in the Administrator or the Install role
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null) throw new ArgumentNullException("httpContext");
@@ -73,11 +99,20 @@ namespace Umbraco.Web.Mvc
/// <param name="filterContext"></param>
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = (ActionResult)new HttpUnauthorizedResult("You must login to view this resource.");
if (_redirectUrl.IsNullOrWhiteSpace())
{
filterContext.Result = (ActionResult)new HttpUnauthorizedResult("You must login to view this resource.");
}
else
{
filterContext.Result = new RedirectResult(_redirectUrl);
}
//DON'T do a FormsAuth redirect... argh!! thankfully we're running .Net 4.5 :)
filterContext.RequestContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
}
}
}
}
@@ -15,7 +15,7 @@ namespace Umbraco.Web.PropertyEditors
[ParameterEditor("propertyTypePickerMultiple", "Name", "textbox")]
[ParameterEditor("contentTypeMultiple", "Name", "textbox")]
[ParameterEditor("tabPickerMultiple", "Name", "textbox")]
[PropertyEditor(Constants.PropertyEditors.DropDownListMultipleAlias, "Dropdown list multiple", "dropdown", Group = "lists", Icon="icon-bulleted-list")]
[PropertyEditor(Constants.PropertyEditors.DropDownListMultipleAlias, "Dropdown list multiple", "dropdown", Group = "lists", Icon="icon-bulleted-list", IsDeprecated = true)]
public class DropDownMultiplePropertyEditor : DropDownMultipleWithKeysPropertyEditor
{
protected override PropertyValueEditor CreateValueEditor()
@@ -28,4 +28,4 @@ namespace Umbraco.Web.PropertyEditors
}
}
@@ -12,7 +12,7 @@ namespace Umbraco.Web.PropertyEditors
/// Due to maintaining backwards compatibility this data type stores the value as a string which is a comma separated value of the
/// ids of the individual items so we have logic in here to deal with that.
/// </remarks>
[PropertyEditor(Constants.PropertyEditors.DropdownlistMultiplePublishKeysAlias, "Dropdown list multiple, publish keys", "dropdown", Group = "lists", Icon = "icon-bulleted-list")]
[PropertyEditor(Constants.PropertyEditors.DropdownlistMultiplePublishKeysAlias, "Dropdown list multiple, publish keys", "dropdown", Group = "lists", Icon = "icon-bulleted-list", IsDeprecated = true)]
public class DropDownMultipleWithKeysPropertyEditor : DropDownPropertyEditor
{
protected override PropertyValueEditor CreateValueEditor()
@@ -62,4 +62,4 @@ namespace Umbraco.Web.PropertyEditors
}
}
}
}
@@ -16,7 +16,7 @@ namespace Umbraco.Web.PropertyEditors
/// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the string value is published
/// in cache and not the int ID.
/// </remarks>
[PropertyEditor(Constants.PropertyEditors.DropDownListAlias, "Dropdown list", "dropdown", ValueType = PropertyEditorValueTypes.String, Group = "lists", Icon = "icon-indent")]
[PropertyEditor(Constants.PropertyEditors.DropDownListAlias, "Dropdown list", "dropdown", ValueType = PropertyEditorValueTypes.String, Group = "lists", Icon = "icon-indent", IsDeprecated = true)]
public class DropDownPropertyEditor : DropDownWithKeysPropertyEditor
{
/// <summary>
@@ -29,4 +29,4 @@ namespace Umbraco.Web.PropertyEditors
}
}
}
}
@@ -11,7 +11,7 @@ namespace Umbraco.Web.PropertyEditors
/// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the INT ID value is published
/// in cache and not the string value.
/// </remarks>
[PropertyEditor(Constants.PropertyEditors.DropdownlistPublishingKeysAlias, "Dropdown list, publishing keys", "dropdown", ValueType = PropertyEditorValueTypes.Integer, Group = "lists", Icon = "icon-indent")]
[PropertyEditor(Constants.PropertyEditors.DropdownlistPublishingKeysAlias, "Dropdown list, publishing keys", "dropdown", ValueType = PropertyEditorValueTypes.Integer, Group = "lists", Icon = "icon-indent", IsDeprecated = true)]
public class DropDownWithKeysPropertyEditor : PropertyEditor
{
@@ -24,4 +24,4 @@ namespace Umbraco.Web.PropertyEditors
return new ValueListPreValueEditor();
}
}
}
}
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
[PropertyEditor(Constants.PropertyEditors.DropDownListFlexibleAlias, "Dropdown", "dropdownFlexible", Group = "lists", Icon = "icon-indent")]
public class DropdownFlexiblePropertyEditor : PropertyEditor
{
private static readonly string _multipleKey = "multiple";
/// <summary>
/// Return a custom pre-value editor
/// </summary>
/// <returns></returns>
/// <remarks>
/// We are just going to re-use the ValueListPreValueEditor
/// </remarks>
protected override PreValueEditor CreatePreValueEditor()
{
return new DropdownFlexiblePreValueEditor();
}
/// <summary>
/// We need to override the value editor so that we can ensure the string value is published in cache and not the integer ID value.
/// </summary>
/// <returns></returns>
protected override PropertyValueEditor CreateValueEditor()
{
return new PublishValuesMultipleValueEditor(false, base.CreateValueEditor());
}
internal class DropdownFlexiblePreValueEditor : ValueListPreValueEditor
{
public DropdownFlexiblePreValueEditor()
{
Fields.Insert(0, new PreValueField
{
Key = "multiple",
Name = "Enable multiple choice",
Description = "When checked, the dropdown will be a select multiple / combo box style dropdown",
View = "boolean"
});
}
public override IDictionary<string, PreValue> ConvertEditorToDb(IDictionary<string, object> editorValue, PreValueCollection currentValue)
{
var result = base.ConvertEditorToDb(editorValue, currentValue);
// get multiple config
var multipleValue = editorValue[_multipleKey] != null ? editorValue[_multipleKey].ToString() : "0";
result.Add(_multipleKey, new PreValue(-1, multipleValue));
return result;
}
public override IDictionary<string, object> ConvertDbToEditor(IDictionary<string, object> defaultPreVals, PreValueCollection persistedPreVals)
{
// weird way, but as the value stored is 0 or 1 need to do it this way
string multipleMode = "0";
if (persistedPreVals != null && persistedPreVals.PreValuesAsDictionary[_multipleKey] != null)
{
multipleMode = persistedPreVals.PreValuesAsDictionary[_multipleKey].Value;
// remove from the collection sent to the base multiple values collection
persistedPreVals.PreValuesAsDictionary.Remove(_multipleKey);
}
var returnVal = base.ConvertDbToEditor(defaultPreVals, persistedPreVals);
returnVal[_multipleKey] = multipleMode;
return returnVal;
}
}
}
}
@@ -47,8 +47,7 @@ namespace Umbraco.Web.PublishedCache
/// <remarks>Considers published or unpublished content depending on context.</remarks>
public IPublishedContent GetById(Guid contentId)
{
var intId = UmbracoContext.Application.Services.EntityService.GetIdForKey(contentId, UmbracoObjectTypes.Document);
return GetById(intId.Success ? intId.Result : -1);
return GetById(UmbracoContext.InPreviewMode, contentId);
}
/// <summary>
@@ -59,6 +58,15 @@ namespace Umbraco.Web.PublishedCache
/// <returns>The content, or null.</returns>
public abstract IPublishedContent GetById(bool preview, int contentId);
// same with Guid
// cannot make this public nor abstract without breaking backward compatibility
public virtual IPublishedContent GetById(bool preview, Guid contentKey)
{
// original implementation - override in concrete classes
var intId = UmbracoContext.Application.Services.EntityService.GetIdForKey(contentKey, UmbracoObjectTypes.Document);
return GetById(intId.Success ? intId.Result : -1);
}
/// <summary>
/// Gets content at root.
/// </summary>
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using Umbraco.Core.Models;
using Umbraco.Web.PublishedCache.XmlPublishedCache;
namespace Umbraco.Web.PublishedCache
{
@@ -20,6 +21,14 @@ namespace Umbraco.Web.PublishedCache
: base(umbracoContext, cache)
{ }
public override IPublishedContent GetById(bool preview, Guid contentKey)
{
if (InnerCache is PublishedContentCache cc)
return cc.GetById(UmbracoContext, preview, contentKey);
return base.GetById(preview, contentKey);
}
/// <summary>
/// Gets content identified by a route.
/// </summary>
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using Umbraco.Core.Models;
using Umbraco.Web.PublishedCache.XmlPublishedCache;
namespace Umbraco.Web.PublishedCache
{
@@ -19,5 +20,13 @@ namespace Umbraco.Web.PublishedCache
internal ContextualPublishedMediaCache(IPublishedMediaCache cache, UmbracoContext umbracoContext)
: base(umbracoContext, cache)
{ }
public override IPublishedContent GetById(bool preview, Guid contentKey)
{
if (InnerCache is PublishedMediaCache cc)
return cc.GetById(UmbracoContext, preview, contentKey);
return base.GetById(preview, contentKey);
}
}
}
@@ -346,11 +346,89 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
#region Getters
private readonly object _idkMapLocker = new object();
private IdkMap _idkMap;
// populate the idkmap by indexing the content cache
// assuming that the content cache cannot be corrupted
private void EnsureIdkMap(UmbracoContext umbracoContext)
{
lock (_idkMapLocker)
{
if (_idkMap != null) return;
_idkMap = ApplicationContext.Current.Services.IdkMap; // fixme inject
// give the map a fast mapper
_idkMap.SetMapper(UmbracoObjectTypes.Document, GetKeyForId, GetIdForKey);
// populate the map with what we know, so far
var xml = GetXml(umbracoContext, false);
var nav = xml.CreateNavigator();
var iter = nav.SelectDescendants(XPathNodeType.Element, true);
_idkMap.Populate(Enumerate(iter), UmbracoObjectTypes.Document);
}
}
private IEnumerable<(int id, Guid key)> Enumerate(XPathNodeIterator iter)
{
while (iter.MoveNext())
{
string idString = null;
string keyString = null;
if (iter.Current.MoveToFirstAttribute())
{
do
{
switch (iter.Current.Name)
{
case "id":
idString = iter.Current.Value;
break;
case "key":
keyString = iter.Current.Value;
break;
}
} while ((idString == null || keyString == null) && iter.Current.MoveToNextAttribute());
iter.Current.MoveToParent();
}
if (idString == null || keyString == null) continue;
var id = int.Parse(idString);
var key = Guid.Parse(keyString);
yield return (id, key);
}
}
private Guid GetKeyForId(int id)
{
var xml = GetXml(UmbracoContext.Current, false);
var elt = xml.GetElementById(id.ToString(CultureInfo.InvariantCulture));
return elt == null ? default (Guid) : Guid.Parse(elt.GetAttribute("key"));
}
private int GetIdForKey(Guid key)
{
var xml = GetXml(UmbracoContext.Current, false);
var elt = xml.SelectSingleNode("//* [@key=$guid]", new XPathVariable("guid", key.ToString())) as XmlElement;
return elt == null ? default (int) : int.Parse(elt.GetAttribute("id"));
}
public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, int nodeId)
{
return ConvertToDocument(GetXml(umbracoContext, preview).GetElementById(nodeId.ToString(CultureInfo.InvariantCulture)), preview);
}
public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, Guid nodeKey)
{
EnsureIdkMap(umbracoContext);
var mapAttempt = _idkMap.GetIdForKey(nodeKey, UmbracoObjectTypes.Document);
return mapAttempt ? GetById(umbracoContext, preview, mapAttempt.Result) : null;
}
public virtual IEnumerable<IPublishedContent> GetAtRoot(UmbracoContext umbracoContext, bool preview)
{
return ConvertToDocuments(GetXml(umbracoContext, preview).SelectNodes(XPathStrings.RootDocuments), preview);
@@ -490,4 +568,4 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
#endregion
}
}
}
@@ -72,6 +72,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
return GetUmbracoMedia(nodeId);
}
public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, Guid nodeKey)
{
// TODO optimize with Examine?
var mapAttempt = ApplicationContext.Current.Services.IdkMap.GetIdForKey(nodeKey, UmbracoObjectTypes.Media);
return mapAttempt ? GetById(umbracoContext, preview, mapAttempt.Result) : null;
}
public virtual IEnumerable<IPublishedContent> GetAtRoot(UmbracoContext umbracoContext, bool preview)
{
var searchProvider = GetSearchProviderSafe();
+4 -8
View File
@@ -13,6 +13,7 @@ using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Dynamics;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Xml;
using Umbraco.Web.Models;
using Umbraco.Web.PublishedCache;
@@ -232,13 +233,9 @@ namespace Umbraco.Web
return doc;
}
private IPublishedContent TypedDocumentById(Guid id, ContextualPublishedCache cache)
private IPublishedContent TypedDocumentById(Guid key, ContextualPublishedCache cache)
{
// todo: in v8, implement in a more efficient way
var legacyXml = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema;
var xpath = legacyXml ? "//node [@key=$guid]" : "//* [@key=$guid]";
var doc = cache.GetSingleByXPath(xpath, new XPathVariable("guid", id.ToString()));
return doc;
return cache.GetById(key);
}
private IPublishedContent TypedDocumentByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache)
@@ -261,7 +258,6 @@ namespace Umbraco.Web
private IEnumerable<IPublishedContent> TypedDocumentsByIds(ContextualPublishedCache cache, IEnumerable<Guid> ids)
{
// todo: in v8, implement in a more efficient way
return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull();
}
@@ -548,4 +544,4 @@ namespace Umbraco.Web
#endregion
}
}
}
@@ -3,7 +3,7 @@
'lib/angular/1.1.5/angular.min.js',
'lib/underscore/underscore-min.js',
'lib/moment/moment-with-locales.js',
'lib/moment/moment.min.js',
'lib/jquery-ui/jquery-ui.min.js',
'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js',
+7
View File
@@ -202,6 +202,9 @@
<Reference Include="System.Threading.Tasks.Dataflow, Version=4.6.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Dataflow.4.7.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Web.Abstractions">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
@@ -316,6 +319,8 @@
<Compile Include="Cache\TemplateCacheRefresher.cs" />
<Compile Include="Cache\UnpublishedPageCacheRefresher.cs" />
<Compile Include="Cache\UserCacheRefresher.cs" />
<Compile Include="Editors\PreviewController.cs" />
<Compile Include="Editors\BackOfficeAssetsController.cs" />
<Compile Include="Features\DisabledFeatures.cs" />
<Compile Include="Editors\BackOfficeNotificationsController.cs" />
<Compile Include="Editors\BackOfficeServerVariables.cs" />
@@ -333,10 +338,12 @@
<Compile Include="Editors\TourController.cs" />
<Compile Include="Features\EnabledFeatures.cs" />
<Compile Include="Models\BackOfficeTourFilter.cs" />
<Compile Include="Models\ContentEditing\BackOfficePreview.cs" />
<Compile Include="Models\Mapping\AutoMapperExtensions.cs" />
<Compile Include="Models\Mapping\ContentTreeNodeUrlResolver.cs" />
<Compile Include="Models\Mapping\MemberTreeNodeUrlResolver.cs" />
<Compile Include="Models\Trees\ExportMember.cs" />
<Compile Include="PropertyEditors\DropdownFlexiblePropertyEditor.cs" />
<Compile Include="TourFilterResolver.cs" />
<Compile Include="Editors\UserEditorAuthorizationHelper.cs" />
<Compile Include="Editors\UserGroupAuthorizationAttribute.cs" />
@@ -17,7 +17,7 @@ namespace Umbraco.Web.WebApi.Filters
if (FeaturesResolver.HasCurrent == false) return true;
var controllerType = actionContext.ControllerContext.ControllerDescriptor.ControllerType;
return FeaturesResolver.Current.Features.IsEnabled(controllerType);
return FeaturesResolver.Current.Features.IsControllerEnabled(controllerType);
}
}
}
+1
View File
@@ -28,4 +28,5 @@
<package id="Owin" version="1.0" targetFramework="net45" />
<package id="semver" version="1.1.2" targetFramework="net45" />
<package id="System.Threading.Tasks.Dataflow" version="4.7.0" targetFramework="net45" />
<package id="System.ValueTuple" version="4.4.0" targetFramework="net45" />
</packages>