Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 78735ff349 | |||
| 1af43498d9 | |||
| 015ad64e30 | |||
| 6dbb988903 | |||
| 926acb910e | |||
| 57e3187e3a | |||
| 988d51c4c8 | |||
| 4733256ca1 | |||
| 4133a1fe76 | |||
| 37bf6fe938 | |||
| 31716e574c | |||
| 126c4cbd46 | |||
| 7cc91f71c2 | |||
| 7c52b9602c | |||
| c5f1cc15fd | |||
| d089518681 | |||
| cd6ef35bf9 | |||
| 47cdc79fcb | |||
| f7382255c2 | |||
| 96d5bdd7b2 |
@@ -10,6 +10,8 @@ namespace Umbraco.Core
|
||||
///</summary>
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
internal static bool IsCollectionEmpty<T>(this IReadOnlyCollection<T> list) => list == null || list.Count == 0;
|
||||
|
||||
internal static bool HasDuplicates<T>(this IEnumerable<T> items, bool includeNull)
|
||||
{
|
||||
var hs = new HashSet<T>();
|
||||
@@ -112,7 +114,6 @@ namespace Umbraco.Core
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if all items in the other collection exist in this collection
|
||||
/// </summary>
|
||||
|
||||
@@ -74,6 +74,11 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
throw new InvalidOperationException($"Version {currentVersion} cannot be migrated to {UmbracoVersion.SemanticVersion}."
|
||||
+ $" Please upgrade first to at least {minVersion}.");
|
||||
|
||||
// Force versions between 7.14.*-7.15.* into into 7.14 initial state. Because there is no db-changes,
|
||||
// and we don't want users to workaround my putting in version 7.14.0 them self.
|
||||
if (minVersion <= currentVersion && currentVersion < new SemVersion(7, 16))
|
||||
return GetInitState(minVersion);
|
||||
|
||||
// initial state is eg "{init-7.14.0}"
|
||||
return GetInitState(currentVersion);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,20 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
.Where<DataTypeDto>(x => x.EditorAlias == toAlias));
|
||||
|
||||
if (oldCount > 0)
|
||||
throw new InvalidOperationException($"Cannot rename datatype alias \"{fromAlias}\" to \"{toAlias}\" because the target alias is already used.");
|
||||
{
|
||||
// If we throw it means that the upgrade will exit and cannot continue.
|
||||
// This will occur if a v7 site has the old "Obsolete" property editors that are already named with the `toAlias` name.
|
||||
// TODO: We should have an additional upgrade step when going from 7 -> 8 like we did with 6 -> 7 that shows a compatibility report,
|
||||
// this would include this check and then we can provide users with information on what they should do (i.e. before upgrading to v8 they will
|
||||
// need to migrate these old obsolete editors to non-obsolete editors)
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot rename datatype alias \"{fromAlias}\" to \"{toAlias}\" because the target alias is already used." +
|
||||
$"This is generally because when upgrading from a v7 to v8 site, the v7 site contains Data Types that reference old and already Obsolete " +
|
||||
$"Property Editors. Before upgrading to v8, any Data Types using property editors that are named with the prefix '(Obsolete)' must be migrated " +
|
||||
$"to the non-obsolete v7 property editors of the same type.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Database.Execute(Sql()
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Umbraco.Core.Models
|
||||
private string _alias;
|
||||
private string _description;
|
||||
private int _dataTypeId;
|
||||
private Guid _dataTypeKey;
|
||||
private Lazy<int> _propertyGroupId;
|
||||
private string _propertyEditorAlias;
|
||||
private ValueStorageType _valueStorageType;
|
||||
@@ -139,6 +140,13 @@ namespace Umbraco.Core.Models
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _dataTypeId, nameof(DataTypeId));
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public Guid DataTypeKey
|
||||
{
|
||||
get => _dataTypeKey;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _dataTypeKey, nameof(DataTypeKey));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias of the property editor for this property type.
|
||||
/// </summary>
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
/// <summary>
|
||||
/// Refreshes the factory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This will typically re-compiled models/classes into a new DLL that are used to populate the cache.</para>
|
||||
/// <para>This is called prior to refreshing the cache.</para>
|
||||
/// </remarks>
|
||||
void Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
propertyType.Alias = typeDto.Alias;
|
||||
propertyType.DataTypeId = typeDto.DataTypeId;
|
||||
propertyType.DataTypeKey = typeDto.DataTypeDto.NodeDto.UniqueId;
|
||||
propertyType.Description = typeDto.Description;
|
||||
propertyType.Id = typeDto.Id;
|
||||
propertyType.Key = typeDto.UniqueId;
|
||||
|
||||
@@ -7,6 +7,7 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Factories;
|
||||
using Umbraco.Core.Scoping;
|
||||
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
@@ -188,10 +189,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var groupDtos = Database.Fetch<PropertyTypeGroupDto>(sql1);
|
||||
|
||||
var sql2 = Sql()
|
||||
.Select<PropertyTypeDto>(r => r.Select(x => x.DataTypeDto))
|
||||
.Select<PropertyTypeDto>(r => r.Select(x => x.DataTypeDto, r1 => r1.Select(x => x.NodeDto)))
|
||||
.AndSelect<MemberPropertyTypeDto>()
|
||||
.From<PropertyTypeDto>()
|
||||
.InnerJoin<DataTypeDto>().On<PropertyTypeDto, DataTypeDto>((pt, dt) => pt.DataTypeId == dt.NodeId)
|
||||
.InnerJoin<NodeDto>().On<DataTypeDto, NodeDto>((dt, n) => dt.NodeId == n.NodeId)
|
||||
.InnerJoin<ContentTypeDto>().On<PropertyTypeDto, ContentTypeDto>((pt, ct) => pt.ContentTypeId == ct.NodeId)
|
||||
.LeftJoin<PropertyTypeGroupDto>().On<PropertyTypeDto, PropertyTypeGroupDto>((pt, ptg) => pt.PropertyTypeGroupId == ptg.Id)
|
||||
.LeftJoin<MemberPropertyTypeDto>().On<PropertyTypeDto, MemberPropertyTypeDto>((pt, mpt) => pt.Id == mpt.PropertyTypeId)
|
||||
@@ -290,6 +292,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
Description = dto.Description,
|
||||
DataTypeId = dto.DataTypeId,
|
||||
DataTypeKey = dto.DataTypeDto.NodeDto.UniqueId,
|
||||
Id = dto.Id,
|
||||
Key = dto.UniqueId,
|
||||
Mandatory = dto.Mandatory,
|
||||
|
||||
@@ -1013,8 +1013,9 @@ AND umbracoNode.id <> @id",
|
||||
if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
var sql = Sql()
|
||||
.SelectAll()
|
||||
.Select<DataTypeDto>(dt => dt.Select(x => x.NodeDto))
|
||||
.From<DataTypeDto>()
|
||||
.InnerJoin<NodeDto>().On<DataTypeDto, NodeDto>((dt, n) => dt.NodeId == n.NodeId)
|
||||
.Where("propertyEditorAlias = @propertyEditorAlias", new { propertyEditorAlias = propertyType.PropertyEditorAlias })
|
||||
.OrderBy<DataTypeDto>(typeDto => typeDto.NodeId);
|
||||
var datatype = Database.FirstOrDefault<DataTypeDto>(sql);
|
||||
@@ -1022,6 +1023,7 @@ AND umbracoNode.id <> @id",
|
||||
if (datatype != null)
|
||||
{
|
||||
propertyType.DataTypeId = datatype.NodeId;
|
||||
propertyType.DataTypeKey = datatype.NodeDto.UniqueId;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -225,6 +225,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
//this reset's its current data type reference which will be re-assigned based on the property editor assigned on the next line
|
||||
propertyType.DataTypeId = 0;
|
||||
propertyType.DataTypeKey = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,14 @@ namespace Umbraco.Core
|
||||
public static class PublishedModelFactoryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes an action with a safe live factory/
|
||||
/// Returns true if the current <see cref="IPublishedModelFactory"/> is an implementation of <see cref="ILivePublishedModelFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Executes an action with a safe live factory
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If the factory is a live factory, ensures it is refreshed and locked while executing the action.</para>
|
||||
@@ -20,6 +27,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
lock (liveFactory.SyncRoot)
|
||||
{
|
||||
//Call refresh on the live factory to re-compile the models
|
||||
liveFactory.Refresh();
|
||||
action();
|
||||
}
|
||||
|
||||
@@ -6,9 +6,25 @@ namespace Umbraco.Core.Services.Changes
|
||||
public enum ContentTypeChangeTypes : byte
|
||||
{
|
||||
None = 0,
|
||||
Create = 1, // item type has been created, no impact
|
||||
RefreshMain = 2, // changed, impacts content (adding property or composition does NOT)
|
||||
RefreshOther = 4, // changed, other changes
|
||||
Remove = 8 // item type has been removed
|
||||
|
||||
/// <summary>
|
||||
/// Item type has been created, no impact
|
||||
/// </summary>
|
||||
Create = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Content type changes impact only the Content type being saved
|
||||
/// </summary>
|
||||
RefreshMain = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Content type changes impacts the content type being saved and others used that are composed of it
|
||||
/// </summary>
|
||||
RefreshOther = 4, // changed, other change
|
||||
|
||||
/// <summary>
|
||||
/// Content type was removed
|
||||
/// </summary>
|
||||
Remove = 8
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ angular.module("umbraco.directives")
|
||||
uniqueId: '=',
|
||||
value: '=',
|
||||
configuration: "=", //this is the RTE configuration
|
||||
datatypeId: '@',
|
||||
datatypeKey: '@',
|
||||
ignoreUserStartNodes: '@'
|
||||
},
|
||||
templateUrl: 'views/components/grid/grid-rte.html',
|
||||
@@ -43,7 +43,7 @@ angular.module("umbraco.directives")
|
||||
scope.config = {
|
||||
ignoreUserStartNodes: scope.ignoreUserStartNodes === "true"
|
||||
}
|
||||
scope.dataTypeId = scope.datatypeId; //Yes - this casing is rediculous, but it's because the var starts with `data` so it can't be `data-type-id` :/
|
||||
scope.dataTypeKey = scope.datatypeKey; //Yes - this casing is rediculous, but it's because the var starts with `data` so it can't be `data-type-id` :/
|
||||
|
||||
//stores a reference to the editor
|
||||
var tinyMceEditor = null;
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ function treeSearchBox(localizationService, searchService, $q) {
|
||||
searchFromName: "@",
|
||||
showSearch: "@",
|
||||
section: "@",
|
||||
datatypeId: "@",
|
||||
datatypeKey: "@",
|
||||
hideSearchCallback: "=",
|
||||
searchCallback: "="
|
||||
},
|
||||
@@ -63,8 +63,8 @@ function treeSearchBox(localizationService, searchService, $q) {
|
||||
}
|
||||
|
||||
//append dataTypeId value if there is one
|
||||
if (scope.datatypeId) {
|
||||
searchArgs["dataTypeId"] = scope.datatypeId;
|
||||
if (scope.datatypeKey) {
|
||||
searchArgs["dataTypeKey"] = scope.datatypeKey;
|
||||
}
|
||||
|
||||
searcher(searchArgs).then(function (data) {
|
||||
|
||||
@@ -327,8 +327,8 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
{ type: type },
|
||||
{ culture: culture}
|
||||
];
|
||||
if (options && options.dataTypeId) {
|
||||
args.push({ dataTypeId: options.dataTypeId });
|
||||
if (options && options.dataTypeKey) {
|
||||
args.push({ dataTypeKey: options.dataTypeKey });
|
||||
}
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
@@ -356,8 +356,8 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
getChildren: function (id, type, options) {
|
||||
|
||||
var args = [{ id: id }, { type: type }];
|
||||
if (options && options.dataTypeId) {
|
||||
args.push({ dataTypeId: options.dataTypeId });
|
||||
if (options && options.dataTypeKey) {
|
||||
args.push({ dataTypeKey: options.dataTypeKey });
|
||||
}
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
@@ -434,7 +434,7 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
orderBy: options.orderBy,
|
||||
orderDirection: options.orderDirection,
|
||||
filter: encodeURIComponent(options.filter),
|
||||
dataTypeId: options.dataTypeId
|
||||
dataTypeKey: options.dataTypeKey
|
||||
}
|
||||
)),
|
||||
'Failed to retrieve child data for id ' + parentId);
|
||||
@@ -476,7 +476,7 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
filter: '',
|
||||
orderDirection: "Ascending",
|
||||
orderBy: "SortOrder",
|
||||
dataTypeId: null
|
||||
dataTypeKey: null
|
||||
};
|
||||
if (options === undefined) {
|
||||
options = {};
|
||||
@@ -506,7 +506,7 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
orderBy: options.orderBy,
|
||||
orderDirection: options.orderDirection,
|
||||
filter: encodeURIComponent(options.filter),
|
||||
dataTypeId: options.dataTypeId
|
||||
dataTypeKey: options.dataTypeKey
|
||||
}
|
||||
)),
|
||||
'Failed to retrieve child data for id ' + parentId);
|
||||
@@ -535,15 +535,15 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
* @returns {Promise} resourcePromise object containing the entity array.
|
||||
*
|
||||
*/
|
||||
search: function (query, type, searchFrom, canceler, dataTypeId) {
|
||||
search: function (query, type, searchFrom, canceler, dataTypeKey) {
|
||||
|
||||
var args = [{ query: query }, { type: type }];
|
||||
if (searchFrom) {
|
||||
args.push({ searchFrom: searchFrom });
|
||||
}
|
||||
|
||||
if (dataTypeId) {
|
||||
args.push({ dataTypeId: dataTypeId });
|
||||
if (dataTypeKey) {
|
||||
args.push({ dataTypeKey: dataTypeKey });
|
||||
}
|
||||
|
||||
var httpConfig = {};
|
||||
|
||||
@@ -67,7 +67,7 @@ angular.module('umbraco.services')
|
||||
throw "args.term is required";
|
||||
}
|
||||
|
||||
return entityResource.search(args.term, "Document", args.searchFrom, args.canceler, args.dataTypeId).then(function (data) {
|
||||
return entityResource.search(args.term, "Document", args.searchFrom, args.canceler, args.dataTypeKey).then(function (data) {
|
||||
_.each(data, function (item) {
|
||||
searchResultFormatter.configureContentResult(item);
|
||||
});
|
||||
@@ -92,7 +92,7 @@ angular.module('umbraco.services')
|
||||
throw "args.term is required";
|
||||
}
|
||||
|
||||
return entityResource.search(args.term, "Media", args.searchFrom, args.canceler, args.dataTypeId).then(function (data) {
|
||||
return entityResource.search(args.term, "Media", args.searchFrom, args.canceler, args.dataTypeKey).then(function (data) {
|
||||
_.each(data, function (item) {
|
||||
searchResultFormatter.configureMediaResult(item);
|
||||
});
|
||||
|
||||
@@ -1121,7 +1121,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
entityResource.getAnchors(args.model.value).then(function (anchorValues) {
|
||||
var linkPicker = {
|
||||
currentTarget: currentTarget,
|
||||
dataTypeId: args.model.dataTypeId,
|
||||
dataTypeKey: args.model.dataTypeKey,
|
||||
ignoreUserStartNodes: args.model.config.ignoreUserStartNodes,
|
||||
anchors: anchorValues,
|
||||
submit: function (model) {
|
||||
@@ -1159,7 +1159,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
disableFolderSelect: true,
|
||||
startNodeId: startNodeId,
|
||||
startNodeIsVirtual: startNodeIsVirtual,
|
||||
dataTypeId: args.model.dataTypeId,
|
||||
dataTypeKey: args.model.dataTypeKey,
|
||||
submit: function (model) {
|
||||
self.insertMediaInEditor(args.editor, model.selection[0]);
|
||||
editorService.close();
|
||||
|
||||
+3
-3
@@ -20,14 +20,14 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
|
||||
$scope.model.title = value;
|
||||
});
|
||||
}
|
||||
$scope.customTreeParams = dialogOptions.dataTypeId ? "dataTypeId=" + dialogOptions.dataTypeId : "";
|
||||
$scope.customTreeParams = dialogOptions.dataTypeKey ? "dataTypeKey=" + dialogOptions.dataTypeKey : "";
|
||||
$scope.dialogTreeApi = {};
|
||||
$scope.model.target = {};
|
||||
$scope.searchInfo = {
|
||||
searchFromId: null,
|
||||
searchFromName: null,
|
||||
showSearch: false,
|
||||
dataTypeId: dialogOptions.dataTypeId,
|
||||
dataTypeKey: dialogOptions.dataTypeKey,
|
||||
results: [],
|
||||
selectedSearchResults: []
|
||||
};
|
||||
@@ -175,7 +175,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
|
||||
var mediaPicker = {
|
||||
startNodeId: startNodeId,
|
||||
startNodeIsVirtual: startNodeIsVirtual,
|
||||
dataTypeId: dialogOptions.dataTypeId,
|
||||
dataTypeKey: dialogOptions.dataTypeKey,
|
||||
submit: function (model) {
|
||||
var media = model.selection[0];
|
||||
|
||||
|
||||
+16
-16
@@ -25,7 +25,7 @@
|
||||
ng-model="model.target.url"
|
||||
ng-disabled="model.target.id || model.target.udi" />
|
||||
</umb-control-group>
|
||||
|
||||
|
||||
<umb-control-group label="@defaultdialogs_anchorLinkPicker">
|
||||
<input type="text"
|
||||
list="anchors"
|
||||
@@ -33,13 +33,13 @@
|
||||
placeholder="@placeholders_anchor"
|
||||
class="umb-property-editor umb-textstring"
|
||||
ng-model="model.target.anchor" />
|
||||
|
||||
|
||||
<datalist id="anchors">
|
||||
<option value="{{a}}" ng-repeat="a in anchorValues"></option>
|
||||
</datalist>
|
||||
</umb-control-group>
|
||||
</div>
|
||||
|
||||
|
||||
<umb-control-group label="@defaultdialogs_nodeNameLinkPicker">
|
||||
<input type="text"
|
||||
localize="placeholder"
|
||||
@@ -47,7 +47,7 @@
|
||||
class="umb-property-editor umb-textstring"
|
||||
ng-model="model.target.name" />
|
||||
</umb-control-group>
|
||||
|
||||
|
||||
<umb-control-group ng-if="showTarget" label="@content_target">
|
||||
<umb-checkbox
|
||||
model="vm.openInNewWindow"
|
||||
@@ -58,28 +58,28 @@
|
||||
|
||||
<div class="umb-control-group">
|
||||
<h5><localize key="defaultdialogs_linkToPage">Link to page</localize></h5>
|
||||
|
||||
|
||||
<div ng-hide="miniListView">
|
||||
<umb-tree-search-box
|
||||
<umb-tree-search-box
|
||||
hide-search-callback="hideSearch"
|
||||
search-callback="onSearchResults"
|
||||
search-from-id="{{searchInfo.searchFromId}}"
|
||||
search-from-name="{{searchInfo.searchFromName}}"
|
||||
datatype-id="{{searchInfo.dataTypeId}}"
|
||||
datatype-key="{{searchInfo.dataTypeKey}}"
|
||||
show-search="{{searchInfo.showSearch}}"
|
||||
section="{{section}}">
|
||||
</umb-tree-search-box>
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
<umb-tree-search-results
|
||||
|
||||
<umb-tree-search-results
|
||||
ng-if="searchInfo.showSearch"
|
||||
results="searchInfo.results"
|
||||
select-result-callback="selectResult">
|
||||
</umb-tree-search-results>
|
||||
|
||||
|
||||
<div ng-hide="searchInfo.showSearch">
|
||||
<umb-tree
|
||||
<umb-tree
|
||||
section="content"
|
||||
hideheader="true"
|
||||
hideoptions="true"
|
||||
@@ -92,17 +92,17 @@
|
||||
</umb-tree>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<umb-mini-list-view
|
||||
|
||||
<umb-mini-list-view
|
||||
ng-if="miniListView"
|
||||
node="miniListView"
|
||||
entity-type="Document"
|
||||
on-select="selectListViewNode(node)"
|
||||
on-close="closeMiniListView()">
|
||||
</umb-mini-list-view>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="umb-control-group">
|
||||
<h5><localize key="defaultdialogs_linkToMedia">Link to media</localize></h5>
|
||||
<a href ng-click="switchToMediaPicker()" class="btn">
|
||||
|
||||
+3
-3
@@ -57,7 +57,7 @@ angular.module("umbraco")
|
||||
totalItems: 0,
|
||||
totalPages: 0,
|
||||
filter: '',
|
||||
dataTypeId: $scope.model.dataTypeId
|
||||
dataTypeKey: $scope.model.dataTypeKey
|
||||
};
|
||||
|
||||
//preload selected item
|
||||
@@ -160,7 +160,7 @@ angular.module("umbraco")
|
||||
}
|
||||
|
||||
if (folder.id > 0) {
|
||||
entityResource.getAncestors(folder.id, "media", null, { dataTypeId: $scope.model.dataTypeId })
|
||||
entityResource.getAncestors(folder.id, "media", null, { dataTypeKey: $scope.model.dataTypeKey })
|
||||
.then(function (anc) {
|
||||
$scope.path = _.filter(anc,
|
||||
function (f) {
|
||||
@@ -318,7 +318,7 @@ angular.module("umbraco")
|
||||
totalItems: 0,
|
||||
totalPages: 0,
|
||||
filter: '',
|
||||
dataTypeId: $scope.model.dataTypeId
|
||||
dataTypeKey: $scope.model.dataTypeKey
|
||||
};
|
||||
getChildren($scope.currentFolder.id);
|
||||
}
|
||||
|
||||
+4
-4
@@ -28,12 +28,12 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController",
|
||||
vm.treeAlias = $scope.model.treeAlias;
|
||||
vm.multiPicker = $scope.model.multiPicker;
|
||||
vm.hideHeader = (typeof $scope.model.hideHeader) === "boolean" ? $scope.model.hideHeader : true;
|
||||
vm.dataTypeId = $scope.model.dataTypeId;
|
||||
vm.dataTypeKey = $scope.model.dataTypeKey;
|
||||
vm.searchInfo = {
|
||||
searchFromId: $scope.model.startNodeId,
|
||||
searchFromName: null,
|
||||
showSearch: false,
|
||||
dataTypeId: vm.dataTypeId,
|
||||
dataTypeKey: vm.dataTypeKey,
|
||||
results: [],
|
||||
selectedSearchResults: []
|
||||
}
|
||||
@@ -192,8 +192,8 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController",
|
||||
if (vm.selectedLanguage && vm.selectedLanguage.id) {
|
||||
queryParams["culture"] = vm.selectedLanguage.culture;
|
||||
}
|
||||
if (vm.dataTypeId) {
|
||||
queryParams["dataTypeId"] = vm.dataTypeId;
|
||||
if (vm.dataTypeKey) {
|
||||
queryParams["dataTypeKey"] = vm.dataTypeKey;
|
||||
}
|
||||
|
||||
var queryString = $.param(queryParams); //create the query string from the params object
|
||||
|
||||
+11
-11
@@ -27,7 +27,7 @@
|
||||
<a ng-click="vm.selectLanguage(language)" ng-repeat="language in vm.languages" href="">{{language.name}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="umb-control-group">
|
||||
<umb-tree-search-box
|
||||
ng-if="vm.enableSearh"
|
||||
@@ -36,22 +36,22 @@
|
||||
search-from-id="{{vm.searchInfo.searchFromId}}"
|
||||
search-from-name="{{vm.searchInfo.searchFromName}}"
|
||||
show-search="{{vm.searchInfo.showSearch}}"
|
||||
datatype-id="{{vm.searchInfo.dataTypeId}}"
|
||||
datatype-key="{{vm.searchInfo.dataTypeKey}}"
|
||||
section="{{vm.section}}">
|
||||
</umb-tree-search-box>
|
||||
</div>
|
||||
|
||||
|
||||
<umb-tree-search-results ng-if="vm.searchInfo.showSearch"
|
||||
results="vm.searchInfo.results"
|
||||
select-result-callback="vm.selectResult">
|
||||
</umb-tree-search-results>
|
||||
|
||||
|
||||
<umb-empty-state ng-if="!vm.hasItems && vm.emptyStateMessage" position="center">
|
||||
{{ vm.emptyStateMessage }}
|
||||
</umb-empty-state>
|
||||
|
||||
|
||||
<div ng-if="vm.treeReady" ng-hide="vm.searchInfo.showSearch" ng-animate="'tree-fade-out'">
|
||||
<umb-tree
|
||||
<umb-tree
|
||||
section="{{vm.section}}"
|
||||
treealias="{{vm.treeAlias}}"
|
||||
hideheader="{{vm.hideHeader}}"
|
||||
@@ -65,10 +65,10 @@
|
||||
api="vm.dialogTreeApi">
|
||||
</umb-tree>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<umb-mini-list-view
|
||||
|
||||
<umb-mini-list-view
|
||||
ng-if="vm.miniListView"
|
||||
node="vm.miniListView"
|
||||
entity-type="{{vm.entityType}}"
|
||||
@@ -93,7 +93,7 @@
|
||||
shortcut="esc"
|
||||
action="vm.close()">
|
||||
</umb-button>
|
||||
|
||||
|
||||
<umb-button
|
||||
ng-if="vm.multiPicker"
|
||||
type="button"
|
||||
@@ -101,7 +101,7 @@
|
||||
label-key="general_submit"
|
||||
action="vm.submit(model)">
|
||||
</umb-button>
|
||||
|
||||
|
||||
</umb-editor-footer-content-right>
|
||||
|
||||
</umb-editor-footer>
|
||||
|
||||
@@ -139,7 +139,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR
|
||||
|
||||
//anytime a user is changing a member's password without the oldPassword, we are in effect resetting it so we need to set that flag here
|
||||
var passwordProp = _.find(contentEditingHelper.getAllProps($scope.content), function (e) { return e.alias === '_umb_password' });
|
||||
if (!passwordProp.value.reset) {
|
||||
if (passwordProp && passwordProp.value && !passwordProp.value.reset) {
|
||||
//so if the admin is not explicitly resetting the password, flag it for resetting if a new password is being entered
|
||||
passwordProp.value.reset = !passwordProp.value.oldPassword && passwordProp.config.allowManuallyChangingPassword;
|
||||
}
|
||||
|
||||
+3
-3
@@ -86,7 +86,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
|
||||
showOpenButton: false,
|
||||
showEditButton: false,
|
||||
showPathOnHover: false,
|
||||
dataTypeId: null,
|
||||
dataTypeKey: null,
|
||||
maxNumber: 1,
|
||||
minNumber: 0,
|
||||
startNode: {
|
||||
@@ -140,7 +140,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
|
||||
entityType: entityType,
|
||||
filterCssClass: "not-allowed not-published",
|
||||
startNodeId: null,
|
||||
dataTypeId: $scope.model.dataTypeId,
|
||||
dataTypeKey: $scope.model.dataTypeKey,
|
||||
currentNode: editorState ? editorState.current : null,
|
||||
callback: function (data) {
|
||||
if (angular.isArray(data)) {
|
||||
@@ -162,7 +162,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
|
||||
// pre-value config on to the dialog options
|
||||
angular.extend(dialogOptions, $scope.model.config);
|
||||
|
||||
dialogOptions.dataTypeId = $scope.model.dataTypeId;
|
||||
dialogOptions.dataTypeKey = $scope.model.dataTypeKey;
|
||||
|
||||
// if we can't pick more than one item, explicitly disable multiPicker in the dialog options
|
||||
if ($scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) === 1) {
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ angular.module("umbraco")
|
||||
showDetails: true,
|
||||
disableFolderSelect: true,
|
||||
onlyImages: true,
|
||||
dataTypeId: $scope.model.dataTypeId,
|
||||
dataTypeKey: $scope.model.dataTypeKey,
|
||||
submit: function(model) {
|
||||
var selectedImage = model.selection[0];
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
configuration="model.config.rte"
|
||||
value="control.value"
|
||||
unique-id="control.$uniqueId"
|
||||
datatype-id="{{model.dataTypeId}}"
|
||||
datatype-key="{{model.dataTypeKey}}"
|
||||
ignore-user-start-nodes="{{model.config.ignoreUserStartNodes}}">
|
||||
</grid-rte>
|
||||
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
|
||||
var mediaPicker = {
|
||||
startNodeId: $scope.model.config.startNodeId,
|
||||
startNodeIsVirtual: $scope.model.config.startNodeIsVirtual,
|
||||
dataTypeId: $scope.model.dataTypeId,
|
||||
dataTypeKey: $scope.model.dataTypeKey,
|
||||
multiPicker: multiPicker,
|
||||
onlyImages: onlyImages,
|
||||
disableFolderSelect: disableFolderSelect,
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en
|
||||
|
||||
var linkPicker = {
|
||||
currentTarget: target,
|
||||
dataTypeId: $scope.model.dataTypeId,
|
||||
dataTypeKey: $scope.model.dataTypeKey,
|
||||
ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes,
|
||||
submit: function (model) {
|
||||
if (model.target.url || model.target.anchor) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
@@ -96,6 +97,7 @@ namespace Umbraco.Web.Cache
|
||||
base.Refresh(payloads);
|
||||
}
|
||||
|
||||
|
||||
public override void RefreshAll()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
|
||||
@@ -104,10 +104,10 @@ namespace Umbraco.Web.Editors
|
||||
/// <param name="searchFrom">
|
||||
/// A starting point for the search, generally a node id, but for members this is a member type alias
|
||||
/// </param>
|
||||
/// <param name="dataTypeId">If set used to look up whether user and group start node permissions will be ignored.</param>
|
||||
/// <param name="dataTypeKey">If set used to look up whether user and group start node permissions will be ignored.</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public IEnumerable<EntityBasic> Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeId = null)
|
||||
public IEnumerable<EntityBasic> Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeKey = null)
|
||||
{
|
||||
// NOTE: Theoretically you shouldn't be able to see member data if you don't have access to members right? ... but there is a member picker, so can't really do that
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
//TODO: This uses the internal UmbracoTreeSearcher, this instead should delgate to the ISearchableTree implementation for the type
|
||||
|
||||
var ignoreUserStartNodes = dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value);
|
||||
var ignoreUserStartNodes = dataTypeKey.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value);
|
||||
return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes);
|
||||
}
|
||||
|
||||
@@ -424,7 +424,7 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
#endregion
|
||||
|
||||
public IEnumerable<EntityBasic> GetChildren(int id, UmbracoEntityTypes type, Guid? dataTypeId = null)
|
||||
public IEnumerable<EntityBasic> GetChildren(int id, UmbracoEntityTypes type, Guid? dataTypeKey = null)
|
||||
{
|
||||
var objectType = ConvertToObjectType(type);
|
||||
if (objectType.HasValue)
|
||||
@@ -433,7 +433,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
var startNodes = GetStartNodes(type);
|
||||
|
||||
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId);
|
||||
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey);
|
||||
|
||||
// root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes
|
||||
if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes)
|
||||
@@ -482,7 +482,7 @@ namespace Umbraco.Web.Editors
|
||||
string orderBy = "SortOrder",
|
||||
Direction orderDirection = Direction.Ascending,
|
||||
string filter = "",
|
||||
Guid? dataTypeId = null)
|
||||
Guid? dataTypeKey = null)
|
||||
{
|
||||
if (int.TryParse(id, out var intId))
|
||||
{
|
||||
@@ -507,7 +507,7 @@ namespace Umbraco.Web.Editors
|
||||
//the EntityService can search paged members from the root
|
||||
|
||||
intId = -1;
|
||||
return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, dataTypeId);
|
||||
return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, dataTypeKey);
|
||||
}
|
||||
|
||||
//the EntityService cannot search members of a certain type, this is currently not supported and would require
|
||||
@@ -543,7 +543,7 @@ namespace Umbraco.Web.Editors
|
||||
string orderBy = "SortOrder",
|
||||
Direction orderDirection = Direction.Ascending,
|
||||
string filter = "",
|
||||
Guid? dataTypeId = null)
|
||||
Guid? dataTypeKey = null)
|
||||
{
|
||||
if (pageNumber <= 0)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
@@ -558,7 +558,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
var startNodes = GetStartNodes(type);
|
||||
|
||||
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId);
|
||||
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey);
|
||||
|
||||
// root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes
|
||||
if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes)
|
||||
@@ -642,7 +642,7 @@ namespace Umbraco.Web.Editors
|
||||
string orderBy = "SortOrder",
|
||||
Direction orderDirection = Direction.Ascending,
|
||||
string filter = "",
|
||||
Guid? dataTypeId = null)
|
||||
Guid? dataTypeKey = null)
|
||||
{
|
||||
if (pageNumber <= 0)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
@@ -661,7 +661,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
int[] aids = GetStartNodes(type);
|
||||
|
||||
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId);
|
||||
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey);
|
||||
entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes
|
||||
? Services.EntityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords,
|
||||
SqlContext.Query<IUmbracoEntity>().Where(x => x.Name.Contains(filter)),
|
||||
@@ -704,7 +704,7 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeId) => dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value);
|
||||
private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeKey) => dataTypeKey.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value);
|
||||
|
||||
public IEnumerable<EntityBasic> GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings)
|
||||
{
|
||||
|
||||
@@ -22,8 +22,9 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[Required]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember(Name = "dataTypeId", IsRequired = false)]
|
||||
public Guid? DataTypeId { get; set; }
|
||||
[DataMember(Name = "dataTypeKey", IsRequired = false)]
|
||||
[ReadOnly(true)]
|
||||
public Guid DataTypeKey { get; set; }
|
||||
|
||||
[DataMember(Name = "value")]
|
||||
public object Value { get; set; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
@@ -43,6 +44,10 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[Required]
|
||||
public int DataTypeId { get; set; }
|
||||
|
||||
[DataMember(Name = "dataTypeKey")]
|
||||
[ReadOnly(true)]
|
||||
public Guid DataTypeKey { get; set; }
|
||||
|
||||
//SD: Is this really needed ?
|
||||
[DataMember(Name = "groupId")]
|
||||
public int GroupId { get; set; }
|
||||
|
||||
@@ -50,13 +50,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
dest.Alias = property.Alias;
|
||||
dest.PropertyEditor = editor;
|
||||
dest.Editor = editor.Alias;
|
||||
|
||||
//fixme: although this might get cached, if a content item has 100 properties of different data types, then this means this is going to be 100 extra DB queries :( :( :(
|
||||
// - ideally, we'd just have the DataTypeKey alongside the DataTypeId which is loaded in the single sql statement which should be relatively easy.
|
||||
var dataTypeKey = _entityService.GetKey(property.PropertyType.DataTypeId, UmbracoObjectTypes.DataType);
|
||||
if (!dataTypeKey.Success)
|
||||
throw new InvalidOperationException("Can't get the unique key from the id: " + property.PropertyType.DataTypeId);
|
||||
dest.DataTypeId = dataTypeKey.Result;
|
||||
dest.DataTypeKey = property.PropertyType.DataTypeKey;
|
||||
|
||||
// if there's a set of property aliases specified, we will check if the current property's value should be mapped.
|
||||
// if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value.
|
||||
|
||||
@@ -219,6 +219,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
target.Name = source.Label;
|
||||
target.DataTypeId = source.DataTypeId;
|
||||
target.DataTypeKey = source.DataTypeKey;
|
||||
target.Mandatory = source.Validation.Mandatory;
|
||||
target.ValidationRegExp = source.Validation.Pattern;
|
||||
target.Variations = source.AllowCultureVariant ? ContentVariation.Culture : ContentVariation.Nothing;
|
||||
@@ -334,6 +335,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
target.Alias = source.Alias;
|
||||
target.AllowCultureVariant = source.AllowCultureVariant;
|
||||
target.DataTypeId = source.DataTypeId;
|
||||
target.DataTypeKey = source.DataTypeKey;
|
||||
target.Description = source.Description;
|
||||
target.GroupId = source.GroupId;
|
||||
target.Id = source.Id;
|
||||
@@ -349,6 +351,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
target.Alias = source.Alias;
|
||||
target.AllowCultureVariant = source.AllowCultureVariant;
|
||||
target.DataTypeId = source.DataTypeId;
|
||||
target.DataTypeKey = source.DataTypeKey;
|
||||
target.Description = source.Description;
|
||||
target.GroupId = source.GroupId;
|
||||
target.Id = source.Id;
|
||||
|
||||
@@ -231,6 +231,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
GroupId = groupId,
|
||||
Inherited = inherited,
|
||||
DataTypeId = p.DataTypeId,
|
||||
DataTypeKey = p.DataTypeKey,
|
||||
SortOrder = p.SortOrder,
|
||||
ContentTypeId = contentType.Id,
|
||||
ContentTypeName = contentType.Name,
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web.Mvc;
|
||||
|
||||
namespace Umbraco.Web.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception filter checking if we get a <see cref="ModelBindingException" /> or <see cref="InvalidCastException" /> with the same model. in which case it returns a redirect to the same page after 1 sec.
|
||||
/// </summary>
|
||||
internal class ModelBindingExceptionFilter : FilterAttribute, IExceptionFilter
|
||||
{
|
||||
private static readonly Regex GetPublishedModelsTypesRegex = new Regex("Umbraco.Web.PublishedModels.(\\w+)", RegexOptions.Compiled);
|
||||
|
||||
public void OnException(ExceptionContext filterContext)
|
||||
{
|
||||
if (!filterContext.ExceptionHandled
|
||||
&& ((filterContext.Exception is ModelBindingException || filterContext.Exception is InvalidCastException)
|
||||
&& IsMessageAboutTheSameModelType(filterContext.Exception.Message)))
|
||||
{
|
||||
filterContext.HttpContext.Response.Headers.Add(HttpResponseHeader.RetryAfter.ToString(), "1");
|
||||
filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.RawUrl, false);
|
||||
|
||||
filterContext.ExceptionHandled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the message is about two models with the same name.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Message could be something like:
|
||||
/// <para>
|
||||
/// InvalidCastException:
|
||||
/// [A]Umbraco.Web.PublishedModels.Home cannot be cast to [B]Umbraco.Web.PublishedModels.Home. Type A originates from 'App_Web_all.generated.cs.8f9494c4.rtdigm_z, Version=0.0.0.3, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\User\AppData\Local\Temp\Temporary ASP.NET Files\root\c5c63f4d\c168d9d4\App_Web_all.generated.cs.8f9494c4.rtdigm_z.dll'. Type B originates from 'App_Web_all.generated.cs.8f9494c4.rbyqlplu, Version=0.0.0.5, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\User\AppData\Local\Temp\Temporary ASP.NET Files\root\c5c63f4d\c168d9d4\App_Web_all.generated.cs.8f9494c4.rbyqlplu.dll'.
|
||||
///</para>
|
||||
/// <para>
|
||||
/// ModelBindingException:
|
||||
/// Cannot bind source content type Umbraco.Web.PublishedModels.Home to model type Umbraco.Web.PublishedModels.Home. Both view and content models are PureLive, with different versions. The application is in an unstable state and is going to be restarted. The application is restarting now.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private bool IsMessageAboutTheSameModelType(string exceptionMessage)
|
||||
{
|
||||
var matches = GetPublishedModelsTypesRegex.Matches(exceptionMessage);
|
||||
|
||||
if (matches.Count >= 2)
|
||||
{
|
||||
return string.Equals(matches[0].Value, matches[1].Value, StringComparison.InvariantCulture);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,12 @@ using Umbraco.Web.Routing;
|
||||
|
||||
namespace Umbraco.Web.Mvc
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents the default front-end rendering controller.
|
||||
/// </summary>
|
||||
[PreRenderViewActionFilter]
|
||||
[ModelBindingExceptionFilter]
|
||||
public class RenderMvcController : UmbracoController, IRenderMvcController
|
||||
{
|
||||
private PublishedRequest _publishedRequest;
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CSharpTest.Net.Collections;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Scoping;
|
||||
@@ -275,6 +276,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
public void UpdateContentTypes(IEnumerable<IPublishedContentType> types)
|
||||
{
|
||||
//nothing to do if this is empty, no need to lock/allocate/iterate/etc...
|
||||
if (!types.Any()) return;
|
||||
|
||||
var lockInfo = new WriteLockInfo();
|
||||
try
|
||||
{
|
||||
@@ -330,13 +334,16 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateContentTypes(IEnumerable<int> removedIds, IEnumerable<IPublishedContentType> refreshedTypes, IEnumerable<ContentNodeKit> kits)
|
||||
public void UpdateContentTypes(IReadOnlyCollection<int> removedIds, IReadOnlyCollection<IPublishedContentType> refreshedTypes, IReadOnlyCollection<ContentNodeKit> kits)
|
||||
{
|
||||
var removedIdsA = removedIds?.ToArray() ?? Array.Empty<int>();
|
||||
var refreshedTypesA = refreshedTypes?.ToArray() ?? Array.Empty<IPublishedContentType>();
|
||||
var refreshedIdsA = refreshedTypesA.Select(x => x.Id).ToArray();
|
||||
var removedIdsA = removedIds ?? Array.Empty<int>();
|
||||
var refreshedTypesA = refreshedTypes ?? Array.Empty<IPublishedContentType>();
|
||||
var refreshedIdsA = refreshedTypesA.Select(x => x.Id).ToList();
|
||||
kits = kits ?? Array.Empty<ContentNodeKit>();
|
||||
|
||||
if (kits.Count == 0 && refreshedIdsA.Count == 0 && removedIdsA.Count == 0)
|
||||
return; //exit - there is nothing to do here
|
||||
|
||||
var lockInfo = new WriteLockInfo();
|
||||
try
|
||||
{
|
||||
|
||||
@@ -98,6 +98,8 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
|
||||
|
||||
public IEnumerable<ContentNodeKit> GetTypeContentSources(IScope scope, IEnumerable<int> ids)
|
||||
{
|
||||
if (!ids.Any()) return Enumerable.Empty<ContentNodeKit>();
|
||||
|
||||
var sql = ContentSourcesSelect(scope)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed)
|
||||
.WhereIn<ContentDto>(x => x.ContentTypeId, ids)
|
||||
@@ -169,6 +171,8 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
|
||||
|
||||
public IEnumerable<ContentNodeKit> GetTypeMediaSources(IScope scope, IEnumerable<int> ids)
|
||||
{
|
||||
if (!ids.Any()) return Enumerable.Empty<ContentNodeKit>();
|
||||
|
||||
var sql = MediaSourcesSelect(scope)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed)
|
||||
.WhereIn<ContentDto>(x => x.ContentTypeId, ids)
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
private readonly IMemberRepository _memberRepository;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IEntityXmlSerializer _entitySerializer;
|
||||
private readonly IPublishedModelFactory _publishedModelFactory;
|
||||
private readonly IDefaultCultureAccessor _defaultCultureAccessor;
|
||||
private readonly UrlSegmentProviderCollection _urlSegmentProviders;
|
||||
|
||||
@@ -73,7 +74,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository,
|
||||
IDefaultCultureAccessor defaultCultureAccessor,
|
||||
IDataSource dataSource, IGlobalSettings globalSettings,
|
||||
IEntityXmlSerializer entitySerializer, IPublishedModelFactory publishedModelFactory,
|
||||
IEntityXmlSerializer entitySerializer,
|
||||
IPublishedModelFactory publishedModelFactory,
|
||||
UrlSegmentProviderCollection urlSegmentProviders)
|
||||
: base(publishedSnapshotAccessor, variationContextAccessor)
|
||||
{
|
||||
@@ -95,6 +97,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
// we need an Xml serializer here so that the member cache can support XPath,
|
||||
// for members this is done by navigating the serialized-to-xml member
|
||||
_entitySerializer = entitySerializer;
|
||||
_publishedModelFactory = publishedModelFactory;
|
||||
|
||||
// we always want to handle repository events, configured or not
|
||||
// assuming no repository event will trigger before the whole db is ready
|
||||
@@ -708,6 +711,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged)
|
||||
{
|
||||
// no cache, trash everything
|
||||
@@ -800,6 +804,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads)
|
||||
{
|
||||
// no cache, nothing we can do
|
||||
@@ -812,33 +817,49 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
Notify<IContentType>(_contentStore, payloads, RefreshContentTypesLocked);
|
||||
Notify<IMediaType>(_mediaStore, payloads, RefreshMediaTypesLocked);
|
||||
|
||||
if (_publishedModelFactory.IsLiveFactory())
|
||||
{
|
||||
//In the case of Pure Live - we actually need to refresh all of the content and the media
|
||||
//see https://github.com/umbraco/Umbraco-CMS/issues/5671
|
||||
//The underlying issue is that in Pure Live the ILivePublishedModelFactory will re-compile all of the classes/models
|
||||
//into a new DLL for the application which includes both content types and media types.
|
||||
//Since the models in the cache are based on these actual classes, all of the objects in the cache need to be updated
|
||||
//to use the newest version of the class.
|
||||
using (_contentStore.GetScopedWriteLock(_scopeProvider))
|
||||
using (_mediaStore.GetScopedWriteLock(_scopeProvider))
|
||||
{
|
||||
NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out var draftChanged, out var publishedChanged);
|
||||
NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out var anythingChanged);
|
||||
}
|
||||
}
|
||||
|
||||
((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync();
|
||||
}
|
||||
|
||||
private void Notify<T>(ContentStore store, ContentTypeCacheRefresher.JsonPayload[] payloads, Action<IEnumerable<int>, IEnumerable<int>, IEnumerable<int>, IEnumerable<int>> action)
|
||||
private void Notify<T>(ContentStore store, ContentTypeCacheRefresher.JsonPayload[] payloads, Action<List<int>, List<int>, List<int>, List<int>> action)
|
||||
where T : IContentTypeComposition
|
||||
{
|
||||
if (payloads.Length == 0) return; //nothing to do
|
||||
|
||||
var nameOfT = typeof(T).Name;
|
||||
|
||||
var removedIds = new List<int>();
|
||||
var refreshedIds = new List<int>();
|
||||
var otherIds = new List<int>();
|
||||
var newIds = new List<int>();
|
||||
List<int> removedIds = null, refreshedIds = null, otherIds = null, newIds = null;
|
||||
|
||||
foreach (var payload in payloads)
|
||||
{
|
||||
if (payload.ItemType != nameOfT) continue;
|
||||
|
||||
if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Remove))
|
||||
removedIds.Add(payload.Id);
|
||||
AddToList(ref removedIds, payload.Id);
|
||||
else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshMain))
|
||||
refreshedIds.Add(payload.Id);
|
||||
AddToList(ref refreshedIds, payload.Id);
|
||||
else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshOther))
|
||||
otherIds.Add(payload.Id);
|
||||
AddToList(ref otherIds, payload.Id);
|
||||
else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Create))
|
||||
newIds.Add(payload.Id);
|
||||
AddToList(ref newIds, payload.Id);
|
||||
}
|
||||
|
||||
if (removedIds.Count == 0 && refreshedIds.Count == 0 && otherIds.Count == 0 && newIds.Count == 0) return;
|
||||
if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty()) return;
|
||||
|
||||
using (store.GetScopedWriteLock(_scopeProvider))
|
||||
{
|
||||
@@ -925,15 +946,19 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
}
|
||||
}
|
||||
|
||||
//Methods used to prevent allocations of lists
|
||||
private void AddToList(ref List<int> list, int val) => GetOrCreateList(ref list).Add(val);
|
||||
private List<int> GetOrCreateList(ref List<int> list) => list ?? (list = new List<int>());
|
||||
|
||||
#endregion
|
||||
|
||||
#region Content Types
|
||||
|
||||
private IEnumerable<IPublishedContentType> CreateContentTypes(PublishedItemType itemType, int[] ids)
|
||||
private IReadOnlyCollection<IPublishedContentType> CreateContentTypes(PublishedItemType itemType, int[] ids)
|
||||
{
|
||||
// XxxTypeService.GetAll(empty) returns everything!
|
||||
if (ids.Length == 0)
|
||||
return Enumerable.Empty<IPublishedContentType>();
|
||||
return Array.Empty<IPublishedContentType>();
|
||||
|
||||
IEnumerable<IContentTypeComposition> contentTypes;
|
||||
switch (itemType)
|
||||
@@ -953,7 +978,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
// some may be missing - not checking here
|
||||
|
||||
return contentTypes.Select(x => _publishedContentTypeFactory.CreateContentType(x));
|
||||
return contentTypes.Select(x => _publishedContentTypeFactory.CreateContentType(x)).ToList();
|
||||
}
|
||||
|
||||
private IPublishedContentType CreateContentType(PublishedItemType itemType, int id)
|
||||
@@ -977,44 +1002,58 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
return contentType == null ? null : _publishedContentTypeFactory.CreateContentType(contentType);
|
||||
}
|
||||
|
||||
private void RefreshContentTypesLocked(IEnumerable<int> removedIds, IEnumerable<int> refreshedIds, IEnumerable<int> otherIds, IEnumerable<int> newIds)
|
||||
private void RefreshContentTypesLocked(List<int> removedIds, List<int> refreshedIds, List<int> otherIds, List<int> newIds)
|
||||
{
|
||||
if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty())
|
||||
return;
|
||||
|
||||
// locks:
|
||||
// content (and content types) are read-locked while reading content
|
||||
// contentStore is wlocked (so readable, only no new views)
|
||||
// and it can be wlocked by 1 thread only at a time
|
||||
|
||||
var refreshedIdsA = refreshedIds.ToArray();
|
||||
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.ContentTypes);
|
||||
|
||||
var typesA = CreateContentTypes(PublishedItemType.Content, refreshedIdsA).ToArray();
|
||||
var kits = _dataSource.GetTypeContentSources(scope, refreshedIdsA);
|
||||
var typesA = refreshedIds.IsCollectionEmpty()
|
||||
? Array.Empty<IPublishedContentType>()
|
||||
: CreateContentTypes(PublishedItemType.Content, refreshedIds.ToArray()).ToArray();
|
||||
|
||||
var kits = refreshedIds.IsCollectionEmpty()
|
||||
? Array.Empty<ContentNodeKit>()
|
||||
: _dataSource.GetTypeContentSources(scope, refreshedIds).ToArray();
|
||||
|
||||
_contentStore.UpdateContentTypes(removedIds, typesA, kits);
|
||||
_contentStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray()).ToArray());
|
||||
_contentStore.NewContentTypes(CreateContentTypes(PublishedItemType.Content, newIds.ToArray()).ToArray());
|
||||
if (!otherIds.IsCollectionEmpty())
|
||||
_contentStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray()));
|
||||
if (!newIds.IsCollectionEmpty())
|
||||
_contentStore.NewContentTypes(CreateContentTypes(PublishedItemType.Content, newIds.ToArray()));
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshMediaTypesLocked(IEnumerable<int> removedIds, IEnumerable<int> refreshedIds, IEnumerable<int> otherIds, IEnumerable<int> newIds)
|
||||
private void RefreshMediaTypesLocked(List<int> removedIds, List<int> refreshedIds, List<int> otherIds, List<int> newIds)
|
||||
{
|
||||
if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty())
|
||||
return;
|
||||
|
||||
// locks:
|
||||
// media (and content types) are read-locked while reading media
|
||||
// mediaStore is wlocked (so readable, only no new views)
|
||||
// and it can be wlocked by 1 thread only at a time
|
||||
|
||||
var refreshedIdsA = refreshedIds.ToArray();
|
||||
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MediaTypes);
|
||||
|
||||
var typesA = CreateContentTypes(PublishedItemType.Media, refreshedIdsA).ToArray();
|
||||
var kits = _dataSource.GetTypeMediaSources(scope, refreshedIdsA);
|
||||
var typesA = refreshedIds == null
|
||||
? Array.Empty<IPublishedContentType>()
|
||||
: CreateContentTypes(PublishedItemType.Media, refreshedIds.ToArray()).ToArray();
|
||||
|
||||
var kits = refreshedIds == null
|
||||
? Array.Empty<ContentNodeKit>()
|
||||
: _dataSource.GetTypeMediaSources(scope, refreshedIds).ToArray();
|
||||
|
||||
_mediaStore.UpdateContentTypes(removedIds, typesA, kits);
|
||||
_mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray());
|
||||
|
||||
@@ -338,7 +338,7 @@ namespace Umbraco.Web.Trees
|
||||
//Here we need to figure out if the node is a container and if so check if the user has a custom start node, then check if that start node is a child
|
||||
// of this container node. If that is true, the HasChildren must be true so that the tree node still renders even though this current node is a container/list view.
|
||||
if (isContainer && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false)
|
||||
{
|
||||
{
|
||||
var startNodes = Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes);
|
||||
//if any of these start nodes' parent is current, then we need to render children normally so we need to switch some logic and tell
|
||||
// the UI that this node does have children and that it isn't a container
|
||||
@@ -396,7 +396,7 @@ namespace Umbraco.Web.Trees
|
||||
}
|
||||
|
||||
var menu = new MenuItemCollection();
|
||||
// only add empty recycle bin if the current user is allowed to delete by default
|
||||
// only add empty recycle bin if the current user is allowed to delete by default
|
||||
if (deleteAllowed)
|
||||
{
|
||||
menu.Items.Add(new MenuItem("emptyRecycleBin", Services.TextService)
|
||||
@@ -538,8 +538,8 @@ namespace Umbraco.Web.Trees
|
||||
{
|
||||
if (_ignoreUserStartNodes.HasValue) return _ignoreUserStartNodes.Value;
|
||||
|
||||
var dataTypeId = queryStrings.GetValue<Guid?>(TreeQueryStringParameters.DataTypeId);
|
||||
_ignoreUserStartNodes = dataTypeId.HasValue ? Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value) : false;
|
||||
var dataTypeKey = queryStrings.GetValue<Guid?>(TreeQueryStringParameters.DataTypeKey);
|
||||
_ignoreUserStartNodes = dataTypeKey.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value);
|
||||
|
||||
return _ignoreUserStartNodes.Value;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
public const string Use = "use";
|
||||
public const string Application = "application";
|
||||
public const string StartNodeId = "startNodeId";
|
||||
public const string DataTypeId = "dataTypeId";
|
||||
public const string DataTypeKey = "dataTypeKey";
|
||||
//public const string OnNodeClick = "OnNodeClick";
|
||||
//public const string RenderParent = "RenderParent";
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
<Compile Include="Dashboards\HealthCheckDashboard.cs" />
|
||||
<Compile Include="Dashboards\MediaDashboard.cs" />
|
||||
<Compile Include="Dashboards\MembersDashboard.cs" />
|
||||
<Compile Include="Dashboards\ProfilerDashboard.cs" />
|
||||
<Compile Include="Dashboards\PublishedStatusDashboard.cs" />
|
||||
<Compile Include="Dashboards\RedirectUrlDashboard.cs" />
|
||||
<Compile Include="Dashboards\SettingsDashboards.cs" />
|
||||
@@ -218,6 +219,7 @@
|
||||
<Compile Include="Models\Mapping\MapperContextExtensions.cs" />
|
||||
<Compile Include="Models\PublishedContent\HybridVariationContextAccessor.cs" />
|
||||
<Compile Include="Models\TemplateQuery\QueryConditionExtensions.cs" />
|
||||
<Compile Include="Mvc\ModelBindingExceptionFilter.cs" />
|
||||
<Compile Include="Mvc\SurfaceControllerTypeCollectionBuilder.cs" />
|
||||
<Compile Include="Profiling\WebProfilingController.cs" />
|
||||
<Compile Include="PublishedCache\NuCache\Snap\GenObj.cs" />
|
||||
|
||||
Reference in New Issue
Block a user