Merge branch 'netcore/dev' into netcore/feature/untangle-membership

# Conflicts:
#	src/Umbraco.Web/Runtime/WebInitialComposer.cs
This commit is contained in:
Bjarke Berg
2020-03-02 13:19:38 +01:00
118 changed files with 726 additions and 517 deletions

View File

@@ -1,4 +1,5 @@
using System.Configuration;
using Umbraco.Configuration;
using Umbraco.Core.Configuration.HealthChecks;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
@@ -10,6 +11,7 @@ namespace Umbraco.Core.Configuration
public IHostingSettings HostingSettings { get; } = new HostingSettings();
public ICoreDebug CoreDebug { get; } = new CoreDebug();
public IMachineKeyConfig MachineKeyConfig { get; } = new MachineKeyConfig();
public IUmbracoSettingsSection UmbracoSettings { get; }
@@ -27,6 +29,7 @@ namespace Umbraco.Core.Configuration
configs.AddPasswordConfigurations();
configs.Add(() => CoreDebug);
configs.Add(() => MachineKeyConfig);
configs.Add<IConnectionStrings>(() => new ConnectionStrings(ioHelper));
configs.AddCoreConfigs(ioHelper);
return configs;

View File

@@ -0,0 +1,20 @@
using System.Configuration;
using Umbraco.Core.Configuration;
namespace Umbraco.Configuration
{
public class MachineKeyConfig : IMachineKeyConfig
{
//TODO all the machineKey stuff should be replaced: https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/compatibility/replacing-machinekey?view=aspnetcore-3.1
public bool HasMachineKey
{
get
{
var machineKeySection =
ConfigurationManager.GetSection("system.web/machineKey") as ConfigurationSection;
return !(machineKeySection?.ElementInformation?.Source is null);
}
}
}
}

View File

@@ -53,7 +53,7 @@ namespace Umbraco.Web.Cache
{
macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey<IMacro>(payload.Id));
}
};
}
base.Refresh(json);
}

View File

@@ -126,7 +126,7 @@ namespace Umbraco.Core.Collections
if (_items.TryGetValue(key, out value))
yield return value;
else if (throwOnMissing)
throw new Exception(MissingDependencyError);
throw new Exception($"{MissingDependencyError} Error in type {typeof(TItem).Name}, with key {key}");
}
}
}

View File

@@ -79,9 +79,12 @@ namespace Umbraco.Core.Composing
foreach (var type in types)
EnsureType(type, "register");
// register them
// register them - ensuring that each item is registered with the same lifetime as the collection.
// NOTE: Previously each one was not registered with the same lifetime which would mean that if there
// was a dependency on an individual item, it would resolve a brand new transient instance which isn't what
// we would expect to happen. The same item should be resolved from the container as the collection.
foreach (var type in types)
register.Register(type);
register.Register(type, CollectionLifetime);
_registeredTypes = types;
}

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Core.Configuration
{
public interface IMachineKeyConfig
{
bool HasMachineKey { get;}
}
}

View File

@@ -41,7 +41,7 @@ namespace Umbraco.Core
public void AddDateTime(DateTime d)
{
_writer.Write(d.Ticks);;
_writer.Write(d.Ticks);
}
public void AddString(string s)

View File

@@ -22,11 +22,6 @@ namespace Umbraco.Core.Hosting
string MapPath(string path);
string ToAbsolute(string virtualPath, string root);
/// <summary>
/// Terminates the current application. The application restarts the next time a request is received for it.
/// </summary>
void LazyRestartApplication();
void RegisterObject(IRegisteredObject registeredObject);
void UnregisterObject(IRegisteredObject registeredObject);
}

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Net;
namespace Umbraco.Core.Manifest
{
@@ -13,13 +14,13 @@ namespace Umbraco.Core.Manifest
private static volatile bool _isRestarting;
private readonly ILogger _logger;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
private readonly List<FileSystemWatcher> _fws = new List<FileSystemWatcher>();
public ManifestWatcher(ILogger logger, IHostingEnvironment hostingEnvironment)
public ManifestWatcher(ILogger logger, IUmbracoApplicationLifetime umbracoApplicationLifetime)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_hostingEnvironment = hostingEnvironment;
_umbracoApplicationLifetime = umbracoApplicationLifetime;
}
public void Start(params string[] packageFolders)
@@ -57,7 +58,7 @@ namespace Umbraco.Core.Manifest
_isRestarting = true;
_logger.Info<ManifestWatcher>("Manifest has changed, app pool is restarting ({Path})", e.FullPath);
_hostingEnvironment.LazyRestartApplication();
_umbracoApplicationLifetime.Restart();
Dispose(); // uh? if the app restarts then this should be disposed anyways?
}
}

View File

@@ -6,7 +6,7 @@ namespace Umbraco.Web.Models
/// <summary>
/// The model used when rendering Partial View Macros
/// </summary>
public class PartialViewMacroModel
public class PartialViewMacroModel : IContentModel
{
public PartialViewMacroModel(IPublishedContent page,

View File

@@ -0,0 +1,14 @@
namespace Umbraco.Net
{
public interface IUmbracoApplicationLifetime
{
/// <summary>
/// A value indicating whether the application is restarting after the current request.
/// </summary>
bool IsRestarting { get; }
/// <summary>
/// Terminates the current application. The application restarts the next time a request is received for it.
/// </summary>
void Restart();
}
}

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Net
{
public interface IUserAgentProvider
{
string GetUserAgent();
}
}

View File

@@ -7,7 +7,7 @@ using Umbraco.Core.Services;
namespace Umbraco.Core.Packaging
{
public class ConflictingPackageData
public class ConflictingPackageData
{
private readonly IMacroService _macroService;
private readonly IFileService _fileService;
@@ -23,7 +23,7 @@ namespace Umbraco.Core.Packaging
return stylesheetNodes
.Select(n =>
{
var xElement = n.Element("Name") ?? n.Element("name"); ;
var xElement = n.Element("Name") ?? n.Element("name");
if (xElement == null)
throw new FormatException("Missing \"Name\" element");

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Core.Models.Packaging
/// <remarks>
/// This is used only for conversions and will not 'get' a PackageDefinition from the repository with a valid ID
/// </remarks>
internal static PackageDefinition FromCompiledPackage(CompiledPackage compiled)
public static PackageDefinition FromCompiledPackage(CompiledPackage compiled)
{
return new PackageDefinition
{

View File

@@ -90,7 +90,7 @@ namespace Umbraco.Core.Packaging
{
var packagesXml = EnsureStorage(out _);
if (packagesXml?.Root == null)
yield break;;
yield break;
foreach (var packageXml in packagesXml.Root.Elements("package"))
yield return _parser.ToPackageDefinition(packageXml);
@@ -518,7 +518,6 @@ namespace Umbraco.Core.Packaging
private XElement GetStylesheetXml(string name, bool includeProperties)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name));
;
var sts = _fileService.GetStylesheetByName(name);
if (sts == null) return null;
var stylesheetXml = new XElement("Stylesheet");

View File

@@ -51,7 +51,10 @@ namespace Umbraco.Core.Services
IEnumerable<TItem> GetComposedOf(int id); // composition axis
IEnumerable<TItem> GetChildren(int id);
IEnumerable<TItem> GetChildren(Guid id);
bool HasChildren(int id);
bool HasChildren(Guid id);
void Save(TItem item, int userId = Constants.Security.SuperUserId);
void Save(IEnumerable<TItem> items, int userId = Constants.Security.SuperUserId);

View File

@@ -0,0 +1,19 @@
using System;
using Umbraco.Web;
namespace Umbraco.Core
{
public static class UmbracoContextAccessorExtensions
{
public static IUmbracoContext GetRequiredUmbracoContext(this IUmbracoContextAccessor umbracoContextAccessor)
{
if (umbracoContextAccessor == null) throw new ArgumentNullException(nameof(umbracoContextAccessor));
var umbracoContext = umbracoContextAccessor.UmbracoContext;
if(umbracoContext is null) throw new InvalidOperationException("UmbracoContext is null");
return umbracoContext;
}
}
}

View File

@@ -336,7 +336,7 @@ namespace Umbraco.Core.Xml
var child = parent.SelectSingleNode(name);
if (child != null)
{
child.InnerXml = "<![CDATA[" + value + "]]>"; ;
child.InnerXml = "<![CDATA[" + value + "]]>";
return child;
}
return AddCDataNode(xd, name, value);

View File

@@ -4,6 +4,7 @@ using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Manifest;
using Umbraco.Net;
namespace Umbraco.Core.Compose
{
@@ -12,18 +13,18 @@ namespace Umbraco.Core.Compose
private readonly IRuntimeState _runtimeState;
private readonly ILogger _logger;
private readonly IIOHelper _ioHelper;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
// if configured and in debug mode, a ManifestWatcher watches App_Plugins folders for
// package.manifest chances and restarts the application on any change
private ManifestWatcher _mw;
public ManifestWatcherComponent(IRuntimeState runtimeState, ILogger logger, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment)
public ManifestWatcherComponent(IRuntimeState runtimeState, ILogger logger, IIOHelper ioHelper, IUmbracoApplicationLifetime umbracoApplicationLifetime)
{
_runtimeState = runtimeState;
_logger = logger;
_ioHelper = ioHelper;
_hostingEnvironment = hostingEnvironment;
_umbracoApplicationLifetime = umbracoApplicationLifetime;
}
public void Initialize()
@@ -36,7 +37,7 @@ namespace Umbraco.Core.Compose
var appPlugins = _ioHelper.MapPath("~/App_Plugins/");
if (Directory.Exists(appPlugins) == false) return;
_mw = new ManifestWatcher(_logger, _hostingEnvironment);
_mw = new ManifestWatcher(_logger, _umbracoApplicationLifetime);
_mw.Start(Directory.GetDirectories(appPlugins));
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Cookie;
@@ -11,9 +10,9 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Serialization;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Net;
using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install
@@ -22,25 +21,28 @@ namespace Umbraco.Web.Install
{
private static HttpClient _httpClient;
private readonly DatabaseBuilder _databaseBuilder;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger _logger;
private readonly IGlobalSettings _globalSettings;
private readonly IUmbracoVersion _umbracoVersion;
private readonly IConnectionStrings _connectionStrings;
private readonly IInstallationService _installationService;
private readonly ICookieManager _cookieManager;
private readonly IUserAgentProvider _userAgentProvider;
private readonly IUmbracoDatabaseFactory _umbracoDatabaseFactory;
private readonly IJsonSerializer _jsonSerializer;
private InstallationType? _installationType;
public InstallHelper(IHttpContextAccessor httpContextAccessor,
DatabaseBuilder databaseBuilder,
public InstallHelper(DatabaseBuilder databaseBuilder,
ILogger logger,
IGlobalSettings globalSettings,
IUmbracoVersion umbracoVersion,
IConnectionStrings connectionStrings,
IInstallationService installationService,
ICookieManager cookieManager)
ICookieManager cookieManager,
IUserAgentProvider userAgentProvider,
IUmbracoDatabaseFactory umbracoDatabaseFactory,
IJsonSerializer jsonSerializer)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
_globalSettings = globalSettings;
_umbracoVersion = umbracoVersion;
@@ -48,6 +50,9 @@ namespace Umbraco.Web.Install
_connectionStrings = connectionStrings ?? throw new ArgumentNullException(nameof(connectionStrings));
_installationService = installationService;
_cookieManager = cookieManager;
_userAgentProvider = userAgentProvider;
_umbracoDatabaseFactory = umbracoDatabaseFactory;
_jsonSerializer = jsonSerializer;
}
public InstallationType GetInstallationType()
@@ -57,11 +62,9 @@ namespace Umbraco.Web.Install
public async Task InstallStatus(bool isCompleted, string errorMsg)
{
var httpContext = _httpContextAccessor.GetRequiredHttpContext();
try
{
var userAgent = httpContext.Request.UserAgent;
var userAgent = _userAgentProvider.GetUserAgent();
// Check for current install Id
var installId = Guid.NewGuid();
@@ -88,7 +91,7 @@ namespace Umbraco.Web.Install
{
// we don't have DatabaseProvider anymore... doing it differently
//dbProvider = ApplicationContext.Current.DatabaseContext.DatabaseProvider.ToString();
dbProvider = GetDbProviderString(Current.SqlContext);
dbProvider = _umbracoDatabaseFactory.SqlContext.SqlSyntax.DbProvider;
}
var installLog = new InstallLog(installId: installId, isUpgrade: IsBrandNewInstall == false,
@@ -105,23 +108,6 @@ namespace Umbraco.Web.Install
}
}
internal static string GetDbProviderString(ISqlContext sqlContext)
{
var dbProvider = string.Empty;
// we don't have DatabaseProvider anymore...
//dbProvider = ApplicationContext.Current.DatabaseContext.DatabaseProvider.ToString();
//
// doing it differently
var syntax = sqlContext.SqlSyntax;
if (syntax is SqlCeSyntaxProvider)
dbProvider = "SqlServerCE";
else if (syntax is SqlServerSyntaxProvider)
dbProvider = (syntax as SqlServerSyntaxProvider).ServerVersion.IsAzure ? "SqlAzure" : "SqlServer";
return dbProvider;
}
/// <summary>
/// Checks if this is a brand new install meaning that there is no configured version and there is no configured database connection
/// </summary>
@@ -162,7 +148,10 @@ namespace Umbraco.Web.Install
using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
{
var response = _httpClient.SendAsync(request).Result;
packages = response.Content.ReadAsAsync<IEnumerable<Package>>().Result.ToList();
var json = response.Content.ReadAsStringAsync().Result;
packages = _jsonSerializer.Deserialize<IEnumerable<Package>>(json).ToList();
}
}
catch (AggregateException ex)

View File

@@ -1,7 +1,7 @@
using System.Linq;
using System.Threading.Tasks;
using System.Web.Configuration;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Security;
using Umbraco.Web.Install.Models;
@@ -12,13 +12,15 @@ namespace Umbraco.Web.Install.InstallSteps
"ConfigureMachineKey", "machinekey", 2,
"Updating some security settings...",
PerformsAppRestart = true)]
internal class ConfigureMachineKey : InstallSetupStep<bool?>
public class ConfigureMachineKey : InstallSetupStep<bool?>
{
private readonly IIOHelper _ioHelper;
private readonly IMachineKeyConfig _machineKeyConfig;
public ConfigureMachineKey(IIOHelper ioHelper)
public ConfigureMachineKey(IIOHelper ioHelper, IMachineKeyConfig machineKeyConfig)
{
_ioHelper = ioHelper;
_machineKeyConfig = machineKeyConfig;
}
public override string View => HasMachineKey() == false ? base.View : "";
@@ -27,10 +29,9 @@ namespace Umbraco.Web.Install.InstallSteps
/// Don't display the view or execute if a machine key already exists
/// </summary>
/// <returns></returns>
private static bool HasMachineKey()
private bool HasMachineKey()
{
var section = (MachineKeySection) WebConfigurationManager.GetSection("system.web/machineKey");
return section.ElementInformation.Source != null;
return _machineKeyConfig.HasMachineKey;
}
/// <summary>

View File

@@ -1,44 +1,31 @@
using System.Threading.Tasks;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Services;
using Umbraco.Web.Cache;
using Umbraco.Web.Composing;
using Umbraco.Web.Install.Models;
using Umbraco.Web.Security;
namespace Umbraco.Web.Install.InstallSteps
{
[InstallSetupStep(InstallationType.NewInstall | InstallationType.Upgrade,
"UmbracoVersion", 50, "Installation is complete!, get ready to be redirected to your new CMS.",
"UmbracoVersion", 50, "Installation is complete! Get ready to be redirected to your new CMS.",
PerformsAppRestart = true)]
internal class SetUmbracoVersionStep : InstallSetupStep<object>
public class SetUmbracoVersionStep : InstallSetupStep<object>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly InstallHelper _installHelper;
private readonly IGlobalSettings _globalSettings;
private readonly IUserService _userService;
private readonly IUmbracoVersion _umbracoVersion;
private readonly IIOHelper _ioHelper;
public SetUmbracoVersionStep(IHttpContextAccessor httpContextAccessor, InstallHelper installHelper, IGlobalSettings globalSettings, IUserService userService, IUmbracoVersion umbracoVersion, IIOHelper ioHelper)
public SetUmbracoVersionStep(IUmbracoContextAccessor umbracoContextAccessor, InstallHelper installHelper, IGlobalSettings globalSettings, IUmbracoVersion umbracoVersion)
{
_httpContextAccessor = httpContextAccessor;
_umbracoContextAccessor = umbracoContextAccessor;
_installHelper = installHelper;
_globalSettings = globalSettings;
_userService = userService;
_umbracoVersion = umbracoVersion;
_ioHelper = ioHelper;
}
public override Task<InstallSetupResult> ExecuteAsync(object model)
{
var security = new WebSecurity(_httpContextAccessor, _userService, _globalSettings, _ioHelper);
var security = _umbracoContextAccessor.GetRequiredUmbracoContext().Security;
if (security.IsAuthenticated() == false && _globalSettings.ConfigurationStatus.IsNullOrWhiteSpace())
{
security.PerformLogin(-1);

View File

@@ -1,13 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Umbraco.Core.Services;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models.Packaging;
using Umbraco.Web.Composing;
using Umbraco.Net;
using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps
@@ -20,14 +18,16 @@ namespace Umbraco.Web.Install.InstallSteps
private readonly InstallHelper _installHelper;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IUmbracoVersion _umbracoVersion;
private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
private readonly IContentService _contentService;
private readonly IPackagingService _packageService;
public StarterKitDownloadStep(IContentService contentService, IPackagingService packageService, InstallHelper installHelper, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoVersion umbracoVersion)
public StarterKitDownloadStep(IContentService contentService, IPackagingService packageService, InstallHelper installHelper, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoVersion umbracoVersion, IUmbracoApplicationLifetime umbracoApplicationLifetime)
{
_installHelper = installHelper;
_umbracoContextAccessor = umbracoContextAccessor;
_umbracoVersion = umbracoVersion;
_umbracoApplicationLifetime = umbracoApplicationLifetime;
_contentService = contentService;
_packageService = packageService;
}
@@ -54,7 +54,7 @@ namespace Umbraco.Web.Install.InstallSteps
var (packageFile, packageId) = await DownloadPackageFilesAsync(starterKitId.Value);
UmbracoApplication.Restart();
_umbracoApplicationLifetime.Restart();
return new InstallSetupResult(new Dictionary<string, object>
{

View File

@@ -2,9 +2,8 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Net;
using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps
@@ -14,13 +13,13 @@ namespace Umbraco.Web.Install.InstallSteps
PerformsAppRestart = true)]
internal class StarterKitInstallStep : InstallSetupStep<object>
{
private readonly IHttpContextAccessor _httContextAccessor;
private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IPackagingService _packagingService;
public StarterKitInstallStep(IHttpContextAccessor httContextAccessor, IUmbracoContextAccessor umbracoContextAccessor, IPackagingService packagingService)
public StarterKitInstallStep(IUmbracoApplicationLifetime umbracoApplicationLifetime, IUmbracoContextAccessor umbracoContextAccessor, IPackagingService packagingService)
{
_httContextAccessor = httContextAccessor;
_umbracoApplicationLifetime = umbracoApplicationLifetime;
_umbracoContextAccessor = umbracoContextAccessor;
_packagingService = packagingService;
}
@@ -34,7 +33,9 @@ namespace Umbraco.Web.Install.InstallSteps
InstallBusinessLogic(packageId);
UmbracoApplication.Restart(_httContextAccessor.GetRequiredHttpContext());
_umbracoApplicationLifetime.Restart();
return Task.FromResult<InstallSetupResult>(null);
}

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Web.Media.Exif
public ExifBitConverter(ByteOrder from, ByteOrder to)
: base(from, to)
{
;
}
#endregion

View File

@@ -28,7 +28,7 @@ namespace Umbraco.Web.Media.Exif
public ExifEnumProperty(ExifTag tag, T value)
: this(tag, value, false)
{
;
}
public override ExifInterOperability Interoperability
@@ -210,13 +210,13 @@ namespace Umbraco.Web.Media.Exif
public ExifPointSubjectArea(ExifTag tag, ushort[] value)
: base(tag, value)
{
;
}
public ExifPointSubjectArea(ExifTag tag, ushort x, ushort y)
: base(tag, new ushort[] { x, y })
: base(tag, new ushort[] {x, y})
{
;
}
}
@@ -239,13 +239,13 @@ namespace Umbraco.Web.Media.Exif
public ExifCircularSubjectArea(ExifTag tag, ushort[] value)
: base(tag, value)
{
;
}
public ExifCircularSubjectArea(ExifTag tag, ushort x, ushort y, ushort d)
: base(tag, new ushort[] { x, y, d })
{
;
}
}
@@ -269,13 +269,13 @@ namespace Umbraco.Web.Media.Exif
public ExifRectangularSubjectArea(ExifTag tag, ushort[] value)
: base(tag, value)
{
;
}
public ExifRectangularSubjectArea(ExifTag tag, ushort x, ushort y, ushort w, ushort h)
: base(tag, new ushort[] { x, y, w, h })
{
;
}
}
@@ -303,13 +303,13 @@ namespace Umbraco.Web.Media.Exif
public GPSLatitudeLongitude(ExifTag tag, MathEx.UFraction32[] value)
: base(tag, value)
{
;
}
public GPSLatitudeLongitude(ExifTag tag, float d, float m, float s)
: base(tag, new MathEx.UFraction32[] { new MathEx.UFraction32(d), new MathEx.UFraction32(m), new MathEx.UFraction32(s) })
{
;
}
}
@@ -331,13 +331,13 @@ namespace Umbraco.Web.Media.Exif
public GPSTimeStamp(ExifTag tag, MathEx.UFraction32[] value)
: base(tag, value)
{
;
}
public GPSTimeStamp(ExifTag tag, float h, float m, float s)
: base(tag, new MathEx.UFraction32[] { new MathEx.UFraction32(h), new MathEx.UFraction32(m), new MathEx.UFraction32(s) })
{
;
}
}

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Web.Media.Exif
public JFIFVersion(ExifTag tag, ushort value)
: base(tag, value)
{
;
}
public override string ToString()

View File

@@ -45,7 +45,7 @@
public JPEGSection(JPEGMarker marker)
: this(marker, new byte[0], new byte[0])
{
;
}
#endregion

View File

@@ -403,37 +403,37 @@ namespace Umbraco.Web.Media.Exif
public Fraction32(int numerator, int denominator)
: this(numerator, denominator, 0)
{
;
}
public Fraction32(int numerator)
: this(numerator, (int)1)
{
;
}
public Fraction32(Fraction32 f)
: this(f.Numerator, f.Denominator, f.Error)
{
;
}
public Fraction32(float value)
: this((double)value)
{
;
}
public Fraction32(double value)
: this(FromDouble(value))
{
;
}
public Fraction32(string s)
: this(FromString(s))
{
;
}
#endregion
@@ -1033,37 +1033,37 @@ namespace Umbraco.Web.Media.Exif
public UFraction32(uint numerator, uint denominator)
: this(numerator, denominator, 0)
{
;
}
public UFraction32(uint numerator)
: this(numerator, (uint)1)
{
;
}
public UFraction32(UFraction32 f)
: this(f.Numerator, f.Denominator, f.Error)
{
;
}
public UFraction32(float value)
: this((double)value)
{
;
}
public UFraction32(double value)
: this(FromDouble(value))
{
;
}
public UFraction32(string s)
: this(FromString(s))
{
;
}
#endregion

View File

@@ -36,7 +36,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
if (string.IsNullOrEmpty(dataType.Configuration))
{
config.Format = "YYYY-MM-DD";
};
}
}
catch (Exception ex)
{

View File

@@ -158,7 +158,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IMacro entity)
{
entity.UpdatingEntity();
;
var dto = MacroFactory.BuildDto(entity);
Database.Update(dto);

View File

@@ -219,7 +219,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//Save updated entity to db
template.UpdateDate = DateTime.Now;
;
var dto = TemplateFactory.BuildDto(template, NodeObjectTypeId, templateDto.PrimaryKey);
Database.Update(dto.NodeDto);

View File

@@ -77,12 +77,13 @@ namespace Umbraco.Core.Persistence.SqlSyntax
string ConvertIntegerToOrderableString { get; }
string ConvertDateToOrderableString { get; }
string ConvertDecimalToOrderableString { get; }
/// <summary>
/// Returns the default isolation level for the database
/// </summary>
IsolationLevel DefaultIsolationLevel { get; }
string DbProvider { get; }
IEnumerable<string> GetTablesInSchema(IDatabase db);
IEnumerable<ColumnInfo> GetColumnsInSchema(IDatabase db);

View File

@@ -175,6 +175,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return items.Select(x => new Tuple<string, string, string, string>(x.TableName, x.ColumnName, x.Name, x.Definition));
}
public override string DbProvider => ServerVersion.IsAzure ? "SqlAzure" : "SqlServer";
public override IEnumerable<string> GetTablesInSchema(IDatabase db)
{
var items = db.Fetch<dynamic>("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");

View File

@@ -202,6 +202,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
}
public abstract IsolationLevel DefaultIsolationLevel { get; }
public abstract string DbProvider { get; }
public virtual IEnumerable<string> GetTablesInSchema(IDatabase db)
{

View File

@@ -48,7 +48,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
value = new ImageCropperValue { Src = sourceString };
}
value.ApplyConfiguration(propertyType.DataType.ConfigurationAs<ImageCropperConfiguration>());
value?.ApplyConfiguration(propertyType.DataType.ConfigurationAs<ImageCropperConfiguration>());
return value;
}

View File

@@ -55,6 +55,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
}
public override System.Data.IsolationLevel DefaultIsolationLevel => System.Data.IsolationLevel.RepeatableRead;
public override string DbProvider => "SqlServerCE";
public override string FormatColumnRename(string tableName, string oldName, string newName)
{

View File

@@ -1309,7 +1309,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
var member = args.Entity;
// refresh the edited data
OnRepositoryRefreshed(db, member, true);
OnRepositoryRefreshed(db, member, false);
}
private void OnRepositoryRefreshed(IUmbracoDatabase db, IContentBase content, bool published)

View File

@@ -223,7 +223,7 @@ namespace Umbraco.TestData
{
var content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias);
content.SetValue("review", faker.Rant.Review());
content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective()))); ;
content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective())));
content.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]);
Services.ContentService.Save(content);

View File

@@ -357,7 +357,7 @@ namespace Umbraco.Tests.Composing
var col2 = factory.GetInstance<TestCollection>();
AssertCollection(col2, typeof(Resolved1), typeof(Resolved2));
AssertSameCollection(col1, col2);
AssertSameCollection(factory, col1, col2);
}
}
@@ -416,11 +416,11 @@ namespace Umbraco.Tests.Composing
{
col1A = factory.GetInstance<TestCollection>();
col1B = factory.GetInstance<TestCollection>();
}
AssertCollection(col1A, typeof(Resolved1), typeof(Resolved2));
AssertCollection(col1B, typeof(Resolved1), typeof(Resolved2));
AssertSameCollection(col1A, col1B);
AssertCollection(col1A, typeof(Resolved1), typeof(Resolved2));
AssertCollection(col1B, typeof(Resolved1), typeof(Resolved2));
AssertSameCollection(factory, col1A, col1B);
}
TestCollection col2;
@@ -455,7 +455,7 @@ namespace Umbraco.Tests.Composing
Assert.IsInstanceOf(expected[i], colA[i]);
}
private static void AssertSameCollection(IEnumerable<Resolved> col1, IEnumerable<Resolved> col2)
private static void AssertSameCollection(IFactory factory, IEnumerable<Resolved> col1, IEnumerable<Resolved> col2)
{
Assert.AreSame(col1, col2);
@@ -463,8 +463,19 @@ namespace Umbraco.Tests.Composing
var col2A = col2.ToArray();
Assert.AreEqual(col1A.Length, col2A.Length);
// Ensure each item in each collection is the same but also
// resolve each item from the factory to ensure it's also the same since
// it should have the same lifespan.
for (var i = 0; i < col1A.Length; i++)
{
Assert.AreSame(col1A[i], col2A[i]);
var itemA = factory.GetInstance(col1A[i].GetType());
var itemB = factory.GetInstance(col2A[i].GetType());
Assert.AreSame(itemA, itemB);
}
}
private static void AssertNotSameCollection(IEnumerable<Resolved> col1, IEnumerable<Resolved> col2)
@@ -475,8 +486,11 @@ namespace Umbraco.Tests.Composing
var col2A = col2.ToArray();
Assert.AreEqual(col1A.Length, col2A.Length);
for (var i = 0; i < col1A.Length; i++)
{
Assert.AreNotSame(col1A[i], col2A[i]);
}
}
#endregion

View File

@@ -164,7 +164,7 @@ namespace Umbraco.Tests.Logging
//Query @Level='Warning' BUT we pass in array of LogLevels for Debug & Info (Expect to get 0 results)
string[] logLevelMismatch = { "Debug", "Information" };
var filterLevelQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: "@Level='Warning'", logLevels: logLevelMismatch); ;
var filterLevelQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: "@Level='Warning'", logLevels: logLevelMismatch);
Assert.AreEqual(0, filterLevelQuery.TotalItems);
}

View File

@@ -133,6 +133,17 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
}
};
scope.currentSectionInOverflow = function () {
if (scope.overflowingSections === 0) {
return false;
}
var currentSection = scope.sections.filter(s => s.alias === scope.currentSection);
return (scope.sections.indexOf(currentSection[0]) >= scope.maxSections);
};
loadSections();
}

View File

@@ -18,9 +18,9 @@
function link(scope, element, attrs, ctrl) {
scope.close = function() {
if(scope.onClose) {
scope.onClose();
scope.close = function () {
if (scope.onClose) {
scope.onClose();
}
}

View File

@@ -42,7 +42,7 @@
$scope.page.isNew = Object.toBoolean(newVal);
//We fetch all ancestors of the node to generate the footer breadcrumb navigation
if (content.parentId && content.parentId !== -1) {
if (content.parentId && content.parentId !== -1 && content.parentId !== -20) {
loadBreadcrumb();
if (!watchingCulture) {
$scope.$watch('culture',
@@ -287,6 +287,8 @@
$scope.page.showSaveButton = true;
// add ellipsis to the save button if it opens the variant overlay
$scope.page.saveButtonEllipsis = content.variants && content.variants.length > 1 ? "true" : "false";
} else {
$scope.page.showSaveButton = false;
}
// create the pubish combo button

View File

@@ -328,6 +328,9 @@
// invariant nodes
scope.currentUrls = scope.node.urls;
}
// figure out if multiple cultures apply across the content urls
scope.currentUrlsHaveMultipleCultures = _.keys(_.groupBy(scope.currentUrls, url => url.culture)).length > 1;
}
// load audit trail and redirects when on the info tab

View File

@@ -99,13 +99,18 @@ Use this directive to render a ui component for selecting child items to a paren
@param {string} parentName (<code>binding</code>): The parent name.
@param {string} parentIcon (<code>binding</code>): The parent icon.
@param {number} parentId (<code>binding</code>): The parent id.
@param {callback} onRemove (<code>binding</code>): Callback when the remove button is clicked on an item.
@param {callback} onRemove (<code>binding</code>): Callback when removing an item.
<h3>The callback returns:</h3>
<ul>
<li><code>child</code>: The selected item.</li>
<li><code>$index</code>: The selected item index.</li>
</ul>
@param {callback} onAdd (<code>binding</code>): Callback when the add button is clicked.
@param {callback} onAdd (<code>binding</code>): Callback when adding an item.
<h3>The callback returns:</h3>
<ul>
<li><code>$event</code>: The select event.</li>
</ul>
@param {callback} onSort (<code>binding</code>): Callback when sorting an item.
<h3>The callback returns:</h3>
<ul>
<li><code>$event</code>: The select event.</li>
@@ -174,16 +179,15 @@ Use this directive to render a ui component for selecting child items to a paren
eventBindings.push(scope.$watch('parentName', function(newValue, oldValue){
if (newValue === oldValue) { return; }
if ( oldValue === undefined || newValue === undefined) { return; }
if (oldValue === undefined || newValue === undefined) { return; }
syncParentName();
}));
eventBindings.push(scope.$watch('parentIcon', function(newValue, oldValue){
if (newValue === oldValue) { return; }
if ( oldValue === undefined || newValue === undefined) { return; }
if (oldValue === undefined || newValue === undefined) { return; }
syncParentIcon();
}));
@@ -191,6 +195,7 @@ Use this directive to render a ui component for selecting child items to a paren
// sortable options for allowed child content types
scope.sortableOptions = {
axis: "y",
cancel: ".unsortable",
containment: "parent",
distance: 10,
opacity: 0.7,
@@ -199,7 +204,7 @@ Use this directive to render a ui component for selecting child items to a paren
zIndex: 6000,
update: function (e, ui) {
if(scope.onSort) {
scope.onSort();
scope.onSort();
}
}
};

View File

@@ -24,8 +24,7 @@ function valPropertyMsg(serverValidationManager, localizationService) {
var hasError = false;
//create properties on our custom scope so we can use it in our template
scope.errorMsg = "";
scope.errorMsg = "";
//the property form controller api
var formCtrl = ctrl[0];
@@ -34,11 +33,15 @@ function valPropertyMsg(serverValidationManager, localizationService) {
//the property controller api
var umbPropCtrl = ctrl[2];
//the variants controller api
var umbVariantCtrl = ctrl[3];
var umbVariantCtrl = ctrl[3];
var currentProperty = umbPropCtrl.property;
scope.currentProperty = currentProperty;
var currentCulture = currentProperty.culture;
// validation object won't exist when editor loads outside the content form (ie in settings section when modifying a content type)
var isMandatory = currentProperty.validation ? currentProperty.validation.mandatory : undefined;
var labels = {};
localizationService.localize("errors_propertyHasErrors").then(function (data) {
@@ -91,23 +94,25 @@ function valPropertyMsg(serverValidationManager, localizationService) {
if (!watcher) {
watcher = scope.$watch("currentProperty.value",
function (newValue, oldValue) {
if (angular.equals(newValue, oldValue)) {
return;
}
var errCount = 0;
for (var e in formCtrl.$error) {
if (angular.isArray(formCtrl.$error[e])) {
errCount++;
}
}
}
//we are explicitly checking for valServer errors here, since we shouldn't auto clear
// based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg
// is the only one, then we'll clear.
if (errCount === 0 || (errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) {
if (errCount === 0
|| (errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg))
|| (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) {
scope.errorMsg = "";
formCtrl.$setValidity('valPropertyMsg', true);
} else if (showValidation && scope.errorMsg === "") {
@@ -136,6 +141,21 @@ function valPropertyMsg(serverValidationManager, localizationService) {
}
//if there are any errors in the current property form that are not valPropertyMsg
else if (_.without(_.keys(formCtrl.$error), "valPropertyMsg").length > 0) {
// errors exist, but if the property is NOT mandatory and has no value, the errors should be cleared
if (isMandatory !== undefined && isMandatory === false && !currentProperty.value) {
hasError = false;
showValidation = false;
scope.errorMsg = "";
// if there's no value, the controls can be reset, which clears the error state on formCtrl
for (let control of formCtrl.$getControls()) {
control.$setValidity();
}
return;
}
hasError = true;
//update the validation message if we don't already have one assigned.
if (showValidation && scope.errorMsg === "") {

View File

@@ -525,7 +525,7 @@ When building a custom infinite editor view you can use the same components as a
function rollback(editor) {
editor.view = "views/common/infiniteeditors/rollback/rollback.html";
if (!editor.size) editor.size = "small";
if (!editor.size) editor.size = "medium";
open(editor);
}
@@ -784,7 +784,7 @@ When building a custom infinite editor view you can use the same components as a
* @methodOf umbraco.services.editorService
*
* @description
* Opens the user group picker in infinite editing, the submit callback returns the saved template
* Opens the template editor in infinite editing, the submit callback returns the saved template
* @param {Object} editor rendering options
* @param {String} editor.id The template id
* @param {Callback} editor.submit Submits the editor

View File

@@ -103,14 +103,21 @@
.umb-toggle.umb-toggle--disabled.umb-toggle--checked,
.umb-toggle.umb-toggle--disabled {
.umb-toggle__toggle {
cursor: not-allowed;
border-color: @gray-5;
background-color: @gray-9;
border-color: @gray-9;
}
.umb-toggle__icon--left {
color: @gray-6;
}
.umb-toggle__icon--right {
color: @gray-6;
}
.umb-toggle__handler {
background-color: @gray-5;
background-color: @gray-10;
}
}

View File

@@ -16,23 +16,24 @@
.umb-child-selector__child.-placeholder {
border: 1px dashed @gray-8;
background: none;
cursor: pointer;
text-align: center;
justify-content: center;
color:@ui-action-type;
width: 100%;
color: @ui-action-type;
&:hover {
color:@ui-action-type-hover;
border-color:@ui-action-type-hover;
text-decoration:none;
color: @ui-action-type-hover;
border-color: @ui-action-type-hover;
text-decoration: none;
}
}
.umb-child-selector__children-container {
margin-left: 30px;
.umb-child-selector__child {
cursor: move;
}
margin-left: 30px;
.umb-child-selector__child.ui-sortable-handle {
cursor: move;
}
}
.umb-child-selector__child-description {
@@ -65,5 +66,6 @@
}
.umb-child-selector__child-remove {
cursor: pointer;
background: none;
border: none;
}

View File

@@ -162,5 +162,6 @@
&.umb-form-check--disabled {
cursor: not-allowed !important;
opacity: 0.5;
pointer-events: none;
}
}

View File

@@ -23,6 +23,7 @@
background-color: transparent;
border-color: @ui-action-discreet-border;
transition: background-color .1s linear, border-color .1s linear, color .1s linear, width .1s ease-in-out, padding-left .1s ease-in-out;
cursor: pointer;
}
&:focus-within, &:hover {
@@ -39,6 +40,7 @@
background-color: white;
color: @ui-action-discreet-border-hover;
border-color: @ui-action-discreet-border-hover;
cursor: unset;
}
input:focus, &:focus-within input, &.--has-value input {

View File

@@ -135,6 +135,8 @@
.umb-nested-content__header-bar:hover .umb-nested-content__icons,
.umb-nested-content__header-bar:focus .umb-nested-content__icons,
.umb-nested-content__header-bar:focus-within .umb-nested-content__icons,
.umb-nested-content__item--active > .umb-nested-content__header-bar .umb-nested-content__icons {
opacity: 1;
}

View File

@@ -29,7 +29,8 @@
.umb-node-preview__icon {
display: flex;
width: 25px;
height: 25px;
min-height: 25px;
height: 100%;
justify-content: center;
align-items: center;
font-size: 20px;

View File

@@ -61,6 +61,7 @@
opacity: 0;
transition: opacity 120ms;
}
.umb-property:focus-within .umb-property-actions__toggle,
.umb-property:hover .umb-property-actions__toggle,
.umb-property .umb-property-actions__toggle:focus {
opacity: 1;

View File

@@ -1,6 +1,7 @@
.umb-property-file-upload {
.umb-upload-button-big {
max-width: (@propertyEditorLimitedWidth - 40);
display: block;
padding: 20px;
opacity: 1;

View File

@@ -418,7 +418,7 @@
// --------------------------------------------------
// Limit width of specific property editors
.umb-property-editor--limit-width {
max-width: 800px;
max-width: @propertyEditorLimitedWidth;
}
// Horizontal dividers

View File

@@ -722,6 +722,10 @@
//
// File upload
// --------------------------------------------------
.umb-fileupload {
display: flex;
}
.umb-fileupload .preview {
border-radius: 5px;
border: 1px solid @gray-6;

View File

@@ -164,4 +164,13 @@
.mce-fullscreen {
position: absolute;
.mce-in {
position: fixed;
top: 35px !important;
}
umb-editor__overlay, .umb-editor {
position: fixed;
}
}

View File

@@ -55,7 +55,7 @@ ul.sections {
transition: opacity .1s linear, box-shadow .1s;
}
&.current a {
&.current > a {
color: @ui-active;
&::after {
@@ -76,6 +76,13 @@ ul.sections {
transition: opacity .1s linear;
}
&.current {
i {
opacity: 1;
background: @ui-active;
}
}
&:hover i {
opacity: 1;
}

View File

@@ -244,6 +244,7 @@
@paddingSmall: 2px 10px; // 26px
@paddingMini: 0 6px; // 22px
@propertyEditorLimitedWidth: 800px;
// Disabled this to keep consistency throughout the backoffice UI. Untill a better solution is thought up, this will do.
@baseBorderRadius: 3px; // 2px;

View File

@@ -257,6 +257,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
evts.push(eventsService.on("app.ready", function (evt, data) {
$scope.authenticated = true;
ensureInit();
ensureMainCulture();
}));
// event for infinite editors
@@ -279,8 +280,22 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
}
}));
/**
* For multi language sites, this ensures that mculture is set to either the last selected language or the default one
*/
function ensureMainCulture() {
if ($location.search().mculture) {
return;
}
var language = lastLanguageOrDefault();
if (!language) {
return;
}
// trigger a language selection in the next digest cycle
$timeout(function () {
$scope.selectLanguage(language);
});
}
/**
* Based on the current state of the application, this configures the scope variables that control the main tree and language drop down
@@ -385,28 +400,19 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
if ($scope.languages.length > 1) {
//if there's already one set, check if it exists
var currCulture = null;
var language = null;
var mainCulture = $location.search().mculture;
if (mainCulture) {
currCulture = _.find($scope.languages, function (l) {
language = _.find($scope.languages, function (l) {
return l.culture.toLowerCase() === mainCulture.toLowerCase();
});
}
if (!currCulture) {
// no culture in the request, let's look for one in the cookie that's set when changing language
var defaultCulture = $cookies.get("UMB_MCULTURE");
if (!defaultCulture || !_.find($scope.languages, function (l) {
return l.culture.toLowerCase() === defaultCulture.toLowerCase();
})) {
// no luck either, look for the default language
var defaultLang = _.find($scope.languages, function (l) {
return l.isDefault;
});
if (defaultLang) {
defaultCulture = defaultLang.culture;
}
if (!language) {
language = lastLanguageOrDefault();
if (language) {
$location.search("mculture", language.culture);
}
$location.search("mculture", defaultCulture ? defaultCulture : null);
}
}
@@ -431,6 +437,25 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
});
});
}
function lastLanguageOrDefault() {
if (!$scope.languages || $scope.languages.length <= 1) {
return null;
}
// see if we can find a culture in the cookie set when changing language
var lastCulture = $cookies.get("UMB_MCULTURE");
var language = lastCulture ? _.find($scope.languages, function (l) {
return l.culture.toLowerCase() === lastCulture.toLowerCase();
}) : null;
if (!language) {
// no luck, look for the default language
language = _.find($scope.languages, function (l) {
return l.isDefault;
});
}
return language;
}
function nodeExpandedHandler(args) {
//store the reference to the expanded node path
if (args.node) {

View File

@@ -56,7 +56,8 @@
"validation_validateAsEmail",
"validation_validateAsNumber",
"validation_validateAsUrl",
"validation_enterCustomValidation"
"validation_enterCustomValidation",
"validation_fieldIsMandatory"
];
localizationService.localizeMany(labels)
@@ -66,6 +67,7 @@
vm.labels.validateAsNumber = data[1];
vm.labels.validateAsUrl = data[2];
vm.labels.customValidation = data[3];
vm.labels.fieldIsMandatory = data[4];
vm.validationTypes = [
{

View File

@@ -84,14 +84,15 @@
<h5><localize key="validation_validation"></localize></h5>
<label>
<localize key="validation_fieldIsMandatory"></localize>
</label>
<umb-toggle data-element="validation_mandatory"
checked="model.property.validation.mandatory"
on-click="vm.toggleValidation()">
on-click="vm.toggleValidation()"
label-on="{{vm.labels.fieldIsMandatory}}"
label-off="{{vm.labels.fieldIsMandatory}}"
show-labels="true"
label-position="right"
focus-when="{{vm.focusOnMandatoryField}}"
class="mb1">
</umb-toggle>
<input type="text"

View File

@@ -130,8 +130,8 @@
}
// diff requires a string
property.value = property.value ? property.value : "";
oldProperty.value = oldProperty.value ? oldProperty.value : "";
property.value = property.value ? property.value + "" : "";
oldProperty.value = oldProperty.value ? oldProperty.value + "" : "";
var diffProperty = {
"alias": property.alias,

View File

@@ -11,7 +11,7 @@
</a>
</li>
<li data-element="section-expand" class="expand" ng-class="{ 'open': showTray === true }" ng-show="needTray">
<li data-element="section-expand" class="expand" ng-class="{ 'open': showTray === true, current: currentSectionInOverflow() }" ng-show="needTray">
<a href="#" ng-click="trayClick()" prevent-default>
<span class="section__name"><i></i><i></i><i></i></span>
</a>

View File

@@ -70,7 +70,7 @@
<!-- Dom element not found error -->
<div ng-if="elementNotFound && !loadingStep">
<umb-tour-step class="tc">
<umb-tour-step class="tc" on-close="model.endTour()">
<umb-tour-step-header>
<h4 class="bold color-red">Oh, we got lost!</h4>
</umb-tour-step-header>

View File

@@ -1,8 +1,7 @@
<div class="umb-tour-step umb-tour-step--{{size}}">
<div ng-if="hideClose !== true">
<button class="icon-wrong umb-tour-step__close" ng-click="close()">
<button type="button" class="icon-wrong umb-tour-step__close" hotkey="esc" ng-click="close()">
<span class="sr-only">
<localize key="general_close">Close</localize>
</span>

View File

@@ -8,13 +8,13 @@
<ul class="nav nav-stacked" style="margin-bottom: 0;">
<li ng-repeat="url in currentUrls">
<a href="{{url.text}}" target="_blank" ng-if="url.isUrl">
<span ng-if="node.variants.length === 1 && url.culture" style="font-size: 13px; color: #cccccc; width: 50px;display: inline-block">{{url.culture}}</span>
<span ng-if="currentUrlsHaveMultipleCultures && url.culture" style="font-size: 13px; color: #cccccc; width: 50px;display: inline-block">{{url.culture}}</span>
<i class="icon icon-out"></i>
<span>{{url.text}}</span>
</a>
<div ng-if="!url.isUrl" style="margin-top: 4px;">
<span ng-if="node.variants.length === 1 && url.culture" style="font-size: 13px; color: #cccccc; width: 50px;display: inline-block">{{url.culture}}</span>
<span ng-if="currentUrlsHaveMultipleCultures && url.culture" style="font-size: 13px; color: #cccccc; width: 50px;display: inline-block">{{url.culture}}</span>
<em>{{url.text}}</em>
</div>

View File

@@ -19,18 +19,20 @@
<div class="umb-child-selector__child" ng-repeat="selectedChild in selectedChildren">
<div class="umb-child-selector__child-description">
<div class="umb-child-selector__child-icon-holder">
<i class="umb-child-selector__child-icon {{ selectedChild.icon }}"></i>
<i class="umb-child-selector__child-icon {{selectedChild.icon}}" aria-hidden="true"></i>
</div>
<span class="umb-child-selector__child-name"> {{ selectedChild.name }}</span>
<span class="umb-child-selector__child-name">{{selectedChild.name}}</span>
</div>
<div class="umb-child-selector__child-actions">
<i class="umb-child-selector__child-remove icon-trash" ng-click="removeChild(selectedChild, $index)"></i>
<button type="button" class="umb-child-selector__child-remove" ng-click="removeChild(selectedChild, $index)">
<i class="icon-trash" aria-hidden="true"></i>
</button>
</div>
</div>
<a href="" class="umb-child-selector__child -placeholder" ng-click="addChild($event)" hotkey="alt+shift+c">
<button type="button" class="umb-child-selector__child -placeholder unsortable" ng-click="addChild($event)" hotkey="alt+shift+c">
<div class="umb-child-selector__child-name -blue"><strong><localize key="shortcuts_addChild">Add Child</localize></strong></div>
</a>
</button>
</div>

View File

@@ -1,12 +1,11 @@
<div class="umb-user-preview">
<div class="umb-user-preview__avatar">
<umb-avatar
size="xxs"
color="secondary"
name="{{name}}"
img-src="{{avatars[0]}}"
img-srcset="{{avatars[1]}} 2x, {{avatars[2]}} 3x">
<umb-avatar size="xxs"
color="secondary"
name="{{name}}"
img-src="{{avatars[0]}}"
img-srcset="{{avatars[1]}} 2x, {{avatars[2]}} 3x">
</umb-avatar>
</div>
@@ -15,7 +14,6 @@
</div>
<div class="umb-user-preview__actions">
<a class="umb-user-preview__action umb-user-preview__action--red" title="Remove" href="" ng-if="allowRemove" ng-click="onRemove()"><localize key="general_remove">Remove</localize></a>
<div>
</div>
<button type="button" class="umb-user-preview__action umb-user-preview__action--red btn-link" title="Remove" ng-if="allowRemove" ng-click="onRemove()"><localize key="general_remove">Remove</localize></button>
</div>
</div>

View File

@@ -22,10 +22,14 @@ function contentCreateController($scope,
function initialize() {
$scope.loading = true;
$scope.allowedTypes = null;
$scope.countTypes = contentTypeResource.getCount;
var getAllowedTypes = contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) {
$scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
if ($scope.allowedTypes.length === 0) {
contentTypeResource.getCount().then(function(count) {
$scope.countTypes = count;
});
}
});
var getCurrentUser = authResource.getCurrentUser().then(function (currentUser) {
if (currentUser.allowedSections.indexOf("settings") > -1) {

View File

@@ -220,7 +220,7 @@ function startupLatestEditsController($scope) {
}
angular.module("umbraco").controller("Umbraco.Dashboard.StartupLatestEditsController", startupLatestEditsController);
function MediaFolderBrowserDashboardController($rootScope, $scope, $location, contentTypeResource, userService) {
function MediaFolderBrowserDashboardController($scope, $routeParams, $location, contentTypeResource, userService) {
var currentUser = {};
@@ -251,6 +251,8 @@ function MediaFolderBrowserDashboardController($rootScope, $scope, $location, co
view: dt.view
};
// tell the list view to list content at root
$routeParams.id = -1;
});
} else if (currentUser.startMediaIds.length > 0){

View File

@@ -4,7 +4,7 @@
<umb-box>
<umb-box-content>
<h3><localize key="settingsDashboardVideos_trainingHeadline">Hours of Umbraco training videos are only a click away</localize></h3>
<h3 class="bold"><localize key="settingsDashboardVideos_trainingHeadline">Hours of Umbraco training videos are only a click away</localize></h3>
<localize key="settingsDashboardVideos_trainingDescription">
<p>Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit <a href="http://umbraco.tv" target="_blank">umbraco.tv</a> for even more Umbraco videos</p>
</localize>

View File

@@ -73,14 +73,14 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource,
var content = $scope.content;
// we need to check wether an app is present in the current data, if not we will present the default app.
// we need to check whether an app is present in the current data, if not we will present the default app.
var isAppPresent = false;
// on first init, we dont have any apps. but if we are re-initializing, we do, but ...
if ($scope.app) {
// lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.)
_.forEach(content.apps, function(app) {
content.apps.forEach(app => {
if (app === $scope.app) {
isAppPresent = true;
}
@@ -88,7 +88,7 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource,
// if we did reload our DocType, but still have the same app we will try to find it by the alias.
if (isAppPresent === false) {
_.forEach(content.apps, function(app) {
content.apps.forEach(app => {
if (app.alias === $scope.app.alias) {
isAppPresent = true;
app.active = true;
@@ -182,24 +182,26 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource,
formHelper.resetForm({ scope: $scope });
contentEditingHelper.handleSuccessfulSave({
scope: $scope,
savedContent: data,
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
});
editorState.set($scope.content);
syncTreeNode($scope.content, data.path);
init();
$scope.page.saveButtonState = "success";
// close the editor if it's infinite mode
// submit function manages rebinding changes
if(infiniteMode && $scope.model.submit) {
$scope.model.mediaNode = $scope.content;
$scope.model.submit($scope.model);
} else {
// if not infinite mode, rebind changed props etc
contentEditingHelper.handleSuccessfulSave({
scope: $scope,
savedContent: data,
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
});
editorState.set($scope.content);
syncTreeNode($scope.content, data.path);
$scope.page.saveButtonState = "success";
init();
}
}, function(err) {
@@ -245,7 +247,7 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource,
syncTreeNode($scope.content, data.path, true);
}
if ($scope.content.parentId && $scope.content.parentId != -1) {
if ($scope.content.parentId && $scope.content.parentId !== -1 && $scope.content.parentId !== -21) {
//We fetch all ancestors of the node to generate the footer breadcrump navigation
entityResource.getAncestors(nodeId, "media")
.then(function (anc) {

View File

@@ -276,6 +276,9 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time
}
$scope.reloadView = function (id, reloadActiveNode) {
if (!id) {
return;
}
$scope.viewLoaded = false;
$scope.folders = [];
@@ -713,45 +716,12 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time
}
function initView() {
//default to root id if the id is undefined
var id = $routeParams.id;
if (id === undefined) {
id = -1;
// no ID found in route params - don't list anything as we don't know for sure where we are
return;
}
getContentTypesCallback(id).then(function (listViewAllowedTypes) {
$scope.listViewAllowedTypes = listViewAllowedTypes;
var blueprints = false;
_.each(listViewAllowedTypes, function (allowedType) {
if (_.isEmpty(allowedType.blueprints)) {
// this helps the view understand that there are no blueprints available
allowedType.blueprints = null;
}
else {
blueprints = true;
// turn the content type blueprints object into an array of sortable objects for the view
allowedType.blueprints = _.map(_.pairs(allowedType.blueprints || {}), function (pair) {
return {
id: pair[0],
name: pair[1]
};
});
}
});
if (listViewAllowedTypes.length === 1 && blueprints === false) {
$scope.createAllowedButtonSingle = true;
}
if (listViewAllowedTypes.length === 1 && blueprints === true) {
$scope.createAllowedButtonSingleWithBlueprints = true;
}
if (listViewAllowedTypes.length > 1) {
$scope.createAllowedButtonMultiWithBlueprints = true;
}
});
$scope.contentId = id;
$scope.isTrashed = editorState.current ? editorState.current.trashed : id === "-20" || id === "-21";
@@ -765,6 +735,40 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time
$scope.options.allowBulkMove ||
$scope.options.allowBulkDelete;
if ($scope.isTrashed === false) {
getContentTypesCallback(id).then(function (listViewAllowedTypes) {
$scope.listViewAllowedTypes = listViewAllowedTypes;
var blueprints = false;
_.each(listViewAllowedTypes, function (allowedType) {
if (_.isEmpty(allowedType.blueprints)) {
// this helps the view understand that there are no blueprints available
allowedType.blueprints = null;
}
else {
blueprints = true;
// turn the content type blueprints object into an array of sortable objects for the view
allowedType.blueprints = _.map(_.pairs(allowedType.blueprints || {}), function (pair) {
return {
id: pair[0],
name: pair[1]
};
});
}
});
if (listViewAllowedTypes.length === 1 && blueprints === false) {
$scope.createAllowedButtonSingle = true;
}
if (listViewAllowedTypes.length === 1 && blueprints === true) {
$scope.createAllowedButtonSingleWithBlueprints = true;
}
if (listViewAllowedTypes.length > 1) {
$scope.createAllowedButtonMultiWithBlueprints = true;
}
});
}
$scope.reloadView($scope.contentId);
}

View File

@@ -48,49 +48,45 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
// This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders
// when there is no match for a selected id. This will ensure that the values being set on save, are the same as before.
medias = _.map(ids,
function (id) {
var found = _.find(medias,
function (m) {
// We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and
// it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString()
// compares and be completely sure it works.
return m.udi.toString() === id.toString() || m.id.toString() === id.toString();
});
if (found) {
return found;
} else {
return {
name: vm.labels.deletedItem,
id: $scope.model.config.idType !== "udi" ? id : null,
udi: $scope.model.config.idType === "udi" ? id : null,
icon: "icon-picture",
thumbnail: null,
trashed: true
};
}
});
medias = ids.map(id => {
var found = medias.find(m =>
// We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and
// it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString()
// compares and be completely sure it works.
m.udi.toString() === id.toString() || m.id.toString() === id.toString());
if (found) {
return found;
} else {
return {
name: vm.labels.deletedItem,
id: $scope.model.config.idType !== "udi" ? id : null,
udi: $scope.model.config.idType === "udi" ? id : null,
icon: "icon-picture",
thumbnail: null,
trashed: true
};
}
});
_.each(medias,
function (media, i) {
medias.forEach(media => {
if (!media.extension && media.id && media.metaData) {
media.extension = mediaHelper.getFileExtension(media.metaData.MediaPath);
}
if (!media.extension && media.id && media.metaData) {
media.extension = mediaHelper.getFileExtension(media.metaData.MediaPath);
}
// if there is no thumbnail, try getting one if the media is not a placeholder item
if (!media.thumbnail && media.id && media.metaData) {
media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
}
// if there is no thumbnail, try getting one if the media is not a placeholder item
if (!media.thumbnail && media.id && media.metaData) {
media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
}
$scope.mediaItems.push(media);
$scope.mediaItems.push(media);
if ($scope.model.config.idType === "udi") {
$scope.ids.push(media.udi);
} else {
$scope.ids.push(media.id);
}
});
if ($scope.model.config.idType === "udi") {
$scope.ids.push(media.udi);
} else {
$scope.ids.push(media.id);
}
});
sync();
});
@@ -100,7 +96,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
function sync() {
$scope.model.value = $scope.ids.join();
removeAllEntriesAction.isDisabled = $scope.ids.length === 0;
};
}
function setDirty() {
angularHelper.getCurrentForm($scope).$setDirty();
@@ -111,18 +107,17 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
// reload. We only reload the images that is already picked but has been updated.
// We have to get the entities from the server because the media
// can be edited without being selected
_.each($scope.images,
function (image, i) {
if (updatedMediaNodes.indexOf(image.udi) !== -1) {
image.loading = true;
entityResource.getById(image.udi, "media")
.then(function (mediaEntity) {
angular.extend(image, mediaEntity);
image.thumbnail = mediaHelper.resolveFileFromEntity(image, true);
image.loading = false;
});
}
});
$scope.mediaItems.forEach(media => {
if (updatedMediaNodes.indexOf(media.udi) !== -1) {
media.loading = true;
entityResource.getById(media.udi, "Media")
.then(function (mediaEntity) {
angular.extend(media, mediaEntity);
media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
media.loading = false;
});
}
});
}
function init() {
@@ -177,20 +172,20 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
// the media picker is using media entities so we get the
// entity so we easily can format it for use in the media grid
if (model && model.mediaNode) {
entityResource.getById(model.mediaNode.id, "media")
entityResource.getById(model.mediaNode.id, "Media")
.then(function (mediaEntity) {
// if an image is selecting more than once
// we need to update all the media items
angular.forEach($scope.images, function (image) {
if (image.id === model.mediaNode.id) {
angular.extend(image, mediaEntity);
image.thumbnail = mediaHelper.resolveFileFromEntity(image, true);
$scope.mediaItems.forEach(media => {
if (media.id === model.mediaNode.id) {
angular.extend(media, mediaEntity);
media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
}
});
});
}
},
close: function (model) {
close: function () {
editorService.close();
}
};
@@ -210,7 +205,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
editorService.close();
_.each(model.selection, function (media, i) {
model.selection.forEach(media => {
// if there is no thumbnail, try getting one if the media is not a placeholder item
if (!media.thumbnail && media.id && media.metaData) {
media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
@@ -280,16 +275,14 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
disabled: !multiPicker,
items: "li:not(.add-wrapper)",
cancel: ".unsortable",
update: function (e, ui) {
update: function () {
setDirty();
$timeout(function() {
// TODO: Instead of doing this with a timeout would be better to use a watch like we do in the
// content picker. Then we don't have to worry about setting ids, render models, models, we just set one and let the
// watch do all the rest.
$scope.ids = _.map($scope.mediaItems,
function (item) {
return $scope.model.config.idType === "udi" ? item.udi : item.id;
});
$scope.ids = $scope.mediaItems.map(media => $scope.model.config.idType === "udi" ? media.udi : media.id);
sync();
});
}

View File

@@ -36,16 +36,16 @@
</umb-file-icon>
<div class="umb-sortable-thumbnails__actions" data-element="sortable-thumbnail-actions">
<button aria-label="Edit media" ng-if="allowEditMedia" class="umb-sortable-thumbnails__action btn-reset" data-element="action-edit" ng-click="vm.editItem(media)">
<button type="button" aria-label="Edit media" ng-if="allowEditMedia" class="umb-sortable-thumbnails__action btn-reset" data-element="action-edit" ng-click="vm.editItem(media)">
<i class="icon icon-edit" aria-hidden="true"></i>
</button>
<button aria-label="Remove" class="umb-sortable-thumbnails__action -red btn-reset" data-element="action-remove" ng-click="vm.remove($index)">
<button type="button" aria-label="Remove" class="umb-sortable-thumbnails__action -red btn-reset" data-element="action-remove" ng-click="vm.remove($index)">
<i class="icon icon-delete" aria-hidden="true"></i>
</button>
</div>
</li>
<li style="border: none;" class="add-wrapper unsortable" ng-if="vm.showAdd() && allowAddMedia">
<button aria-label="Open media picker" data-element="sortable-thumbnails-add" class="add-link btn-reset umb-outline umb-outline--surrounding" ng-click="vm.add()" ng-class="{'add-link-square': (mediaItems.length === 0 || isMultiPicker)}" prevent-default>
<button type="button" aria-label="Open media picker" data-element="sortable-thumbnails-add" class="add-link btn-reset umb-outline umb-outline--surrounding" ng-click="vm.add()" ng-class="{'add-link-square': (mediaItems.length === 0 || isMultiPicker)}">
<i class="icon icon-add large" aria-hidden="true"></i>
</button>
</li>

View File

@@ -221,6 +221,7 @@
if (vm.overlayMenu.availableItems.length === 1 && vm.overlayMenu.pasteItems.length === 0) {
// only one scaffold type - no need to display the picker
addNode(vm.scaffolds[0].contentTypeAlias);
vm.overlayMenu = null;
return;
}
@@ -276,6 +277,9 @@
};
vm.getName = function (idx) {
if (!model.value || !model.value.length) {
return "";
}
var name = "";
@@ -325,6 +329,10 @@
};
vm.getIcon = function (idx) {
if (!model.value || !model.value.length) {
return "";
}
var scaffold = getScaffold(model.value[idx].ncContentTypeAlias);
return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder";
}
@@ -480,10 +488,12 @@
}
// Enforce min items if we only have one scaffold type
var modelWasChanged = false;
if (vm.nodes.length < vm.minItems && vm.scaffolds.length === 1) {
for (var i = vm.nodes.length; i < model.config.minItems; i++) {
addNode(vm.scaffolds[0].contentTypeAlias);
}
modelWasChanged = true;
}
// If there is only one item, set it as current node
@@ -495,6 +505,10 @@
vm.inited = true;
if (modelWasChanged) {
updateModel();
}
updatePropertyActionStates();
checkAbilityToPasteContent();
}

View File

@@ -56,7 +56,7 @@
return value.toFixed(stepDecimalPlaces);
},
from: function (value) {
return value;
return Number(value);
}
},
"range": {

View File

@@ -40,13 +40,12 @@
on-remove="vm.removeSelectedItem($index, vm.userGroup.sections)">
</umb-node-preview>
<a href=""
style="max-width: 100%;"
class="umb-node-preview-add"
ng-click="vm.openSectionPicker()"
prevent-default>
<button type="button"
class="umb-node-preview-add"
style="max-width: 100%;"
ng-click="vm.openSectionPicker()">
<localize key="general_add">Add</localize>
</a>
</button>
</umb-control-group>
<umb-control-group style="margin-bottom: 20px;" label="@user_startnode" description="@user_startnodehelp">
@@ -61,14 +60,14 @@
on-remove="vm.clearStartNode('content')">
</umb-node-preview>
<a href=""
ng-if="!vm.userGroup.contentStartNode"
style="max-width: 100%;"
class="umb-node-preview-add"
ng-click="vm.openContentPicker()"
prevent-default>
<button type="button"
ng-if="!vm.userGroup.contentStartNode"
class="umb-node-preview-add"
style="max-width: 100%;"
ng-click="vm.openContentPicker()">
<localize key="general_add">Add</localize>
</a>
</button>
</umb-control-group>
@@ -85,14 +84,14 @@
on-remove="vm.clearStartNode('media')">
</umb-node-preview>
<a href=""
ng-if="!vm.userGroup.mediaStartNode"
style="max-width: 100%;"
class="umb-node-preview-add"
ng-click="vm.openMediaPicker()"
prevent-default>
<button type="button"
ng-if="!vm.userGroup.mediaStartNode"
class="umb-node-preview-add"
style="max-width: 100%;"
ng-click="vm.openMediaPicker()">
<localize key="general_add">Add</localize>
</a>
</button>
</umb-control-group>
@@ -127,13 +126,12 @@
on-edit="vm.setPermissionsForNode(node)">
</umb-node-preview>
<a href=""
style="max-width: 100%;"
class="umb-node-preview-add"
ng-click="vm.openGranularPermissionsPicker()"
prevent-default>
<button type="button"
class="umb-node-preview-add"
style="max-width: 100%;"
ng-click="vm.openGranularPermissionsPicker()">
<localize key="general_add">Add</localize>
</a>
</button>
</umb-control-group>
</umb-box-content>
@@ -155,13 +153,11 @@
on-remove="vm.removeSelectedItem($index, vm.userGroup.users)">
</umb-user-preview>
<a href=""
style="max-width: 100%;"
class="umb-node-preview-add"
ng-click="vm.openUserPicker()"
prevent-default>
<button type="button"
class="umb-node-preview-add"
ng-click="vm.openUserPicker()">
<localize key="general_add">Add</localize>
</a>
</button>
</umb-box-content>
</umb-box>

View File

@@ -75,13 +75,11 @@
on-remove="model.removeSelectedItem($index, model.user.userGroups)">
</umb-user-group-preview>
<a href=""
style="max-width: 100%;"
class="umb-node-preview-add"
ng-click="model.openUserGroupPicker()"
prevent-default>
<button type="button"
class="umb-node-preview-add"
ng-click="model.openUserGroupPicker()">
<localize key="general_add">Add</localize>
</a>
</button>
</umb-control-group>
@@ -100,13 +98,12 @@
name="model.labels.noStartNodes">
</umb-node-preview>
<a href=""
class="umb-node-preview-add"
id="content-start-add"
ng-click="model.openContentPicker()"
prevent-default>
<button type="button"
class="umb-node-preview-add"
id="content-start-add"
ng-click="model.openContentPicker()">
<localize key="general_add">Add</localize>
</a>
</button>
</umb-control-group>
@@ -125,13 +122,12 @@
name="model.labels.noStartNodes">
</umb-node-preview>
<a href=""
class="umb-node-preview-add"
ng-click="model.openMediaPicker()"
id="media-start-add"
prevent-default>
<button type="button"
class="umb-node-preview-add"
ng-click="model.openMediaPicker()"
id="media-start-add">
<localize key="general_add">Add</localize>
</a>
</button>
</umb-control-group>

View File

@@ -31,13 +31,13 @@
@* a single image *@
if (media.IsDocumentType("Image"))
{
@Render(media);
@Render(media)
}
@* a folder with images under it *@
foreach (var image in media.Children(Current.VariationContextAccessor))
{
@Render(image);
@Render(image)
}
}
</div>

View File

@@ -2,31 +2,31 @@
@using Umbraco.Web.Templates
@using Newtonsoft.Json.Linq
@*
@*
Razor helpers located at the bottom of this file
*@
@if (Model != null && Model.sections != null)
{
var oneColumn = ((System.Collections.ICollection)Model.sections).Count == 1;
<div class="umb-grid">
@if (oneColumn)
{
foreach (var section in Model.sections) {
<div class="grid-section">
@foreach (var row in section.rows) {
@renderRow(row);
@renderRow(row)
}
</div>
}
}else {
}
}else {
<div class="row clearfix">
@foreach (var s in Model.sections) {
<div class="grid-section">
<div class="col-md-@s.grid column">
@foreach (var row in s.rows) {
@renderRow(row);
@renderRow(row)
}
</div>
</div>
@@ -85,4 +85,4 @@
return new MvcHtmlString(string.Join(" ", attrs));
}
}
}

View File

@@ -12,7 +12,7 @@
foreach (var section in Model.sections) {
<div class="grid-section">
@foreach (var row in section.rows) {
@renderRow(row, true);
@renderRow(row, true)
}
</div>
}
@@ -23,7 +23,7 @@
<div class="grid-section">
<div class="col-md-@s.grid column">
@foreach (var row in s.rows) {
@renderRow(row, false);
@renderRow(row, false)
}
</div>
</div>

View File

@@ -49,11 +49,6 @@ namespace Umbraco.Web.Hosting
}
public string ToAbsolute(string virtualPath, string root) => VirtualPathUtility.ToAbsolute(virtualPath, root);
public void LazyRestartApplication()
{
HttpRuntime.UnloadAppDomain();
}
public void RegisterObject(IRegisteredObject registeredObject)
{
var wrapped = new RegisteredObjectWrapper(registeredObject);

View File

@@ -0,0 +1,34 @@
using System.Threading;
using System.Web;
using Umbraco.Net;
namespace Umbraco.Web.AspNet
{
public class AspNetUmbracoApplicationLifetime : IUmbracoApplicationLifetime
{
private readonly IHttpContextAccessor _httpContextAccessor;
public AspNetUmbracoApplicationLifetime(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public bool IsRestarting { get; set; }
public void Restart()
{
IsRestarting = true;
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext != null)
{
// unload app domain - we must null out all identities otherwise we get serialization errors
// http://www.zpqrtbnk.net/posts/custom-iidentity-serialization-issue
httpContext.User = null;
}
Thread.CurrentPrincipal = null;
HttpRuntime.UnloadAppDomain();
}
}
}

View File

@@ -0,0 +1,19 @@
using Umbraco.Net;
namespace Umbraco.Web.AspNet
{
public class AspNetUserAgentProvider : IUserAgentProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
public AspNetUserAgentProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string GetUserAgent()
{
return _httpContextAccessor.GetRequiredHttpContext().Request.UserAgent;
}
}
}

View File

@@ -89,7 +89,7 @@ namespace Umbraco.Web.Cache
protected override void EnterWriteLock()
{
_locker.EnterWriteLock();;
_locker.EnterWriteLock();
}
protected override void ExitReadLock()

View File

@@ -263,7 +263,8 @@ namespace Umbraco.Web.Composing
public static IUmbracoVersion UmbracoVersion => Factory.GetInstance<IUmbracoVersion>();
public static IPublishedUrlProvider PublishedUrlProvider => Factory.GetInstance<IPublishedUrlProvider>();
public static IMenuItemCollectionFactory MenuItemCollectionFactory => Factory.GetInstance<IMenuItemCollectionFactory>();
public static MembershipHelper MembershipHelper => Factory.GetInstance<MembershipHelper>();
public static MembershipHelper MembershipHelper => Factory.GetInstance<MembershipHelper>();
public static IUmbracoApplicationLifetime UmbracoApplicationLifetime => Factory.GetInstance<IUmbracoApplicationLifetime>();
#endregion
}

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