Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Models/PropertyTagsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static class PropertyTagsExtensions
: dataTypeService.GetDataType(property.PropertyType.DataTypeId)?.Configuration;
TagConfiguration? configuration = ConfigurationEditor.ConfigurationAs<TagConfiguration>(configurationObject);

if (configuration?.Delimiter == default && configuration?.Delimiter is not null)
if (configuration is not null && configuration.Delimiter == default)
{
configuration.Delimiter = tagAttribute.Delimiter;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ public class BlockGridLayoutItem : IBlockLayoutItem
[JsonProperty("rowSpan", NullValueHandling = NullValueHandling.Ignore)]
public int? RowSpan { get; set; }

[JsonProperty("forceLeft")]
public bool ForceLeft { get; set; }

[JsonProperty("forceRight")]
public bool ForceRight { get; set; }

[JsonProperty("areas", NullValueHandling = NullValueHandling.Ignore)]
public BlockGridLayoutAreaItem[] Areas { get; set; } = Array.Empty<BlockGridLayoutAreaItem>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public override bool IsConverter(IPublishedPropertyType propertyType)

blockItem.RowSpan = layoutItem.RowSpan!.Value;
blockItem.ColumnSpan = layoutItem.ColumnSpan!.Value;
blockItem.ForceLeft = layoutItem.ForceLeft;
blockItem.ForceRight = layoutItem.ForceRight;
blockItem.AreaGridColumns = blockConfig.AreaGridColumns;
blockItem.GridColumns = configuration.GridColumns;
blockItem.Areas = layoutItem.Areas.Select(area =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Web;

namespace Umbraco.Cms.Web.Common.AspNetCore;

/// <summary>
/// Notification handler which will listen to the <see cref="UmbracoRequestBeginNotification"/>, and ensure that
/// the applicationUrl is set on the first request.
/// </summary>
internal class ApplicationUrlRequestBeginNotificationHandler : INotificationHandler<UmbracoRequestBeginNotification>
{
private readonly IRequestAccessor _requestAccessor;

public ApplicationUrlRequestBeginNotificationHandler(IRequestAccessor requestAccessor) =>
_requestAccessor = requestAccessor;

public void Handle(UmbracoRequestBeginNotification notification)
{
// If someone has replaced the AspNetCoreRequestAccessor we'll do nothing and assume they handle it themselves.
if (_requestAccessor is AspNetCoreRequestAccessor accessor)
{
accessor.EnsureApplicationUrl();
}
}
}
35 changes: 24 additions & 11 deletions src/Umbraco.Web.Common/AspNetCore/AspNetCoreRequestAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Umbraco.Cms.Web.Common.AspNetCore;

public class AspNetCoreRequestAccessor : IRequestAccessor, INotificationHandler<UmbracoRequestBeginNotification>
public class AspNetCoreRequestAccessor : IRequestAccessor, INotificationHandler<UmbracoRequestBeginNotification>, IDisposable
{
private readonly ISet<string> _applicationUrls = new HashSet<string>();
private readonly IHttpContextAccessor _httpContextAccessor;
Expand All @@ -18,6 +18,7 @@ public class AspNetCoreRequestAccessor : IRequestAccessor, INotificationHandler<
private object _initLocker = new();
private bool _isInit;
private WebRoutingSettings _webRoutingSettings;
private readonly IDisposable? _onChangeDisposable;

/// <summary>
/// Initializes a new instance of the <see cref="AspNetCoreRequestAccessor" /> class.
Expand All @@ -28,20 +29,19 @@ public AspNetCoreRequestAccessor(
{
_httpContextAccessor = httpContextAccessor;
_webRoutingSettings = webRoutingSettings.CurrentValue;
webRoutingSettings.OnChange(x => _webRoutingSettings = x);
_onChangeDisposable = webRoutingSettings.OnChange(x => _webRoutingSettings = x);
}

/// <summary>
/// This just initializes the application URL on first request attempt
/// TODO: This doesn't belong here, the GetApplicationUrl doesn't belong to IRequestAccessor
/// this should be part of middleware not a lazy init based on an INotification
/// <para>
/// This is now a NoOp, and is no longer used, instead ApplicationUrlRequestBeginNotificationHandler is used
/// </para>
/// </summary>
[Obsolete("This is no longer used, AspNetCoreRequestAccessor will no longer implement INotificationHandler in V12, see ApplicationUrlRequestBeginNotificationHandler instead.")]
public void Handle(UmbracoRequestBeginNotification notification)
=> LazyInitializer.EnsureInitialized(ref _hasAppUrl, ref _isInit, ref _initLocker, () =>
{
GetApplicationUrl();
return true;
});
{
// NoOP
}

/// <inheritdoc />
public string? GetRequestValue(string name) => GetFormValue(name) ?? GetQueryStringValue(name);
Expand All @@ -54,6 +54,17 @@ public void Handle(UmbracoRequestBeginNotification notification)
? new Uri(_httpContextAccessor.HttpContext.Request.GetEncodedUrl())
: null;

/// <summary>
/// Ensure that the ApplicationUrl is set on the first request, this is using a LazyInitializer, so the code will only be run the first time
/// </summary>
/// TODO: This doesn't belong here, the GetApplicationUrl doesn't belong to IRequestAccessor
internal void EnsureApplicationUrl() =>
LazyInitializer.EnsureInitialized(ref _hasAppUrl, ref _isInit, ref _initLocker, () =>
{
GetApplicationUrl();
return true;
});

public Uri? GetApplicationUrl()
{
// Fixme: This causes problems with site swap on azure because azure pre-warms a site by calling into `localhost` and when it does that
Expand All @@ -63,7 +74,7 @@ public void Handle(UmbracoRequestBeginNotification notification)
// see U4-10626 - in some cases we want to reset the application url
// (this is a simplified version of what was in 7.x)
// note: should this be optional? is it expensive?
if (!(_webRoutingSettings.UmbracoApplicationUrl is null))
if (_webRoutingSettings.UmbracoApplicationUrl is not null)
{
return new Uri(_webRoutingSettings.UmbracoApplicationUrl);
}
Expand Down Expand Up @@ -96,4 +107,6 @@ public void Handle(UmbracoRequestBeginNotification notification)

return request.Form[name];
}

public void Dispose() => _onChangeDisposable?.Dispose();
}
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ public static IUmbracoBuilder AddWebComponents(this IUmbracoBuilder builder)
// AspNetCore specific services
builder.Services.AddUnique<IRequestAccessor, AspNetCoreRequestAccessor>();
builder.AddNotificationHandler<UmbracoRequestBeginNotification, AspNetCoreRequestAccessor>();
builder.AddNotificationHandler<UmbracoRequestBeginNotification, ApplicationUrlRequestBeginNotificationHandler>();

// Password hasher
builder.Services.AddUnique<IPasswordHasher, AspNetCorePasswordHasher>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function ourPackageRepositoryResource($q, $http, umbDataFormatter, umbRequestHel
if (canceler) {
httpConfig["timeout"] = canceler;
}

if (category === undefined) {
category = "";
}
Expand All @@ -69,8 +69,10 @@ function ourPackageRepositoryResource($q, $http, umbDataFormatter, umbRequestHel
var order = !orderBy ? "&order=Default" : ("&order=" + orderBy);

return umbRequestHelper.resourcePromise(
$http.get(baseurl + "?pageIndex=" + pageIndex + "&pageSize=" + pageSize + "&category=" + category + "&query=" + query + order + "&version=" + Umbraco.Sys.ServerVariables.application.version),
httpConfig,
$http.get(
baseurl + "?pageIndex=" + pageIndex + "&pageSize=" + pageSize + "&category=" + category + "&query=" + query + order + "&version=" + Umbraco.Sys.ServerVariables.application.version,
httpConfig
),
'Failed to query packages');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe
return $q.reject({
errorMsg: result.errorMsg,
data: result.data,
status: result.status
status: result.status,
xhrStatus: response.xhrStatus
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<div class="control-header" ng-hide="vm.property.hideLabel === true">

<label class="control-label" for="{{vm.property.alias}}" ng-attr-title="{{vm.controlLabelTitle}}">{{vm.property.label}}<span ng-if="vm.property.validation.mandatory || vm.property.ncMandatory"><strong class="umb-control-required">*</strong></span></label>
<label data-element="property-label-{{vm.property.alias}}" class="control-label" for="{{vm.property.alias}}" ng-attr-title="{{vm.controlLabelTitle}}">{{vm.property.label}}<span ng-if="vm.property.validation.mandatory || vm.property.ncMandatory"><strong class="umb-control-required">*</strong></span></label>

<umb-property-actions actions="vm.propertyActions"></umb-property-actions>

Expand Down Expand Up @@ -41,11 +41,11 @@
<span ng-if="vm.property.variations === 'Culture' || vm.property.variations === 'CultureAndSegment'" class="umb-property-variant-label">
{{ vm.property.culture }}
</span>

<span ng-if="vm.property.variations === 'Culture' && vm.node.documentType.variations === 'CultureAndSegment'" class="umb-property-variant-label">
<localize key="languages_invariantSegmentProperty"></localize>
</span>

<span ng-if="vm.property.variations === 'Segment' || vm.property.variations === 'CultureAndSegment'" class="umb-property-variant-label">
<span>{{ vm.property.segment }}</span>
<span ng-if="!vm.property.segment">Default</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<input type="hidden" name="partialView" ng-model="mandatoryViewValidator" ng-required="!model.macro.node" />

<button type="button"
id="{{vm.macroPartialViewPickerProperty.alias}}"
class="umb-node-preview-add"
ng-show="!model.macro.node"
ng-click="model.openViewPicker()">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(function () {
"use strict";

function PackagesRepoController($scope, $timeout, ourPackageRepositoryResource, $q, localizationService) {
function PackagesRepoController($scope, $timeout, ourPackageRepositoryResource, $q, localizationService, notificationsService) {

var vm = this;

Expand Down Expand Up @@ -197,33 +197,47 @@


var searchDebounced = _.debounce(function (e) {
//a canceler exists, so perform the cancelation operation and reset
if (canceler) {
canceler.resolve();
}

$scope.$apply(function () {

//a canceler exists, so perform the cancelation operation and reset
if (canceler) {
canceler.resolve();
canceler = $q.defer();
}
else {
canceler = $q.defer();
}
canceler = $q.defer();

$scope.$apply(function () {
currSort = vm.searchQuery ? "Default" : "Latest";

ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1,
vm.pagination.pageSize,
currSort,
"",
vm.searchQuery,
canceler)
canceler.promise)
.then(function (pack) {
vm.packages = pack.packages;
vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
vm.pagination.pageNumber = 1;
vm.loading = false;
//set back to null so it can be re-created
canceler = null;
})
.catch(function (err) {
canceler = null;

if (err) {
// If an abort happened, ignore it since it happened because of a new search
if (err.xhrStatus === 'abort') {
return;
}

// Otherwise, show the error
if (err.errorMsg) {
notificationsService.error(err.errorMsg);
return;
}
}

console.error(err);
});

});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
localize="placeholder"
placeholder="@packager_packageSearch"
ng-model="vm.searchQuery"
ng-change="vm.search()"
ng-on-input="vm.search()"
no-dirty-check />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ test.describe('Macros', () => {
await form.locator(".btn-primary").click();

// Adds partial view to macro
await page.locator('[label="Macro partial view"]').click();
await page.locator('[data-element="property-label-macroPartialViewPickerProperty"]').click();
await page.locator('[data-element="tree-item-' + partialViewName + '.cshtml"]').click();

await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.save));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,9 @@ public void Create_Tag_Data_Bulk_Publish_Operation()
var dataType = DataTypeService.GetDataType(1041);
dataType.Configuration = new TagConfiguration { Group = "test", StorageType = TagsStorageType.Csv };

// updating the data type with the new configuration
DataTypeService.Save(dataType);

var template = TemplateBuilder.CreateTextPageTemplate();
FileService.SaveTemplate(template);

Expand Down Expand Up @@ -822,6 +825,76 @@ public void Can_Remove_Tag_Data_To_Published_Content()
}
}

[Test]
public void Does_Not_Save_Multiple_Tags_As_One_When_CSV_Storage()
{
// Arrange
// set configuration
var dataType = DataTypeService.GetDataType(1041);
dataType.Configuration = new TagConfiguration { Group = "test", StorageType = TagsStorageType.Csv };

// updating the data type with the new configuration
DataTypeService.Save(dataType);

var template = TemplateBuilder.CreateTextPageTemplate();
FileService.SaveTemplate(template);

var contentType = ContentTypeBuilder.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type",
mandatoryProperties: true, defaultTemplateId: template.Id);
CreateAndAddTagsPropertyType(contentType);

ContentTypeService.Save(contentType);

IContent content = ContentBuilder.CreateSimpleContent(contentType, "Tagged content");
content.AssignTags(PropertyEditorCollection, DataTypeService, Serializer, "tags",
new[] { "hello,world,tags", "new"});

ContentService.SaveAndPublish(content);

// Act
content = ContentService.GetById(content.Id);
var savedTags = content.Properties["tags"].GetTagsValue(PropertyEditorCollection, DataTypeService, Serializer)
.ToArray();

// Assert
Assert.AreEqual(4, savedTags.Length);
}

[Test]
public void Can_Save_Tag_With_Comma_Separated_Values_As_One_When_JSON_Storage()
{
// Arrange
// set configuration
var dataType = DataTypeService.GetDataType(1041);
dataType.Configuration = new TagConfiguration { Group = "test", StorageType = TagsStorageType.Json };

// updating the data type with the new configuration
DataTypeService.Save(dataType);

var template = TemplateBuilder.CreateTextPageTemplate();
FileService.SaveTemplate(template);

var contentType = ContentTypeBuilder.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type",
mandatoryProperties: true, defaultTemplateId: template.Id);
CreateAndAddTagsPropertyType(contentType);

ContentTypeService.Save(contentType);

IContent content = ContentBuilder.CreateSimpleContent(contentType, "Tagged content");
content.AssignTags(PropertyEditorCollection, DataTypeService, Serializer, "tags",
new[] { "hello,world,tags", "new"});

ContentService.SaveAndPublish(content);

// Act
content = ContentService.GetById(content.Id);
var savedTags = content.Properties["tags"].GetTagsValue(PropertyEditorCollection, DataTypeService, Serializer)
.ToArray();

// Assert
Assert.AreEqual(2, savedTags.Length);
}

private PropertyType CreateAndAddTagsPropertyType(ContentType contentType,
ContentVariation variations = ContentVariation.Nothing)
{
Expand Down