diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 295dc54371d6..89d70f9fa3d7 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -128,7 +128,7 @@ In order to build the Umbraco source code locally, first make sure you have the
* npm v6.4.1+ (installed with Node.js)
* [Git command line](https://git-scm.com/download/)
-The easiest way to get started is to open `src\umbraco.sln` in Visual Studio 2017 (version 15.9.7 or higher, [the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile.
+The easiest way to get started is to open `src\umbraco.sln` in Visual Studio 2019 (version 16.3 or higher, [the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile.
Alternatively, you can run `build.ps1` from the Powershell command line, which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details.
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index 3f512bc65a85..f829770a5962 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -18,5 +18,5 @@
[assembly: AssemblyVersion("8.0.0")]
// these are FYI and changed automatically
-[assembly: AssemblyFileVersion("8.8.0")]
-[assembly: AssemblyInformationalVersion("8.8.0")]
+[assembly: AssemblyFileVersion("8.9.0")]
+[assembly: AssemblyInformationalVersion("8.9.0-rc")]
diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs
index ced9a9386a56..2fcf446d6b8b 100644
--- a/src/Umbraco.Core/CompositionExtensions.cs
+++ b/src/Umbraco.Core/CompositionExtensions.cs
@@ -64,7 +64,7 @@ public static PropertyValueConverterCollectionBuilder PropertyValueConverters(th
=> composition.WithCollectionBuilder();
///
- /// Gets the url segment providers collection builder.
+ /// Gets the URL segment providers collection builder.
///
/// The composition.
public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this Composition composition)
diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs
index 0b817cc0bbc4..2c79c0008655 100644
--- a/src/Umbraco.Core/Configuration/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs
@@ -68,9 +68,9 @@ public static bool HasSmtpServerConfigured(string appPath)
internal static bool? HasSmtpServer { get; set; }
///
- /// Gets the reserved urls from web.config.
+ /// Gets the reserved URLs from web.config.
///
- /// The reserved urls.
+ /// The reserved URLs.
public string ReservedUrls
{
get
@@ -354,10 +354,10 @@ public string DefaultUILanguage
}
///
- /// Gets a value indicating whether umbraco should hide top level nodes from generated urls.
+ /// Gets a value indicating whether umbraco should hide top level nodes from generated URLs.
///
///
- /// true if umbraco hides top level nodes from urls; otherwise, false.
+ /// true if umbraco hides top level nodes from URLs; otherwise, false.
///
public bool HideTopLevelNodeFromPath
{
diff --git a/src/Umbraco.Core/Configuration/IGlobalSettings.cs b/src/Umbraco.Core/Configuration/IGlobalSettings.cs
index ed71e9344cd5..fd5140042c72 100644
--- a/src/Umbraco.Core/Configuration/IGlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/IGlobalSettings.cs
@@ -6,9 +6,9 @@
public interface IGlobalSettings
{
///
- /// Gets the reserved urls from web.config.
+ /// Gets the reserved URLs from web.config.
///
- /// The reserved urls.
+ /// The reserved URLs.
string ReservedUrls { get; }
///
@@ -45,10 +45,10 @@ public interface IGlobalSettings
string DefaultUILanguage { get; }
///
- /// Gets a value indicating whether umbraco should hide top level nodes from generated urls.
+ /// Gets a value indicating whether umbraco should hide top level nodes from generated URLs.
///
///
- /// true if umbraco hides top level nodes from urls; otherwise, false.
+ /// true if umbraco hides top level nodes from URLs; otherwise, false.
///
bool HideTopLevelNodeFromPath { get; }
diff --git a/src/Umbraco.Core/Constants-AppSettings.cs b/src/Umbraco.Core/Constants-AppSettings.cs
index 8bf49057266a..cb3b0d48d085 100644
--- a/src/Umbraco.Core/Constants-AppSettings.cs
+++ b/src/Umbraco.Core/Constants-AppSettings.cs
@@ -64,7 +64,7 @@ public static class AppSettings
public const string MediaPath = "umbracoMediaPath";
///
- /// The reserved urls from web.config.
+ /// The reserved URLs from web.config.
///
public const string ReservedUrls = "Umbraco.Core.ReservedUrls";
@@ -101,7 +101,7 @@ public static class AppSettings
public const string DefaultUILanguage = "Umbraco.Core.DefaultUILanguage";
///
- /// A true/false value indicating whether umbraco should hide top level nodes from generated urls.
+ /// A true/false value indicating whether umbraco should hide top level nodes from generated URLs.
///
public const string HideTopLevelNodeFromPath = "Umbraco.Core.HideTopLevelNodeFromPath";
diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs
index dcd7eb9d0546..90f5fbd0d0e5 100644
--- a/src/Umbraco.Core/Constants-PropertyEditors.cs
+++ b/src/Umbraco.Core/Constants-PropertyEditors.cs
@@ -192,7 +192,7 @@ public static class Aliases
public const string NestedContent = "Umbraco.NestedContent";
///
- /// Alias for the multi url picker editor.
+ /// Alias for the multi URL picker editor.
///
public const string MultiUrlPicker = "Umbraco.MultiUrlPicker";
}
diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs
index 43c989805baa..f900288ef54d 100644
--- a/src/Umbraco.Core/Constants-Security.cs
+++ b/src/Umbraco.Core/Constants-Security.cs
@@ -56,6 +56,8 @@ public static class Security
public const string AllowedApplicationsClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/allowedapp";
public const string SessionIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/sessionid";
+ public const string BackOfficeExternalLoginOptionsProperty = "UmbracoBackOfficeExternalLoginOptions";
+
}
}
}
diff --git a/src/Umbraco.Core/GuidUdi.cs b/src/Umbraco.Core/GuidUdi.cs
index d78a33a435ec..93f670cd01b4 100644
--- a/src/Umbraco.Core/GuidUdi.cs
+++ b/src/Umbraco.Core/GuidUdi.cs
@@ -34,7 +34,7 @@ public GuidUdi(Uri uriValue)
{
Guid guid;
if (Guid.TryParse(uriValue.AbsolutePath.TrimStart('/'), out guid) == false)
- throw new FormatException("Url \"" + uriValue + "\" is not a guid entity id.");
+ throw new FormatException("URI \"" + uriValue + "\" is not a GUID entity ID.");
Guid = guid;
}
@@ -48,7 +48,7 @@ public GuidUdi(Uri uriValue)
{
var udi = Udi.Parse(s);
if (udi is GuidUdi == false)
- throw new FormatException("String \"" + s + "\" is not a guid entity id.");
+ throw new FormatException("String \"" + s + "\" is not a GUID entity id.");
return (GuidUdi) udi;
}
diff --git a/src/Umbraco.Core/IO/IFileSystem.cs b/src/Umbraco.Core/IO/IFileSystem.cs
index 0405c5925ac9..9de1ea40f258 100644
--- a/src/Umbraco.Core/IO/IFileSystem.cs
+++ b/src/Umbraco.Core/IO/IFileSystem.cs
@@ -102,7 +102,7 @@ public interface IFileSystem
///
/// Returns the application relative path to the file.
///
- /// The full path or url.
+ /// The full path or URL.
///
/// The representing the relative path.
///
@@ -118,11 +118,11 @@ public interface IFileSystem
string GetFullPath(string path);
///
- /// Returns the application relative url to the file.
+ /// Returns the application relative URL to the file.
///
- /// The path to return the url for.
+ /// The path to return the URL for.
///
- /// representing the relative url.
+ /// representing the relative URL.
///
string GetUrl(string path);
diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs
index 96aaf7ca2792..5a216e8554d4 100644
--- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs
+++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs
@@ -19,7 +19,7 @@ public class PhysicalFileSystem : IFileSystem
// (is used in GetRelativePath)
private readonly string _rootPathFwd;
- // the relative url, using url separator chars, NOT ending with a separator
+ // the relative URL, using URL separator chars, NOT ending with a separator
// eg "" or "/Views" or "/Media" or "//Media" in case of a virtual path
private readonly string _rootUrl;
@@ -243,9 +243,9 @@ public bool FileExists(string path)
}
///
- /// Gets the filesystem-relative path of a full path or of an url.
+ /// Gets the filesystem-relative path of a full path or of an URL.
///
- /// The full path or url.
+ /// The full path or URL.
/// The path, relative to this filesystem's root.
///
/// The relative path is relative to this filesystem's root, not starting with any
@@ -253,10 +253,10 @@ public bool FileExists(string path)
///
public string GetRelativePath(string fullPathOrUrl)
{
- // test url
- var path = fullPathOrUrl.Replace('\\', '/'); // ensure url separator char
+ // test URL
+ var path = fullPathOrUrl.Replace('\\', '/'); // ensure URL separator char
- // if it starts with the root url, strip it and trim the starting slash to make it relative
+ // if it starts with the root URL, strip it and trim the starting slash to make it relative
// eg "/Media/1234/img.jpg" => "1234/img.jpg"
if (IOHelper.PathStartsWith(path, _rootUrl, '/'))
return path.Substring(_rootUrl.Length).TrimStart('/');
@@ -319,10 +319,10 @@ public string GetFullPath(string path)
}
///
- /// Gets the url.
+ /// Gets the URL.
///
/// The filesystem-relative path.
- /// The url.
+ /// The URL.
/// All separators are forward-slashes.
public string GetUrl(string path)
{
diff --git a/src/Umbraco.Core/IRuntimeState.cs b/src/Umbraco.Core/IRuntimeState.cs
index 30c768ab01ae..9f188b5308e1 100644
--- a/src/Umbraco.Core/IRuntimeState.cs
+++ b/src/Umbraco.Core/IRuntimeState.cs
@@ -41,7 +41,7 @@ public interface IRuntimeState
ServerRole ServerRole { get; }
///
- /// Gets the Umbraco application url.
+ /// Gets the Umbraco application URL.
///
/// This is eg "http://www.example.com".
Uri ApplicationUrl { get; }
diff --git a/src/Umbraco.Core/Media/IEmbedProvider.cs b/src/Umbraco.Core/Media/IEmbedProvider.cs
index 99b162e0b7cb..39da6fae0dc5 100644
--- a/src/Umbraco.Core/Media/IEmbedProvider.cs
+++ b/src/Umbraco.Core/Media/IEmbedProvider.cs
@@ -15,7 +15,7 @@ public interface IEmbedProvider
string[] UrlSchemeRegex { get; }
///
- /// A collection of querystring request parameters to append to the API Url
+ /// A collection of querystring request parameters to append to the API URL
///
/// ?key=value&key2=value2
Dictionary RequestParams { get; }
diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
index 03ba58d15e20..30f2606bfabd 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
@@ -7,6 +7,7 @@
using Umbraco.Core.Migrations.Upgrade.V_8_0_1;
using Umbraco.Core.Migrations.Upgrade.V_8_1_0;
using Umbraco.Core.Migrations.Upgrade.V_8_6_0;
+using Umbraco.Core.Migrations.Upgrade.V_8_9_0;
namespace Umbraco.Core.Migrations.Upgrade
{
@@ -175,24 +176,27 @@ protected void DefinePlan()
To("{78BAF571-90D0-4D28-8175-EF96316DA789}");
// release-8.0.0
- // to 8.0.1...
+ // to 8.0.1
To("{80C0A0CB-0DD5-4573-B000-C4B7C313C70D}");
// release-8.0.1
- // to 8.1.0...
+ // to 8.1.0
To("{B69B6E8C-A769-4044-A27E-4A4E18D1645A}");
To("{0372A42B-DECF-498D-B4D1-6379E907EB94}");
To("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}");
- // to 8.6.0...
+ // to 8.6.0
To("{4759A294-9860-46BC-99F9-B4C975CAE580}");
To("{0BC866BC-0665-487A-9913-0290BD0169AD}");
To("{3D67D2C8-5E65-47D0-A9E1-DC2EE0779D6B}");
To("{EE288A91-531B-4995-8179-1D62D9AA3E2E}");
To("{2AB29964-02A1-474D-BD6B-72148D2A53A2}");
- // to 8.7.0...
+ // to 8.7.0
To("{a78e3369-8ea3-40ec-ad3f-5f76929d2b20}");
+
+ // to 8.9.0
+ To("{B5838FF5-1D22-4F6C-BCEB-F83ACB14B575}");
//FINAL
}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_9_0/ExternalLoginTableUserData.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_9_0/ExternalLoginTableUserData.cs
new file mode 100644
index 000000000000..45bc1c620be5
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_9_0/ExternalLoginTableUserData.cs
@@ -0,0 +1,20 @@
+using Umbraco.Core.Persistence.Dtos;
+
+namespace Umbraco.Core.Migrations.Upgrade.V_8_9_0
+{
+ public class ExternalLoginTableUserData : MigrationBase
+ {
+ public ExternalLoginTableUserData(IMigrationContext context)
+ : base(context)
+ {
+ }
+
+ ///
+ /// Adds new column to the External Login table
+ ///
+ public override void Migrate()
+ {
+ AddColumn(Constants.DatabaseSchema.Tables.ExternalLogin, "userData");
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs
index 944000014636..f7daf79ec9b2 100644
--- a/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs
+++ b/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs
@@ -7,7 +7,7 @@ public interface IMediaEntitySlim : IContentEntitySlim
{
///
- /// The media file's path/url
+ /// The media file's path/URL
///
string MediaPath { get; }
}
diff --git a/src/Umbraco.Core/Models/IRedirectUrl.cs b/src/Umbraco.Core/Models/IRedirectUrl.cs
index e06688164512..527dad57da3f 100644
--- a/src/Umbraco.Core/Models/IRedirectUrl.cs
+++ b/src/Umbraco.Core/Models/IRedirectUrl.cs
@@ -5,7 +5,7 @@
namespace Umbraco.Core.Models
{
///
- /// Represents a redirect url.
+ /// Represents a redirect URL.
///
public interface IRedirectUrl : IEntity, IRememberBeingDirty
{
@@ -22,7 +22,7 @@ public interface IRedirectUrl : IEntity, IRememberBeingDirty
Guid ContentKey { get; set; }
///
- /// Gets or sets the redirect url creation date.
+ /// Gets or sets the redirect URL creation date.
///
[DataMember]
DateTime CreateDateUtc { get; set; }
@@ -34,7 +34,7 @@ public interface IRedirectUrl : IEntity, IRememberBeingDirty
string Culture { get; set; }
///
- /// Gets or sets the redirect url route.
+ /// Gets or sets the redirect URL route.
///
/// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo.
[DataMember]
diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
index 8dc056d555e7..b7bde26fae6a 100644
--- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
+++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
@@ -275,16 +275,23 @@ public override ICollection Logins
{
get
{
- if (_getLogins != null && _getLogins.IsValueCreated == false)
+ // return if it exists
+ if (_logins != null) return _logins;
+
+ _logins = new ObservableCollection();
+
+ // if the callback is there and hasn't been created yet then execute it and populate the logins
+ if (_getLogins != null && !_getLogins.IsValueCreated)
{
- _logins = new ObservableCollection();
foreach (var l in _getLogins.Value)
{
_logins.Add(l);
}
- //now assign events
- _logins.CollectionChanged += Logins_CollectionChanged;
}
+
+ //now assign events
+ _logins.CollectionChanged += Logins_CollectionChanged;
+
return _logins;
}
}
diff --git a/src/Umbraco.Core/Models/Identity/ExternalLogin.cs b/src/Umbraco.Core/Models/Identity/ExternalLogin.cs
new file mode 100644
index 000000000000..6e4abf29061c
--- /dev/null
+++ b/src/Umbraco.Core/Models/Identity/ExternalLogin.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Umbraco.Core.Models.Identity
+{
+ ///
+ public class ExternalLogin : IExternalLogin
+ {
+ public ExternalLogin(string loginProvider, string providerKey, string userData = null)
+ {
+ LoginProvider = loginProvider ?? throw new ArgumentNullException(nameof(loginProvider));
+ ProviderKey = providerKey ?? throw new ArgumentNullException(nameof(providerKey));
+ UserData = userData;
+ }
+
+ ///
+ public string LoginProvider { get; }
+
+ ///
+ public string ProviderKey { get; }
+
+ ///
+ public string UserData { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Identity/IExternalLogin.cs b/src/Umbraco.Core/Models/Identity/IExternalLogin.cs
new file mode 100644
index 000000000000..68f66a5ceece
--- /dev/null
+++ b/src/Umbraco.Core/Models/Identity/IExternalLogin.cs
@@ -0,0 +1,12 @@
+namespace Umbraco.Core.Models.Identity
+{
+ ///
+ /// Used to persist external login data for a user
+ ///
+ public interface IExternalLogin
+ {
+ string LoginProvider { get; }
+ string ProviderKey { get; }
+ string UserData { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs
index 276f60177116..feb8af24f3a9 100644
--- a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs
+++ b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs
@@ -2,23 +2,30 @@
namespace Umbraco.Core.Models.Identity
{
+ // TODO: Merge these in v8! This is here purely for backward compat
+
+ public interface IIdentityUserLoginExtended : IIdentityUserLogin
+ {
+ ///
+ /// Used to store any arbitrary data for the user and external provider - like user tokens returned from the provider
+ ///
+ string UserData { get; set; }
+ }
+
public interface IIdentityUserLogin : IEntity, IRememberBeingDirty
{
///
/// The login provider for the login (i.e. Facebook, Google)
- ///
///
string LoginProvider { get; set; }
///
/// Key representing the login for the provider
- ///
///
string ProviderKey { get; set; }
///
/// User Id for the user who owns this login
- ///
///
int UserId { get; set; }
}
diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs
index 5876f420b492..66911b08acd9 100644
--- a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs
+++ b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs
@@ -3,11 +3,11 @@
namespace Umbraco.Core.Models.Identity
{
+
///
/// Entity type for a user's login (i.e. Facebook, Google)
- ///
///
- public class IdentityUserLogin : EntityBase, IIdentityUserLogin
+ public class IdentityUserLogin : EntityBase, IIdentityUserLoginExtended
{
public IdentityUserLogin(string loginProvider, string providerKey, int userId)
{
@@ -25,22 +25,16 @@ public IdentityUserLogin(int id, string loginProvider, string providerKey, int u
CreateDate = createDate;
}
- ///
- /// The login provider for the login (i.e. Facebook, Google)
- ///
- ///
+ ///
public string LoginProvider { get; set; }
- ///
- /// Key representing the login for the provider
- ///
- ///
+ ///
public string ProviderKey { get; set; }
- ///
- /// User Id for the user who owns this login
- ///
- ///
+ ///
public int UserId { get; set; }
+
+ ///
+ public string UserData { get; set; }
}
}
diff --git a/src/Umbraco.Core/Models/MediaExtensions.cs b/src/Umbraco.Core/Models/MediaExtensions.cs
index 96f183b6e63e..c985ace8b0e1 100644
--- a/src/Umbraco.Core/Models/MediaExtensions.cs
+++ b/src/Umbraco.Core/Models/MediaExtensions.cs
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Models
public static class MediaExtensions
{
///
- /// Gets the url of a media item.
+ /// Gets the URL of a media item.
///
public static string GetUrl(this IMedia media, string propertyAlias, ILogger logger)
{
@@ -29,7 +29,7 @@ public static string GetUrl(this IMedia media, string propertyAlias, ILogger log
}
///
- /// Gets the urls of a media item.
+ /// Gets the URLs of a media item.
///
public static string[] GetUrls(this IMedia media, IContentSection contentSection, ILogger logger)
{
diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs
index 4e154bf5145e..23861dfa9d02 100644
--- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs
@@ -31,7 +31,7 @@ public interface IPublishedContent : IPublishedElement
string Name { get; }
///
- /// Gets the url segment of the content item for the current culture.
+ /// Gets the URL segment of the content item for the current culture.
///
string UrlSegment { get; }
@@ -93,7 +93,7 @@ public interface IPublishedContent : IPublishedElement
DateTime UpdateDate { get; }
///
- /// Gets the url of the content item for the current culture.
+ /// Gets the URL of the content item for the current culture.
///
///
/// The value of this property is contextual. It depends on the 'current' request uri,
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs
index 908b97fc3695..58241ade24ce 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs
@@ -32,7 +32,7 @@ public PublishedCultureInfo(string culture, string name, string urlSegment, Date
internal string Name { get; }
///
- /// Gets the url segment of the item.
+ /// Gets the URL segment of the item.
///
internal string UrlSegment { get; }
diff --git a/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs
index 4cd6a680f423..d11459bb9e37 100644
--- a/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs
@@ -1,27 +1,27 @@
namespace Umbraco.Core.Models.PublishedContent
{
///
- /// Specifies the type of urls that the url provider should produce, Auto is the default.
+ /// Specifies the type of URLs that the URL provider should produce, Auto is the default.
///
public enum UrlMode
{
///
- /// Indicates that the url provider should do what it has been configured to do.
+ /// Indicates that the URL provider should do what it has been configured to do.
///
Default = 0,
///
- /// Indicates that the url provider should produce relative urls exclusively.
+ /// Indicates that the URL provider should produce relative URLs exclusively.
///
Relative,
///
- /// Indicates that the url provider should produce absolute urls exclusively.
+ /// Indicates that the URL provider should produce absolute URLs exclusively.
///
Absolute,
///
- /// Indicates that the url provider should determine automatically whether to return relative or absolute urls.
+ /// Indicates that the URL provider should determine automatically whether to return relative or absolute URLs.
///
Auto
}
diff --git a/src/Umbraco.Core/Models/ServerRegistration.cs b/src/Umbraco.Core/Models/ServerRegistration.cs
index 7dae5d5393e6..a862b11c23ee 100644
--- a/src/Umbraco.Core/Models/ServerRegistration.cs
+++ b/src/Umbraco.Core/Models/ServerRegistration.cs
@@ -24,7 +24,7 @@ public ServerRegistration()
/// Initializes a new instance of the class.
///
/// The unique id of the server registration.
- /// The server url.
+ /// The server URL.
/// The unique server identity.
/// The date and time the registration was created.
/// The date and time the registration was last accessed.
@@ -45,7 +45,7 @@ public ServerRegistration(int id, string serverAddress, string serverIdentity, D
///
/// Initializes a new instance of the class.
///
- /// The server url.
+ /// The server URL.
/// The unique server identity.
/// The date and time the registration was created.
public ServerRegistration(string serverAddress, string serverIdentity, DateTime registered)
@@ -58,7 +58,7 @@ public ServerRegistration(string serverAddress, string serverIdentity, DateTime
}
///
- /// Gets or sets the server url.
+ /// Gets or sets the server URL.
///
public string ServerAddress
{
diff --git a/src/Umbraco.Core/Persistence/Dtos/ExternalLoginDto.cs b/src/Umbraco.Core/Persistence/Dtos/ExternalLoginDto.cs
index 1b774854a6fd..0a56552000e4 100644
--- a/src/Umbraco.Core/Persistence/Dtos/ExternalLoginDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/ExternalLoginDto.cs
@@ -14,9 +14,13 @@ internal class ExternalLoginDto
[PrimaryKeyColumn(Name = "PK_umbracoExternalLogin")]
public int Id { get; set; }
+ // TODO: This is completely missing a FK!!?
+
[Column("userId")]
public int UserId { get; set; }
+ // TODO: There should be an index on both LoginProvider and ProviderKey
+
[Column("loginProvider")]
[Length(4000)]
[NullSetting(NullSetting = NullSettings.NotNull)]
@@ -30,5 +34,13 @@ internal class ExternalLoginDto
[Column("createDate")]
[Constraint(Default = SystemMethods.CurrentDateTime)]
public DateTime CreateDate { get; set; }
+
+ ///
+ /// Used to store any arbitrary data for the user and external provider - like user tokens returned from the provider
+ ///
+ [Column("userData")]
+ [NullSetting(NullSetting = NullSettings.Null)]
+ [SpecialDbType(SpecialDbTypes.NTEXT)]
+ public string UserData { get; set; }
}
}
diff --git a/src/Umbraco.Core/Persistence/Factories/ExternalLoginFactory.cs b/src/Umbraco.Core/Persistence/Factories/ExternalLoginFactory.cs
index 4309fe511fc9..6c1af68acd00 100644
--- a/src/Umbraco.Core/Persistence/Factories/ExternalLoginFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/ExternalLoginFactory.cs
@@ -1,13 +1,17 @@
-using Umbraco.Core.Models.Identity;
+using System;
+using Umbraco.Core.Models.Identity;
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Persistence.Factories
{
internal static class ExternalLoginFactory
{
- public static IIdentityUserLogin BuildEntity(ExternalLoginDto dto)
+ public static IIdentityUserLoginExtended BuildEntity(ExternalLoginDto dto)
{
- var entity = new IdentityUserLogin(dto.Id, dto.LoginProvider, dto.ProviderKey, dto.UserId, dto.CreateDate);
+ var entity = new IdentityUserLogin(dto.Id, dto.LoginProvider, dto.ProviderKey, dto.UserId, dto.CreateDate)
+ {
+ UserData = dto.UserData
+ };
// reset dirty initial properties (U4-1946)
entity.ResetDirtyProperties(false);
@@ -16,13 +20,30 @@ public static IIdentityUserLogin BuildEntity(ExternalLoginDto dto)
public static ExternalLoginDto BuildDto(IIdentityUserLogin entity)
{
+ var asExtended = entity as IIdentityUserLoginExtended;
var dto = new ExternalLoginDto
{
Id = entity.Id,
CreateDate = entity.CreateDate,
LoginProvider = entity.LoginProvider,
ProviderKey = entity.ProviderKey,
- UserId = entity.UserId
+ UserId = entity.UserId,
+ UserData = asExtended?.UserData
+ };
+
+ return dto;
+ }
+
+ public static ExternalLoginDto BuildDto(int userId, IExternalLogin entity, int? id = null)
+ {
+ var dto = new ExternalLoginDto
+ {
+ Id = id ?? default,
+ UserId = userId,
+ LoginProvider = entity.LoginProvider,
+ ProviderKey = entity.ProviderKey,
+ UserData = entity.UserData,
+ CreateDate = DateTime.Now
};
return dto;
diff --git a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs
index 6d145e9961a2..a5a93449d3cd 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using Microsoft.AspNet.Identity;
using Umbraco.Core.Models.Identity;
@@ -6,7 +7,9 @@ namespace Umbraco.Core.Persistence.Repositories
{
public interface IExternalLoginRepository : IReadWriteQueryRepository
{
+ [Obsolete("Use the overload specifying IIdentityUserLoginExtended instead")]
void SaveUserLogins(int memberId, IEnumerable logins);
+ void Save(int userId, IEnumerable logins);
void DeleteUserLogins(int memberId);
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs
index d05f4e007cf8..a8a88e54dab3 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs
@@ -10,72 +10,72 @@ namespace Umbraco.Core.Persistence.Repositories
public interface IRedirectUrlRepository : IReadWriteQueryRepository
{
///
- /// Gets a redirect url.
+ /// Gets a redirect URL.
///
- /// The Umbraco redirect url route.
+ /// The Umbraco redirect URL route.
/// The content unique key.
/// The culture.
///
IRedirectUrl Get(string url, Guid contentKey, string culture);
///
- /// Deletes a redirect url.
+ /// Deletes a redirect URL.
///
- /// The redirect url identifier.
+ /// The redirect URL identifier.
void Delete(Guid id);
///
- /// Deletes all redirect urls.
+ /// Deletes all redirect URLs.
///
void DeleteAll();
///
- /// Deletes all redirect urls for a given content.
+ /// Deletes all redirect URLs for a given content.
///
/// The content unique key.
void DeleteContentUrls(Guid contentKey);
///
- /// Gets the most recent redirect url corresponding to an Umbraco redirect url route.
+ /// Gets the most recent redirect URL corresponding to an Umbraco redirect URL route.
///
- /// The Umbraco redirect url route.
- /// The most recent redirect url corresponding to the route.
+ /// The Umbraco redirect URL route.
+ /// The most recent redirect URL corresponding to the route.
IRedirectUrl GetMostRecentUrl(string url);
///
- /// Gets all redirect urls for a content item.
+ /// Gets all redirect URLs for a content item.
///
/// The content unique key.
- /// All redirect urls for the content item.
+ /// All redirect URLs for the content item.
IEnumerable GetContentUrls(Guid contentKey);
///
- /// Gets all redirect urls.
+ /// Gets all redirect URLs.
///
/// The page index.
/// The page size.
- /// The total count of redirect urls.
- /// The redirect urls.
+ /// The total count of redirect URLs.
+ /// The redirect URLs.
IEnumerable GetAllUrls(long pageIndex, int pageSize, out long total);
///
- /// Gets all redirect urls below a given content item.
+ /// Gets all redirect URLs below a given content item.
///
/// The content unique identifier.
/// The page index.
/// The page size.
- /// The total count of redirect urls.
- /// The redirect urls.
+ /// The total count of redirect URLs.
+ /// The redirect URLs.
IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total);
///
- /// Searches for all redirect urls that contain a given search term in their URL property.
+ /// Searches for all redirect URLs that contain a given search term in their URL property.
///
/// The term to search for.
/// The page index.
/// The page size.
- /// The total count of redirect urls.
- /// The redirect urls.
+ /// The total count of redirect URLs.
+ /// The redirect URLs.
IEnumerable SearchUrls(string searchTerm, long pageIndex, int pageSize, out long total);
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs
index f708590ea845..ad53a2d52234 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs
@@ -22,25 +22,65 @@ public ExternalLoginRepository(IScopeAccessor scopeAccessor, AppCaches cache, IL
public void DeleteUserLogins(int memberId)
{
- Database.Execute("DELETE FROM ExternalLogins WHERE UserId=@userId", new { userId = memberId });
+ Database.Delete("WHERE userId=@userId", new { userId = memberId });
}
- public void SaveUserLogins(int memberId, IEnumerable logins)
+ public void Save(int userId, IEnumerable logins)
{
- //clear out logins for member
- Database.Execute("DELETE FROM umbracoExternalLogin WHERE userId=@userId", new { userId = memberId });
-
- //add them all
- foreach (var l in logins)
+ var sql = Sql()
+ .Select()
+ .From()
+ .Where(x => x.UserId == userId)
+ .ForUpdate();
+
+ // deduplicate the logins
+ logins = logins.DistinctBy(x => x.ProviderKey + x.LoginProvider).ToList();
+
+ var toUpdate = new Dictionary();
+ var toDelete = new List();
+ var toInsert = new List(logins);
+
+ var existingLogins = Database.Query(sql).OrderByDescending(x => x.CreateDate).ToList();
+ // used to track duplicates so they can be removed
+ var keys = new HashSet<(string, string)>();
+
+ foreach (var existing in existingLogins)
{
- Database.Insert(new ExternalLoginDto
+ if (!keys.Add((existing.ProviderKey, existing.LoginProvider)))
{
- LoginProvider = l.LoginProvider,
- ProviderKey = l.ProviderKey,
- UserId = memberId,
- CreateDate = DateTime.Now
- });
+ // if it already exists we need to remove this one
+ toDelete.Add(existing.Id);
+ }
+ else
+ {
+ var found = logins.FirstOrDefault(x =>
+ x.LoginProvider.Equals(existing.LoginProvider, StringComparison.InvariantCultureIgnoreCase)
+ && x.ProviderKey.Equals(existing.ProviderKey, StringComparison.InvariantCultureIgnoreCase));
+
+ if (found != null)
+ {
+ toUpdate.Add(existing.Id, found);
+ // if it's an update then it's not an insert
+ toInsert.RemoveAll(x => x.ProviderKey == found.ProviderKey && x.LoginProvider == found.LoginProvider);
+ }
+ else
+ {
+ toDelete.Add(existing.Id);
+ }
+ }
}
+
+ // do the deletes, updates and inserts
+ if (toDelete.Count > 0)
+ Database.DeleteMany().Where(x => toDelete.Contains(x.Id)).Execute();
+ foreach (var u in toUpdate)
+ Database.Update(ExternalLoginFactory.BuildDto(userId, u.Value, u.Key));
+ Database.InsertBulk(toInsert.Select(i => ExternalLoginFactory.BuildDto(userId, i)));
+ }
+
+ public void SaveUserLogins(int memberId, IEnumerable logins)
+ {
+ Save(memberId, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey)));
}
protected override IIdentityUserLogin PerformGet(int id)
@@ -67,7 +107,7 @@ protected override IEnumerable PerformGetAll(params int[] id
return PerformGetAllOnIds(ids);
}
- var sql = GetBaseQuery(false);
+ var sql = GetBaseQuery(false).OrderByDescending(x => x.CreateDate);
return ConvertFromDtos(Database.Fetch(sql))
.ToArray();// we don't want to re-iterate again!
@@ -103,7 +143,7 @@ protected override IEnumerable PerformGetByQuery(IQuery
- /// Gets the value image url for a specified crop.
+ /// Gets the value image URL for a specified crop.
///
[Obsolete("Use the overload that takes an IImageUrlGenerator")]
public string GetCropUrl(string alias, bool useCropDimensions = true, bool useFocalPoint = false, string cacheBusterValue = null) => GetCropUrl(alias, Current.ImageUrlGenerator, useCropDimensions, useFocalPoint, cacheBusterValue);
///
- /// Gets the value image url for a specified crop.
+ /// Gets the value image URL for a specified crop.
///
public string GetCropUrl(string alias, IImageUrlGenerator imageUrlGenerator, bool useCropDimensions = true, bool useFocalPoint = false, string cacheBusterValue = null)
{
@@ -110,13 +110,13 @@ public string GetCropUrl(string alias, IImageUrlGenerator imageUrlGenerator, boo
}
///
- /// Gets the value image url for a specific width and height.
+ /// Gets the value image URL for a specific width and height.
///
[Obsolete("Use the overload that takes an IImageUrlGenerator")]
public string GetCropUrl(int width, int height, bool useFocalPoint = false, string cacheBusterValue = null) => GetCropUrl(width, height, Current.ImageUrlGenerator, useFocalPoint, cacheBusterValue);
///
- /// Gets the value image url for a specific width and height.
+ /// Gets the value image URL for a specific width and height.
///
public string GetCropUrl(int width, int height, IImageUrlGenerator imageUrlGenerator, bool useFocalPoint = false, string cacheBusterValue = null)
{
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs
index 36ffddb8634a..8926174c035e 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs
@@ -42,7 +42,7 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub
}
catch (Exception ex)
{
- // cannot deserialize, assume it may be a raw image url
+ // cannot deserialize, assume it may be a raw image URL
Current.Logger.Error(ex, "Could not deserialize string '{JsonString}' into an image cropper value.", sourceString);
value = new ImageCropperValue { Src = sourceString };
}
diff --git a/src/Umbraco.Core/PublishedContentExtensions.cs b/src/Umbraco.Core/PublishedContentExtensions.cs
index 921883b822fc..4aa3b77a7b4b 100644
--- a/src/Umbraco.Core/PublishedContentExtensions.cs
+++ b/src/Umbraco.Core/PublishedContentExtensions.cs
@@ -61,10 +61,10 @@ public static string Name(this IPublishedContent content, string culture = null)
///
- /// Gets the url segment of the content item.
+ /// Gets the URL segment of the content item.
///
/// The content item.
- /// The specific culture to get the url segment for. If null is used the current culture is used (Default is null).
+ /// The specific culture to get the URL segment for. If null is used the current culture is used (Default is null).
public static string UrlSegment(this IPublishedContent content, string culture = null)
{
// invariant has invariant value (whatever the requested culture)
@@ -104,7 +104,7 @@ public static DateTime CultureDate(this IPublishedContent content, string cultur
///
/// The content item.
///
- /// The specific culture to get the url children for. Default is null which will use the current culture in
+ /// The specific culture to get the URL children for. Default is null which will use the current culture in
///
///
/// Gets children that are available for the specified culture.
diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs
index 74ec70828be9..8a30a97e7b28 100644
--- a/src/Umbraco.Core/RuntimeState.cs
+++ b/src/Umbraco.Core/RuntimeState.cs
@@ -102,7 +102,7 @@ internal void EnsureApplicationUrl(HttpRequestBase request = null)
// about this is that this is here specifically for the slot swap scenario https://issues.umbraco.org/issue/U4-10626
- // see U4-10626 - in some cases we want to reset the application url
+ // see U4-10626 - in some cases we want to reset the application URL
// (this is a simplified version of what was in 7.x)
// note: should this be optional? is it expensive?
var url = request == null ? null : ApplicationUrlHelper.GetApplicationUrlFromCurrentRequest(request, _globalSettings);
diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs
index dc271452e10d..7df328b5b7ba 100644
--- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs
+++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs
@@ -96,9 +96,10 @@ public Task CreateAsync(BackOfficeIdentityUser user)
IsLockedOut = user.IsLockedOut,
};
- UpdateMemberProperties(userEntity, user);
+ // we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it.
+ var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(BackOfficeIdentityUser.Logins));
- // TODO: We should deal with Roles --> User Groups here which we currently are not doing
+ UpdateMemberProperties(userEntity, user);
_userService.Save(userEntity);
@@ -107,6 +108,16 @@ public Task CreateAsync(BackOfficeIdentityUser user)
//re-assign id
user.Id = userEntity.Id;
+ if (isLoginsPropertyDirty)
+ {
+ _externalLoginService.Save(
+ user.Id,
+ user.Logins.Select(x => new ExternalLogin(
+ x.LoginProvider,
+ x.ProviderKey,
+ (x is IIdentityUserLoginExtended extended) ? extended.UserData : null)));
+ }
+
return Task.FromResult(0);
}
@@ -115,7 +126,7 @@ public Task CreateAsync(BackOfficeIdentityUser user)
///
///
///
- public async Task UpdateAsync(BackOfficeIdentityUser user)
+ public Task UpdateAsync(BackOfficeIdentityUser user)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
@@ -126,11 +137,13 @@ public async Task UpdateAsync(BackOfficeIdentityUser user)
throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
}
+ // TODO: Wrap this in a scope!
+
var found = _userService.GetUserById(asInt.Result);
if (found != null)
{
// we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it.
- var isLoginsPropertyDirty = user.IsPropertyDirty("Logins");
+ var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(BackOfficeIdentityUser.Logins));
if (UpdateMemberProperties(found, user))
{
@@ -139,10 +152,16 @@ public async Task UpdateAsync(BackOfficeIdentityUser user)
if (isLoginsPropertyDirty)
{
- var logins = await GetLoginsAsync(user);
- _externalLoginService.SaveUserLogins(found.Id, logins);
+ _externalLoginService.Save(
+ found.Id,
+ user.Logins.Select(x => new ExternalLogin(
+ x.LoginProvider,
+ x.ProviderKey,
+ (x is IIdentityUserLoginExtended extended) ? extended.UserData : null)));
}
}
+
+ return Task.CompletedTask;
}
///
@@ -382,7 +401,7 @@ public Task FindAsync(UserLoginInfo login)
if (login == null) throw new ArgumentNullException(nameof(login));
//get all logins associated with the login id
- var result = _externalLoginService.Find(login).ToArray();
+ var result = _externalLoginService.Find(login.LoginProvider, login.ProviderKey).ToArray();
if (result.Any())
{
//return the first user that matches the result
@@ -633,7 +652,7 @@ private bool UpdateMemberProperties(IUser user, BackOfficeIdentityUser identityU
//don't assign anything if nothing has changed as this will trigger the track changes of the model
- if (identityUser.IsPropertyDirty("LastLoginDateUtc")
+ if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastLoginDateUtc))
|| (user.LastLoginDate != default(DateTime) && identityUser.LastLoginDateUtc.HasValue == false)
|| identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value)
{
@@ -642,33 +661,33 @@ private bool UpdateMemberProperties(IUser user, BackOfficeIdentityUser identityU
var dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime();
user.LastLoginDate = dt;
}
- if (identityUser.IsPropertyDirty("LastPasswordChangeDateUtc")
+ if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastPasswordChangeDateUtc))
|| (user.LastPasswordChangeDate != default(DateTime) && identityUser.LastPasswordChangeDateUtc.HasValue == false)
|| identityUser.LastPasswordChangeDateUtc.HasValue && user.LastPasswordChangeDate.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value)
{
anythingChanged = true;
user.LastPasswordChangeDate = identityUser.LastPasswordChangeDateUtc.Value.ToLocalTime();
}
- if (identityUser.IsPropertyDirty("EmailConfirmed")
+ if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.EmailConfirmed))
|| (user.EmailConfirmedDate.HasValue && user.EmailConfirmedDate.Value != default(DateTime) && identityUser.EmailConfirmed == false)
|| ((user.EmailConfirmedDate.HasValue == false || user.EmailConfirmedDate.Value == default(DateTime)) && identityUser.EmailConfirmed))
{
anythingChanged = true;
user.EmailConfirmedDate = identityUser.EmailConfirmed ? (DateTime?)DateTime.Now : null;
}
- if (identityUser.IsPropertyDirty("Name")
+ if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Name))
&& user.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
user.Name = identityUser.Name;
}
- if (identityUser.IsPropertyDirty("Email")
+ if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Email))
&& user.Email != identityUser.Email && identityUser.Email.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
user.Email = identityUser.Email;
}
- if (identityUser.IsPropertyDirty("AccessFailedCount")
+ if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.AccessFailedCount))
&& user.FailedPasswordAttempts != identityUser.AccessFailedCount)
{
anythingChanged = true;
@@ -686,32 +705,32 @@ private bool UpdateMemberProperties(IUser user, BackOfficeIdentityUser identityU
}
}
- if (identityUser.IsPropertyDirty("UserName")
+ if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.UserName))
&& user.Username != identityUser.UserName && identityUser.UserName.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
user.Username = identityUser.UserName;
}
- if (identityUser.IsPropertyDirty("PasswordHash")
+ if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.PasswordHash))
&& user.RawPasswordValue != identityUser.PasswordHash && identityUser.PasswordHash.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
user.RawPasswordValue = identityUser.PasswordHash;
}
- if (identityUser.IsPropertyDirty("Culture")
+ if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Culture))
&& user.Language != identityUser.Culture && identityUser.Culture.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
user.Language = identityUser.Culture;
}
- if (identityUser.IsPropertyDirty("StartMediaIds")
+ if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.StartMediaIds))
&& user.StartMediaIds.UnsortedSequenceEqual(identityUser.StartMediaIds) == false)
{
anythingChanged = true;
user.StartMediaIds = identityUser.StartMediaIds;
}
- if (identityUser.IsPropertyDirty("StartContentIds")
+ if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.StartContentIds))
&& user.StartContentIds.UnsortedSequenceEqual(identityUser.StartContentIds) == false)
{
anythingChanged = true;
@@ -724,7 +743,7 @@ private bool UpdateMemberProperties(IUser user, BackOfficeIdentityUser identityU
}
// TODO: Fix this for Groups too
- if (identityUser.IsPropertyDirty("Roles") || identityUser.IsPropertyDirty("Groups"))
+ if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Roles)) || identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Groups)))
{
var userGroupAliases = user.Groups.Select(x => x.Alias).ToArray();
diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs
index 633e12bcc1e9..0bc8de492acb 100644
--- a/src/Umbraco.Core/Security/MembershipProviderBase.cs
+++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs
@@ -31,7 +31,9 @@ public string HashPasswordForStorage(string password)
public bool VerifyPassword(string password, string hashedPassword)
{
- if (string.IsNullOrWhiteSpace(hashedPassword)) throw new ArgumentException("Value cannot be null or whitespace.", "hashedPassword");
+ if (string.IsNullOrWhiteSpace(hashedPassword))
+ return false;
+
return CheckPassword(password, hashedPassword);
}
diff --git a/src/Umbraco.Core/Services/IExternalLoginService.cs b/src/Umbraco.Core/Services/IExternalLoginService.cs
index a81543cf2d79..2749ca7b8bac 100644
--- a/src/Umbraco.Core/Services/IExternalLoginService.cs
+++ b/src/Umbraco.Core/Services/IExternalLoginService.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using Microsoft.AspNet.Identity;
using Umbraco.Core.Models.Identity;
@@ -16,20 +17,38 @@ public interface IExternalLoginService : IService
///
IEnumerable GetAll(int userId);
+ [Obsolete("Use the overload specifying loginProvider and providerKey instead")]
+ IEnumerable Find(UserLoginInfo login);
+
///
/// Returns all logins matching the login info - generally there should only be one but in some cases
/// there might be more than one depending on if an administrator has been editing/removing members
///
- ///
+ ///
+ ///
///
- IEnumerable Find(UserLoginInfo login);
+ IEnumerable Find(string loginProvider, string providerKey);
+
+ [Obsolete("Use the Save method instead")]
+ void SaveUserLogins(int userId, IEnumerable logins);
///
- /// Save user logins
+ /// Saves the external logins associated with the user
///
- ///
+ ///
+ /// The user associated with the logins
+ ///
///
- void SaveUserLogins(int userId, IEnumerable logins);
+ ///
+ /// This will replace all external login provider information for the user
+ ///
+ void Save(int userId, IEnumerable logins);
+
+ ///
+ /// Save a single external login record
+ ///
+ ///
+ void Save(IIdentityUserLoginExtended login);
///
/// Deletes all user logins - normally used when a member is deleted
diff --git a/src/Umbraco.Core/Services/IIconService.cs b/src/Umbraco.Core/Services/IIconService.cs
index 963edb22a593..72174a05047c 100644
--- a/src/Umbraco.Core/Services/IIconService.cs
+++ b/src/Umbraco.Core/Services/IIconService.cs
@@ -9,13 +9,13 @@ public interface IIconService
/// Gets an IconModel containing the icon name and SvgString according to an icon name found at the global icons path
///
///
- ///
+ ///
IconModel GetIcon(string iconName);
///
/// Gets a list of all svg icons found at at the global icons path.
///
- ///
+ /// A list of
IList GetAllIcons();
}
}
diff --git a/src/Umbraco.Core/Services/IRedirectUrlService.cs b/src/Umbraco.Core/Services/IRedirectUrlService.cs
index 3bd9b6f2cf5e..d509dda1a850 100644
--- a/src/Umbraco.Core/Services/IRedirectUrlService.cs
+++ b/src/Umbraco.Core/Services/IRedirectUrlService.cs
@@ -10,78 +10,78 @@ namespace Umbraco.Core.Services
public interface IRedirectUrlService : IService
{
///
- /// Registers a redirect url.
+ /// Registers a redirect URL.
///
- /// The Umbraco url route.
+ /// The Umbraco URL route.
/// The content unique key.
/// The culture.
/// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo.
void Register(string url, Guid contentKey, string culture = null);
///
- /// Deletes all redirect urls for a given content.
+ /// Deletes all redirect URLs for a given content.
///
/// The content unique key.
void DeleteContentRedirectUrls(Guid contentKey);
///
- /// Deletes a redirect url.
+ /// Deletes a redirect URL.
///
- /// The redirect url to delete.
+ /// The redirect URL to delete.
void Delete(IRedirectUrl redirectUrl);
///
- /// Deletes a redirect url.
+ /// Deletes a redirect URL.
///
- /// The redirect url identifier.
+ /// The redirect URL identifier.
void Delete(Guid id);
///
- /// Deletes all redirect urls.
+ /// Deletes all redirect URLs.
///
void DeleteAll();
///
- /// Gets the most recent redirect urls corresponding to an Umbraco redirect url route.
+ /// Gets the most recent redirect URLs corresponding to an Umbraco redirect URL route.
///
- /// The Umbraco redirect url route.
- /// The most recent redirect urls corresponding to the route.
+ /// The Umbraco redirect URL route.
+ /// The most recent redirect URLs corresponding to the route.
IRedirectUrl GetMostRecentRedirectUrl(string url);
///
- /// Gets all redirect urls for a content item.
+ /// Gets all redirect URLs for a content item.
///
/// The content unique key.
- /// All redirect urls for the content item.
+ /// All redirect URLs for the content item.
IEnumerable GetContentRedirectUrls(Guid contentKey);
///
- /// Gets all redirect urls.
+ /// Gets all redirect URLs.
///
/// The page index.
/// The page size.
- /// The total count of redirect urls.
- /// The redirect urls.
+ /// The total count of redirect URLs.
+ /// The redirect URLs.
IEnumerable GetAllRedirectUrls(long pageIndex, int pageSize, out long total);
///
- /// Gets all redirect urls below a given content item.
+ /// Gets all redirect URLs below a given content item.
///
/// The content unique identifier.
/// The page index.
/// The page size.
- /// The total count of redirect urls.
- /// The redirect urls.
+ /// The total count of redirect URLs.
+ /// The redirect URLs.
IEnumerable GetAllRedirectUrls(int rootContentId, long pageIndex, int pageSize, out long total);
///
- /// Searches for all redirect urls that contain a given search term in their URL property.
+ /// Searches for all redirect URLs that contain a given search term in their URL property.
///
/// The term to search for.
/// The page index.
/// The page size.
- /// The total count of redirect urls.
- /// The redirect urls.
+ /// The total count of redirect URLs.
+ /// The redirect URLs.
IEnumerable SearchRedirectUrls(string searchTerm, long pageIndex, int pageSize, out long total);
}
}
diff --git a/src/Umbraco.Core/Services/IServerRegistrationService.cs b/src/Umbraco.Core/Services/IServerRegistrationService.cs
index 47bf3838f215..62bb68eb145a 100644
--- a/src/Umbraco.Core/Services/IServerRegistrationService.cs
+++ b/src/Umbraco.Core/Services/IServerRegistrationService.cs
@@ -10,7 +10,7 @@ public interface IServerRegistrationService
///
/// Touches a server to mark it as active; deactivate stale servers.
///
- /// The server url.
+ /// The server URL.
/// The server unique identity.
/// The time after which a server is considered stale.
void TouchServer(string serverAddress, string serverIdentity, TimeSpan staleTimeout);
diff --git a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs
index 5189b3422e27..d751a82dfb01 100644
--- a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs
+++ b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs
@@ -177,7 +177,7 @@ public XElement Serialize(IDataType dataType)
var folderNames = string.Empty;
if (dataType.Level != 1)
{
- //get url encoded folder names
+ //get URL encoded folder names
var folders = _dataTypeService.GetContainers(dataType)
.OrderBy(x => x.Level)
.Select(x => HttpUtility.UrlEncode(x.Name));
@@ -518,7 +518,7 @@ public XElement Serialize(IContentType contentType)
//don't add folders if this is a child doc type
if (contentType.Level != 1 && masterContentType == null)
{
- //get url encoded folder names
+ //get URL encoded folder names
var folders = _contentTypeService.GetContainers(contentType)
.OrderBy(x => x.Level)
.Select(x => HttpUtility.UrlEncode(x.Name));
diff --git a/src/Umbraco.Core/Services/Implement/ExternalLoginService.cs b/src/Umbraco.Core/Services/Implement/ExternalLoginService.cs
index aedf3874dd77..b0d9a80a6500 100644
--- a/src/Umbraco.Core/Services/Implement/ExternalLoginService.cs
+++ b/src/Umbraco.Core/Services/Implement/ExternalLoginService.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Identity;
using Umbraco.Core.Events;
@@ -20,53 +21,60 @@ public ExternalLoginService(IScopeProvider provider, ILogger logger, IEventMessa
_externalLoginRepository = externalLoginRepository;
}
- ///
- /// Returns all user logins assigned
- ///
- ///
- ///
+ ///
public IEnumerable GetAll(int userId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _externalLoginRepository.Get(Query().Where(x => x.UserId == userId))
- .ToList(); // ToList is important here, must evaluate within uow! // ToList is important here, must evaluate within uow!
+ .ToList();
}
}
- ///
- /// Returns all logins matching the login info - generally there should only be one but in some cases
- /// there might be more than one depending on if an administrator has been editing/removing members
- ///
- ///
- ///
+ [Obsolete("Use the overload specifying loginProvider and providerKey instead")]
public IEnumerable Find(UserLoginInfo login)
+ {
+ return Find(login.LoginProvider, login.ProviderKey);
+ }
+
+ ///
+ public IEnumerable Find(string loginProvider, string providerKey)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
- return _externalLoginRepository.Get(Query().Where(x => x.ProviderKey == login.ProviderKey && x.LoginProvider == login.LoginProvider))
- .ToList(); // ToList is important here, must evaluate within uow! // ToList is important here, must evaluate within uow!
+ return _externalLoginRepository.Get(Query()
+ .Where(x => x.ProviderKey == providerKey && x.LoginProvider == loginProvider))
+ .ToList();
}
}
- ///
- /// Save user logins
- ///
- ///
- ///
+ [Obsolete("Use the Save method instead")]
public void SaveUserLogins(int userId, IEnumerable logins)
+ {
+ Save(userId, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey)));
+ }
+
+ ///
+ public void Save(int userId, IEnumerable logins)
+ {
+ using (var scope = ScopeProvider.CreateScope())
+ {
+ _externalLoginRepository.Save(userId, logins);
+ scope.Complete();
+ }
+ }
+
+ ///
+ public void Save(IIdentityUserLoginExtended login)
{
using (var scope = ScopeProvider.CreateScope())
{
- _externalLoginRepository.SaveUserLogins(userId, logins);
+ _externalLoginRepository.Save(login);
scope.Complete();
}
}
- ///
- /// Deletes all user logins - normally used when a member is deleted
- ///
- ///
+ ///
public void DeleteUserLogins(int userId)
{
using (var scope = ScopeProvider.CreateScope())
@@ -75,5 +83,7 @@ public void DeleteUserLogins(int userId)
scope.Complete();
}
}
+
+
}
}
diff --git a/src/Umbraco.Core/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/Implement/NotificationService.cs
index 2b21945ba855..507da9cec3d3 100644
--- a/src/Umbraco.Core/Services/Implement/NotificationService.cs
+++ b/src/Umbraco.Core/Services/Implement/NotificationService.cs
@@ -394,7 +394,7 @@ private NotificationRequest CreateNotificationRequest(IUser performingUser, IUse
content.Id.ToString(CultureInfo.InvariantCulture),
string.Format("{2}://{0}/{1}",
string.Concat(siteUri.Authority),
- // TODO: RE-enable this so we can have a nice url
+ // TODO: RE-enable this so we can have a nice URL
/*umbraco.library.NiceUrl(documentObject.Id))*/
string.Concat(content.Id, ".aspx"),
protocol),
diff --git a/src/Umbraco.Core/Services/Implement/ServerRegistrationService.cs b/src/Umbraco.Core/Services/Implement/ServerRegistrationService.cs
index d9ce978274b3..056d4d9fd9ba 100644
--- a/src/Umbraco.Core/Services/Implement/ServerRegistrationService.cs
+++ b/src/Umbraco.Core/Services/Implement/ServerRegistrationService.cs
@@ -40,7 +40,7 @@ public ServerRegistrationService(IScopeProvider scopeProvider, ILogger logger, I
///
/// Touches a server to mark it as active; deactivate stale servers.
///
- /// The server url.
+ /// The server URL.
/// The server unique identity.
/// The time after which a server is considered stale.
public void TouchServer(string serverAddress, string serverIdentity, TimeSpan staleTimeout)
diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs
index c37f8bdf353d..57ef9c0d42ff 100644
--- a/src/Umbraco.Core/StringExtensions.cs
+++ b/src/Umbraco.Core/StringExtensions.cs
@@ -106,7 +106,7 @@ public static string GetFileExtension(this string file)
///
internal static Attempt DetectIsJavaScriptPath(this string input)
{
- //validate that this is a url, if it is not, we'll assume that it is a text block and render it as a text
+ //validate that this is a URL, if it is not, we'll assume that it is a text block and render it as a text
//block instead.
var isValid = true;
@@ -114,7 +114,7 @@ internal static Attempt DetectIsJavaScriptPath(this string input)
{
//ok it validates, but so does alert('hello'); ! so we need to do more checks
- //here are the valid chars in a url without escaping
+ //here are the valid chars in a URL without escaping
if (Regex.IsMatch(input, @"[^a-zA-Z0-9-._~:/?#\[\]@!$&'\(\)*\+,%;=]"))
isValid = false;
@@ -856,7 +856,7 @@ internal static string UrlTokenEncode(byte[] input)
var pos = str.IndexOf('=');
if (pos < 0) pos = str.Length;
- // replace chars that would cause problems in urls
+ // replace chars that would cause problems in URLs
var chArray = new char[pos];
for (var i = 0; i < pos; i++)
{
@@ -1095,13 +1095,13 @@ public static string ToSafeAlias(this string alias, string culture)
return Current.ShortStringHelper.CleanStringForSafeAlias(alias, culture);
}
- // the new methods to get a url segment
+ // the new methods to get a URL segment
///
- /// Cleans a string to produce a string that can safely be used in an url segment.
+ /// Cleans a string to produce a string that can safely be used in an URL segment.
///
/// The text to filter.
- /// The safe url segment.
+ /// The safe URL segment.
public static string ToUrlSegment(this string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
@@ -1111,11 +1111,11 @@ public static string ToUrlSegment(this string text)
}
///
- /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an url segment.
+ /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an URL segment.
///
/// The text to filter.
/// The culture.
- /// The safe url segment.
+ /// The safe URL segment.
public static string ToUrlSegment(this string text, string culture)
{
if (text == null) throw new ArgumentNullException(nameof(text));
@@ -1124,7 +1124,7 @@ public static string ToUrlSegment(this string text, string culture)
return Current.ShortStringHelper.CleanStringForUrlSegment(text, culture);
}
- // the new methods to clean a string (to alias, url segment...)
+ // the new methods to clean a string (to alias, URL segment...)
///
/// Cleans a string.
@@ -1205,7 +1205,7 @@ internal static string SpaceCamelCasing(this string phrase)
///
/// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename,
- /// both internally (on disk) and externally (as a url).
+ /// both internally (on disk) and externally (as a URL).
///
/// The text to filter.
/// The safe filename.
@@ -1216,7 +1216,7 @@ public static string ToSafeFileName(this string text)
///
/// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename,
- /// both internally (on disk) and externally (as a url).
+ /// both internally (on disk) and externally (as a URL).
///
/// The text to filter.
/// The culture.
diff --git a/src/Umbraco.Core/Strings/ContentBaseExtensions.cs b/src/Umbraco.Core/Strings/ContentBaseExtensions.cs
index 43e3506a84ab..01560f3b8a22 100644
--- a/src/Umbraco.Core/Strings/ContentBaseExtensions.cs
+++ b/src/Umbraco.Core/Strings/ContentBaseExtensions.cs
@@ -6,17 +6,17 @@
namespace Umbraco.Core.Strings
{
///
- /// Provides extension methods to IContentBase to get url segments.
+ /// Provides extension methods to IContentBase to get URL segments.
///
internal static class ContentBaseExtensions
{
///
- /// Gets the url segment for a specified content and culture.
+ /// Gets the URL segment for a specified content and culture.
///
/// The content.
/// The culture.
///
- /// The url segment.
+ /// The URL segment.
public static string GetUrlSegment(this IContentBase content, IEnumerable urlSegmentProviders, string culture = null)
{
if (content == null) throw new ArgumentNullException(nameof(content));
diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs
index 636118660494..15a87717d230 100644
--- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs
+++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs
@@ -8,7 +8,7 @@
namespace Umbraco.Core.Strings
{
///
- /// New default implementation of string functions for short strings such as aliases or url segments.
+ /// New default implementation of string functions for short strings such as aliases or URL segments.
///
///
/// Not optimized to work on large bodies of text.
@@ -92,10 +92,10 @@ public virtual string CleanStringForSafeAlias(string text, string culture)
}
///
- /// Cleans a string to produce a string that can safely be used in an url segment.
+ /// Cleans a string to produce a string that can safely be used in an URL segment.
///
/// The text to filter.
- /// The safe url segment.
+ /// The safe URL segment.
///
/// The string will be cleaned in the context of the default culture.
/// Url segments are Ascii only (no accents...).
@@ -106,11 +106,11 @@ public virtual string CleanStringForUrlSegment(string text)
}
///
- /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an url segment.
+ /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an URL segment.
///
/// The text to filter.
/// The culture.
- /// The safe url segment.
+ /// The safe URL segment.
///
/// Url segments are Ascii only (no accents...).
///
@@ -121,7 +121,7 @@ public virtual string CleanStringForUrlSegment(string text, string culture)
///
/// Cleans a string, in the context of the default culture, to produce a string that can safely be used as a filename,
- /// both internally (on disk) and externally (as a url).
+ /// both internally (on disk) and externally (as a URL).
///
/// The text to filter.
/// The safe filename.
@@ -133,7 +133,7 @@ public virtual string CleanStringForSafeFileName(string text)
///
/// Cleans a string to produce a string that can safely be used as a filename,
- /// both internally (on disk) and externally (as a url).
+ /// both internally (on disk) and externally (as a URL).
///
/// The text to filter.
/// The culture.
diff --git a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs
index 9472ff4823fa..a0f8c735486e 100644
--- a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs
+++ b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs
@@ -8,11 +8,11 @@ namespace Umbraco.Core.Strings
public class DefaultUrlSegmentProvider : IUrlSegmentProvider
{
///
- /// Gets the url segment for a specified content and culture.
+ /// Gets the URL segment for a specified content and culture.
///
/// The content.
/// The culture.
- /// The url segment.
+ /// The URL segment.
public string GetUrlSegment(IContentBase content, string culture = null)
{
return GetUrlSegmentSource(content, culture).ToUrlSegment(culture);
diff --git a/src/Umbraco.Core/Strings/IShortStringHelper.cs b/src/Umbraco.Core/Strings/IShortStringHelper.cs
index fecbeaaee96b..37873caded08 100644
--- a/src/Umbraco.Core/Strings/IShortStringHelper.cs
+++ b/src/Umbraco.Core/Strings/IShortStringHelper.cs
@@ -1,7 +1,7 @@
namespace Umbraco.Core.Strings
{
///
- /// Provides string functions for short strings such as aliases or url segments.
+ /// Provides string functions for short strings such as aliases or URL segments.
///
/// Not necessarily optimized to work on large bodies of text.
public interface IShortStringHelper
@@ -26,24 +26,24 @@ public interface IShortStringHelper
string CleanStringForSafeAlias(string text, string culture);
///
- /// Cleans a string to produce a string that can safely be used in an url segment.
+ /// Cleans a string to produce a string that can safely be used in an URL segment.
///
/// The text to filter.
- /// The safe url segment.
+ /// The safe URL segment.
/// The string will be cleaned in the context of the IShortStringHelper default culture.
string CleanStringForUrlSegment(string text);
///
- /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an url segment.
+ /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an URL segment.
///
/// The text to filter.
/// The culture.
- /// The safe url segment.
+ /// The safe URL segment.
string CleanStringForUrlSegment(string text, string culture);
///
/// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename,
- /// both internally (on disk) and externally (as a url).
+ /// both internally (on disk) and externally (as a URL).
///
/// The text to filter.
/// The safe filename.
@@ -52,7 +52,7 @@ public interface IShortStringHelper
///
/// Cleans a string, in the context of a specified culture, to produce a string that can safely be used as a filename,
- /// both internally (on disk) and externally (as a url).
+ /// both internally (on disk) and externally (as a URL).
///
/// The text to filter.
/// The culture.
diff --git a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs
index 12d2ef9a17f0..fddb87e71611 100644
--- a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs
+++ b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs
@@ -4,20 +4,20 @@
namespace Umbraco.Core.Strings
{
///
- /// Provides url segments for content.
+ /// Provides URL segments for content.
///
/// Url segments should comply with IETF RFCs regarding content, encoding, etc.
public interface IUrlSegmentProvider
{
///
- /// Gets the url segment for a specified content and culture.
+ /// Gets the URL segment for a specified content and culture.
///
/// The content.
/// The culture.
- /// The url segment.
- /// This is for when Umbraco is capable of managing more than one url
+ /// The URL segment.
+ /// This is for when Umbraco is capable of managing more than one URL
/// per content, in 1-to-1 multilingual configurations. Then there would be one
- /// url per culture.
+ /// URL per culture.
string GetUrlSegment(IContentBase content, string culture = null);
// TODO: For the 301 tracking, we need to add another extended interface to this so that
diff --git a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs
index 2a558f85aac2..95b08c8377fd 100644
--- a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs
+++ b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs
@@ -9,7 +9,7 @@
namespace Umbraco.Core.Sync
{
///
- /// A helper used to determine the current server umbraco application url.
+ /// A helper used to determine the current server umbraco application URL.
///
public static class ApplicationUrlHelper
{
@@ -17,12 +17,12 @@ public static class ApplicationUrlHelper
private static readonly Type TypeOfApplicationUrlHelper = typeof(ApplicationUrlHelper);
///
- /// Gets or sets a custom provider for the umbraco application url.
+ /// Gets or sets a custom provider for the umbraco application URL.
///
///
/// Receives the current request as a parameter, and it may be null. Must return a properly
- /// formatted url with scheme and umbraco dir and no trailing slash eg "http://www.mysite.com/umbraco",
- /// or null. To be used in auto-load-balancing scenarios where the application url is not
+ /// formatted URL with scheme and umbraco dir and no trailing slash eg "http://www.mysite.com/umbraco",
+ /// or null. To be used in auto-load-balancing scenarios where the application URL is not
/// in config files but is determined programmatically.
/// Must be assigned before resolution is frozen.
///
@@ -67,7 +67,7 @@ internal static string TryGetApplicationUrl(IUmbracoSettingsSection settings, IL
}
// try the server registrar
- // which is assumed to return a url that:
+ // which is assumed to return a URL that:
// - end with SystemDirectories.Umbraco
// - contain a scheme
// - end or not with a slash, it will be taken care of
diff --git a/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs b/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs
index 276ce6097c6e..a4315c33e991 100644
--- a/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs
+++ b/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs
@@ -46,11 +46,11 @@ public ServerRole GetCurrentServerRole()
}
///
- /// Gets the current umbraco application url.
+ /// Gets the current umbraco application URL.
///
public string GetCurrentServerUmbracoApplicationUrl()
{
- // this registrar does not provide the umbraco application url
+ // this registrar does not provide the umbraco application URL
return null;
}
diff --git a/src/Umbraco.Core/Sync/IServerRegistrar.cs b/src/Umbraco.Core/Sync/IServerRegistrar.cs
index a4edb1d0e903..8bc8021eec82 100644
--- a/src/Umbraco.Core/Sync/IServerRegistrar.cs
+++ b/src/Umbraco.Core/Sync/IServerRegistrar.cs
@@ -18,11 +18,11 @@ public interface IServerRegistrar
ServerRole GetCurrentServerRole();
///
- /// Gets the current umbraco application url.
+ /// Gets the current umbraco application URL.
///
///
- /// If the registrar does not provide the umbraco application url, should return null.
- /// Must return null, or a url that ends with SystemDirectories.Umbraco, and contains a scheme, eg "http://www.mysite.com/umbraco".
+ /// If the registrar does not provide the umbraco application URL, should return null.
+ /// Must return null, or a URL that ends with SystemDirectories.Umbraco, and contains a scheme, eg "http://www.mysite.com/umbraco".
///
string GetCurrentServerUmbracoApplicationUrl();
}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index b7b9867618cd..04ae182b46c6 100755
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -132,6 +132,7 @@
+
@@ -143,6 +144,8 @@
+
+
diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs
index 15ca2cca2476..4051bc3bd551 100644
--- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Umbraco.Core;
@@ -25,6 +26,13 @@ protected override IEnumerable Validate(TModel model)
//don't do anything if we're not enabled
if (!_config.Enable) yield break;
+ //list of reserved/disallowed aliases for content/media/member types - more can be added as the need arises
+ var reservedModelAliases = new[] { "system" };
+ if(reservedModelAliases.Contains(model.Alias, StringComparer.OrdinalIgnoreCase))
+ {
+ yield return new ValidationResult($"The model alias {model.Alias} is a reserved term and cannot be used", new[] { "Alias" });
+ }
+
var properties = model.Groups.SelectMany(x => x.Properties)
.Where(x => x.Inherited == false)
.ToArray();
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs
index c599785711ca..826b3925927b 100644
--- a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs
@@ -8,7 +8,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
///
/// Special component used for when MB is disabled with the legacy MB is detected
///
- internal class DisabledModelsBuilderComponent : IComponent
+ public sealed class DisabledModelsBuilderComponent : IComponent
{
private readonly UmbracoFeatures _features;
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs
index 7434131dc2b4..a475e4e1c354 100644
--- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs
@@ -58,7 +58,7 @@ public void Terminate()
private void InstallServerVars()
{
- // register our url - for the backoffice api
+ // register our URL - for the backoffice API
ServerVariablesParser.Parsing += ServerVariablesParser_Parsing;
}
diff --git a/src/Umbraco.TestData/UmbracoTestDataController.cs b/src/Umbraco.TestData/UmbracoTestDataController.cs
index 02949d5345b7..f55ced0dbc1b 100644
--- a/src/Umbraco.TestData/UmbracoTestDataController.cs
+++ b/src/Umbraco.TestData/UmbracoTestDataController.cs
@@ -172,7 +172,7 @@ private IEnumerable CreateMediaTree(string company, Faker faker, int count,
{
var imageUrl = faker.Image.PicsumUrl();
- // we are appending a &ext=.jpg to the end of this for a reason. The result of this url will be something like:
+ // we are appending a &ext=.jpg to the end of this for a reason. The result of this URL will be something like:
// https://picsum.photos/640/480/?image=106
// and due to the way that we detect images there must be an extension so we'll change it to
// https://picsum.photos/640/480/?image=106&ext=.jpg
diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs
index 343994b03a69..43a120d1544f 100644
--- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs
+++ b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs
@@ -887,12 +887,12 @@ public void ShadowGetRelativePath()
}
///
- /// Ensure the url returned contains the path relative to the FS root,
+ /// Ensure the URL returned contains the path relative to the FS root,
/// but including the rootUrl the FS was initialized with.
///
///
/// This file stuff in this test is kinda irrelevant with the current implementation.
- /// We do tests that the files are written to the correct places and the url is returned correct,
+ /// We do tests that the files are written to the correct places and the URL is returned correct,
/// but GetUrl is currently really just string manipulation so files are not actually hit by the code.
/// Leaving the file stuff in here for now in case the method becomes more clever at some point.
///
diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs
index 48f0e7b27ef8..893a170f5a67 100644
--- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs
+++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs
@@ -96,7 +96,7 @@ private void AddToCacheIfDeepestRoute(IPublishedContent content, string route)
// so we have a route that maps to a content... say "1234/path/to/content" - however, there could be a
// domain set on "to" and route "4567/content" would also map to the same content - and due to how
- // urls computing work (by walking the tree up to the first domain we find) it is that second route
+ // URLs computing work (by walking the tree up to the first domain we find) it is that second route
// that would be returned - the "deepest" route - and that is the route we want to cache, *not* the
// longer one - so make sure we don't cache the wrong route
@@ -257,7 +257,7 @@ string DetermineRouteById(bool preview, int contentId)
if (node == null) return null;
// walk up from that node until we hit a node with a domain,
- // or we reach the content root, collecting urls in the way
+ // or we reach the content root, collecting URLs in the way
var pathParts = new List();
var n = node;
var hasDomains = _domainCache.HasAssigned(n.Id);
diff --git a/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs b/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs
index 79878a59a146..163effb676ba 100644
--- a/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs
+++ b/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs
@@ -77,7 +77,7 @@ public void SetApplicationUrlViaProvider()
[Test]
public void SetApplicationUrlWhenNoSettings()
{
- // no applicable settings, cannot set url
+ // no applicable settings, cannot set URL
var settings = Mock.Of(section =>
section.WebRouting == Mock.Of(wrSection => wrSection.UmbracoApplicationUrl == (string) null));
diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs
index 85168e449072..18bb6bdfb5cb 100644
--- a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs
+++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs
@@ -24,7 +24,7 @@ public class ContentFinderByUrlTests : BaseWebTest
[TestCase("/home/sub1", -1)] // should fail
// these two are special. getNiceUrl(1046) returns "/" but getNiceUrl(1172) cannot also return "/" so
- // we've made it return "/test-page" => we have to support that url back in the lookup...
+ // we've made it return "/test-page" => we have to support that URL back in the lookup...
[TestCase("/home", 1046)]
[TestCase("/test-page", 1172)]
public void Match_Document_By_Url_Hide_Top_Level(string urlString, int expectedId)
diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs
index ac2e62b1efdb..18eafe508ea8 100644
--- a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs
+++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs
@@ -134,7 +134,7 @@ public void Lookup_SingleDomain(string url, int expectedId)
var publishedRouter = CreatePublishedRouter(Factory);
var frequest = publishedRouter.CreateRequest(umbracoContext);
- // must lookup domain else lookup by url fails
+ // must lookup domain else lookup by URL fails
publishedRouter.FindDomain(frequest);
var lookup = new ContentFinderByUrl(Logger);
@@ -176,7 +176,7 @@ public void Lookup_NestedDomains(string url, int expectedId, string expectedCult
var publishedRouter = CreatePublishedRouter(Factory);
var frequest = publishedRouter.CreateRequest(umbracoContext);
- // must lookup domain else lookup by url fails
+ // must lookup domain else lookup by URL fails
publishedRouter.FindDomain(frequest);
Assert.AreEqual(expectedCulture, frequest.Culture.Name);
diff --git a/src/Umbraco.Tests/Routing/RouteTestExtensions.cs b/src/Umbraco.Tests/Routing/RouteTestExtensions.cs
index fae8e2a6fa36..642488c25627 100644
--- a/src/Umbraco.Tests/Routing/RouteTestExtensions.cs
+++ b/src/Umbraco.Tests/Routing/RouteTestExtensions.cs
@@ -9,7 +9,7 @@ public static class RouteTestExtensions
{
///
- /// Return the route data for the url based on a mocked context
+ /// Return the route data for the URL based on a mocked context
///
///
///
@@ -21,7 +21,7 @@ public static RouteData GetDataForRoute(this RouteCollection routes, string requ
}
///
- /// Get the route data based on the url and http context
+ /// Get the route data based on the URL and HTTP context
///
///
///
diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs
index e95fdfc87c59..94660d40cf5c 100644
--- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs
+++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs
@@ -100,7 +100,7 @@ public void Is_Client_Side_Request(string url, bool assert)
[Test]
public void Is_Client_Side_Request_InvalidPath_ReturnFalse()
{
- //This url is invalid. Default to false when the extension cannot be determined
+ //This URL is invalid. Default to false when the extension cannot be determined
var uri = new Uri("http://test.com/installing-modules+foobar+\"yipee\"");
var result = uri.IsClientSideRequest();
Assert.AreEqual(false, result);
diff --git a/src/Umbraco.Tests/Routing/UrlProviderTests.cs b/src/Umbraco.Tests/Routing/UrlProviderTests.cs
index 8043e256619e..d61517b41e93 100644
--- a/src/Umbraco.Tests/Routing/UrlProviderTests.cs
+++ b/src/Umbraco.Tests/Routing/UrlProviderTests.cs
@@ -184,7 +184,7 @@ public void Get_Url_For_Culture_Variant_Without_Domains_Non_Current_Url()
globalSettings: globalSettings.Object,
snapshotService: snapshotService.Object);
- //even though we are asking for a specific culture URL, there are no domains assigned so all that can be returned is a normal relative url.
+ //even though we are asking for a specific culture URL, there are no domains assigned so all that can be returned is a normal relative URL.
var url = umbracoContext.UrlProvider.GetUrl(1234, culture: "fr-FR");
Assert.AreEqual("/home/test-fr/", url);
diff --git a/src/Umbraco.Tests/Routing/UrlRoutesTests.cs b/src/Umbraco.Tests/Routing/UrlRoutesTests.cs
index 4b8d708df685..724a263b542b 100644
--- a/src/Umbraco.Tests/Routing/UrlRoutesTests.cs
+++ b/src/Umbraco.Tests/Routing/UrlRoutesTests.cs
@@ -12,7 +12,7 @@
namespace Umbraco.Tests.Routing
{
// purpose: test the values returned by PublishedContentCache.GetRouteById
- // and .GetByRoute (no caching at all, just routing nice urls) including all
+ // and .GetByRoute (no caching at all, just routing nice URLs) including all
// the quirks due to hideTopLevelFromPath and backward compatibility.
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
@@ -146,7 +146,7 @@ cache route (as not trusted)
node = node(id)
- walk up from node to domain or root, assemble parts = url segments
+ walk up from node to domain or root, assemble parts = URL segments
if !domain and global.hide:
if id.parent:
diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs
index 6587b2e4f6cf..363f87395d96 100644
--- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs
+++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs
@@ -16,7 +16,7 @@ namespace Umbraco.Tests.Routing
[TestFixture]
public class UrlsWithNestedDomains : UrlRoutingTestBase
{
- // in the case of nested domains more than 1 url may resolve to a document
+ // in the case of nested domains more than 1 URL may resolve to a document
// but only one route can be cached - the 'canonical' route ie the route
// using the closest domain to the node - here we test that if we request
// a non-canonical route, it is not cached / the cache is not polluted
@@ -40,7 +40,7 @@ public void DoNotPolluteCache()
const string url = "http://domain1.com/1001-1/1001-1-1";
- // get the nice url for 100111
+ // get the nice URL for 100111
var umbracoContext = GetUmbracoContext(url, 9999, umbracoSettings: settings, urlProviders: new []
{
new DefaultUrlProvider(settings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper())
@@ -53,7 +53,7 @@ public void DoNotPolluteCache()
var cachedRoutes = cache.RoutesCache.GetCachedRoutes();
Assert.AreEqual("10011/1001-1-1", cachedRoutes[100111]);
- // route a rogue url
+ // route a rogue URL
var publishedRouter = CreatePublishedRouter();
var frequest = publishedRouter.CreateRequest(umbracoContext);
@@ -71,7 +71,7 @@ public void DoNotPolluteCache()
Assert.AreEqual("10011/1001-1-1", cachedRoutes[100111]); // no
//Assert.AreEqual("1001/1001-1/1001-1-1", cachedRoutes[100111]); // yes
- // what's the nice url now?
+ // what's the nice URL now?
Assert.AreEqual("http://domain2.com/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111)); // good
//Assert.AreEqual("http://domain1.com/1001-1/1001-1-1", routingContext.NiceUrlProvider.GetNiceUrl(100111, true)); // bad
}
diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs
index 9157a76773b0..93b4dd0766ca 100644
--- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs
+++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs
@@ -201,7 +201,7 @@ public void StandaloneTest()
Assert.AreEqual("test", pcontent.Name());
Assert.IsTrue(pcontent.IsDraft());
- // no published url
+ // no published URL
Assert.AreEqual("#", pcontent.Url());
// now publish the document + make some unpublished changes
@@ -215,7 +215,7 @@ public void StandaloneTest()
Assert.AreEqual("test", pcontent.Name());
Assert.IsFalse(pcontent.IsDraft());
- // but the url is the published one - no draft url
+ // but the URL is the published one - no draft URL
Assert.AreEqual("/test/", pcontent.Url());
// and also an updated draft document
@@ -224,7 +224,7 @@ public void StandaloneTest()
Assert.AreEqual("testx", pcontent.Name());
Assert.IsTrue(pcontent.IsDraft());
- // and the published document has a url
+ // and the published document has a URL
Assert.AreEqual("/test/", pcontent.Url());
umbracoContextReference.Dispose();
diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs
index 0598b8cea285..75f3662ee2af 100644
--- a/src/Umbraco.Tests/Services/EntityServiceTests.cs
+++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs
@@ -8,12 +8,12 @@
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Services;
-using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
namespace Umbraco.Tests.Services
{
+
///
/// Tests covering the EntityService
///
diff --git a/src/Umbraco.Tests/Services/ExternalLoginServiceTests.cs b/src/Umbraco.Tests/Services/ExternalLoginServiceTests.cs
new file mode 100644
index 000000000000..8a31518ca08d
--- /dev/null
+++ b/src/Umbraco.Tests/Services/ExternalLoginServiceTests.cs
@@ -0,0 +1,249 @@
+using System;
+using System.Linq;
+using System.Threading;
+using Microsoft.AspNet.Identity;
+using NUnit.Framework;
+using Umbraco.Core.Models.Identity;
+using Umbraco.Core.Models.Membership;
+using Umbraco.Core.Persistence.Dtos;
+using Umbraco.Tests.TestHelpers;
+using Umbraco.Tests.Testing;
+
+namespace Umbraco.Tests.Services
+{
+ [TestFixture]
+ [Apartment(ApartmentState.STA)]
+ [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
+ public class ExternalLoginServiceTests : TestWithDatabaseBase
+ {
+ [Test]
+ public void Removes_Existing_Duplicates_On_Save()
+ {
+ var user = new User("Test", "test@test.com", "test", "helloworldtest");
+ ServiceContext.UserService.Save(user);
+
+ var providerKey = Guid.NewGuid().ToString("N");
+ var latest = DateTime.Now.AddDays(-1);
+ var oldest = DateTime.Now.AddDays(-10);
+
+ using (var scope = ScopeProvider.CreateScope())
+ {
+ // insert duplicates manuall
+ scope.Database.Insert(new ExternalLoginDto
+ {
+ UserId = user.Id,
+ LoginProvider = "test1",
+ ProviderKey = providerKey,
+ CreateDate = latest
+ });
+ scope.Database.Insert(new ExternalLoginDto
+ {
+ UserId = user.Id,
+ LoginProvider = "test1",
+ ProviderKey = providerKey,
+ CreateDate = oldest
+ });
+ }
+
+ // try to save 2 other duplicates
+ var externalLogins = new[]
+ {
+ new ExternalLogin("test2", providerKey),
+ new ExternalLogin("test2", providerKey),
+ new ExternalLogin("test1", providerKey)
+ };
+
+ ServiceContext.ExternalLoginService.Save(user.Id, externalLogins);
+
+ var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).ToList();
+
+ // duplicates will be removed, keeping the latest entries
+ Assert.AreEqual(2, logins.Count);
+
+ var test1 = logins.Single(x => x.LoginProvider == "test1");
+ Assert.Greater(test1.CreateDate, latest);
+ }
+
+ [Test]
+ public void Does_Not_Persist_Duplicates()
+ {
+ var user = new User("Test", "test@test.com", "test", "helloworldtest");
+ ServiceContext.UserService.Save(user);
+
+ var providerKey = Guid.NewGuid().ToString("N");
+ var externalLogins = new[]
+ {
+ new ExternalLogin("test1", providerKey),
+ new ExternalLogin("test1", providerKey)
+ };
+
+ ServiceContext.ExternalLoginService.Save(user.Id, externalLogins);
+
+ var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).ToList();
+ Assert.AreEqual(1, logins.Count);
+ }
+
+ [Test]
+ public void Single_Create()
+ {
+ var user = new User("Test", "test@test.com", "test", "helloworldtest");
+ ServiceContext.UserService.Save(user);
+
+ var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id)
+ {
+ UserData = "hello"
+ };
+ ServiceContext.ExternalLoginService.Save(extLogin);
+
+ var found = ServiceContext.ExternalLoginService.GetAll(user.Id);
+
+ Assert.AreEqual(1, found.Count());
+ Assert.IsTrue(extLogin.HasIdentity);
+ Assert.IsTrue(extLogin.Id > 0);
+ }
+
+ [Test]
+ public void Single_Update()
+ {
+ var user = new User("Test", "test@test.com", "test", "helloworldtest");
+ ServiceContext.UserService.Save(user);
+
+ var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id)
+ {
+ UserData = "hello"
+ };
+ ServiceContext.ExternalLoginService.Save(extLogin);
+
+ extLogin.UserData = "world";
+ ServiceContext.ExternalLoginService.Save(extLogin);
+
+ var found = ServiceContext.ExternalLoginService.GetAll(user.Id).Cast().ToList();
+ Assert.AreEqual(1, found.Count);
+ Assert.AreEqual("world", found[0].UserData);
+ }
+
+ [Test]
+ public void Multiple_Update()
+ {
+ var user = new User("Test", "test@test.com", "test", "helloworldtest");
+ ServiceContext.UserService.Save(user);
+
+ var providerKey1 = Guid.NewGuid().ToString("N");
+ var providerKey2 = Guid.NewGuid().ToString("N");
+ var extLogins = new[]
+ {
+ new ExternalLogin("test1", providerKey1, "hello"),
+ new ExternalLogin("test2", providerKey2, "world")
+ };
+ ServiceContext.ExternalLoginService.Save(user.Id, extLogins);
+
+ extLogins = new[]
+ {
+ new ExternalLogin("test1", providerKey1, "123456"),
+ new ExternalLogin("test2", providerKey2, "987654")
+ };
+ ServiceContext.ExternalLoginService.Save(user.Id, extLogins);
+
+ var found = ServiceContext.ExternalLoginService.GetAll(user.Id).Cast().OrderBy(x => x.LoginProvider).ToList();
+ Assert.AreEqual(2, found.Count);
+ Assert.AreEqual("123456", found[0].UserData);
+ Assert.AreEqual("987654", found[1].UserData);
+ }
+
+ [Test]
+ public void Can_Find_As_Extended_Type()
+ {
+ var user = new User("Test", "test@test.com", "test", "helloworldtest");
+ ServiceContext.UserService.Save(user);
+
+ var providerKey1 = Guid.NewGuid().ToString("N");
+ var providerKey2 = Guid.NewGuid().ToString("N");
+ var extLogins = new[]
+ {
+ new ExternalLogin("test1", providerKey1, "hello"),
+ new ExternalLogin("test2", providerKey2, "world")
+ };
+ ServiceContext.ExternalLoginService.Save(user.Id, extLogins);
+
+ var found = ServiceContext.ExternalLoginService.Find("test2", providerKey2).ToList();
+ Assert.AreEqual(1, found.Count);
+ var asExtended = found.Cast().ToList();
+ Assert.AreEqual(1, found.Count);
+
+ }
+
+ [Test]
+ public void Add_Logins()
+ {
+ var user = new User("Test", "test@test.com", "test", "helloworldtest");
+ ServiceContext.UserService.Save(user);
+
+ var externalLogins = new[]
+ {
+ new ExternalLogin("test1", Guid.NewGuid().ToString("N")),
+ new ExternalLogin("test2", Guid.NewGuid().ToString("N"))
+ };
+
+ ServiceContext.ExternalLoginService.Save(user.Id, externalLogins);
+
+ var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList();
+ Assert.AreEqual(2, logins.Count);
+ for (int i = 0; i < logins.Count; i++)
+ {
+ Assert.AreEqual(logins[i].ProviderKey, externalLogins[i].ProviderKey);
+ Assert.AreEqual(logins[i].LoginProvider, externalLogins[i].LoginProvider);
+ }
+ }
+
+ [Test]
+ public void Add_Update_Delete_Logins()
+ {
+ var user = new User("Test", "test@test.com", "test", "helloworldtest");
+ ServiceContext.UserService.Save(user);
+
+ var externalLogins = new[]
+ {
+ new ExternalLogin("test1", Guid.NewGuid().ToString("N")),
+ new ExternalLogin("test2", Guid.NewGuid().ToString("N")),
+ new ExternalLogin("test3", Guid.NewGuid().ToString("N")),
+ new ExternalLogin("test4", Guid.NewGuid().ToString("N"))
+ };
+
+ ServiceContext.ExternalLoginService.Save(user.Id, externalLogins);
+
+ var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList();
+
+ logins.RemoveAt(0); // remove the first one
+ logins.Add(new IdentityUserLogin("test5", Guid.NewGuid().ToString("N"), user.Id)); // add a new one
+
+ // save new list
+ ServiceContext.ExternalLoginService.Save(user.Id, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey)));
+
+ var updatedLogins = ServiceContext.ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList();
+ Assert.AreEqual(4, updatedLogins.Count);
+ for (int i = 0; i < updatedLogins.Count; i++)
+ {
+ Assert.AreEqual(logins[i].LoginProvider, updatedLogins[i].LoginProvider);
+ }
+ }
+
+ [Test]
+ public void Add_Retrieve_User_Data()
+ {
+ var user = new User("Test", "test@test.com", "test", "helloworldtest");
+ ServiceContext.UserService.Save(user);
+
+ var externalLogins = new[]
+ {
+ new ExternalLogin("test1", Guid.NewGuid().ToString("N"), "hello world")
+ };
+
+ ServiceContext.ExternalLoginService.Save(user.Id, externalLogins);
+
+ var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).Cast().ToList();
+
+ Assert.AreEqual("hello world", logins[0].UserData);
+
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs
index 5fd5710a7989..b93d79dcf048 100644
--- a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs
+++ b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs
@@ -338,7 +338,7 @@ public void CleanStringSymbols()
Assert.AreEqual("house*2", helper.CleanString("house (2)", CleanStringType.Alias));
// FIXME: but for a filename we want to keep them!
- // FIXME: and what about a url?
+ // FIXME: and what about a URL?
}
[Test]
@@ -424,7 +424,7 @@ public void CleanStringDefaultConfig()
// lower-cased, utf8 filename, removing illegal filename chars, using dash-separator
Assert.AreEqual("0123-中文测试-中文测试-léger-zôrg-2-a-x", filename, "filename");
- // lower-cased, utf8 url segment, only letters and digits, using dash-separator
+ // lower-cased, utf8 URL segment, only letters and digits, using dash-separator
Assert.AreEqual("0123-中文测试-中文测试-léger-zôrg-2-a-x", segment, "segment");
}
diff --git a/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs b/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs
index 6c40e2842d22..59968024d1f6 100644
--- a/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs
+++ b/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs
@@ -63,7 +63,7 @@ public void Remove_Image_Sources()
[Test]
public void Ensure_Image_Sources()
{
- //setup a mock url provider which we'll use for testing
+ //setup a mock URL provider which we'll use for testing
var mediaType = new PublishedContentType(Guid.NewGuid(), 777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing);
var media = new Mock();
diff --git a/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs b/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs
index 861a7e4db674..f9f82fd31bcf 100644
--- a/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs
+++ b/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs
@@ -49,7 +49,7 @@ public void Returns_Udis_From_LocalLinks()
[TestCase("hello href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"#\" world ")]
public void ParseLocalLinks(string input, string result)
{
- //setup a mock url provider which we'll use for testing
+ //setup a mock URL provider which we'll use for testing
var contentUrlProvider = new Mock();
contentUrlProvider
.Setup(x => x.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs
index 0840d8a18ca4..cc1cfa6a1dd5 100644
--- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs
+++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs
@@ -81,7 +81,7 @@ public void RegisterServices(IRegister register)
///
///
/// An event messages factory.
- /// Some url segment providers.
+ /// Some URL segment providers.
///
/// A container.
///
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index 004945bb4643..6296a8203f51 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -167,6 +167,7 @@
+
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cy.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cy.js
index d2fdd077f527..2863840abb1d 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cy.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cy.js
@@ -121,7 +121,7 @@ tinymce.addI18n('cy',{
"Date\/time": "Dyddiad\/amser",
"Insert date\/time": "Mewnosod dyddiad\/amser",
"Remove link": "Tynnu dolen",
-"Url": "Url",
+"Url": "URL",
"Text to display": "Testun i'w ddangos",
"Anchors": "Angorau",
"Insert link": "Mewnosod dolen",
@@ -227,4 +227,4 @@ tinymce.addI18n('cy',{
"View": "Dangos",
"Table": "Tabl",
"Format": "Fformat"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/da.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/da.js
index 90e4009d92ba..a50f2f10ec44 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/da.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/da.js
@@ -150,7 +150,7 @@ tinymce.addI18n('da',{
"Insert link": "Inds\u00e6t link",
"Insert\/edit link": "Inds\u00e6t\/ret link",
"Text to display": "Vis tekst",
-"Url": "Url",
+"Url": "URL",
"Target": "Target",
"None": "Ingen",
"New window": "Nyt vindue",
@@ -258,4 +258,4 @@ tinymce.addI18n('da',{
"Tools": "V\u00e6rkt\u00f8j",
"Powered by {0}": "Drevet af {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text omr\u00e5de. Tryk ALT-F9 for menu. Tryk ALT-F10 for toolbar. Tryk ALT-0 for hj\u00e6lp"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de_AT.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de_AT.js
index 2af071f5ce5b..0073810c1a3d 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de_AT.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de_AT.js
@@ -150,7 +150,7 @@ tinymce.addI18n('de_AT',{
"Insert link": "Link einf\u00fcgen",
"Insert\/edit link": "Link einf\u00fcgen\/bearbeiten",
"Text to display": "Angezeigter Text",
-"Url": "Url",
+"Url": "URL",
"Target": "Ziel",
"None": "Keine",
"New window": "Neues Fenster",
@@ -258,4 +258,4 @@ tinymce.addI18n('de_AT',{
"Tools": "Extras",
"Powered by {0}": "Betrieben von {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Dr\u00fccken Sie ALT-F9 f\u00fcr das Men\u00fc. Dr\u00fccken Sie ALT-F10 f\u00fcr die Werkzeugleiste. Dr\u00fccken Sie ALT-0 f\u00fcr Hilfe"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_CA.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_CA.js
index f32de017246e..cc07ffd23efa 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_CA.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_CA.js
@@ -150,7 +150,7 @@ tinymce.addI18n('en_CA',{
"Insert link": "Insert link",
"Insert\/edit link": "Insert\/edit link",
"Text to display": "Text to display",
-"Url": "Url",
+"Url": "URL",
"Target": "Target",
"None": "None",
"New window": "New window",
@@ -258,4 +258,4 @@ tinymce.addI18n('en_CA',{
"Tools": "Tools",
"Powered by {0}": "Powered by {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js
index 90eae85800c8..0b50212fd92e 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js
@@ -150,7 +150,7 @@ tinymce.addI18n('en_US',{
"Insert link": "Insert link",
"Insert\/edit link": "Insert\/edit link",
"Text to display": "Text to display",
-"Url": "Url",
+"Url": "URL",
"Target": "Target",
"None": "None",
"New window": "New window",
@@ -258,4 +258,4 @@ tinymce.addI18n('en_US',{
"Tools": "Tools",
"Powered by {0}": "Powered by {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es_MX.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es_MX.js
index b0f2019ffe30..688f14ba5119 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es_MX.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es_MX.js
@@ -150,7 +150,7 @@ tinymce.addI18n('es_MX',{
"Insert link": "Insertar enlace",
"Insert\/edit link": "Inserta\/editar enlace",
"Text to display": "Texto a mostrar",
-"Url": "Url",
+"Url": "URL",
"Target": "Objetivo",
"None": "Ninguno",
"New window": "Nueva ventana",
@@ -258,4 +258,4 @@ tinymce.addI18n('es_MX',{
"Tools": "Herramientas",
"Powered by {0}": "Creado con {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Presione dentro del \u00e1rea de texto ALT-F9 para invocar el men\u00fa, ALT-F10 para la barra de herramientas y ALT-0 para la ayuda."
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/et.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/et.js
index c6beeeb51072..96b763506c41 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/et.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/et.js
@@ -150,7 +150,7 @@ tinymce.addI18n('et',{
"Insert link": "Lisa link",
"Insert\/edit link": "Lisa\/muuda link",
"Text to display": "Kuvatav tekst",
-"Url": "Viide (url)",
+"Url": "Viide (URL)",
"Target": "Sihtm\u00e4rk",
"None": "Puudub",
"New window": "Uus aken",
@@ -258,4 +258,4 @@ tinymce.addI18n('et',{
"Tools": "T\u00f6\u00f6riistad",
"Powered by {0}": "Kasutatud tarkvara {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rikastatud teksti ala. Men\u00fc\u00fc jaoks vajuta ALT-F9. T\u00f6\u00f6riistariba jaoks vajuta ALT-F10. Abi saamiseks vajuta ALT-0."
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/eu.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/eu.js
index c4374394d444..44e62db3f8f3 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/eu.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/eu.js
@@ -150,7 +150,7 @@ tinymce.addI18n('eu',{
"Insert link": "Esteka txertatu",
"Insert\/edit link": "Esteka txertatu\/editatu",
"Text to display": "Bistaratzeko testua",
-"Url": "Url",
+"Url": "URL",
"Target": "Target",
"None": "Bat ere ez",
"New window": "Lehio berria",
@@ -258,4 +258,4 @@ tinymce.addI18n('eu',{
"Tools": "Tresnak",
"Powered by {0}": "{0}rekin egina",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Testu aberastuko area. Sakatu ALT-F9 menurako. Sakatu ALT-F10 tresna-barrarako. Sakatu ALT-0 laguntzarako"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr.js
index 2d074f8c6e1c..d61015efa0aa 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr.js
@@ -150,7 +150,7 @@ tinymce.addI18n('fr_FR',{
"Insert\/Edit Link": "Ins\u00e9rer\/Modifier lien",
"Insert\/edit link": "Ins\u00e9rer\/modifier un lien",
"Text to display": "Texte \u00e0 afficher",
-"Url": "Url",
+"Url": "URL",
"Open link in...": "Ouvrir le lien dans...",
"Current window": "Fen\u00eatre active",
"None": "n\/a",
@@ -386,4 +386,4 @@ tinymce.addI18n('fr_FR',{
"Spellcheck": "V\u00e9rification orthographique",
"Caption": "Titre",
"Insert template": "Ajouter un th\u00e8me"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr_FR.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr_FR.js
index 5c37164b2c24..5ed177c9fc14 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr_FR.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr_FR.js
@@ -150,7 +150,7 @@ tinymce.addI18n('fr_FR',{
"Insert link": "Ins\u00e9rer un lien",
"Insert\/edit link": "Ins\u00e9rer\/modifier un lien",
"Text to display": "Texte \u00e0 afficher",
-"Url": "Url",
+"Url": "URL",
"Target": "Cible",
"None": "n\/a",
"New window": "Nouvelle fen\u00eatre",
@@ -258,4 +258,4 @@ tinymce.addI18n('fr_FR',{
"Tools": "Outils",
"Powered by {0}": "Propuls\u00e9 par {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Zone Texte Riche. Appuyer sur ALT-F9 pour le menu. Appuyer sur ALT-F10 pour la barre d'outils. Appuyer sur ALT-0 pour de l'aide."
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hr.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hr.js
index d52f861ce9d6..617e1f4823b2 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hr.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hr.js
@@ -144,7 +144,7 @@ tinymce.addI18n('hr',{
"Insert link": "Umetni poveznicu",
"Insert\/edit link": "Umetni\/izmijeni poveznicu",
"Text to display": "Tekst za prikaz",
-"Url": "Url",
+"Url": "URL",
"Target": "Meta",
"None": "Ni\u0161ta",
"New window": "Novi prozor",
@@ -250,4 +250,4 @@ tinymce.addI18n('hr',{
"Table": "Tablica",
"Tools": "Alati",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Pritisni ALT-F9 za izbornik. Pritisni ALT-F10 za alatnu traku. Pritisni ALT-0 za pomo\u0107"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hu_HU.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hu_HU.js
index 3972dc2b3c2d..13bb4984a9f0 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hu_HU.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hu_HU.js
@@ -150,7 +150,7 @@ tinymce.addI18n('hu_HU',{
"Insert link": "Hivatkoz\u00e1s beilleszt\u00e9se",
"Insert\/edit link": "Hivatkoz\u00e1s beilleszt\u00e9se\/szerkeszt\u00e9se",
"Text to display": "Megjelen\u0151 sz\u00f6veg",
-"Url": "Url",
+"Url": "URL",
"Target": "C\u00e9l",
"None": "Nincs",
"New window": "\u00daj ablak",
@@ -258,4 +258,4 @@ tinymce.addI18n('hu_HU',{
"Tools": "Eszk\u00f6z\u00f6k",
"Powered by {0}": "\u00dczemelteti: {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text ter\u00fclet. Nyomj ALT-F9-et a men\u00fch\u00f6z. Nyomj ALT-F10-et az eszk\u00f6zt\u00e1rhoz. Nyomj ALT-0-t a s\u00fag\u00f3hoz"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/it.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/it.js
index 5ffc0c0f8058..a97118da2140 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/it.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/it.js
@@ -150,7 +150,7 @@ tinymce.addI18n('it',{
"Insert link": "Inserisci il Link",
"Insert\/edit link": "Inserisci\/Modifica Link",
"Text to display": "Testo da Visualizzare",
-"Url": "Url",
+"Url": "URL",
"Target": "Target",
"None": "No",
"New window": "Nuova Finestra",
@@ -258,4 +258,4 @@ tinymce.addI18n('it',{
"Tools": "Strumenti",
"Powered by {0}": "Fornito da {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Premi ALT-F9 per il men\u00f9. Premi ALT-F10 per la barra degli strumenti. Premi ALT-0 per l'aiuto."
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ka_GE.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ka_GE.js
index 9bffb8040d5a..805a96648958 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ka_GE.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ka_GE.js
@@ -121,7 +121,7 @@ tinymce.addI18n('ka_GE',{
"Date\/time": "\u10d7\u10d0\u10e0\u10d8\u10e6\u10d8\/\u10d3\u10e0\u10dd",
"Insert date\/time": "\u10d7\u10d0\u10e0\u10d8\u10e6\u10d8\/\u10d3\u10e0\u10dd\u10d8\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0",
"Remove link": "\u10d1\u10db\u10e3\u10da\u10d8\u10e1 \u10ec\u10d0\u10e8\u10da\u10d0",
-"Url": "Url",
+"Url": "URL",
"Text to display": "\u10e2\u10d4\u10e5\u10e1\u10e2\u10d8",
"Anchors": "\u10e6\u10e3\u10d6\u10d0",
"Insert link": "\u10d1\u10db\u10e3\u10da\u10d8\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0",
@@ -227,4 +227,4 @@ tinymce.addI18n('ka_GE',{
"View": "\u10dc\u10d0\u10ee\u10d5\u10d0",
"Table": "\u10ea\u10ee\u10e0\u10d8\u10da\u10d8",
"Format": "\u10e4\u10dd\u10e0\u10db\u10d0\u10e2\u10d8"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kab.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kab.js
index b9f9bccf40e9..48f7d3bf5580 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kab.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kab.js
@@ -150,7 +150,7 @@ tinymce.addI18n('kab',{
"Insert link": "Ger azday",
"Insert\/edit link": "Ger\/\u1e93reg azday",
"Text to display": "A\u1e0dris ara yettwabeqq\u1e0den",
-"Url": "Url",
+"Url": "URL",
"Target": "Target",
"None": "Ulac",
"New window": "Asfaylu amaynut",
@@ -258,4 +258,4 @@ tinymce.addI18n('kab',{
"Tools": "Ifecka",
"Powered by {0}": "Iteddu s {0} ",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/km_KH.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/km_KH.js
index 5c4b05512664..381d4c4a4615 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/km_KH.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/km_KH.js
@@ -144,7 +144,7 @@ tinymce.addI18n('km_KH',{
"Insert link": "\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u178f\u17c6\u178e",
"Insert\/edit link": "\u1794\u1789\u17d2\u1785\u17bc\u179b\/\u1780\u17c2 \u178f\u17c6\u178e",
"Text to display": "\u17a2\u1780\u17d2\u179f\u179a\u200b\u178f\u17d2\u179a\u17bc\u179c\u200b\u1794\u1784\u17d2\u17a0\u17b6\u1789",
-"Url": "Url",
+"Url": "URL",
"Target": "\u1791\u17b7\u179f\u178a\u17c5",
"None": "\u1798\u17b7\u1793\u200b\u1798\u17b6\u1793",
"New window": "\u1795\u17d2\u1791\u17b6\u17c6\u1784\u200b\u179c\u17b8\u1793\u178a\u17bc\u200b\u1790\u17d2\u1798\u17b8",
@@ -250,4 +250,4 @@ tinymce.addI18n('km_KH',{
"Table": "\u178f\u17b6\u179a\u17b6\u1784",
"Tools": "\u17a7\u1794\u1780\u179a\u178e\u17cd",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u1791\u17b8\u178f\u17b6\u17c6\u1784\u200b\u17a2\u1780\u17d2\u179f\u179a\u200b\u179f\u17c6\u1794\u17bc\u179a\u1794\u17c2\u1794\u17d4 \u1785\u17bb\u1785 ALT-F9 \u179f\u1798\u17d2\u179a\u17b6\u1794\u17cb\u200b\u1798\u17c9\u17ba\u1793\u17bb\u1799\u17d4 \u1785\u17bb\u1785 ALT-F10 \u179f\u1798\u17d2\u179a\u17b6\u1794\u17cb\u200b\u179a\u1794\u17b6\u179a\u200b\u17a7\u1794\u1780\u179a\u178e\u17cd\u17d4 \u1785\u17bb\u1785 ALT-0 \u179f\u1798\u17d2\u179a\u17b6\u1794\u17cb\u200b\u1787\u17c6\u1793\u17bd\u1799\u17d4"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nb_NO.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nb_NO.js
index 59233450b0e6..e6f1df897126 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nb_NO.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nb_NO.js
@@ -150,7 +150,7 @@ tinymce.addI18n('nb_NO',{
"Insert link": "Sett inn lenke",
"Insert\/edit link": "Sett inn\/endre lenke",
"Text to display": "Tekst som skal vises",
-"Url": "Url",
+"Url": "URL",
"Target": "M\u00e5l",
"None": "Ingen",
"New window": "Nytt vindu",
@@ -258,4 +258,4 @@ tinymce.addI18n('nb_NO',{
"Tools": "Verkt\u00f8y",
"Powered by {0}": "Redigert med {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Tekstredigering. Tast ALT-F9 for meny. Tast ALT-F10 for verkt\u00f8ys-rader. Tast ALT-0 for hjelp."
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nl.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nl.js
index c80590b2978c..d8631c9ab6cb 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nl.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nl.js
@@ -150,7 +150,7 @@ tinymce.addI18n('nl',{
"Insert link": "Hyperlink invoegen",
"Insert\/edit link": "Hyperlink invoegen\/bewerken",
"Text to display": "Linktekst",
-"Url": "Url",
+"Url": "URL",
"Target": "Doel",
"None": "Geen",
"New window": "Nieuw venster",
@@ -258,4 +258,4 @@ tinymce.addI18n('nl',{
"Tools": "Gereedschap",
"Powered by {0}": "Gemaakt door {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Druk ALT-F9 voor het menu. Druk ALT-F10 voor de toolbar. Druk ALT-0 voor help."
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_BR.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_BR.js
index 497043a9d4de..2beccd413b87 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_BR.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_BR.js
@@ -150,7 +150,7 @@ tinymce.addI18n('pt_BR',{
"Insert link": "Inserir link",
"Insert\/edit link": "Inserir\/editar link",
"Text to display": "Texto para mostrar",
-"Url": "Url",
+"Url": "URL",
"Target": "Alvo",
"None": "Nenhum",
"New window": "Nova janela",
@@ -258,4 +258,4 @@ tinymce.addI18n('pt_BR',{
"Tools": "Ferramentas",
"Powered by {0}": "Distribu\u00eddo por {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u00c1rea de texto formatado. Pressione ALT-F9 para exibir o menu, ALT-F10 para exibir a barra de ferramentas ou ALT-0 para exibir a ajuda"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ro.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ro.js
index 0a6a2eadf014..a2f3caec805b 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ro.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ro.js
@@ -121,7 +121,7 @@ tinymce.addI18n('ro',{
"Date\/time": "Data\/ora",
"Insert date\/time": "Insereaz\u0103 data\/ora",
"Remove link": "\u0218terge link-ul",
-"Url": "Url",
+"Url": "URL",
"Text to display": "Text de afi\u0219at",
"Anchors": "Ancor\u0103",
"Insert link": "Inserare link",
@@ -227,4 +227,4 @@ tinymce.addI18n('ro',{
"View": "Vezi",
"Table": "Tabel\u0103",
"Format": "Formateaz\u0103"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sk.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sk.js
index 2362a6dbc6ce..5cc085abdc5f 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sk.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sk.js
@@ -144,7 +144,7 @@ tinymce.addI18n('sk',{
"Insert link": "Vlo\u017ei\u0165 odkaz",
"Insert\/edit link": "Vlo\u017ei\u0165\/upravi\u0165 odkaz",
"Text to display": "Zobrazen\u00fd text",
-"Url": "Url",
+"Url": "URL",
"Target": "Cie\u013e",
"None": "\u017diadne",
"New window": "Nov\u00e9 okno",
@@ -250,4 +250,4 @@ tinymce.addI18n('sk',{
"Table": "Tabu\u013eka",
"Tools": "N\u00e1stroje",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Textov\u00e9 pole. Stla\u010dte ALT-F9 pre zobrazenie menu, ALT-F10 pre zobrazenie panela n\u00e1strojov, ALT-0 pre n\u00e1povedu."
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sr.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sr.js
index 9150c2ed8211..01bfad730307 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sr.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sr.js
@@ -150,7 +150,7 @@ tinymce.addI18n('sr',{
"Insert link": "Ubaci vezu",
"Insert\/edit link": "Ubaci\/promeni vezu",
"Text to display": "Tekst za prikaz",
-"Url": "Url",
+"Url": "URL",
"Target": "Meta",
"None": "Ni\u0161ta",
"New window": "Novi prozor",
@@ -258,4 +258,4 @@ tinymce.addI18n('sr',{
"Tools": "Alatke",
"Powered by {0}": "Pokre\u0107e ga {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Oboga\u0107en tekst. Pritisni te ALT-F9 za meni.Pritisnite ALT-F10 za traku sa alatkama.Pritisnite ALT-0 za pomo\u0107"
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv_SE.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv_SE.js
index 83aaaef59d45..b97ea68a18d0 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv_SE.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv_SE.js
@@ -150,7 +150,7 @@ tinymce.addI18n('sv_SE',{
"Insert link": "Infoga l\u00e4nk",
"Insert\/edit link": "Infoga\/redigera l\u00e4nk",
"Text to display": "Text att visa",
-"Url": "Url",
+"Url": "URL",
"Target": "M\u00e5l",
"None": "Ingen",
"New window": "Nytt f\u00f6nster",
@@ -158,8 +158,8 @@ tinymce.addI18n('sv_SE',{
"Anchors": "Bokm\u00e4rken",
"Link": "L\u00e4nk",
"Paste or type a link": "Klistra in eller skriv en l\u00e4nk",
-"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Urlen du angav verkar vara en epost adress. Vill du l\u00e4gga till ett mailto: prefix?",
-"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Urlen du angav verkar vara en extern l\u00e4nk. Vill du l\u00e4gga till http:\/\/ prefixet?",
+"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "URLen du angav verkar vara en epost adress. Vill du l\u00e4gga till ett mailto: prefix?",
+"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "URLen du angav verkar vara en extern l\u00e4nk. Vill du l\u00e4gga till http:\/\/ prefixet?",
"Link list": "L\u00e4nklista",
"Insert video": "Infoga video",
"Insert\/edit video": "Infoga\/redigera video",
@@ -258,4 +258,4 @@ tinymce.addI18n('sv_SE',{
"Tools": "Verktyg",
"Powered by {0}": "Powered by {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Textredigerare. Tryck ALT-F9 f\u00f6r menyn. Tryck ALT-F10 f\u00f6r verktygsrader. Tryck ALT-0 f\u00f6r hj\u00e4lp."
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr.js
index 7b6959640230..3dd22ca25f7c 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr.js
@@ -150,7 +150,7 @@ tinymce.addI18n('tr',{
"Insert link": "Ba\u011flant\u0131 ekle",
"Insert\/edit link": "Ba\u011flant\u0131 ekle\/d\u00fczenle",
"Text to display": "Yaz\u0131y\u0131 g\u00f6r\u00fcnt\u00fcle",
-"Url": "Url",
+"Url": "URL",
"Target": "Hedef",
"None": "Hi\u00e7biri",
"New window": "Yeni pencere",
@@ -258,4 +258,4 @@ tinymce.addI18n('tr',{
"Tools": "Ara\u00e7lar",
"Powered by {0}": "Powered by {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Zengin Metin Alan\u0131. Men\u00fc i\u00e7in ALT-F9 tu\u015funa bas\u0131n\u0131z. Ara\u00e7 \u00e7ubu\u011fu i\u00e7in ALT-F10 tu\u015funa bas\u0131n\u0131z. Yard\u0131m i\u00e7in ALT-0 tu\u015funa bas\u0131n\u0131z."
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr_TR.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr_TR.js
index ea89bc44c325..496fe3dc21d7 100644
--- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr_TR.js
+++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr_TR.js
@@ -150,7 +150,7 @@ tinymce.addI18n('tr_TR',{
"Insert link": "Ba\u011flant\u0131 ekle",
"Insert\/edit link": "Ba\u011flant\u0131 ekle\/d\u00fczenle",
"Text to display": "G\u00f6r\u00fcnen yaz\u0131",
-"Url": "Url",
+"Url": "URL",
"Target": "Hedef",
"None": "Hi\u00e7biri",
"New window": "Yeni pencere",
@@ -258,4 +258,4 @@ tinymce.addI18n('tr_TR',{
"Tools": "Ara\u00e7lar",
"Powered by {0}": "{0} taraf\u0131ndan yap\u0131lm\u0131\u015ft\u0131r ",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Zengin Metin Alan\u0131. Men\u00fc i\u00e7in ALT-F9 k\u0131sayolunu kullan\u0131n. Ara\u00e7 \u00e7ubu\u011fu i\u00e7in ALT-F10 k\u0131sayolunu kullan\u0131n. Yard\u0131m i\u00e7in ALT-0 k\u0131sayolunu kullan\u0131n."
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Black.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Black.woff
deleted file mode 100644
index d1e2579bf85a..000000000000
Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Black.woff and /dev/null differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BlackItalic.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BlackItalic.woff
deleted file mode 100644
index 142c1c9c48d4..000000000000
Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BlackItalic.woff and /dev/null differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Bold.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Bold.woff
deleted file mode 100644
index cdfcbe0fbcc5..000000000000
Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Bold.woff and /dev/null differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BoldItalic.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BoldItalic.woff
deleted file mode 100644
index 3e683fea7c9b..000000000000
Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BoldItalic.woff and /dev/null differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Italic.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Italic.woff
deleted file mode 100644
index d8cf84c8b96e..000000000000
Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Italic.woff and /dev/null differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Light.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Light.woff
deleted file mode 100644
index e7d4278cce83..000000000000
Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Light.woff and /dev/null differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-LightItalic.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-LightItalic.woff
deleted file mode 100644
index bb72fd2200f1..000000000000
Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-LightItalic.woff and /dev/null differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Regular.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Regular.woff
deleted file mode 100644
index bf73a6d9f97e..000000000000
Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Regular.woff and /dev/null differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-adressbook.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-addressbook.svg
similarity index 100%
rename from src/Umbraco.Web.UI.Client/src/assets/icons/icon-adressbook.svg
rename to src/Umbraco.Web.UI.Client/src/assets/icons/icon-addressbook.svg
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js
index 43598593f441..ed20203d46e9 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js
@@ -13,7 +13,10 @@
}
});
- function UmbLoginController($scope, $location, currentUserResource, formHelper, mediaHelper, umbRequestHelper, Upload, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, $q, $route) {
+ function UmbLoginController($scope, $location, currentUserResource, formHelper,
+ mediaHelper, umbRequestHelper, Upload, localizationService,
+ userService, externalLoginInfo, externalLoginInfoService,
+ resetPasswordCodeInfo, $timeout, authResource, $q, $route) {
const vm = this;
@@ -43,7 +46,15 @@
vm.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.canSendRequiredEmail && Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset;
vm.errorMsg = "";
vm.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl;
- vm.externalLoginProviders = externalLoginInfo.providers;
+ vm.externalLoginProviders = externalLoginInfoService.getLoginProviders();
+ vm.externalLoginProviders.forEach(x => {
+ x.customView = externalLoginInfoService.getLoginProviderView(x);
+ // if there are errors set for this specific provider than assign them directly to the model
+ if (externalLoginInfo.errorProvider === x.authType) {
+ x.errors = externalLoginInfo.errors;
+ }
+ });
+ vm.denyLocalLogin = externalLoginInfoService.hasDenyLocalLogin();
vm.externalLoginInfo = externalLoginInfo;
vm.resetPasswordCodeInfo = resetPasswordCodeInfo;
vm.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage;
@@ -62,7 +73,7 @@
vm.setPasswordSubmit = setPasswordSubmit;
vm.labels = {};
localizationService.localizeMany([
- vm.usernameIsEmail ? "general_email" : "general_username",
+ vm.usernameIsEmail ? "general_email" : "general_username",
vm.usernameIsEmail ? "placeholders_email" : "placeholders_usernameHint",
vm.usernameIsEmail ? "placeholders_emptyEmail" : "placeholders_emptyUsername",
"placeholders_emptyPassword"]
@@ -72,9 +83,11 @@
vm.labels.usernameError = data[2];
vm.labels.passwordError = data[3];
});
-
+
vm.twoFactor = {};
+ vm.loginSuccess = loginSuccess;
+
function onInit() {
// Check if it is a new user
@@ -98,11 +111,11 @@
//localize the text
localizationService.localize("errorHandling_errorInPasswordFormat", [
- vm.invitedUserPasswordModel.passwordPolicies.minPasswordLength,
- vm.invitedUserPasswordModel.passwordPolicies.minNonAlphaNumericChars
- ]).then(function (data) {
- vm.invitedUserPasswordModel.passwordPolicyText = data;
- });
+ vm.invitedUserPasswordModel.passwordPolicies.minPasswordLength,
+ vm.invitedUserPasswordModel.passwordPolicies.minNonAlphaNumericChars
+ ]).then(function (data) {
+ vm.invitedUserPasswordModel.passwordPolicyText = data;
+ });
})
]).then(function () {
vm.inviteStep = Number(inviteVal);
@@ -144,12 +157,12 @@
function getStarted() {
$location.search('invite', null);
- if(vm.onLogin) {
+ if (vm.onLogin) {
vm.onLogin();
}
}
- function inviteSavePassword () {
+ function inviteSavePassword() {
if (formHelper.submitForm({ scope: $scope })) {
@@ -197,37 +210,41 @@
SetTitle();
}
+ function loginSuccess() {
+ vm.loginStates.submitButton = "success";
+ userService._retryRequestQueue(true);
+ if (vm.onLogin) {
+ vm.onLogin();
+ }
+ }
+
function loginSubmit() {
-
- if (formHelper.submitForm({ scope: $scope })) {
+
+ if (formHelper.submitForm({ scope: $scope, formCtrl: vm.loginForm })) {
//if the login and password are not empty we need to automatically
// validate them - this is because if there are validation errors on the server
// then the user has to change both username & password to resubmit which isn't ideal,
// so if they're not empty, we'll just make sure to set them to valid.
- if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) {
+ if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) {
vm.loginForm.username.$setValidity('auth', true);
vm.loginForm.password.$setValidity('auth', true);
}
-
+
if (vm.loginForm.$invalid) {
SetTitle();
return;
}
-
+
// make sure that we are returning to the login view.
vm.view = "login";
vm.loginStates.submitButton = "busy";
userService.authenticate(vm.login, vm.password)
- .then(function(data) {
- vm.loginStates.submitButton = "success";
- userService._retryRequestQueue(true);
- if (vm.onLogin) {
- vm.onLogin();
- }
- },
- function(reason) {
+ .then(function (data) {
+ loginSuccess();
+ },
+ function (reason) {
//is Two Factor required?
if (reason.status === 402) {
@@ -249,13 +266,13 @@
//setup a watch for both of the model values changing, if they change
// while the form is invalid, then revalidate them so that the form can
// be submitted again.
- vm.loginForm.username.$viewChangeListeners.push(function() {
+ vm.loginForm.username.$viewChangeListeners.push(function () {
if (vm.loginForm.$invalid) {
vm.loginForm.username.$setValidity('auth', true);
vm.loginForm.password.$setValidity('auth', true);
}
});
- vm.loginForm.password.$viewChangeListeners.push(function() {
+ vm.loginForm.password.$viewChangeListeners.push(function () {
if (vm.loginForm.$invalid) {
vm.loginForm.username.$setValidity('auth', true);
vm.loginForm.password.$setValidity('auth', true);
@@ -460,7 +477,7 @@
case "2fa-login":
title = "Two Factor Authentication";
break;
- }
+ }
$scope.$emit("$changeTitle", title);
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js
index 5e007a7ff4a7..a236e7f5acb6 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js
@@ -86,6 +86,7 @@ Use this directive to render an umbraco button. The directive can be used to gen
bindings: {
action: "&?",
href: "@?",
+ hrefTarget: "@?",
type: "@",
buttonStyle: "@?",
state: "",
@@ -123,6 +124,10 @@ Use this directive to render an umbraco button. The directive can be used to gen
vm.innerState = "init";
vm.generalActions = vm.labelKey === "general_actions";
+ if (!vm.type) {
+ vm.type = "button"; // set the default
+ }
+
vm.buttonLabel = vm.label;
// is this a primary button style (i.e. anything but an 'info' button)?
vm.isPrimaryButtonStyle = vm.buttonStyle && vm.buttonStyle !== 'info';
@@ -166,7 +171,7 @@ Use this directive to render an umbraco button. The directive can be used to gen
vm.innerState = changes.state.currentValue;
}
if (changes.state.currentValue === 'success' || changes.state.currentValue === 'error') {
- // set the state back to 'init' after a success or error
+ // set the state back to 'init' after a success or error
$timeout(function () {
vm.innerState = 'init';
}, 2000);
@@ -191,6 +196,13 @@ Use this directive to render an umbraco button. The directive can be used to gen
setButtonLabel();
}
+ // watch for type changes
+ if(changes.type) {
+ if (!vm.type) {
+ vm.type = "button";// set the default
+ }
+ }
+
}
function clickButton(event) {
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js
index 1e929af6e9bb..d9bbb245f56f 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js
@@ -233,7 +233,7 @@
}
function loadRedirectUrls() {
scope.loadingRedirectUrls = true;
- //check if Redirect Url Management is enabled
+ //check if Redirect URL Management is enabled
redirectUrlsResource.getEnableState().then(function (response) {
scope.urlTrackerDisabled = response.enabled !== true;
if (scope.urlTrackerDisabled === false) {
@@ -314,13 +314,13 @@
}
function updateCurrentUrls() {
- // never show urls for element types (if they happen to have been created in the content tree)
+ // never show URLs for element types (if they happen to have been created in the content tree)
if (scope.node.isElement) {
scope.currentUrls = null;
return;
}
- // find the urls for the currently selected language
+ // find the URLs for the currently selected language
if (scope.node.variants.length > 1) {
// nodes with variants
scope.currentUrls = _.filter(scope.node.urls, (url) => (scope.currentVariant.language && scope.currentVariant.language.culture === url.culture));
@@ -329,7 +329,7 @@
scope.currentUrls = scope.node.urls;
}
- // figure out if multiple cultures apply across the content urls
+ // figure out if multiple cultures apply across the content URLs
scope.currentUrlsHaveMultipleCultures = _.keys(_.groupBy(scope.currentUrls, url => url.culture)).length > 1;
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js
index bb076e5bff3f..47ef460c53fc 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js
@@ -10,19 +10,18 @@ angular.module("umbraco.directives")
}
};
- var enabled = true;
//check if there's a value for the attribute, if there is and it's false then we conditionally don't
//use auto focus.
if (attrs.umbAutoFocus) {
attrs.$observe("umbAutoFocus", function (newVal) {
- enabled = (newVal === "false" || newVal === 0 || newVal === false) ? false : true;
+ var enabled = (newVal === "false" || newVal === 0 || newVal === false) ? false : true;
+ if (enabled) {
+ $timeout(function() {
+ update();
+ });
+ }
});
}
- if (enabled) {
- $timeout(function() {
- update();
- });
- }
};
});
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js
index 792931627575..efbc384cb45a 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js
@@ -75,7 +75,8 @@
labelKey: "@?",
onChange: "&?",
autoFocus: "",
- preventSubmitOnEnter: ""
+ preventSubmitOnEnter: "",
+ cssClass: "@?"
}
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbcontrolgroup.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbcontrolgroup.directive.js
index 6717b2306150..a52189a0a834 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbcontrolgroup.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/html/umbcontrolgroup.directive.js
@@ -2,7 +2,16 @@
* @ngdoc directive
* @name umbraco.directives.directive:umbControlGroup
* @restrict E
+
+@param {string=} label The label for the control group field.
+@param {string=} description The description for the control group field.
+@param {boolean=} hideLabel Set to true
to hide the label.
+@param {string=} alias The alias of the field within the control group.
+@param {string=} labelFor The alias of the field that the label is for, used for validation.
+@param {boolean=} required Set to true
to mark the field as required.
+
**/
+
angular.module("umbraco.directives.html")
.directive('umbControlGroup', function (localizationService) {
return {
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js
index 2f682205ad84..1dcccda481c6 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirmaction.directive.js
@@ -14,10 +14,10 @@ The prompt can be opened in four direction up, down, left or right.
+
@@ -71,23 +71,17 @@ The prompt can be opened in four direction up, down, left or right.
function link(scope, el, attr, ctrl) {
- scope.clickButton = function (event) {
- if(scope.onDelete) {
- scope.onDelete({$event: event});
- }
- }
+ scope.clickConfirm = function() {
+ if(scope.onConfirm) {
+ scope.onConfirm();
+ }
+ };
- scope.clickConfirm = function() {
- if(scope.onConfirm) {
- scope.onConfirm();
- }
- };
-
- scope.clickCancel = function() {
- if(scope.onCancel) {
- scope.onCancel();
- }
- };
+ scope.clickCancel = function() {
+ if(scope.onCancel) {
+ scope.onCancel();
+ }
+ };
}
@@ -97,8 +91,6 @@ The prompt can be opened in four direction up, down, left or right.
templateUrl: 'views/components/umb-confirm-action.html',
scope: {
direction: "@",
- show: "<",
- onDelete: "&?",
onConfirm: "&",
onCancel: "&"
},
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbicon.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbicon.directive.js
index 517776388b91..87d976f6d946 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbicon.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbicon.directive.js
@@ -15,16 +15,7 @@ Simple icon
Icon with additional attribute. It can be treated like any other dom element
-
-
-
-Manual svg string
-This format is only used in the iconpicker.html
-
-
-
+
@example
**/
@@ -43,13 +34,19 @@ This format is only used in the iconpicker.html
svgString: "=?"
},
- link: function (scope) {
-
+ link: function (scope, element) {
if (scope.svgString === undefined && scope.svgString !== null && scope.icon !== undefined && scope.icon !== null) {
- var icon = scope.icon.split(" ")[0]; // Ensure that only the first part of the icon is used as sometimes the color is added too, e.g. see umbeditorheader.directive scope.openIconPicker
+ const observer = new IntersectionObserver(_lazyRequestIcon, {rootMargin: "100px"});
+ const iconEl = element[0];
- _requestIcon(icon);
+ observer.observe(iconEl);
+
+ // make sure to disconnect the observer when the scope is destroyed
+ scope.$on('$destroy', function () {
+ observer.disconnect();
+ });
}
+
scope.$watch("icon", function (newValue, oldValue) {
if (newValue && oldValue) {
var newicon = newValue.split(" ")[0];
@@ -61,6 +58,17 @@ This format is only used in the iconpicker.html
}
});
+ function _lazyRequestIcon(entries, observer) {
+ entries.forEach(entry => {
+ if (entry.isIntersecting === true) {
+ observer.disconnect();
+
+ var icon = scope.icon.split(" ")[0]; // Ensure that only the first part of the icon is used as sometimes the color is added too, e.g. see umbeditorheader.directive scope.openIconPicker
+ _requestIcon(icon);
+ }
+ });
+ }
+
function _requestIcon(icon) {
// Reset svg string before requesting new icon.
scope.svgString = null;
diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js
index 69a4fe35c915..6b2d993fd526 100644
--- a/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js
+++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js
@@ -6,6 +6,7 @@ angular.module('umbraco.interceptors', [])
$httpProvider.interceptors.push('securityInterceptor');
$httpProvider.interceptors.push('debugRequestInterceptor');
+ $httpProvider.interceptors.push('requiredHeadersInterceptor');
$httpProvider.interceptors.push('doNotPostDollarVariablesOnPostRequestInterceptor');
$httpProvider.interceptors.push('cultureRequestInterceptor');
diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js
new file mode 100644
index 000000000000..3d96b86acd60
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js
@@ -0,0 +1,28 @@
+(function () {
+ 'use strict';
+
+ /**
+ * Used to set required headers on all requests where necessary
+ * @param {any} $q
+ * @param {any} urlHelper
+ */
+ function requiredHeadersInterceptor($q, urlHelper) {
+ return {
+ //dealing with requests:
+ 'request': function (config) {
+
+ // This is a standard header that should be sent for all ajax requests and is required for
+ // how the server handles auth rejections, etc... see
+ // https://github.com/aspnet/AspNetKatana/blob/e2b18ec84ceab7ffa29d80d89429c9988ab40144/src/Microsoft.Owin.Security.Cookies/Provider/DefaultBehavior.cs
+ // https://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/
+ config.headers["X-Requested-With"] = "XMLHttpRequest";
+
+ return config;
+ }
+ };
+ }
+
+ angular.module('umbraco.interceptors').factory('requiredHeadersInterceptor', requiredHeadersInterceptor);
+
+
+})();
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js
index dfa0eae2973a..12e1144acceb 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js
@@ -51,7 +51,7 @@
} else {
// lets crawl through all properties of layout to make sure get captured all `contentUdi` and `settingsUdi` properties.
var propType = typeof obj[k];
- if(propType === "object" || propType === "array") {
+ if(propType != null && (propType === "object" || propType === "array")) {
replaceUdisOfObject(obj[k], propValue)
}
}
@@ -65,7 +65,7 @@
function replaceRawBlockListUDIsResolver(value, propClearingMethod) {
- if (typeof value === "object") {
+ if (value != null && typeof value === "object") {
// we got an object, and it has these three props then we are most likely dealing with a Block Editor.
if ((value.layout !== undefined && value.contentData !== undefined && value.settingsData !== undefined)) {
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js
index 868b8baba757..77ed357c35b0 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js
@@ -425,7 +425,11 @@
if (self.scaffolds) {
self.scaffolds.push(formatScaffoldData(scaffold));
}
- }));
+ }).catch(
+ () => {
+ // Do nothing if we get an error.
+ }
+ ));
});
return $q.all(tasks);
@@ -439,7 +443,14 @@
* @return {Array} array of strings representing alias.
*/
getAvailableAliasesForBlockContent: function () {
- return this.blockConfigurations.map(blockConfiguration => this.getScaffoldFromKey(blockConfiguration.contentElementTypeKey).contentTypeAlias);
+ return this.blockConfigurations.map(
+ (blockConfiguration) => {
+ var scaffold = this.getScaffoldFromKey(blockConfiguration.contentElementTypeKey);
+ if (scaffold) {
+ return scaffold.contentTypeAlias;
+ }
+ }
+ );
},
/**
@@ -519,7 +530,7 @@
var dataModel = getDataByUdi(contentUdi, this.value.contentData);
if (dataModel === null) {
- console.error("Couldn't find content model of " + contentUdi)
+ console.error("Couldn't find content data of " + contentUdi)
return null;
}
@@ -591,7 +602,7 @@
var settingsData = getDataByUdi(settingsUdi, this.value.settingsData);
if (settingsData === null) {
- console.error("Couldnt find content settings data of " + settingsUdi)
+ console.error("Couldnt find settings data of " + settingsUdi)
return null;
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js b/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js
new file mode 100644
index 000000000000..1d2048b2f5e8
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js
@@ -0,0 +1,67 @@
+/**
+ * @ngdoc service
+ * @name umbraco.services.externalLoginInfoService
+ * @description A service for working with external login providers
+ **/
+function externalLoginInfoService(externalLoginInfo, umbRequestHelper) {
+
+ function getLoginProvider(provider) {
+ if (provider) {
+ var found = _.find(externalLoginInfo.providers, x => x.authType == provider);
+ return found;
+ }
+ return null;
+ }
+
+ function getLoginProviderView(provider) {
+ if (provider && provider.properties.UmbracoBackOfficeExternalLoginOptions && provider.properties.UmbracoBackOfficeExternalLoginOptions.CustomBackOfficeView) {
+ return umbRequestHelper.convertVirtualToAbsolutePath(provider.properties.UmbracoBackOfficeExternalLoginOptions.CustomBackOfficeView);
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if any provider denies local login if `provider` is null, else whether the passed
+ * @param {any} provider
+ */
+ function hasDenyLocalLogin(provider) {
+ if (!provider) {
+ return _.some(externalLoginInfo.providers, x => x.properties.UmbracoBackOfficeExternalLoginOptions.DenyLocalLogin === true);
+ }
+ else {
+ return provider.properties.UmbracoBackOfficeExternalLoginOptions.DenyLocalLogin;
+ }
+ }
+
+ /**
+ * Returns all login providers
+ */
+ function getLoginProviders() {
+ return externalLoginInfo.providers;
+ }
+
+ /** Returns all logins providers that have options that the user can interact with */
+ function getLoginProvidersWithOptions() {
+ // only include providers that allow manual linking or ones that provide a custom view
+ var providers = _.filter(externalLoginInfo.providers, x => {
+ // transform the data and also include the custom view as a nicer property
+ x.customView = getLoginProviderView(x);
+ if (x.customView) {
+ return true;
+ }
+ else {
+ return x.properties.ExternalSignInAutoLinkOptions.AllowManualLinking;
+ }
+ });
+ return providers;
+ }
+
+ return {
+ hasDenyLocalLogin: hasDenyLocalLogin,
+ getLoginProvider: getLoginProvider,
+ getLoginProviders: getLoginProviders,
+ getLoginProvidersWithOptions: getLoginProvidersWithOptions,
+ getLoginProviderView: getLoginProviderView
+ };
+}
+angular.module('umbraco.services').factory('externalLoginInfoService', externalLoginInfoService);
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js
index 731cc5ed368c..8df5a9ce8c56 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js
@@ -464,7 +464,7 @@ function serverValidationManager($timeout) {
* @ngdoc function
* @name addErrorsForModelState
* @methodOf umbraco.services.serverValidationManager
- * @param {any} modelState
+ * @param {any} modelState the modelState object
* @param {any} parentValidationPath optional parameter specifying a nested element's UDI for which this property belongs (for complex editors)
* @description
* This wires up all of the server validation model state so that valServer and valServerField directives work
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js
index de6fbaf782fb..00871caab1b3 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js
@@ -1,5 +1,5 @@
angular.module('umbraco.services')
- .factory('userService', function ($rootScope, eventsService, $q, $location, requestRetryQueue, authResource, emailMarketingResource, $timeout, angularHelper) {
+ .factory('userService', function ($rootScope, eventsService, $q, $location, $window, requestRetryQueue, authResource, emailMarketingResource, $timeout, angularHelper) {
var currentUser = null;
var lastUserId = null;
@@ -166,7 +166,7 @@ angular.module('umbraco.services')
},
/** Internal method to retry all request after sucessfull login */
- _retryRequestQueue: function(success) {
+ _retryRequestQueue: function (success) {
retryRequestQueue(success)
},
@@ -185,18 +185,22 @@ angular.module('umbraco.services')
authenticate: function (login, password) {
return authResource.performLogin(login, password)
- .then(function(data) {
+ .then(function (data) {
// Check if user has a start node set.
- if(data.startContentIds.length === 0 && data.startMediaIds.length === 0){
+ if (data.startContentIds.length === 0 && data.startMediaIds.length === 0) {
var errorMsg = "User has no start-nodes";
var result = { errorMsg: errorMsg, user: data, authenticated: false, lastUserId: lastUserId, loginType: "credentials" };
eventsService.emit("app.notAuthenticated", result);
+ // TODO: How does this make sense? How can you throw from a promise? Does this get caught by the rejection?
+ // If so then return $q.reject should be used.
throw result;
}
-
+
return data;
-
+
+ }, function (err) {
+ return $q.reject(err);
}).then(this.setAuthenticationSuccessful);
},
setAuthenticationSuccessful: function (data) {
@@ -218,8 +222,14 @@ angular.module('umbraco.services')
return authResource.performLogout()
.then(function (data) {
userAuthExpired();
- //done!
- return null;
+
+ if (data && data.signOutRedirectUrl) {
+ $window.location.replace(data.signOutRedirectUrl);
+ }
+ else {
+ //done!
+ return null;
+ }
});
},
@@ -235,9 +245,9 @@ angular.module('umbraco.services')
setCurrentUser(data);
deferred.resolve(currentUser);
- }, function () {
+ }, function (err) {
//it failed, so they are not logged in
- deferred.reject();
+ deferred.reject(err);
});
return deferred.promise;
@@ -245,7 +255,7 @@ angular.module('umbraco.services')
/** Returns the current user object in a promise */
getCurrentUser: function (args) {
-
+
if (!currentUser) {
return authResource.getCurrentUser()
.then(function (data) {
@@ -260,9 +270,9 @@ angular.module('umbraco.services')
setCurrentUser(data);
return $q.when(currentUser);
- }, function () {
+ }, function (err) {
//it failed, so they are not logged in
- return $q.reject(currentUser);
+ return $q.reject(err);
});
}
diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less
index 01e595ac24d3..359c3dd427ce 100644
--- a/src/Umbraco.Web.UI.Client/src/less/belle.less
+++ b/src/Umbraco.Web.UI.Client/src/less/belle.less
@@ -177,6 +177,7 @@
@import "components/umb-range-slider.less";
@import "components/umb-number.less";
@import "components/umb-tags-editor.less";
+@import "components/umb-search-filter.less";
@import "components/buttons/umb-button.less";
@import "components/buttons/umb-button-group.less";
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/card.less b/src/Umbraco.Web.UI.Client/src/less/components/card.less
index 017468fa0ce6..a1a4b4bc5ed9 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/card.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less
@@ -159,6 +159,7 @@
justify-content: center;
flex-direction: column;
background-color: transparent;
+ word-break: break-word;
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less
index d79bbbde202c..f11630b36340 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less
@@ -8,45 +8,88 @@
border-radius: 100px;
}
+.umb-badge__count {
+ display: flex;
+ width: 1rem;
+ height: 1rem;
+ line-height: 1;
+ justify-content: center;
+ align-items: center;
+ border-radius: 50%;
+ font-size: 12px;
+}
+
// Colors
.umb-badge--primary {
background-color: @blueDark;
color: @white;
+
+ .umb-badge__count {
+ background-color: darken(@blueDark, 5%);
+ }
}
.umb-badge--secondary {
background-color: @blueExtraDark;
color: @white;
+
+ .umb-badge__count {
+ background-color: darken(@blueExtraDark, 8%);
+ }
}
.umb-badge--gray {
background-color: @sand-2;
color: @blueDark;
+
+ .umb-badge__count {
+ background-color: darken(@sand-2, 8%);
+ }
}
.umb-badge--danger {
background-color: @red;
color: @white;
+
+ .umb-badge__count {
+ background-color: darken(@red, 8%);
+ }
}
.umb-badge--info {
background-color: @blue;
color: @white;
+
+ .umb-badge__count {
+ background-color: darken(@blue, 8%);
+ }
}
.umb-badge--warning {
background-color: @orange;
color: @white;
+
+ .umb-badge__count {
+ background-color: darken(@orange, 8%);
+ }
}
.umb-badge--success {
background-color: @green;
color: @white;
+
+ .umb-badge__count {
+ background-color: darken(@green, 8%);
+ }
}
.umb-badge--dark {
background-color: @grayDark;
color: @white;
+
+ .umb-badge__count {
+ background-color: darken(@grayDark, 8%);
+ }
}
// Size
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less
index a6548123ac7f..112194f012ee 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less
@@ -1,8 +1,3 @@
-//WRAPPER
-.umb_confirm-action {
- display: inline-block;
-}
-
// OVERLAY
.umb_confirm-action__overlay {
position: absolute;
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less
index cff9980483e4..9567840049f9 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less
@@ -97,7 +97,8 @@
height: 20px;
width: 20px;
position: absolute;
- top: -1px;
+ top: 0;
+
}
&__check {
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-search-filter.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-search-filter.less
new file mode 100644
index 000000000000..bda9fa7a7e88
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-search-filter.less
@@ -0,0 +1,29 @@
+html .umb-search-filter {
+ position: relative;
+ height: 30px;
+ width: 190px;
+
+ &.w-100 {
+ width: 100%;
+ }
+
+ &.mb-15 {
+ margin-bottom: 15px;
+ }
+
+ &__input {
+ padding-left: 30px;
+ padding-right: 6px;
+ width: inherit;
+ margin: 0;
+ }
+
+ .icon-search {
+ color: #d8d7d9;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 10px;
+ margin: auto 0;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/less/fonts.less b/src/Umbraco.Web.UI.Client/src/less/fonts.less
index 73bc36037f80..e9a84656f9ec 100644
--- a/src/Umbraco.Web.UI.Client/src/less/fonts.less
+++ b/src/Umbraco.Web.UI.Client/src/less/fonts.less
@@ -15,8 +15,7 @@
font-family: 'Lato';
src: local('LatoLatin Black'),
local('LatoLatin-Black'),
- url('@{latoPath}/LatoLatin-Black.woff2') format('woff2'), /* Super Modern Browsers */
- url('@{latoPath}/LatoLatin-Black.woff') format('woff'); /* Modern Browsers */
+ url('@{latoPath}/LatoLatin-Black.woff2') format('woff2');
font-style: normal;
font-display: swap;
font-weight: 900;
@@ -28,8 +27,7 @@
font-family: 'Lato';
src: local('LatoLatin BlackItalic'),
local('LatoLatin-BlackItalic'),
- url('@{latoPath}/LatoLatin-BlackItalic.woff2') format('woff2'), /* Super Modern Browsers */
- url('@{latoPath}/LatoLatin-BlackItalic.woff') format('woff'); /* Modern Browsers */
+ url('@{latoPath}/LatoLatin-BlackItalic.woff2') format('woff2');
font-style: italic;
font-weight: 900;
font-display: swap;
@@ -41,8 +39,7 @@
font-family: 'Lato';
src: local('LatoLatin Bold'),
local('LatoLatin-Bold'),
- url('@{latoPath}/LatoLatin-Bold.woff2') format('woff2'), /* Super Modern Browsers */
- url('@{latoPath}/LatoLatin-Bold.woff') format('woff'); /* Modern Browsers */
+ url('@{latoPath}/LatoLatin-Bold.woff2') format('woff2');
font-style: normal;
font-weight: 700;
font-display: swap;
@@ -54,8 +51,7 @@
font-family: 'Lato';
src: local('LatoLatin BoldItalic'),
local('LatoLatin-BoldItalic'),
- url('@{latoPath}/LatoLatin-BoldItalic.woff2') format('woff2'), /* Super Modern Browsers */
- url('@{latoPath}/LatoLatin-BoldItalic.woff') format('woff'); /* Modern Browsers */
+ url('@{latoPath}/LatoLatin-BoldItalic.woff2') format('woff2');
font-style: italic;
font-weight: 700;
font-display: swap;
@@ -67,8 +63,7 @@
font-family: 'Lato';
src: local('LatoLatin Italic'),
local('LatoLatin-Italic'),
- url('@{latoPath}/LatoLatin-Italic.woff2') format('woff2'), /* Super Modern Browsers */
- url('@{latoPath}/LatoLatin-Italic.woff') format('woff'); /* Modern Browsers */
+ url('@{latoPath}/LatoLatin-Italic.woff2') format('woff2');
font-style: italic;
font-weight: 400;
font-display: swap;
@@ -80,8 +75,7 @@
font-family: 'Lato';
src: local('LatoLatin Regular'),
local('LatoLatin-Regular'),
- url('@{latoPath}/LatoLatin-Regular.woff2') format('woff2'), /* Super Modern Browsers */
- url('@{latoPath}/LatoLatin-Regular.woff') format('woff'); /* Modern Browsers */
+ url('@{latoPath}/LatoLatin-Regular.woff2') format('woff2');
font-style: normal;
font-weight: 400;
font-display: swap;
@@ -93,8 +87,7 @@
font-family: 'Lato';
src: local('LatoLatin Light'),
local('LatoLatin-Light'),
- url('@{latoPath}/LatoLatin-Light.woff2') format('woff2'), /* Super Modern Browsers */
- url('@{latoPath}/LatoLatin-Light.woff') format('woff'); /* Modern Browsers */
+ url('@{latoPath}/LatoLatin-Light.woff2') format('woff2');
font-style: normal;
font-weight: 300;
font-display: swap;
@@ -106,8 +99,7 @@
font-family: 'Lato';
src: local('LatoLatin LightItalic'),
local('LatoLatin-LightItalic'),
- url('@{latoPath}/LatoLatin-LightItalic.woff2') format('woff2'), /* Super Modern Browsers */
- url('@{latoPath}/LatoLatin-LightItalic.woff') format('woff'); /* Modern Browsers */
+ url('@{latoPath}/LatoLatin-LightItalic.woff2') format('woff2');
font-style: italic;
font-weight: 300;
font-display: swap;
diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less
index 3bf00fb25ce0..e1e368f2e2c8 100644
--- a/src/Umbraco.Web.UI.Client/src/less/main.less
+++ b/src/Umbraco.Web.UI.Client/src/less/main.less
@@ -208,6 +208,13 @@ umb-property:last-of-type .umb-control-group {
.control-description {
display: block;
clear: both;
+ overflow-wrap: break-word;
+ }
+
+ &::after {
+ content: '';
+ display: block;
+ clear: both;
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html
index 6dea4debb61d..4b08d4e5fce6 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html
@@ -16,7 +16,7 @@