Compare commits
42
Commits
contrib
...
release-9.5.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4df0827035 | ||
|
|
e97d3366c0 | ||
|
|
c018fcb66b | ||
|
|
3f8d688ca6 | ||
|
|
a1dea955c1 | ||
|
|
040801d3dd | ||
|
|
c0bfd25bb2 | ||
|
|
180f205195 | ||
|
|
5eae921e43 | ||
|
|
502d65bea7 | ||
|
|
f6dd2d00c7 | ||
|
|
5030477fdd | ||
|
|
474b14d5ea | ||
|
|
9d4ff5e94a | ||
|
|
b4bbad8a79 | ||
|
|
2bcf052d9b | ||
|
|
39eec09209 | ||
|
|
e536f9fd10 | ||
|
|
a5b7794207 | ||
|
|
1923a20da7 | ||
|
|
54077725c3 | ||
|
|
6b0149803a | ||
|
|
833c2ed13b | ||
|
|
61068e6ef0 | ||
|
|
3cd17b7ede | ||
|
|
a18039b97c | ||
|
|
7710ca27fe | ||
|
|
2a3deb6c14 | ||
|
|
bb9fc9b4d8 | ||
|
|
41662c4cff | ||
|
|
c9b8ebc6f8 | ||
|
|
d5371a15f5 | ||
|
|
7483a706ac | ||
|
|
7474eaa0d8 | ||
|
|
d1fcacb442 | ||
|
|
fd8750f99a | ||
|
|
5f168b59d5 | ||
|
|
16a02e0827 | ||
|
|
9b864ddbeb | ||
|
|
7becf76a02 | ||
|
|
7e6e9c7431 | ||
|
|
eb0ddbd729 |
@@ -1,26 +1,53 @@
|
||||
name: pr-first-response
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
send-response:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Fetch random comment 🗣️
|
||||
uses: JamesIves/fetch-api-data-action@v2.1.0
|
||||
with:
|
||||
ENDPOINT: https://collaboratorsv2.euwest01.umbraco.io/umbraco/api/comments/PostComment
|
||||
CONFIGURATION: '{ "method": "POST", "headers": {"Authorization": "Bearer ${{ secrets.OUR_BOT_API_TOKEN }}", "Content-Type": "application/json" }, "body": { "repo": "${{ github.repository }}", "number": "${{ github.event.number }}", "actor": "${{ github.actor }}", "commentType": "opened-pr-first-comment"} }'
|
||||
- name: Add PR comment
|
||||
if: "${{ env.fetch-api-data != '' }}"
|
||||
uses: actions/github-script@v5
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm install node-fetch@2
|
||||
- name: Fetch random comment 🗣️ and add it to the PR
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `${{ env.fetch-api-data }}`
|
||||
})
|
||||
const fetch = require('node-fetch')
|
||||
|
||||
const response = await fetch('https://collaboratorsv2.euwest01.umbraco.io/umbraco/api/comments/PostComment', {
|
||||
method: 'post',
|
||||
body: JSON.stringify({
|
||||
repo: '${{ github.repository }}',
|
||||
number: '${{ github.event.number }}',
|
||||
actor: '${{ github.actor }}',
|
||||
commentType: 'opened-pr-first-comment'
|
||||
}),
|
||||
headers: {
|
||||
'Authorization': 'Bearer ${{ secrets.OUR_BOT_API_TOKEN }}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const data = await response.text();
|
||||
|
||||
if(response.status === 200 && data !== '') {
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: data
|
||||
});
|
||||
} else {
|
||||
console.log("Status code did not indicate success:", response.status);
|
||||
console.log("Returned data:", data);
|
||||
}
|
||||
} catch(error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ stages:
|
||||
gulpFile: src\Umbraco.Web.UI.Client\gulpfile.js
|
||||
targets: build
|
||||
workingDirectory: src\Umbraco.Web.UI.Client
|
||||
- powershell: Start-Process -FilePath "dotnet" -ArgumentList "run", "-p", "src\Umbraco.Web.UI\Umbraco.Web.UI.csproj"
|
||||
- powershell: Start-Process -FilePath "dotnet" -ArgumentList "run", "-p", "src\Umbraco.Web.UI\Umbraco.Web.UI.csproj /Umbraco:CMS:Global:Id=0000000-0000-0000-0000-000000000042"
|
||||
displayName: dotnet run
|
||||
# - powershell: dotnet run --no-build -p .\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj
|
||||
# displayName: dotnet run
|
||||
@@ -313,7 +313,7 @@ stages:
|
||||
displayName: dotnet run
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: 'nohup dotnet run --no-build -p ./src/Umbraco.Web.UI/ > $(Build.ArtifactStagingDirectory)/dotnet_run_log_linux.txt &'
|
||||
script: 'nohup dotnet run --no-build -p ./src/Umbraco.Web.UI/ /Umbraco:CMS:Global:Id=0000000-0000-0000-0000-000000000042 > $(Build.ArtifactStagingDirectory)/dotnet_run_log_linux.txt &'
|
||||
- task: Bash@3
|
||||
displayName: Generate Cypress.env.json
|
||||
inputs:
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>9.5.1</Version>
|
||||
<AssemblyVersion>9.5.1</AssemblyVersion>
|
||||
<InformationalVersion>9.5.1</InformationalVersion>
|
||||
<FileVersion>9.5.1</FileVersion>
|
||||
<Version>9.5.4</Version>
|
||||
<AssemblyVersion>9.5.4</AssemblyVersion>
|
||||
<InformationalVersion>9.5.4</InformationalVersion>
|
||||
<FileVersion>9.5.4</FileVersion>
|
||||
<LangVersion Condition="'$(LangVersion)' == ''">9.0</LangVersion>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<Company>Umbraco CMS</Company>
|
||||
|
||||
@@ -42,18 +42,17 @@ namespace Umbraco.Cms.Core.Configuration.Models
|
||||
/// </remarks>
|
||||
public bool FlagOutOfDateModels
|
||||
{
|
||||
get => _flagOutOfDateModels;
|
||||
|
||||
set
|
||||
get
|
||||
{
|
||||
if (!ModelsMode.IsAuto())
|
||||
if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsAuto())
|
||||
{
|
||||
_flagOutOfDateModels = false;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_flagOutOfDateModels = value;
|
||||
return _flagOutOfDateModels;
|
||||
}
|
||||
|
||||
set => _flagOutOfDateModels = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
public static class CryptoServiceProviderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a random int withing a specified range.
|
||||
/// </summary>
|
||||
/// <param name="provider">Random bytes provider.</param>
|
||||
/// <param name="minValue">The minimum value of the resulting int.</param>
|
||||
/// <param name="maxValue">The maximum value of the resulting int.</param>
|
||||
/// <returns>A random integer that falls withing the specified range.</returns>
|
||||
public static int GetInt32(this RNGCryptoServiceProvider provider, int minValue, int maxValue)
|
||||
{
|
||||
var randomBytes = new byte[4];
|
||||
provider.GetBytes(randomBytes);
|
||||
|
||||
var randomInt = Math.Abs(BitConverter.ToInt32(randomBytes, 0));
|
||||
|
||||
// We call do mod to ensure that the value is within the specified range.
|
||||
return (randomInt % (maxValue - minValue + 1)) + minValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace Umbraco.Cms.Core.Models
|
||||
public string NodeType { get; set; }
|
||||
|
||||
[DataMember(Name = "udi")]
|
||||
public Udi NodeUdi => Udi.Create(NodeType, NodeKey);
|
||||
public Udi NodeUdi => NodeType == Constants.UdiEntityType.Unknown ? null : Udi.Create(NodeType, NodeKey);
|
||||
|
||||
[DataMember(Name = "icon")]
|
||||
public string ContentTypeIcon { get; set; }
|
||||
|
||||
@@ -16,31 +16,38 @@ namespace Umbraco.Cms.Core.Security
|
||||
{
|
||||
private readonly IPasswordConfiguration _passwordConfiguration;
|
||||
|
||||
public PasswordGenerator(IPasswordConfiguration passwordConfiguration)
|
||||
{
|
||||
public PasswordGenerator(IPasswordConfiguration passwordConfiguration) =>
|
||||
_passwordConfiguration = passwordConfiguration;
|
||||
}
|
||||
|
||||
public string GeneratePassword()
|
||||
{
|
||||
var password = PasswordStore.GeneratePassword(
|
||||
_passwordConfiguration.RequiredLength,
|
||||
_passwordConfiguration.GetMinNonAlphaNumericChars());
|
||||
|
||||
var random = new Random();
|
||||
|
||||
var passwordChars = password.ToCharArray();
|
||||
|
||||
using var numberGenerator = new RNGCryptoServiceProvider();
|
||||
|
||||
if (_passwordConfiguration.RequireDigit && passwordChars.ContainsAny(Enumerable.Range(48, 58).Select(x => (char)x)))
|
||||
password += Convert.ToChar(random.Next(48, 58)); // 0-9
|
||||
{
|
||||
password += Convert.ToChar(numberGenerator.GetInt32(48, 58)); // 0-9
|
||||
}
|
||||
|
||||
if (_passwordConfiguration.RequireLowercase && passwordChars.ContainsAny(Enumerable.Range(97, 123).Select(x => (char)x)))
|
||||
password += Convert.ToChar(random.Next(97, 123)); // a-z
|
||||
{
|
||||
password += Convert.ToChar(numberGenerator.GetInt32(97, 123)); // a-z
|
||||
}
|
||||
|
||||
if (_passwordConfiguration.RequireUppercase && passwordChars.ContainsAny(Enumerable.Range(65, 91).Select(x => (char)x)))
|
||||
password += Convert.ToChar(random.Next(65, 91)); // A-Z
|
||||
{
|
||||
password += Convert.ToChar(numberGenerator.GetInt32(65, 91)); // A-Z
|
||||
}
|
||||
|
||||
if (_passwordConfiguration.RequireNonLetterOrDigit && passwordChars.ContainsAny(Enumerable.Range(33, 48).Select(x => (char)x)))
|
||||
password += Convert.ToChar(random.Next(33, 48)); // symbols !"#$%&'()*+,-./
|
||||
{
|
||||
password += Convert.ToChar(numberGenerator.GetInt32(33, 48)); // symbols !"#$%&'()*+,-./
|
||||
}
|
||||
|
||||
return password;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Telemetry;
|
||||
using Umbraco.Cms.Core.Telemetry.Models;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
@@ -16,18 +18,29 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
|
||||
{
|
||||
public class ReportSiteTask : RecurringHostedServiceBase
|
||||
{
|
||||
private static HttpClient _httpClient = new();
|
||||
private readonly ILogger<ReportSiteTask> _logger;
|
||||
private readonly ITelemetryService _telemetryService;
|
||||
private static HttpClient s_httpClient;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
|
||||
public ReportSiteTask(
|
||||
ILogger<ReportSiteTask> logger,
|
||||
ITelemetryService telemetryService)
|
||||
: base(logger, TimeSpan.FromDays(1), TimeSpan.FromMinutes(1))
|
||||
ITelemetryService telemetryService,
|
||||
IRuntimeState runtimeState)
|
||||
: base(logger, TimeSpan.FromDays(1), TimeSpan.FromMinutes(5))
|
||||
{
|
||||
_logger = logger;
|
||||
_telemetryService = telemetryService;
|
||||
s_httpClient = new HttpClient();
|
||||
_runtimeState = runtimeState;
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
[Obsolete("Use the constructor that takes IRuntimeState, scheduled for removal in V12")]
|
||||
public ReportSiteTask(
|
||||
ILogger<ReportSiteTask> logger,
|
||||
ITelemetryService telemetryService)
|
||||
: this(logger, telemetryService, StaticServiceProvider.Instance.GetRequiredService<IRuntimeState>())
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Use the constructor that takes ITelemetryService instead, scheduled for removal in V11")]
|
||||
@@ -45,6 +58,12 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
|
||||
/// </summary>
|
||||
public override async Task PerformExecuteAsync(object state)
|
||||
{
|
||||
if (_runtimeState.Level is not RuntimeLevel.Run)
|
||||
{
|
||||
// We probably haven't installed yet, so we can't get telemetry.
|
||||
return;
|
||||
}
|
||||
|
||||
if (_telemetryService.TryGetTelemetryReportData(out TelemetryReportData telemetryReportData) is false)
|
||||
{
|
||||
_logger.LogWarning("No telemetry marker found");
|
||||
@@ -54,19 +73,19 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
|
||||
|
||||
try
|
||||
{
|
||||
if (s_httpClient.BaseAddress is null)
|
||||
if (_httpClient.BaseAddress is null)
|
||||
{
|
||||
// Send data to LIVE telemetry
|
||||
s_httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/");
|
||||
_httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/");
|
||||
|
||||
#if DEBUG
|
||||
// Send data to DEBUG telemetry service
|
||||
s_httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/");
|
||||
_httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
s_httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
|
||||
_httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
|
||||
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Post, "installs/"))
|
||||
{
|
||||
@@ -75,7 +94,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
|
||||
// Make a HTTP Post to telemetry service
|
||||
// https://telemetry.umbraco.com/installs/
|
||||
// Fire & Forget, do not need to know if its a 200, 500 etc
|
||||
using (HttpResponseMessage response = await s_httpClient.SendAsync(request))
|
||||
using (await _httpClient.SendAsync(request))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,13 +94,13 @@ namespace Umbraco.Cms.Core.Models.Mapping
|
||||
// Umbraco.Code.MapAll -Udi -Trashed
|
||||
private static void Map(IUser source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Username;
|
||||
target.Alias = source.Key.ToString();
|
||||
target.Icon = Constants.Icons.User;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = "";
|
||||
target.Path = string.Empty;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Trashed
|
||||
|
||||
@@ -796,6 +796,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
memberDto.PasswordConfig = entity.PasswordConfiguration ?? DefaultPasswordConfigJson;
|
||||
changedCols.Add("passwordConfig");
|
||||
}
|
||||
|
||||
if (entity.IsPropertyDirty("EmailConfirmedDate"))
|
||||
{
|
||||
changedCols.Add("emailConfirmedDate");
|
||||
}
|
||||
|
||||
// If userlogin or the email has changed then need to reset security stamp
|
||||
if (changedCols.Contains("Email") || changedCols.Contains("LoginName"))
|
||||
|
||||
+1
-1
@@ -195,7 +195,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
aliasRight: "ct")
|
||||
.LeftJoin<NodeDto>("ctn").On<ContentTypeDto, NodeDto>((left, right) => left.NodeId == right.NodeId,
|
||||
aliasLeft: "ct", aliasRight: "ctn")
|
||||
.Where<UnionHelperDto>(x => x.OtherId != id, "x");
|
||||
.Where<UnionHelperDto>(x => x.Id == id, "x");
|
||||
|
||||
if (filterMustBeIsDependency)
|
||||
{
|
||||
|
||||
@@ -53,11 +53,14 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
{
|
||||
var publishedContentCache = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Content;
|
||||
var publishedContentType = publishedContentCache.GetContentType(contentTypeKey);
|
||||
if (publishedContentType != null)
|
||||
if (publishedContentType != null && publishedContentType.IsElement)
|
||||
{
|
||||
var modelType = ModelType.For(publishedContentType.Alias);
|
||||
|
||||
return _publishedModelFactory.MapModelType(modelType);
|
||||
// TODO Get the model type without having to construct a list
|
||||
var listType = _publishedModelFactory.CreateModelList(publishedContentType.Alias).GetType();
|
||||
if (listType.GenericTypeArguments.Length == 1)
|
||||
{
|
||||
return listType.GenericTypeArguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
return typeof(IPublishedElement);
|
||||
|
||||
+80
-26
@@ -30,7 +30,8 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
=> propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.BlockList);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof(BlockListModel);
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
|
||||
=> typeof(BlockListModel);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
|
||||
@@ -38,24 +39,27 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview)
|
||||
{
|
||||
return source?.ToString();
|
||||
}
|
||||
=> source?.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
|
||||
{
|
||||
// NOTE: The intermediate object is just a json string, we don't actually convert from source -> intermediate since source is always just a json string
|
||||
|
||||
// NOTE: The intermediate object is just a JSON string, we don't actually convert from source -> intermediate since source is always just a JSON string
|
||||
using (_proflog.DebugDuration<BlockListPropertyValueConverter>($"ConvertPropertyToBlockList ({propertyType.DataType.Id})"))
|
||||
{
|
||||
var value = (string)inter;
|
||||
|
||||
// Short-circuit on empty values
|
||||
if (string.IsNullOrWhiteSpace(value)) return BlockListModel.Empty;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return BlockListModel.Empty;
|
||||
}
|
||||
|
||||
var converted = _blockListEditorDataConverter.Deserialize(value);
|
||||
if (converted.BlockValue.ContentData.Count == 0) return BlockListModel.Empty;
|
||||
if (converted.BlockValue.ContentData.Count == 0)
|
||||
{
|
||||
return BlockListModel.Empty;
|
||||
}
|
||||
|
||||
var blockListLayout = converted.Layout.ToObject<IEnumerable<BlockListLayoutItem>>();
|
||||
|
||||
@@ -68,45 +72,69 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
var contentPublishedElements = new Dictionary<Guid, IPublishedElement>();
|
||||
foreach (var data in converted.BlockValue.ContentData)
|
||||
{
|
||||
if (!blockConfigMap.ContainsKey(data.ContentTypeKey)) continue;
|
||||
if (!blockConfigMap.ContainsKey(data.ContentTypeKey))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
|
||||
if (element == null) continue;
|
||||
if (element == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
contentPublishedElements[element.Key] = element;
|
||||
}
|
||||
|
||||
// If there are no content elements, it doesn't matter what is stored in layout
|
||||
if (contentPublishedElements.Count == 0) return BlockListModel.Empty;
|
||||
if (contentPublishedElements.Count == 0)
|
||||
{
|
||||
return BlockListModel.Empty;
|
||||
}
|
||||
|
||||
// Convert the settings data
|
||||
var settingsPublishedElements = new Dictionary<Guid, IPublishedElement>();
|
||||
foreach (var data in converted.BlockValue.SettingsData)
|
||||
{
|
||||
if (!validSettingsElementTypes.Contains(data.ContentTypeKey)) continue;
|
||||
if (!validSettingsElementTypes.Contains(data.ContentTypeKey))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
|
||||
if (element == null) continue;
|
||||
if (element == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
settingsPublishedElements[element.Key] = element;
|
||||
}
|
||||
|
||||
var layout = new List<BlockListItem>();
|
||||
// Cache constructors locally (it's tied to the current IPublishedSnapshot and IPublishedModelFactory)
|
||||
var blockListItemActivator = new BlockListItemActivator(_blockConverter);
|
||||
|
||||
var list = new List<BlockListItem>();
|
||||
foreach (var layoutItem in blockListLayout)
|
||||
{
|
||||
// Get the content reference
|
||||
var contentGuidUdi = (GuidUdi)layoutItem.ContentUdi;
|
||||
if (!contentPublishedElements.TryGetValue(contentGuidUdi.Guid, out var contentData))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!blockConfigMap.TryGetValue(contentData.ContentType.Key, out var blockConfig))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the setting reference
|
||||
IPublishedElement settingsData = null;
|
||||
var settingGuidUdi = layoutItem.SettingsUdi != null ? (GuidUdi)layoutItem.SettingsUdi : null;
|
||||
var settingGuidUdi = (GuidUdi)layoutItem.SettingsUdi;
|
||||
if (settingGuidUdi != null)
|
||||
{
|
||||
settingsPublishedElements.TryGetValue(settingGuidUdi.Guid, out settingsData);
|
||||
}
|
||||
|
||||
// This can happen if they have a settings type, save content, remove the settings type, and display the front-end page before saving the content again
|
||||
// We also ensure that the content types match, since maybe the settings type has been changed after this has been persisted
|
||||
@@ -115,23 +143,49 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
settingsData = null;
|
||||
}
|
||||
|
||||
// Get settings type from configuration
|
||||
var settingsType = blockConfig.SettingsElementTypeKey.HasValue
|
||||
? _blockConverter.GetModelType(blockConfig.SettingsElementTypeKey.Value)
|
||||
: typeof(IPublishedElement);
|
||||
// Create instance (use content/settings type from configuration)
|
||||
var layoutRef = blockListItemActivator.CreateInstance(blockConfig.ContentElementTypeKey, blockConfig.SettingsElementTypeKey, contentGuidUdi, contentData, settingGuidUdi, settingsData);
|
||||
|
||||
// TODO: This should be optimized/cached, as calling Activator.CreateInstance is slow
|
||||
var layoutType = typeof(BlockListItem<,>).MakeGenericType(contentData.GetType(), settingsType);
|
||||
var layoutRef = (BlockListItem)Activator.CreateInstance(layoutType, contentGuidUdi, contentData, settingGuidUdi, settingsData);
|
||||
|
||||
layout.Add(layoutRef);
|
||||
list.Add(layoutRef);
|
||||
}
|
||||
|
||||
var model = new BlockListModel(layout);
|
||||
return model;
|
||||
return new BlockListModel(list);
|
||||
}
|
||||
}
|
||||
|
||||
private class BlockListItemActivator
|
||||
{
|
||||
private readonly BlockEditorConverter _blockConverter;
|
||||
private readonly Dictionary<(Guid, Guid?), Func<Udi, IPublishedElement, Udi, IPublishedElement, BlockListItem>> _contructorCache = new();
|
||||
|
||||
public BlockListItemActivator(BlockEditorConverter blockConverter)
|
||||
=> _blockConverter = blockConverter;
|
||||
|
||||
public BlockListItem CreateInstance(Guid contentTypeKey, Guid? settingsTypeKey, Udi contentUdi, IPublishedElement contentData, Udi settingsUdi, IPublishedElement settingsData)
|
||||
{
|
||||
if (!_contructorCache.TryGetValue((contentTypeKey, settingsTypeKey), out var constructor))
|
||||
{
|
||||
constructor = _contructorCache[(contentTypeKey, settingsTypeKey)] = EmitConstructor(contentTypeKey, settingsTypeKey);
|
||||
}
|
||||
|
||||
return constructor(contentUdi, contentData, settingsUdi, settingsData);
|
||||
}
|
||||
|
||||
private Func<Udi, IPublishedElement, Udi, IPublishedElement, BlockListItem> EmitConstructor(Guid contentTypeKey, Guid? settingsTypeKey)
|
||||
{
|
||||
var contentType = _blockConverter.GetModelType(contentTypeKey);
|
||||
var settingsType = settingsTypeKey.HasValue ? _blockConverter.GetModelType(settingsTypeKey.Value) : typeof(IPublishedElement);
|
||||
var type = typeof(BlockListItem<,>).MakeGenericType(contentType, settingsType);
|
||||
|
||||
var constructor = type.GetConstructor(new[] { typeof(Udi), contentType, typeof(Udi), settingsType });
|
||||
if (constructor == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not find the required public constructor on {type}.");
|
||||
}
|
||||
|
||||
// We use unsafe here, because we know the contructor parameter count and types match
|
||||
return ReflectionUtilities.EmitConstructorUnsafe<Func<Udi, IPublishedElement, Udi, IPublishedElement, BlockListItem>>(constructor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-9
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
@@ -25,9 +25,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
/// </summary>
|
||||
public NestedContentManyValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory, IProfilingLogger proflog)
|
||||
: base(publishedSnapshotAccessor, publishedModelFactory)
|
||||
{
|
||||
_proflog = proflog;
|
||||
}
|
||||
=> _proflog = proflog;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsConverter(IPublishedPropertyType propertyType)
|
||||
@@ -37,6 +35,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
|
||||
{
|
||||
var contentTypes = propertyType.DataType.ConfigurationAs<NestedContentConfiguration>().ContentTypes;
|
||||
|
||||
return contentTypes.Length == 1
|
||||
? typeof(IEnumerable<>).MakeGenericType(ModelType.For(contentTypes[0].Alias))
|
||||
: typeof(IEnumerable<IPublishedElement>);
|
||||
@@ -48,9 +47,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview)
|
||||
{
|
||||
return source?.ToString();
|
||||
}
|
||||
=> source?.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
|
||||
@@ -64,16 +61,24 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
: new List<IPublishedElement>();
|
||||
|
||||
var value = (string)inter;
|
||||
if (string.IsNullOrWhiteSpace(value)) return elements;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return elements;
|
||||
}
|
||||
|
||||
var objects = JsonConvert.DeserializeObject<List<JObject>>(value);
|
||||
if (objects.Count == 0) return elements;
|
||||
if (objects.Count == 0)
|
||||
{
|
||||
return elements;
|
||||
}
|
||||
|
||||
foreach (var sourceObject in objects)
|
||||
{
|
||||
var element = ConvertToElement(sourceObject, referenceCacheLevel, preview);
|
||||
if (element != null)
|
||||
{
|
||||
elements.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
|
||||
+14
-13
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
@@ -25,9 +25,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
/// </summary>
|
||||
public NestedContentSingleValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory, IProfilingLogger proflog)
|
||||
: base(publishedSnapshotAccessor, publishedModelFactory)
|
||||
{
|
||||
_proflog = proflog;
|
||||
}
|
||||
=> _proflog = proflog;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsConverter(IPublishedPropertyType propertyType)
|
||||
@@ -37,9 +35,10 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
|
||||
{
|
||||
var contentTypes = propertyType.DataType.ConfigurationAs<NestedContentConfiguration>().ContentTypes;
|
||||
return contentTypes.Length > 1
|
||||
? typeof(IPublishedElement)
|
||||
: ModelType.For(contentTypes[0].Alias);
|
||||
|
||||
return contentTypes.Length == 1
|
||||
? ModelType.For(contentTypes[0].Alias)
|
||||
: typeof(IPublishedElement);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -48,9 +47,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview)
|
||||
{
|
||||
return source?.ToString();
|
||||
}
|
||||
=> source?.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
|
||||
@@ -58,14 +55,18 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
using (_proflog.DebugDuration<NestedContentSingleValueConverter>($"ConvertPropertyToNestedContent ({propertyType.DataType.Id})"))
|
||||
{
|
||||
var value = (string)inter;
|
||||
if (string.IsNullOrWhiteSpace(value)) return null;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var objects = JsonConvert.DeserializeObject<List<JObject>>(value);
|
||||
if (objects.Count == 0)
|
||||
{
|
||||
return null;
|
||||
if (objects.Count > 1)
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
// Only return the first (existing data might contain more than is currently configured)
|
||||
return ConvertToElement(objects[0], referenceCacheLevel, preview);
|
||||
}
|
||||
}
|
||||
|
||||
+20
-16
@@ -14,52 +14,56 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
|
||||
{
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
|
||||
protected IPublishedModelFactory PublishedModelFactory { get; }
|
||||
|
||||
protected NestedContentValueConverterBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory)
|
||||
{
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
||||
PublishedModelFactory = publishedModelFactory;
|
||||
}
|
||||
|
||||
protected IPublishedModelFactory PublishedModelFactory { get; }
|
||||
|
||||
public static bool IsNested(IPublishedPropertyType publishedProperty)
|
||||
{
|
||||
return publishedProperty.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.NestedContent);
|
||||
}
|
||||
=> publishedProperty.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.NestedContent);
|
||||
|
||||
public static bool IsNestedSingle(IPublishedPropertyType publishedProperty)
|
||||
private static bool IsSingle(IPublishedPropertyType publishedProperty)
|
||||
{
|
||||
if (!IsNested(publishedProperty))
|
||||
return false;
|
||||
|
||||
var config = publishedProperty.DataType.ConfigurationAs<NestedContentConfiguration>();
|
||||
|
||||
return config.MinItems == 1 && config.MaxItems == 1;
|
||||
}
|
||||
|
||||
public static bool IsNestedSingle(IPublishedPropertyType publishedProperty)
|
||||
=> IsNested(publishedProperty) && IsSingle(publishedProperty);
|
||||
|
||||
public static bool IsNestedMany(IPublishedPropertyType publishedProperty)
|
||||
{
|
||||
return IsNested(publishedProperty) && !IsNestedSingle(publishedProperty);
|
||||
}
|
||||
=> IsNested(publishedProperty) && !IsSingle(publishedProperty);
|
||||
|
||||
protected IPublishedElement ConvertToElement(JObject sourceObject, PropertyCacheLevel referenceCacheLevel, bool preview)
|
||||
{
|
||||
var elementTypeAlias = sourceObject[NestedContentPropertyEditor.ContentTypeAliasPropertyKey]?.ToObject<string>();
|
||||
if (string.IsNullOrEmpty(elementTypeAlias))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
|
||||
// only convert element types - content types will cause an exception when PublishedModelFactory creates the model
|
||||
|
||||
// Only convert element types - content types will cause an exception when PublishedModelFactory creates the model
|
||||
var publishedContentType = publishedSnapshot.Content.GetContentType(elementTypeAlias);
|
||||
if (publishedContentType == null || publishedContentType.IsElement == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var propertyValues = sourceObject.ToObject<Dictionary<string, object>>();
|
||||
|
||||
if (!propertyValues.TryGetValue("key", out var keyo)
|
||||
|| !Guid.TryParse(keyo.ToString(), out var key))
|
||||
if (!propertyValues.TryGetValue("key", out var keyo) || !Guid.TryParse(keyo.ToString(), out var key))
|
||||
{
|
||||
key = Guid.Empty;
|
||||
}
|
||||
|
||||
IPublishedElement element = new PublishedElement(publishedContentType, key, propertyValues, preview, referenceCacheLevel, _publishedSnapshotAccessor);
|
||||
element = PublishedModelFactory.CreateModel(element);
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
internal class FileSystemMainDomLock : IMainDomLock
|
||||
{
|
||||
private readonly ILogger<FileSystemMainDomLock> _logger;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IOptionsMonitor<GlobalSettings> _globalSettings;
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
||||
private readonly string _lockFilePath;
|
||||
@@ -29,6 +30,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
IOptionsMonitor<GlobalSettings> globalSettings)
|
||||
{
|
||||
_logger = logger;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_globalSettings = globalSettings;
|
||||
|
||||
var lockFileName = $"MainDom_{mainDomKeyGenerator.GenerateKey()}.lock";
|
||||
@@ -45,6 +47,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(_hostingEnvironment.LocalTempPath);
|
||||
_logger.LogDebug("Attempting to obtain MainDom lock file handle {lockFilePath}", _lockFilePath);
|
||||
_lockFileStream = File.Open(_lockFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
|
||||
DeleteLockReleaseSignalFile();
|
||||
|
||||
@@ -552,7 +552,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
|
||||
public IActionResult Import(string file)
|
||||
{
|
||||
var filePath = Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data), file);
|
||||
var filePath = Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads), file);
|
||||
if (string.IsNullOrEmpty(file) || !System.IO.File.Exists(filePath))
|
||||
{
|
||||
return NotFound();
|
||||
@@ -601,13 +601,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
|
||||
if (ext.InvariantEquals("udt"))
|
||||
{
|
||||
model.TempFileName = Path.Combine(root, fileName);
|
||||
model.TempFileName = fileName;
|
||||
|
||||
var xd = new XmlDocument
|
||||
{
|
||||
XmlResolver = null
|
||||
};
|
||||
xd.Load(model.TempFileName);
|
||||
xd.Load(Path.Combine(root, fileName));
|
||||
|
||||
model.Alias = xd.DocumentElement?.SelectSingleNode("//DocumentType/Info/Alias")?.FirstChild.Value;
|
||||
model.Name = xd.DocumentElement?.SelectSingleNode("//DocumentType/Info/Name")?.FirstChild.Value;
|
||||
|
||||
@@ -226,8 +226,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
var fileName = file.FileName.Trim(new[] { '\"' }).TrimEnd();
|
||||
var safeFileName = fileName.ToSafeFileName(shortStringHelper);
|
||||
var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower();
|
||||
const string allowedAvatarFileTypes = "jpeg,jpg,gif,bmp,png,tiff,tif,webp";
|
||||
|
||||
if (contentSettings.DisallowedUploadFiles.Contains(ext) == false)
|
||||
if (allowedAvatarFileTypes.Contains(ext) == true && contentSettings.DisallowedUploadFiles.Contains(ext) == false)
|
||||
{
|
||||
//generate a path of known data, we don't want this path to be guessable
|
||||
user.Avatar = "UserAvatars/" + (user.Id + safeFileName).GenerateHash<SHA1>() + "." + ext;
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Install;
|
||||
using Umbraco.Cms.Core.Install.Models;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
@@ -93,11 +94,14 @@ namespace Umbraco.Cms.Web.BackOffice.Install
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> CompleteInstall()
|
||||
{
|
||||
|
||||
var levelBeforeRestart = _runtime.State.Level;
|
||||
await _runtime.RestartAsync();
|
||||
|
||||
var identityUser = await _backOfficeUserManager.FindByIdAsync(Core.Constants.Security.SuperUserIdAsString);
|
||||
_backOfficeSignInManager.SignInAsync(identityUser, false);
|
||||
if (levelBeforeRestart == RuntimeLevel.Install)
|
||||
{
|
||||
var identityUser = await _backOfficeUserManager.FindByIdAsync(Core.Constants.Security.SuperUserIdAsString);
|
||||
_backOfficeSignInManager.SignInAsync(identityUser, false);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
@@ -186,7 +186,8 @@ namespace Umbraco.Extensions
|
||||
builder.Services.AddHostedService(provider =>
|
||||
new ReportSiteTask(
|
||||
provider.GetRequiredService<ILogger<ReportSiteTask>>(),
|
||||
provider.GetRequiredService<ITelemetryService>()));
|
||||
provider.GetRequiredService<ITelemetryService>(),
|
||||
provider.GetRequiredService<IRuntimeState>()));
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
+21167
-40
File diff suppressed because it is too large
Load Diff
@@ -39,8 +39,8 @@
|
||||
"diff": "3.5.0",
|
||||
"flatpickr": "4.6.9",
|
||||
"font-awesome": "4.7.0",
|
||||
"jquery": "^3.5.1",
|
||||
"jquery-ui-dist": "1.12.1",
|
||||
"jquery": "3.6.0",
|
||||
"jquery-ui-dist": "1.13.1",
|
||||
"jquery-ui-touch-punch": "0.2.3",
|
||||
"lazyload-js": "1.0.0",
|
||||
"moment": "2.22.2",
|
||||
|
||||
@@ -84,12 +84,20 @@ namespace Umbraco.Cms.Web.Website.Routing
|
||||
switch (publicAccessStatus)
|
||||
{
|
||||
case PublicAccessStatus.NotLoggedIn:
|
||||
_logger.LogDebug("EnsurePublishedContentAccess: Not logged in, redirect to login page");
|
||||
routeValues = await SetPublishedContentAsOtherPageAsync(httpContext, routeValues.PublishedRequest, publicAccessAttempt.Result.LoginNodeId);
|
||||
// redirect if this is not the login page
|
||||
if (publicAccessAttempt.Result.LoginNodeId != publishedContent.Id)
|
||||
{
|
||||
_logger.LogDebug("EnsurePublishedContentAccess: Not logged in, redirect to login page");
|
||||
routeValues = await SetPublishedContentAsOtherPageAsync(httpContext, routeValues.PublishedRequest, publicAccessAttempt.Result.LoginNodeId);
|
||||
}
|
||||
break;
|
||||
case PublicAccessStatus.AccessDenied:
|
||||
_logger.LogDebug("EnsurePublishedContentAccess: Current member has not access, redirect to error page");
|
||||
routeValues = await SetPublishedContentAsOtherPageAsync(httpContext, routeValues.PublishedRequest, publicAccessAttempt.Result.NoAccessNodeId);
|
||||
// Redirect if this is not the access denied page
|
||||
if (publicAccessAttempt.Result.NoAccessNodeId != publishedContent.Id)
|
||||
{
|
||||
_logger.LogDebug("EnsurePublishedContentAccess: Current member has not access, redirect to error page");
|
||||
routeValues = await SetPublishedContentAsOtherPageAsync(httpContext, routeValues.PublishedRequest, publicAccessAttempt.Result.NoAccessNodeId);
|
||||
}
|
||||
break;
|
||||
case PublicAccessStatus.LockedOut:
|
||||
_logger.LogDebug("Current member is locked out, redirect to error page");
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"description": "The version of Umbraco.Cms to add as PackageReference.",
|
||||
"type": "parameter",
|
||||
"datatype": "string",
|
||||
"defaultValue": "9.5.1",
|
||||
"defaultValue": "9.5.4",
|
||||
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE"
|
||||
},
|
||||
"namespaceReplacer": {
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"description": "The version of Umbraco.Cms to add as PackageReference.",
|
||||
"type": "parameter",
|
||||
"datatype": "string",
|
||||
"defaultValue": "9.5.1",
|
||||
"defaultValue": "9.5.4",
|
||||
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE"
|
||||
},
|
||||
"UseHttpsRedirect": {
|
||||
|
||||
@@ -13,7 +13,8 @@ context('Languages', () => {
|
||||
cy.umbracoEnsureLanguageCultureNotExists(culture);
|
||||
cy.umbracoSection('settings');
|
||||
|
||||
cy.get('.umb-tree-root-link').contains('Settings')
|
||||
cy.get('.umb-box-content').should('be.visible');
|
||||
cy.get('li .umb-tree-root:contains("Settings")').should("be.visible");
|
||||
// Enter language tree and create new language
|
||||
cy.umbracoTreeItem('settings', ['Languages']).click();
|
||||
cy.umbracoButtonByLabelKey('languages_addLanguage').click();
|
||||
|
||||
Reference in New Issue
Block a user