Merge remote-tracking branch 'origin/v8/feature/media-tracking' into netcore/dev

# Conflicts:
#	src/Umbraco.Abstractions/Models/IDataValueEditor.cs
#	src/Umbraco.Abstractions/Models/RelationType.cs
#	src/Umbraco.Core/Contants-UdiEntityType.cs
#	src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
#	src/Umbraco.Core/Udi.cs
#	src/Umbraco.Tests/Models/RelationTests.cs
#	src/Umbraco.Tests/Models/RelationTypeTests.cs
#	src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs
#	src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs
#	src/Umbraco.Tests/Published/NestedContentTests.cs
#	src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs
#	src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs
#	src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs
#	src/Umbraco.Tests/Testing/UmbracoTestBase.cs
#	src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs
#	src/Umbraco.Web/Editors/MediaController.cs
#	src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
#	src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs
#	src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs
#	src/Umbraco.Web/Runtime/WebInitialComposer.cs
#	src/Umbraco.Web/Templates/TemplateUtilities.cs
#	src/Umbraco.Web/UmbracoComponentRenderer.cs
This commit is contained in:
Bjarke Berg
2019-12-10 12:37:52 +01:00
122 changed files with 3671 additions and 1350 deletions

View File

@@ -225,34 +225,65 @@ namespace Umbraco.Core
public static class RelationTypes
{
/// <summary>
/// ContentType name for default relation type "Relate Document On Copy".
/// Name for default relation type "Related Media".
/// </summary>
public const string RelatedMediaName = "Related Media";
/// <summary>
/// Alias for default relation type "Related Media"
/// </summary>
public const string RelatedMediaAlias = "umbMedia";
/// <summary>
/// Name for default relation type "Related Document".
/// </summary>
public const string RelatedDocumentName = "Related Document";
/// <summary>
/// Alias for default relation type "Related Document"
/// </summary>
public const string RelatedDocumentAlias = "umbDocument";
/// <summary>
/// Name for default relation type "Relate Document On Copy".
/// </summary>
public const string RelateDocumentOnCopyName = "Relate Document On Copy";
/// <summary>
/// ContentType alias for default relation type "Relate Document On Copy".
/// Alias for default relation type "Relate Document On Copy".
/// </summary>
public const string RelateDocumentOnCopyAlias = "relateDocumentOnCopy";
/// <summary>
/// ContentType name for default relation type "Relate Parent Document On Delete".
/// Name for default relation type "Relate Parent Document On Delete".
/// </summary>
public const string RelateParentDocumentOnDeleteName = "Relate Parent Document On Delete";
/// <summary>
/// ContentType alias for default relation type "Relate Parent Document On Delete".
/// Alias for default relation type "Relate Parent Document On Delete".
/// </summary>
public const string RelateParentDocumentOnDeleteAlias = "relateParentDocumentOnDelete";
/// <summary>
/// ContentType name for default relation type "Relate Parent Media Folder On Delete".
/// Name for default relation type "Relate Parent Media Folder On Delete".
/// </summary>
public const string RelateParentMediaFolderOnDeleteName = "Relate Parent Media Folder On Delete";
/// <summary>
/// ContentType alias for default relation type "Relate Parent Media Folder On Delete".
/// Alias for default relation type "Relate Parent Media Folder On Delete".
/// </summary>
public const string RelateParentMediaFolderOnDeleteAlias = "relateParentMediaFolderOnDelete";
/// <summary>
/// Returns the types of relations that are automatically tracked
/// </summary>
/// <remarks>
/// Developers should not manually use these relation types since they will all be cleared whenever an entity
/// (content, media or member) is saved since they are auto-populated based on property values.
/// </remarks>
public static string[] AutomaticRelationTypes = new[] { RelatedMediaAlias, RelatedDocumentAlias };
//TODO: return a list of built in types so we can use that to prevent deletion in the uI
}
}
}

View File

@@ -15,7 +15,7 @@
// need to keep it around in a field nor to make it readonly
public const string Unknown = "unknown";
public const string Unknown = "unknown";
// guid entity types

View File

@@ -1,5 +1,6 @@
namespace Umbraco.Core.Models.Editors
{
/// <summary>
/// Represents an uploaded file for a property.
/// </summary>

View File

@@ -24,7 +24,7 @@
/// Sets the parent entity.
/// </summary>
/// <remarks>Use this method to set the parent entity when the parent entity is known, but has not
/// been persistent and does not yet have an identity. The parent identifier will we retrieved
/// been persistent and does not yet have an identity. The parent identifier will be retrieved
/// from the parent entity when needed. If the parent entity still does not have an entity by that
/// time, an exception will be thrown by <see cref="ParentId"/> getter.</remarks>
void SetParent(ITreeEntity parent);
@@ -53,4 +53,4 @@
/// </remarks>
bool Trashed { get; }
}
}
}

View File

@@ -1,13 +1,6 @@
namespace Umbraco.Core.Models.Entities
{
public class MemberEntitySlim : EntitySlim, IMemberEntitySlim
public class MemberEntitySlim : ContentEntitySlim, IMemberEntitySlim
{
public string ContentTypeAlias { get; set; }
/// <inheritdoc />
public string ContentTypeIcon { get; set; }
/// <inheritdoc />
public string ContentTypeThumbnail { get; set; }
}
}
}

View File

@@ -3,9 +3,11 @@ using System.ComponentModel.DataAnnotations;
using System.Xml.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.Services;
namespace Umbraco.Core.PropertyEditors
{
/// <summary>
/// Represents an editor for editing data values.
/// </summary>
@@ -62,8 +64,26 @@ namespace Umbraco.Core.PropertyEditors
// TODO: / deal with this when unplugging the xml cache
// why property vs propertyType? services should be injected! etc...
IEnumerable<XElement> ConvertDbToXml(IProperty property, bool published);
XNode ConvertDbToXml(IPropertyType propertyType, object value);
string ConvertDbToString(IPropertyType propertyType, object value);
/// <summary>
/// Used for serializing an <see cref="IContent"/> item for packaging
/// </summary>
/// <param name="property"></param>
/// <param name="dataTypeService"></param>
/// <param name="localizationService"></param>
/// <param name="published"></param>
/// <returns></returns>
IEnumerable<XElement> ConvertDbToXml(IProperty property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published);
/// <summary>
/// Used for serializing an <see cref="IContent"/> item for packaging
/// </summary>
/// <param name="propertyType"></param>
/// <param name="value"></param>
/// <param name="dataTypeService"></param>
/// <returns></returns>
XNode ConvertDbToXml(IPropertyType propertyType, object value, IDataTypeService dataTypeService);
string ConvertDbToString(IPropertyType propertyType, object value, IDataTypeService dataTypeService);
}
}

View File

@@ -1,4 +1,5 @@
using System.Runtime.Serialization;
using System;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
@@ -11,12 +12,18 @@ namespace Umbraco.Core.Models
[DataMember]
int ParentId { get; set; }
[DataMember]
Guid ParentObjectType { get; set; }
/// <summary>
/// Gets or sets the Child Id of the Relation (Destination)
/// </summary>
[DataMember]
int ChildId { get; set; }
[DataMember]
Guid ChildObjectType { get; set; }
/// <summary>
/// Gets or sets the <see cref="RelationType"/> for the Relation
/// </summary>

View File

@@ -29,13 +29,13 @@ namespace Umbraco.Core.Models
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember]
Guid ParentObjectType { get; set; }
Guid? ParentObjectType { get; set; }
/// <summary>
/// Gets or sets the Childs object type id
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember]
Guid ChildObjectType { get; set; }
Guid? ChildObjectType { get; set; }
}
}

View File

@@ -17,13 +17,36 @@ namespace Umbraco.Core.Models
private IRelationType _relationType;
private string _comment;
/// <summary>
/// Constructor for constructing the entity to be created
/// </summary>
/// <param name="parentId"></param>
/// <param name="childId"></param>
/// <param name="relationType"></param>
public Relation(int parentId, int childId, IRelationType relationType)
{
_parentId = parentId;
_childId = childId;
_relationType = relationType;
}
/// <summary>
/// Constructor for reconstructing the entity from the data source
/// </summary>
/// <param name="parentId"></param>
/// <param name="childId"></param>
/// <param name="parentObjectType"></param>
/// <param name="childObjectType"></param>
/// <param name="relationType"></param>
public Relation(int parentId, int childId, Guid parentObjectType, Guid childObjectType, IRelationType relationType)
{
_parentId = parentId;
_childId = childId;
_relationType = relationType;
ParentObjectType = parentObjectType;
ChildObjectType = childObjectType;
}
/// <summary>
/// Gets or sets the Parent Id of the Relation (Source)
@@ -35,6 +58,9 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId));
}
[DataMember]
public Guid ParentObjectType { get; set; }
/// <summary>
/// Gets or sets the Child Id of the Relation (Destination)
/// </summary>
@@ -45,6 +71,9 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _childId, nameof(ChildId));
}
[DataMember]
public Guid ChildObjectType { get; set; }
/// <summary>
/// Gets or sets the <see cref="RelationType"/> for the Relation
/// </summary>

View File

@@ -14,28 +14,24 @@ namespace Umbraco.Core.Models
private string _name;
private string _alias;
private bool _isBidrectional;
private Guid _parentObjectType;
private Guid _childObjectType;
private Guid? _parentObjectType;
private Guid? _childObjectType;
public RelationType(Guid childObjectType, Guid parentObjectType, string alias)
public RelationType(string alias, string name)
: this(name, alias, false, null, null)
{
if (alias == null) throw new ArgumentNullException(nameof(alias));
if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias));
}
_childObjectType = childObjectType;
_parentObjectType = parentObjectType;
public RelationType(string name, string alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType)
{
_name = name;
_alias = alias;
Name = _alias;
_isBidrectional = isBidrectional;
_parentObjectType = parentObjectType;
_childObjectType = childObjectType;
}
public RelationType(Guid childObjectType, Guid parentObjectType, string alias, string name)
: this(childObjectType, parentObjectType, alias)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name));
Name = name;
}
/// <summary>
/// Gets or sets the Name of the RelationType
@@ -72,7 +68,7 @@ namespace Umbraco.Core.Models
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember]
public Guid ParentObjectType
public Guid? ParentObjectType
{
get => _parentObjectType;
set => SetPropertyValueAndDetectChanges(value, ref _parentObjectType, nameof(ParentObjectType));
@@ -83,7 +79,7 @@ namespace Umbraco.Core.Models
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember]
public Guid ChildObjectType
public Guid? ChildObjectType
{
get => _childObjectType;
set => SetPropertyValueAndDetectChanges(value, ref _childObjectType, nameof(ChildObjectType));

View File

@@ -1,9 +1,33 @@
using Umbraco.Core.Models;
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Services;
namespace Umbraco.Core.Persistence.Repositories
{
public interface IRelationRepository : IReadWriteQueryRepository<int, IRelation>
{
IEnumerable<IRelation> GetPagedRelationsByQuery(IQuery<IRelation> query, long pageIndex, int pageSize, out long totalRecords, Ordering ordering);
/// <summary>
/// Persist multiple <see cref="IRelation"/> at once
/// </summary>
/// <param name="relations"></param>
void Save(IEnumerable<IRelation> relations);
/// <summary>
/// Deletes all relations for a parent for any specified relation type alias
/// </summary>
/// <param name="parentId"></param>
/// <param name="relationTypeAliases">
/// A list of relation types to match for deletion, if none are specified then all relations for this parent id are deleted
/// </param>
void DeleteByParent(int parentId, params string[] relationTypeAliases);
IEnumerable<IUmbracoEntity> GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes);
IEnumerable<IUmbracoEntity> GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes);
}
}

View File

@@ -4,6 +4,8 @@ using Umbraco.Core.Manifest;
namespace Umbraco.Core.PropertyEditors
{
public class PropertyEditorCollection : BuilderCollectionBase<IDataEditor>
{
public PropertyEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser)

View File

@@ -8,136 +8,162 @@ namespace Umbraco.Core.Services
public interface IRelationService : IService
{
/// <summary>
/// Gets a <see cref="Relation"/> by its Id
/// Gets a <see cref="IRelation"/> by its Id
/// </summary>
/// <param name="id">Id of the <see cref="Relation"/></param>
/// <returns>A <see cref="Relation"/> object</returns>
/// <param name="id">Id of the <see cref="IRelation"/></param>
/// <returns>A <see cref="IRelation"/> object</returns>
IRelation GetById(int id);
/// <summary>
/// Gets a <see cref="RelationType"/> by its Id
/// Gets a <see cref="IRelationType"/> by its Id
/// </summary>
/// <param name="id">Id of the <see cref="RelationType"/></param>
/// <returns>A <see cref="RelationType"/> object</returns>
/// <param name="id">Id of the <see cref="IRelationType"/></param>
/// <returns>A <see cref="IRelationType"/> object</returns>
IRelationType GetRelationTypeById(int id);
/// <summary>
/// Gets a <see cref="RelationType"/> by its Id
/// Gets a <see cref="IRelationType"/> by its Id
/// </summary>
/// <param name="id">Id of the <see cref="RelationType"/></param>
/// <returns>A <see cref="RelationType"/> object</returns>
/// <param name="id">Id of the <see cref="IRelationType"/></param>
/// <returns>A <see cref="IRelationType"/> object</returns>
IRelationType GetRelationTypeById(Guid id);
/// <summary>
/// Gets a <see cref="RelationType"/> by its Alias
/// Gets a <see cref="IRelationType"/> by its Alias
/// </summary>
/// <param name="alias">Alias of the <see cref="RelationType"/></param>
/// <returns>A <see cref="RelationType"/> object</returns>
/// <param name="alias">Alias of the <see cref="IRelationType"/></param>
/// <returns>A <see cref="IRelationType"/> object</returns>
IRelationType GetRelationTypeByAlias(string alias);
/// <summary>
/// Gets all <see cref="Relation"/> objects
/// Gets all <see cref="IRelation"/> objects
/// </summary>
/// <param name="ids">Optional array of integer ids to return relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetAllRelations(params int[] ids);
/// <summary>
/// Gets all <see cref="Relation"/> objects by their <see cref="RelationType"/>
/// Gets all <see cref="IRelation"/> objects by their <see cref="IRelationType"/>
/// </summary>
/// <param name="relationType"><see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
IEnumerable<IRelation> GetAllRelationsByRelationType(RelationType relationType);
/// <param name="relationType"><see cref="IRelation"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetAllRelationsByRelationType(IRelationType relationType);
/// <summary>
/// Gets all <see cref="Relation"/> objects by their <see cref="RelationType"/>'s Id
/// Gets all <see cref="IRelation"/> objects by their <see cref="IRelationType"/>'s Id
/// </summary>
/// <param name="relationTypeId">Id of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <param name="relationTypeId">Id of the <see cref="IRelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetAllRelationsByRelationType(int relationTypeId);
/// <summary>
/// Gets all <see cref="Relation"/> objects
/// Gets all <see cref="IRelation"/> objects
/// </summary>
/// <param name="ids">Optional array of integer ids to return relationtypes for</param>
/// <returns>An enumerable list of <see cref="RelationType"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelationType> GetAllRelationTypes(params int[] ids);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their parent Id
/// Gets a list of <see cref="IRelation"/> objects by their parent Id
/// </summary>
/// <param name="id">Id of the parent to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByParentId(int id);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their parent entity
/// Gets a list of <see cref="IRelation"/> objects by their parent Id
/// </summary>
/// <param name="id">Id of the parent to retrieve relations for</param>
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByParentId(int id, string relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="IRelation"/> objects by their parent entity
/// </summary>
/// <param name="parent">Parent Entity to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByParent(IUmbracoEntity parent);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their parent entity
/// Gets a list of <see cref="IRelation"/> objects by their parent entity
/// </summary>
/// <param name="parent">Parent Entity to retrieve relations for</param>
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByParent(IUmbracoEntity parent, string relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child Id
/// Gets a list of <see cref="IRelation"/> objects by their child Id
/// </summary>
/// <param name="id">Id of the child to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByChildId(int id);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child Entity
/// Gets a list of <see cref="IRelation"/> objects by their child Id
/// </summary>
/// <param name="id">Id of the child to retrieve relations for</param>
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByChildId(int id, string relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="IRelation"/> objects by their child Entity
/// </summary>
/// <param name="child">Child Entity to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByChild(IUmbracoEntity child);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child Entity
/// Gets a list of <see cref="IRelation"/> objects by their child Entity
/// </summary>
/// <param name="child">Child Entity to retrieve relations for</param>
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByChild(IUmbracoEntity child, string relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child or parent Id.
/// Gets a list of <see cref="IRelation"/> objects by their child or parent Id.
/// Using this method will get you all relations regards of it being a child or parent relation.
/// </summary>
/// <param name="id">Id of the child or parent to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByParentOrChildId(int id);
IEnumerable<IRelation> GetByParentOrChildId(int id, string relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by the Name of the <see cref="RelationType"/>
/// Gets a list of <see cref="IRelation"/> objects by the Name of the <see cref="IRelationType"/>
/// </summary>
/// <param name="relationTypeName">Name of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <param name="relationTypeName">Name of the <see cref="IRelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByRelationTypeName(string relationTypeName);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by the Alias of the <see cref="RelationType"/>
/// Gets a list of <see cref="IRelation"/> objects by the Alias of the <see cref="IRelationType"/>
/// </summary>
/// <param name="relationTypeAlias">Alias of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <param name="relationTypeAlias">Alias of the <see cref="IRelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByRelationTypeAlias(string relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by the Id of the <see cref="RelationType"/>
/// Gets a list of <see cref="IRelation"/> objects by the Id of the <see cref="IRelationType"/>
/// </summary>
/// <param name="relationTypeId">Id of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <param name="relationTypeId">Id of the <see cref="IRelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByRelationTypeId(int relationTypeId);
/// <summary>
/// Gets a paged result of <see cref="IRelation"/>
/// </summary>
/// <param name="relationTypeId"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalChildren"></param>
/// <returns></returns>
IEnumerable<IRelation> GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering ordering = null);
/// <summary>
/// Gets the Child object from a Relation as an <see cref="IUmbracoEntity"/>
/// </summary>
@@ -173,6 +199,26 @@ namespace Umbraco.Core.Services
/// <returns>An enumerable list of <see cref="IUmbracoEntity"/></returns>
IEnumerable<IUmbracoEntity> GetParentEntitiesFromRelations(IEnumerable<IRelation> relations);
/// <summary>
/// Returns paged parent entities for a related child id
/// </summary>
/// <param name="id"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalChildren"></param>
/// <returns></returns>
IEnumerable<IUmbracoEntity> GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes);
/// <summary>
/// Returns paged child entities for a related parent id
/// </summary>
/// <param name="id"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalChildren"></param>
/// <returns></returns>
IEnumerable<IUmbracoEntity> GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes);
/// <summary>
/// Gets the Parent and Child objects from a list of Relations as a list of <see cref="IUmbracoEntity"/> objects.
/// </summary>
@@ -186,7 +232,7 @@ namespace Umbraco.Core.Services
/// <param name="parentId">Id of the parent</param>
/// <param name="childId">Id of the child</param>
/// <param name="relationType">The type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <returns>The created <see cref="IRelation"/></returns>
IRelation Relate(int parentId, int childId, IRelationType relationType);
/// <summary>
@@ -195,7 +241,7 @@ namespace Umbraco.Core.Services
/// <param name="parent">Parent entity</param>
/// <param name="child">Child entity</param>
/// <param name="relationType">The type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <returns>The created <see cref="IRelation"/></returns>
IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType);
/// <summary>
@@ -204,7 +250,7 @@ namespace Umbraco.Core.Services
/// <param name="parentId">Id of the parent</param>
/// <param name="childId">Id of the child</param>
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <returns>The created <see cref="IRelation"/></returns>
IRelation Relate(int parentId, int childId, string relationTypeAlias);
/// <summary>
@@ -213,14 +259,14 @@ namespace Umbraco.Core.Services
/// <param name="parent">Parent entity</param>
/// <param name="child">Child entity</param>
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <returns>The created <see cref="IRelation"/></returns>
IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias);
/// <summary>
/// Checks whether any relations exists for the passed in <see cref="RelationType"/>.
/// Checks whether any relations exists for the passed in <see cref="IRelationType"/>.
/// </summary>
/// <param name="relationType"><see cref="RelationType"/> to check for relations</param>
/// <returns>Returns <c>True</c> if any relations exists for the given <see cref="RelationType"/>, otherwise <c>False</c></returns>
/// <param name="relationType"><see cref="IRelationType"/> to check for relations</param>
/// <returns>Returns <c>True</c> if any relations exists for the given <see cref="IRelationType"/>, otherwise <c>False</c></returns>
bool HasRelations(IRelationType relationType);
/// <summary>
@@ -265,33 +311,35 @@ namespace Umbraco.Core.Services
bool AreRelated(int parentId, int childId, string relationTypeAlias);
/// <summary>
/// Saves a <see cref="Relation"/>
/// Saves a <see cref="IRelation"/>
/// </summary>
/// <param name="relation">Relation to save</param>
void Save(IRelation relation);
void Save(IEnumerable<IRelation> relations);
/// <summary>
/// Saves a <see cref="RelationType"/>
/// Saves a <see cref="IRelationType"/>
/// </summary>
/// <param name="relationType">RelationType to Save</param>
void Save(IRelationType relationType);
/// <summary>
/// Deletes a <see cref="Relation"/>
/// Deletes a <see cref="IRelation"/>
/// </summary>
/// <param name="relation">Relation to Delete</param>
void Delete(IRelation relation);
/// <summary>
/// Deletes a <see cref="RelationType"/>
/// Deletes a <see cref="IRelationType"/>
/// </summary>
/// <param name="relationType">RelationType to Delete</param>
void Delete(IRelationType relationType);
/// <summary>
/// Deletes all <see cref="Relation"/> objects based on the passed in <see cref="RelationType"/>
/// Deletes all <see cref="IRelation"/> objects based on the passed in <see cref="IRelationType"/>
/// </summary>
/// <param name="relationType"><see cref="RelationType"/> to Delete Relations for</param>
/// <param name="relationType"><see cref="IRelationType"/> to Delete Relations for</param>
void DeleteRelationsOfType(IRelationType relationType);
}
}

View File

@@ -1,6 +1,6 @@
namespace Umbraco.Core
{
internal class UnknownTypeUdi : Udi
public class UnknownTypeUdi : Udi
{
private UnknownTypeUdi()
: base("unknown", "umb://unknown/")

View File

@@ -26,10 +26,11 @@ namespace Umbraco.Core.Compose
if (relationType == null)
{
relationType = new RelationType(Constants.ObjectTypes.Document,
relationType = new RelationType(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias,
Constants.Conventions.RelationTypes.RelateDocumentOnCopyName,
true,
Constants.ObjectTypes.Document,
Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias,
Constants.Conventions.RelationTypes.RelateDocumentOnCopyName) { IsBidirectional = true };
Constants.ObjectTypes.Document);
relationService.Save(relationType);
}

View File

@@ -63,7 +63,7 @@ namespace Umbraco.Core.Compose
var documentObjectType = Constants.ObjectTypes.Document;
const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName;
relationType = new RelationType(documentObjectType, documentObjectType, relationTypeAlias, relationTypeName);
relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType);
relationService.Save(relationType);
}
@@ -106,7 +106,7 @@ namespace Umbraco.Core.Compose
{
var documentObjectType = Constants.ObjectTypes.Document;
const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName;
relationType = new RelationType(documentObjectType, documentObjectType, relationTypeAlias, relationTypeName);
relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType);
relationService.Save(relationType);
}
foreach (var item in e.MoveInfoCollection)

View File

@@ -157,6 +157,9 @@ namespace Umbraco.Core.Composing
public static DataEditorCollection DataEditors
=> Factory.GetInstance<DataEditorCollection>();
public static DataValueReferenceFactoryCollection DataValueReferenceFactories
=> Factory.GetInstance<DataValueReferenceFactoryCollection>();
public static PropertyEditorCollection PropertyEditors
=> Factory.GetInstance<PropertyEditorCollection>();

View File

@@ -49,6 +49,13 @@ namespace Umbraco.Core
public static DataEditorCollectionBuilder DataEditors(this Composition composition)
=> composition.WithCollectionBuilder<DataEditorCollectionBuilder>();
/// <summary>
/// Gets the data value reference factory collection builder.
/// </summary>
/// <param name="composition">The composition.</param>
public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this Composition composition)
=> composition.WithCollectionBuilder<DataValueReferenceFactoryCollectionBuilder>();
/// <summary>
/// Gets the property value converters collection builder.
/// </summary>

View File

@@ -309,14 +309,27 @@ namespace Umbraco.Core.Migrations.Install
private void CreateRelationTypeData()
{
var relationType = new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName };
relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid();
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName };
relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid();
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
relationType = new RelationTypeDto { Id = 3, Alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = Constants.ObjectTypes.Media, ParentObjectType = Constants.ObjectTypes.Media, Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName };
relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid();
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
relationType = new RelationTypeDto { Id = 4, Alias = Constants.Conventions.RelationTypes.RelatedMediaAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedMediaName };
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
relationType = new RelationTypeDto { Id = 5, Alias = Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedDocumentName };
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
}
internal static Guid CreateUniqueRelationTypeId(string alias, string name)
{
return (alias + "____" + name).ToGuid();
}
private void CreateKeyValueData()

View File

@@ -184,6 +184,10 @@ namespace Umbraco.Core.Migrations.Upgrade
To<RenameUserLoginDtoDateIndex>("{0372A42B-DECF-498D-B4D1-6379E907EB94}");
To<FixContentNuCascade>("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}");
// to 8.5.0...
To<UpdateRelationTypeTable>("{4759A294-9860-46BC-99F9-B4C975CAE580}");
To<AddNewRelationTypes>("{0BC866BC-0665-487A-9913-0290BD0169AD}");
// to 8.6.0
To<AddPropertyTypeValidationMessageColumns>("{3D67D2C8-5E65-47D0-A9E1-DC2EE0779D6B}");

View File

@@ -0,0 +1,33 @@
using Umbraco.Core.Migrations.Install;
namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
{
/// <summary>
/// Ensures the new relation types are created
/// </summary>
public class AddNewRelationTypes : MigrationBase
{
public AddNewRelationTypes(IMigrationContext context)
: base(context)
{ }
public override void Migrate()
{
CreateRelation(
Constants.Conventions.RelationTypes.RelatedMediaAlias,
Constants.Conventions.RelationTypes.RelatedMediaName);
CreateRelation(
Constants.Conventions.RelationTypes.RelatedDocumentAlias,
Constants.Conventions.RelationTypes.RelatedDocumentName);
}
private void CreateRelation(string alias, string name)
{
var uniqueId = DatabaseDataCreator.CreateUniqueRelationTypeId(alias ,name); //this is the same as how it installs so everything is consistent
Insert.IntoTable(Constants.DatabaseSchema.Tables.RelationType)
.Row(new { typeUniqueId = uniqueId, dual = 0, name, alias })
.Do();
}
}
}

View File

@@ -0,0 +1,38 @@
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
{
public class UpdateRelationTypeTable : MigrationBase
{
public UpdateRelationTypeTable(IMigrationContext context)
: base(context)
{ }
public override void Migrate()
{
Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("parentObjectType").AsGuid().Nullable().Do();
Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("childObjectType").AsGuid().Nullable().Do();
//TODO: We have to update this field to ensure it's not null, we can just copy across the name since that is not nullable
//drop index before we can alter the column
if (IndexExists("IX_umbracoRelationType_alias"))
Delete
.Index("IX_umbracoRelationType_alias")
.OnTable(Constants.DatabaseSchema.Tables.RelationType)
.Do();
//change the column to non nullable
Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("alias").AsString(100).NotNullable().Do();
//re-create the index
Create
.Index("IX_umbracoRelationType_alias")
.OnTable(Constants.DatabaseSchema.Tables.RelationType)
.OnColumn("alias")
.Ascending()
.WithOptions().Unique().WithOptions().NonClustered()
.Do();
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Models.Editors
{
/// <summary>
/// Used to track reference to other entities in a property value
/// </summary>
public struct UmbracoEntityReference : IEquatable<UmbracoEntityReference>
{
private static readonly UmbracoEntityReference _empty = new UmbracoEntityReference(UnknownTypeUdi.Instance, string.Empty);
public UmbracoEntityReference(Udi udi, string relationTypeAlias)
{
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
RelationTypeAlias = relationTypeAlias ?? throw new ArgumentNullException(nameof(relationTypeAlias));
}
public UmbracoEntityReference(Udi udi)
{
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
switch (udi.EntityType)
{
case Constants.UdiEntityType.Media:
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedMediaAlias;
break;
default:
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias;
break;
}
}
public static UmbracoEntityReference Empty() => _empty;
public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty();
public Udi Udi { get; }
public string RelationTypeAlias { get; }
public override bool Equals(object obj)
{
return obj is UmbracoEntityReference reference && Equals(reference);
}
public bool Equals(UmbracoEntityReference other)
{
return EqualityComparer<Udi>.Default.Equals(Udi, other.Udi) &&
RelationTypeAlias == other.RelationTypeAlias;
}
public override int GetHashCode()
{
var hashCode = -487348478;
hashCode = hashCode * -1521134295 + EqualityComparer<Udi>.Default.GetHashCode(Udi);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(RelationTypeAlias);
return hashCode;
}
public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right)
{
return left.Equals(right);
}
public static bool operator !=(UmbracoEntityReference left, UmbracoEntityReference right)
{
return !(left == right);
}
}
}

View File

@@ -41,7 +41,7 @@ namespace Umbraco.Core.Models
/// </summary>
public static UmbracoObjectTypes GetUmbracoObjectType(string name)
{
return (UmbracoObjectTypes) Enum.Parse(typeof (UmbracoObjectTypes), name, false);
return (UmbracoObjectTypes) Enum.Parse(typeof (UmbracoObjectTypes), name, true);
}
#region Guid object type utilities

View File

@@ -34,5 +34,13 @@ namespace Umbraco.Core.Persistence.Dtos
[Column("comment")]
[Length(1000)]
public string Comment { get; set; }
[ResultColumn]
[Column("parentObjectType")]
public Guid ParentObjectType { get; set; }
[ResultColumn]
[Column("childObjectType")]
public Guid ChildObjectType { get; set; }
}
}

View File

@@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Dtos
[ExplicitColumns]
internal class RelationTypeDto
{
public const int NodeIdSeed = 4;
public const int NodeIdSeed = 10;
[Column("id")]
[PrimaryKeyColumn(IdentitySeed = NodeIdSeed)]
@@ -23,17 +23,20 @@ namespace Umbraco.Core.Persistence.Dtos
public bool Dual { get; set; }
[Column("parentObjectType")]
public Guid ParentObjectType { get; set; }
[NullSetting(NullSetting = NullSettings.Null)]
public Guid? ParentObjectType { get; set; }
[Column("childObjectType")]
public Guid ChildObjectType { get; set; }
[NullSetting(NullSetting = NullSettings.Null)]
public Guid? ChildObjectType { get; set; }
[Column("name")]
[NullSetting(NullSetting = NullSettings.NotNull)]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_name")]
public string Name { get; set; }
[Column("alias")]
[NullSetting(NullSetting = NullSettings.Null)]
[NullSetting(NullSetting = NullSettings.NotNull)]
[Length(100)]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")]
public string Alias { get; set; }

View File

@@ -3,20 +3,11 @@ using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Persistence.Factories
{
internal class RelationFactory
internal static class RelationFactory
{
private readonly IRelationType _relationType;
public RelationFactory(IRelationType relationType)
public static IRelation BuildEntity(RelationDto dto, IRelationType relationType)
{
_relationType = relationType;
}
#region Implementation of IEntityFactory<Relation,RelationDto>
public IRelation BuildEntity(RelationDto dto)
{
var entity = new Relation(dto.ParentId, dto.ChildId, _relationType);
var entity = new Relation(dto.ParentId, dto.ChildId, dto.ParentObjectType, dto.ChildObjectType, relationType);
try
{
@@ -37,7 +28,7 @@ namespace Umbraco.Core.Persistence.Factories
}
}
public RelationDto BuildDto(IRelation entity)
public static RelationDto BuildDto(IRelation entity)
{
var dto = new RelationDto
{
@@ -54,6 +45,5 @@ namespace Umbraco.Core.Persistence.Factories
return dto;
}
#endregion
}
}

View File

@@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Factories
public static IRelationType BuildEntity(RelationTypeDto dto)
{
var entity = new RelationType(dto.ChildObjectType, dto.ParentObjectType, dto.Alias);
var entity = new RelationType(dto.Name, dto.Alias, dto.Dual, dto.ChildObjectType, dto.ParentObjectType);
try
{
@@ -17,8 +17,6 @@ namespace Umbraco.Core.Persistence.Factories
entity.Id = dto.Id;
entity.Key = dto.UniqueId;
entity.IsBidirectional = dto.Dual;
entity.Name = dto.Name;
// reset dirty initial properties (U4-1946)
entity.ResetDirtyProperties(false);

View File

@@ -14,7 +14,21 @@ namespace Umbraco.Core.Persistence
/// </summary>
public static partial class NPocoDatabaseExtensions
{
// TODO: review NPoco native InsertBulk to replace the code below
/// <summary>
/// Configures NPoco's SqlBulkCopyHelper to use the correct SqlConnection and SqlTransaction instances from the underlying RetryDbConnection and ProfiledDbTransaction
/// </summary>
/// <remarks>
/// This is required to use NPoco's own <see cref="Database.InsertBulk{T}(IEnumerable{T})" /> method because we use wrapped DbConnection and DbTransaction instances.
/// NPoco's InsertBulk method only caters for efficient bulk inserting records for Sql Server, it does not cater for bulk inserting of records for
/// any other database type and in which case will just insert records one at a time.
/// NPoco's InsertBulk method also deals with updating the passed in entity's PK/ID once it's inserted whereas our own BulkInsertRecords methods
/// do not handle this scenario.
/// </remarks>
public static void ConfigureNPocoBulkExtensions()
{
SqlBulkCopyHelper.SqlConnectionResolver = dbConn => GetTypedConnection<SqlConnection>(dbConn);
SqlBulkCopyHelper.SqlTransactionResolver = dbTran => GetTypedTransaction<SqlTransaction>(dbTran);
}
/// <summary>
/// Bulk-inserts records within a transaction.
@@ -235,7 +249,7 @@ namespace Umbraco.Core.Persistence
//we need to add column mappings here because otherwise columns will be matched by their order and if the order of them are different in the DB compared
//to the order in which they are declared in the model then this will not work, so instead we will add column mappings by name so that this explicitly uses
//the names instead of their ordering.
foreach(var col in bulkReader.ColumnMappings)
foreach (var col in bulkReader.ColumnMappings)
{
copy.ColumnMappings.Add(col.DestinationColumn, col.DestinationColumn);
}

View File

@@ -14,10 +14,6 @@ namespace Umbraco.Core.Persistence
public static partial class NPocoSqlExtensions
{
#region Special extensions
#endregion
#region Where
/// <summary>

View File

@@ -1,4 +1,5 @@
using System;
using NPoco;
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -15,10 +16,22 @@ namespace Umbraco.Core.Persistence.Repositories
IEntitySlim Get(int id, Guid objectTypeId);
IEntitySlim Get(Guid key, Guid objectTypeId);
IEnumerable<IEntitySlim> GetAll(Guid objectType, params int[] ids);
IEnumerable<IEntitySlim> GetAll(Guid objectType, params int[] ids);
IEnumerable<IEntitySlim> GetAll(Guid objectType, params Guid[] keys);
/// <summary>
/// Gets entities for a query
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query);
/// <summary>
/// Gets entities for a query and a specific object type allowing the query to be slightly more optimized
/// </summary>
/// <param name="query"></param>
/// <param name="objectType"></param>
/// <returns></returns>
IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query, Guid objectType);
UmbracoObjectTypes GetObjectType(int id);
@@ -30,7 +43,41 @@ namespace Umbraco.Core.Persistence.Repositories
bool Exists(int id);
bool Exists(Guid key);
/// <summary>
/// Gets paged entities for a query and a subset of object types
/// </summary>
/// <param name="query"></param>
/// <param name="objectTypes"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="filter"></param>
/// <param name="ordering"></param>
/// <param name="sqlCustomization">
/// A callback providing the ability to customize the generated SQL used to retrieve entities
/// </param>
/// <returns>
/// A collection of mixed entity types which would be of type <see cref="IEntitySlim"/>, <see cref="IDocumentEntitySlim"/>, <see cref="IMediaEntitySlim"/>,
/// <see cref="IMemberEntitySlim"/>
/// </returns>
IEnumerable<IEntitySlim> GetPagedResultsByQuery(
IQuery<IUmbracoEntity> query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords,
IQuery<IUmbracoEntity> filter, Ordering ordering, Action<Sql<ISqlContext>> sqlCustomization = null);
/// <summary>
/// Gets paged entities for a query and a specific object type
/// </summary>
/// <param name="query"></param>
/// <param name="objectType"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="filter"></param>
/// <param name="ordering"></param>
/// <returns></returns>
IEnumerable<IEntitySlim> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid objectType, long pageIndex, int pageSize, out long totalRecords,
IQuery<IUmbracoEntity> filter, Ordering ordering);
}
}

View File

@@ -9,6 +9,7 @@ using Umbraco.Core.Composing;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
@@ -23,26 +24,48 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal sealed class ContentRepositoryBase
{
/// <summary>
///
/// This is used for unit tests ONLY
/// </summary>
public static bool ThrowOnWarning = false;
}
internal abstract class ContentRepositoryBase<TId, TEntity, TRepository> : NPocoRepositoryBase<TId, TEntity>, IContentRepository<TId, TEntity>
where TEntity : class, IUmbracoEntity
where TEntity : class, IContentBase
where TRepository : class, IRepository
{
protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILanguageRepository languageRepository, ILogger logger)
private readonly Lazy<PropertyEditorCollection> _propertyEditors;
private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories;
/// <summary>
///
/// </summary>
/// <param name="scopeAccessor"></param>
/// <param name="cache"></param>
/// <param name="logger"></param>
/// <param name="languageRepository"></param>
/// <param name="propertyEditors">
/// Lazy property value collection - must be lazy because we have a circular dependency since some property editors require services, yet these services require property editors
/// </param>
protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger,
ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories)
: base(scopeAccessor, cache, logger)
{
LanguageRepository = languageRepository;
RelationRepository = relationRepository;
RelationTypeRepository = relationTypeRepository;
_propertyEditors = propertyEditors;
_dataValueReferenceFactories = dataValueReferenceFactories;
}
protected abstract TRepository This { get; }
protected ILanguageRepository LanguageRepository { get; }
protected IRelationRepository RelationRepository { get; }
protected IRelationTypeRepository RelationTypeRepository { get; }
protected PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // TODO: inject
protected PropertyEditorCollection PropertyEditors => _propertyEditors.Value;
#region Versions
@@ -796,5 +819,56 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
#endregion
protected void PersistRelations(TEntity entity)
{
// Get all references from our core built in DataEditors/Property Editors
// Along with seeing if deverlopers want to collect additional references from the DataValueReferenceFactories collection
var trackedRelations = new List<UmbracoEntityReference>();
trackedRelations.AddRange(_dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors));
//First delete all auto-relations for this entity
RelationRepository.DeleteByParent(entity.Id, Constants.Conventions.RelationTypes.AutomaticRelationTypes);
if (trackedRelations.Count == 0) return;
trackedRelations = trackedRelations.Distinct().ToList();
var udiToGuids = trackedRelations.Select(x => x.Udi as GuidUdi)
.ToDictionary(x => (Udi)x, x => x.Guid);
//lookup in the DB all INT ids for the GUIDs and chuck into a dictionary
var keyToIds = Database.Fetch<NodeIdKey>(Sql().Select<NodeDto>(x => x.NodeId, x => x.UniqueId).From<NodeDto>().WhereIn<NodeDto>(x => x.UniqueId, udiToGuids.Values))
.ToDictionary(x => x.UniqueId, x => x.NodeId);
var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty<int>())
.ToDictionary(x => x.Alias, x => x);
var toSave = trackedRelations.Select(rel =>
{
if (!allRelationTypes.TryGetValue(rel.RelationTypeAlias, out var relationType))
throw new InvalidOperationException($"The relation type {rel.RelationTypeAlias} does not exist");
if (!udiToGuids.TryGetValue(rel.Udi, out var guid))
return null; // This shouldn't happen!
if (!keyToIds.TryGetValue(guid, out var id))
return null; // This shouldn't happen!
return new Relation(entity.Id, id, relationType);
}).WhereNotNull();
// Save bulk relations
RelationRepository.Save(toSave);
}
private class NodeIdKey
{
[Column("id")]
public int NodeId { get; set; }
[Column("uniqueId")]
public Guid UniqueId { get; set; }
}
}
}

View File

@@ -2,6 +2,7 @@
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.Repositories.Implement
@@ -17,8 +18,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </remarks>
internal class DocumentBlueprintRepository : DocumentRepository, IDocumentBlueprintRepository
{
public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository)
public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger,
IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditorCollection, DataValueReferenceFactoryCollection dataValueReferenceFactories)
: base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFactories)
{
}

View File

@@ -12,6 +12,7 @@ using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
@@ -30,8 +31,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private readonly ContentByGuidReadRepository _contentByGuidReadRepository;
private readonly IScopeAccessor _scopeAccessor;
public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, appCaches, languageRepository, logger)
/// <summary>
/// Constructor
/// </summary>
/// <param name="scopeAccessor"></param>
/// <param name="appCaches"></param>
/// <param name="logger"></param>
/// <param name="contentTypeRepository"></param>
/// <param name="templateRepository"></param>
/// <param name="tagRepository"></param>
/// <param name="languageRepository"></param>
/// <param name="propertyEditors">
/// Lazy property value collection - must be lazy because we have a circular dependency since some property editors require services, yet these services require property editors
/// </param>
public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger,
IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories)
: base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFactories)
{
_contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository));
_templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository));
@@ -468,6 +484,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
ClearEntityTags(entity, _tagRepository);
}
PersistRelations(entity);
entity.ResetDirtyProperties();
// troubleshooting
@@ -671,6 +689,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
ClearEntityTags(entity, _tagRepository);
}
PersistRelations(entity);
// TODO: note re. tags: explicitly unpublished entities have cleared tags, but masked or trashed entities *still* have tags in the db - so what?
entity.ResetDirtyProperties();

View File

@@ -34,21 +34,33 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected ISqlSyntaxProvider SqlSyntax => _scopeAccessor.AmbientScope.SqlContext.SqlSyntax;
#region Repository
// get a page of entities
public IEnumerable<IEntitySlim> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid objectType, long pageIndex, int pageSize, out long totalRecords,
IQuery<IUmbracoEntity> filter, Ordering ordering)
{
var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint;
var isMedia = objectType == Constants.ObjectTypes.Media;
var isMember = objectType == Constants.ObjectTypes.Member;
return GetPagedResultsByQuery(query, new[] { objectType }, pageIndex, pageSize, out totalRecords, filter, ordering);
}
var sql = GetBaseWhere(isContent, isMedia, isMember, false, x =>
// get a page of entities
public IEnumerable<IEntitySlim> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords,
IQuery<IUmbracoEntity> filter, Ordering ordering, Action<Sql<ISqlContext>> sqlCustomization = null)
{
var isContent = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint);
var isMedia = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Media);
var isMember = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Member);
var sql = GetBaseWhere(isContent, isMedia, isMember, false, s =>
{
if (filter == null) return;
foreach (var filterClause in filter.GetWhereClauses())
x.Where(filterClause.Item1, filterClause.Item2);
}, objectType);
sqlCustomization?.Invoke(s);
if (filter != null)
{
foreach (var filterClause in filter.GetWhereClauses())
s.Where(filterClause.Item1, filterClause.Item2);
}
}, objectTypes);
ordering = ordering ?? Ordering.ByDefault();
@@ -69,35 +81,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// for content we must query for ContentEntityDto entities to produce the correct culture variant entity names
var pageIndexToFetch = pageIndex + 1;
IEnumerable<BaseDto> dtos;
if(isContent)
{
var page = Database.Page<ContentEntityDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
}
else if (isMedia)
{
var page = Database.Page<MediaEntityDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
}
else if (isMember)
{
var page = Database.Page<MemberEntityDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
}
else
{
var page = Database.Page<BaseDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
}
var page = Database.Page<GenericContentEntityDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
var entities = dtos.Select(x => BuildEntity(isContent, isMedia, isMember, x)).ToArray();
var entities = dtos.Select(BuildEntity).ToArray();
if (isContent)
BuildVariants(entities.Cast<DocumentEntitySlim>());
BuildVariants(entities.OfType<DocumentEntitySlim>());
return entities;
}
@@ -106,7 +96,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var sql = GetBaseWhere(false, false, false, false, key);
var dto = Database.FirstOrDefault<BaseDto>(sql);
return dto == null ? null : BuildEntity(false, false, false, dto);
return dto == null ? null : BuildEntity(dto);
}
@@ -115,7 +105,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//isContent is going to return a 1:M result now with the variants so we need to do different things
if (isContent)
{
var cdtos = Database.Fetch<ContentEntityDto>(sql);
var cdtos = Database.Fetch<DocumentEntityDto>(sql);
return cdtos.Count == 0 ? null : BuildVariants(BuildDocumentEntity(cdtos[0]));
}
@@ -126,7 +116,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (dto == null) return null;
var entity = BuildEntity(false, isMedia, isMember, dto);
var entity = BuildEntity(dto);
return entity;
}
@@ -145,7 +135,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var sql = GetBaseWhere(false, false, false, false, id);
var dto = Database.FirstOrDefault<BaseDto>(sql);
return dto == null ? null : BuildEntity(false, false, false, dto);
return dto == null ? null : BuildEntity(dto);
}
public IEntitySlim Get(int id, Guid objectTypeId)
@@ -177,7 +167,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//isContent is going to return a 1:M result now with the variants so we need to do different things
if (isContent)
{
var cdtos = Database.Fetch<ContentEntityDto>(sql);
var cdtos = Database.Fetch<DocumentEntityDto>(sql);
return cdtos.Count == 0
? Enumerable.Empty<IEntitySlim>()
@@ -188,7 +178,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
? (IEnumerable<BaseDto>)Database.Fetch<MediaEntityDto>(sql)
: Database.Fetch<BaseDto>(sql);
var entities = dtos.Select(x => BuildEntity(false, isMedia, isMember, x)).ToArray();
var entities = dtos.Select(BuildEntity).ToArray();
return entities;
}
@@ -232,7 +222,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var sql = translator.Translate();
sql = AddGroupBy(false, false, false, sql, true);
var dtos = Database.Fetch<BaseDto>(sql);
return dtos.Select(x => BuildEntity(false, false, false, x)).ToList();
return dtos.Select(BuildEntity).ToList();
}
public IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query, Guid objectType)
@@ -241,7 +231,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var isMedia = objectType == Constants.ObjectTypes.Media;
var isMember = objectType == Constants.ObjectTypes.Member;
var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, objectType);
var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, new[] { objectType });
var translator = new SqlTranslator<IUmbracoEntity>(sql, query);
sql = translator.Translate();
@@ -355,14 +345,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// gets the full sql for a given object type, with a given filter
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Action<Sql<ISqlContext>> filter)
{
var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, objectType);
var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, new[] { objectType });
return AddGroupBy(isContent, isMedia, isMember, sql, true);
}
// gets the base SELECT + FROM [+ filter] sql
// always from the 'current' content version
protected Sql<ISqlContext> GetBase(bool isContent, bool isMedia, bool isMember, Action<Sql<ISqlContext>> filter, bool isCount = false)
{
{
var sql = Sql();
if (isCount)
@@ -400,15 +390,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (isContent || isMedia || isMember)
{
sql
.InnerJoin<ContentVersionDto>().On<NodeDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId && right.Current)
.InnerJoin<ContentDto>().On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId)
.InnerJoin<ContentTypeDto>().On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId);
.LeftJoin<ContentVersionDto>().On<NodeDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId && right.Current)
.LeftJoin<ContentDto>().On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId)
.LeftJoin<ContentTypeDto>().On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId);
}
if (isContent)
{
sql
.InnerJoin<DocumentDto>().On<NodeDto, DocumentDto>((left, right) => left.NodeId == right.NodeId);
.LeftJoin<DocumentDto>().On<NodeDto, DocumentDto>((left, right) => left.NodeId == right.NodeId);
}
if (isMedia)
@@ -432,10 +422,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// gets the base SELECT + FROM [+ filter] + WHERE sql
// for a given object type, with a given filter
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action<Sql<ISqlContext>> filter, Guid objectType)
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action<Sql<ISqlContext>> filter, Guid[] objectTypes)
{
return GetBase(isContent, isMedia, isMember, filter, isCount)
.Where<NodeDto>(x => x.NodeObjectType == objectType);
var sql = GetBase(isContent, isMedia, isMember, filter, isCount);
if (objectTypes.Length > 0)
{
sql.WhereIn<NodeDto>(x => x.NodeObjectType, objectTypes);
}
return sql;
}
// gets the base SELECT + FROM + WHERE sql
@@ -509,8 +503,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (sql == null) throw new ArgumentNullException(nameof(sql));
if (ordering == null) throw new ArgumentNullException(nameof(ordering));
// TODO: although this works for name, it probably doesn't work for others without an alias of some sort
var orderBy = ordering.OrderBy;
// TODO: although the default ordering string works for name, it wont work for others without a table or an alias of some sort
// As more things are attempted to be sorted we'll prob have to add more expressions here
string orderBy;
switch (ordering.OrderBy.ToUpperInvariant())
{
case "PATH":
orderBy = SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path");
break;
default:
orderBy = ordering.OrderBy;
break;
}
if (ordering.Direction == Direction.Ascending)
sql.OrderBy(orderBy);
@@ -523,9 +528,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#region Classes
/// <summary>
/// The DTO used to fetch results for a content item with its variation info
/// The DTO used to fetch results for a generic content item which could be either a document, media or a member
/// </summary>
private class ContentEntityDto : BaseDto
private class GenericContentEntityDto : DocumentEntityDto
{
public string MediaPath { get; set; }
}
/// <summary>
/// The DTO used to fetch results for a document item with its variation info
/// </summary>
private class DocumentEntityDto : BaseDto
{
public ContentVariation Variations { get; set; }
@@ -533,11 +546,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public bool Edited { get; set; }
}
/// <summary>
/// The DTO used to fetch results for a media item with its media path info
/// </summary>
private class MediaEntityDto : BaseDto
{
public string MediaPath { get; set; }
}
/// <summary>
/// The DTO used to fetch results for a member item
/// </summary>
private class MemberEntityDto : BaseDto
{
}
@@ -588,13 +607,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#region Factory
private EntitySlim BuildEntity(bool isContent, bool isMedia, bool isMember, BaseDto dto)
private EntitySlim BuildEntity(BaseDto dto)
{
if (isContent)
if (dto.NodeObjectType == Constants.ObjectTypes.Document)
return BuildDocumentEntity(dto);
if (isMedia)
if (dto.NodeObjectType == Constants.ObjectTypes.Media)
return BuildMediaEntity(dto);
if (isMember)
if (dto.NodeObjectType == Constants.ObjectTypes.Member)
return BuildMemberEntity(dto);
// EntitySlim does not track changes
@@ -649,7 +668,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var entity = new DocumentEntitySlim();
BuildContentEntity(entity, dto);
if (dto is ContentEntityDto contentDto)
if (dto is DocumentEntityDto contentDto)
{
// fill in the invariant info
entity.Edited = contentDto.Edited;

View File

@@ -11,6 +11,7 @@ using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
@@ -26,8 +27,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private readonly ITagRepository _tagRepository;
private readonly MediaByGuidReadRepository _mediaByGuidReadRepository;
public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, languageRepository, logger)
public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditorCollection, DataValueReferenceFactoryCollection dataValueReferenceFactories)
: base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFactories)
{
_mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository));
_tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository));
@@ -286,6 +288,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// set tags
SetEntityTags(entity, _tagRepository);
PersistRelations(entity);
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();
@@ -342,6 +346,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
SetEntityTags(entity, _tagRepository);
PersistRelations(entity);
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();

View File

@@ -10,6 +10,7 @@ using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
@@ -25,8 +26,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private readonly ITagRepository _tagRepository;
private readonly IMemberGroupRepository _memberGroupRepository;
public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, languageRepository, logger)
public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger,
IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories)
: base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFactories)
{
_memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository));
_tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository));
@@ -317,6 +320,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
SetEntityTags(entity, _tagRepository);
PersistRelations(entity);
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();
@@ -382,6 +387,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
SetEntityTags(entity, _tagRepository);
PersistRelations(entity);
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();

View File

@@ -9,7 +9,10 @@ using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -19,11 +22,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal class RelationRepository : NPocoRepositoryBase<int, IRelation>, IRelationRepository
{
private readonly IRelationTypeRepository _relationTypeRepository;
private readonly IEntityRepository _entityRepository;
public RelationRepository(IScopeAccessor scopeAccessor, ILogger logger, IRelationTypeRepository relationTypeRepository)
public RelationRepository(IScopeAccessor scopeAccessor, ILogger logger, IRelationTypeRepository relationTypeRepository, IEntityRepository entityRepository)
: base(scopeAccessor, AppCaches.NoCache, logger)
{
_relationTypeRepository = relationTypeRepository;
_entityRepository = entityRepository;
}
#region Overrides of RepositoryBase<int,Relation>
@@ -39,10 +44,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var relationType = _relationTypeRepository.Get(dto.RelationType);
if (relationType == null)
throw new Exception(string.Format("RelationType with Id: {0} doesn't exist", dto.RelationType));
throw new InvalidOperationException(string.Format("RelationType with Id: {0} doesn't exist", dto.RelationType));
var factory = new RelationFactory(relationType);
return DtoToEntity(dto, factory);
return DtoToEntity(dto, relationType);
}
protected override IEnumerable<IRelation> PerformGetAll(params int[] ids)
@@ -67,26 +71,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private IEnumerable<IRelation> DtosToEntities(IEnumerable<RelationDto> dtos)
{
// in most cases, the relation type will be the same for all of them,
// plus we've ordered the relations by type, so try to allocate as few
// factories as possible - bearing in mind that relation types are cached
RelationFactory factory = null;
var relationTypeId = -1;
//NOTE: This is N+1, BUT ALL relation types are cached so shouldn't matter
return dtos.Select(x =>
{
if (relationTypeId != x.RelationType)
factory = new RelationFactory(_relationTypeRepository.Get(relationTypeId = x.RelationType));
return DtoToEntity(x, factory);
}).ToList();
return dtos.Select(x => DtoToEntity(x, _relationTypeRepository.Get(x.RelationType))).ToList();
}
private static IRelation DtoToEntity(RelationDto dto, RelationFactory factory)
private static IRelation DtoToEntity(RelationDto dto, IRelationType relationType)
{
var entity = factory.BuildEntity(dto);
var entity = RelationFactory.BuildEntity(dto, relationType);
// reset dirty initial properties (U4-1946)
((BeingDirtyBase)entity).ResetDirtyProperties(false);
entity.ResetDirtyProperties(false);
return entity;
}
@@ -97,14 +92,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override Sql<ISqlContext> GetBaseQuery(bool isCount)
{
var sql = Sql();
if (isCount)
{
return Sql().SelectCount().From<RelationDto>();
}
sql = isCount
? sql.SelectCount()
: sql.Select<RelationDto>();
var sql = Sql().Select<RelationDto>()
.AndSelect<NodeDto>("uchild", x => Alias(x.NodeObjectType, "childObjectType"))
.AndSelect<NodeDto>("uparent", x => Alias(x.NodeObjectType, "parentObjectType"))
.From<RelationDto>()
.InnerJoin<NodeDto>("uchild").On<RelationDto, NodeDto>((rel, node) => rel.ChildId == node.NodeId, aliasRight: "uchild")
.InnerJoin<NodeDto>("uparent").On<RelationDto, NodeDto>((rel, node) => rel.ParentId == node.NodeId, aliasRight: "uparent");
sql
.From<RelationDto>();
return sql;
}
@@ -136,11 +135,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
entity.AddingEntity();
var factory = new RelationFactory(entity.RelationType);
var dto = factory.BuildDto(entity);
var dto = RelationFactory.BuildDto(entity);
var id = Convert.ToInt32(Database.Insert(dto));
entity.Id = id;
PopulateObjectTypes(entity);
entity.ResetDirtyProperties();
}
@@ -149,13 +149,192 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
entity.UpdatingEntity();
var factory = new RelationFactory(entity.RelationType);
var dto = factory.BuildDto(entity);
var dto = RelationFactory.BuildDto(entity);
Database.Update(dto);
PopulateObjectTypes(entity);
entity.ResetDirtyProperties();
}
#endregion
/// <summary>
/// Used for joining the entity query with relations for the paging methods
/// </summary>
/// <param name="sql"></param>
private void SqlJoinRelations(Sql<ISqlContext> sql)
{
// add left joins for relation tables (this joins on both child or parent, so beware that this will normally return entities for
// both sides of the relation type unless the IUmbracoEntity query passed in filters one side out).
sql.LeftJoin<RelationDto>().On<NodeDto, RelationDto>((left, right) => left.NodeId == right.ChildId || left.NodeId == right.ParentId);
sql.LeftJoin<RelationTypeDto>().On<RelationDto, RelationTypeDto>((left, right) => left.RelationType == right.Id);
}
public IEnumerable<IUmbracoEntity> GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes)
{
// var contentObjectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member }
// we could pass in the contentObjectTypes so that the entity repository sql is configured to do full entity lookups so that we get the full data
// required to populate content, media or members, else we get the bare minimum data needed to populate an entity. BUT if we do this it
// means that the SQL is less efficient and returns data that is probably not needed for what we need this lookup for. For the time being we
// will just return the bare minimum entity data.
return _entityRepository.GetPagedResultsByQuery(Query<IUmbracoEntity>(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql =>
{
SqlJoinRelations(sql);
sql.Where<RelationDto>(rel => rel.ChildId == childId);
sql.Where<RelationDto, NodeDto>((rel, node) => rel.ParentId == childId || node.NodeId != childId);
});
}
public IEnumerable<IUmbracoEntity> GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes)
{
// var contentObjectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member }
// we could pass in the contentObjectTypes so that the entity repository sql is configured to do full entity lookups so that we get the full data
// required to populate content, media or members, else we get the bare minimum data needed to populate an entity. BUT if we do this it
// means that the SQL is less efficient and returns data that is probably not needed for what we need this lookup for. For the time being we
// will just return the bare minimum entity data.
return _entityRepository.GetPagedResultsByQuery(Query<IUmbracoEntity>(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql =>
{
SqlJoinRelations(sql);
sql.Where<RelationDto>(rel => rel.ParentId == parentId);
sql.Where<RelationDto, NodeDto>((rel, node) => rel.ChildId == parentId || node.NodeId != parentId);
});
}
public void Save(IEnumerable<IRelation> relations)
{
foreach (var hasIdentityGroup in relations.GroupBy(r => r.HasIdentity))
{
if (hasIdentityGroup.Key)
{
// Do updates, we can't really do a bulk update so this is still a 1 by 1 operation
// however we can bulk populate the object types. It might be possible to bulk update
// with SQL but would be pretty ugly and we're not really too worried about that for perf,
// it's the bulk inserts we care about.
var asArray = hasIdentityGroup.ToArray();
foreach (var relation in hasIdentityGroup)
{
relation.UpdatingEntity();
var dto = RelationFactory.BuildDto(relation);
Database.Update(dto);
}
PopulateObjectTypes(asArray);
}
else
{
// Do bulk inserts
var entitiesAndDtos = hasIdentityGroup.ToDictionary(
r => // key = entity
{
r.AddingEntity();
return r;
},
RelationFactory.BuildDto); // value = DTO
// Use NPoco's own InsertBulk command which will automatically re-populate the new Ids on the entities, our own
// BulkInsertRecords does not cater for this.
Database.InsertBulk(entitiesAndDtos.Values);
// All dtos now have IDs assigned
foreach (var de in entitiesAndDtos)
{
// re-assign ID to the entity
de.Key.Id = de.Value.Id;
}
PopulateObjectTypes(entitiesAndDtos.Keys.ToArray());
}
}
}
public IEnumerable<IRelation> GetPagedRelationsByQuery(IQuery<IRelation> query, long pageIndex, int pageSize, out long totalRecords, Ordering ordering)
{
var sql = GetBaseQuery(false);
if (ordering == null || ordering.IsEmpty)
ordering = Ordering.By(SqlSyntax.GetQuotedColumn(Constants.DatabaseSchema.Tables.Relation, "id"));
var translator = new SqlTranslator<IRelation>(sql, query);
sql = translator.Translate();
// apply ordering
ApplyOrdering(ref sql, ordering);
var pageIndexToFetch = pageIndex + 1;
var page = Database.Page<RelationDto>(pageIndexToFetch, pageSize, sql);
var dtos = page.Items;
totalRecords = page.TotalItems;
var relTypes = _relationTypeRepository.GetMany(dtos.Select(x => x.RelationType).Distinct().ToArray())
.ToDictionary(x => x.Id, x => x);
var result = dtos.Select(r =>
{
if (!relTypes.TryGetValue(r.RelationType, out var relType))
throw new InvalidOperationException(string.Format("RelationType with Id: {0} doesn't exist", r.RelationType));
return DtoToEntity(r, relType);
}).ToList();
return result;
}
public void DeleteByParent(int parentId, params string[] relationTypeAliases)
{
var subQuery = Sql().Select<RelationDto>(x => x.Id)
.From<RelationDto>()
.InnerJoin<RelationTypeDto>().On<RelationDto, RelationTypeDto>(x => x.RelationType, x => x.Id)
.Where<RelationDto>(x => x.ParentId == parentId);
if (relationTypeAliases.Length > 0)
{
subQuery.WhereIn<RelationTypeDto>(x => x.Alias, relationTypeAliases);
}
Database.Execute(Sql().Delete<RelationDto>().WhereIn<RelationDto>(x => x.Id, subQuery));
}
/// <summary>
/// Used to populate the object types after insert/update
/// </summary>
/// <param name="entities"></param>
private void PopulateObjectTypes(params IRelation[] entities)
{
var entityIds = entities.Select(x => x.ParentId).Concat(entities.Select(y => y.ChildId)).Distinct();
var nodes = Database.Fetch<NodeDto>(Sql().Select<NodeDto>().From<NodeDto>()
.WhereIn<NodeDto>(x => x.NodeId, entityIds))
.ToDictionary(x => x.NodeId, x => x.NodeObjectType);
foreach (var e in entities)
{
if (nodes.TryGetValue(e.ParentId, out var parentObjectType))
{
e.ParentObjectType = parentObjectType.GetValueOrDefault();
}
if (nodes.TryGetValue(e.ChildId, out var childObjectType))
{
e.ChildObjectType = childObjectType.GetValueOrDefault();
}
}
}
private void ApplyOrdering(ref Sql<ISqlContext> sql, Ordering ordering)
{
if (sql == null) throw new ArgumentNullException(nameof(sql));
if (ordering == null) throw new ArgumentNullException(nameof(ordering));
// TODO: although this works for name, it probably doesn't work for others without an alias of some sort
var orderBy = ordering.OrderBy;
if (ordering.Direction == Direction.Ascending)
sql.OrderBy(orderBy);
else
sql.OrderByDescending(orderBy);
}
}
}

View File

@@ -134,7 +134,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IRelationType entity)
{
entity.AddingEntity();
CheckNullObjectTypeValues(entity);
var dto = RelationTypeFactory.BuildDto(entity);
var id = Convert.ToInt32(Database.Insert(dto));
@@ -146,7 +148,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IRelationType entity)
{
entity.UpdatingEntity();
CheckNullObjectTypeValues(entity);
var dto = RelationTypeFactory.BuildDto(entity);
Database.Update(dto);
@@ -154,5 +158,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
#endregion
private void CheckNullObjectTypeValues(IRelationType entity)
{
if (entity.ParentObjectType.HasValue && entity.ParentObjectType == Guid.Empty)
entity.ParentObjectType = null;
if (entity.ChildObjectType.HasValue && entity.ChildObjectType == Guid.Empty)
entity.ChildObjectType = null;
}
}
}

View File

@@ -35,6 +35,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// </remarks>
public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery, WhereInType whereInType = WhereInType.In)
{
//TODO: This is no longer necessary since this used to be a specific requirement for MySql!
// Now we can do a Delete<T> + sub query, see RelationRepository.DeleteByParent for example
return
new Sql(string.Format(

View File

@@ -44,6 +44,8 @@ namespace Umbraco.Core.Persistence
_commandRetryPolicy = commandRetryPolicy;
EnableSqlTrace = EnableSqlTraceDefault;
NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions();
}
/// <summary>
@@ -57,6 +59,8 @@ namespace Umbraco.Core.Persistence
_logger = logger;
EnableSqlTrace = EnableSqlTraceDefault;
NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions();
}
#endregion

View File

@@ -19,6 +19,7 @@ namespace Umbraco.Core.PropertyEditors
public class DataEditor : IDataEditor
{
private IDictionary<string, object> _defaultConfiguration;
private IDataValueEditor _dataValueEditor;
/// <summary>
/// Initializes a new instance of the <see cref="DataEditor"/> class.
@@ -90,7 +91,7 @@ namespace Umbraco.Core.PropertyEditors
/// simple enough for now.</para>
/// </remarks>
// TODO: point of that one? shouldn't we always configure?
public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor();
public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_dataValueEditor ?? (_dataValueEditor = CreateValueEditor()));
/// <inheritdoc />
/// <remarks>
@@ -113,7 +114,7 @@ namespace Umbraco.Core.PropertyEditors
return ExplicitValueEditor;
var editor = CreateValueEditor();
((DataValueEditor) editor).Configuration = configuration; // TODO: casting is bad
((DataValueEditor)editor).Configuration = configuration; // TODO: casting is bad
return editor;
}
@@ -163,7 +164,7 @@ namespace Umbraco.Core.PropertyEditors
protected virtual IDataValueEditor CreateValueEditor()
{
if (Attribute == null)
throw new InvalidOperationException("The editor does not specify a view.");
throw new InvalidOperationException($"The editor is not attributed with {nameof(DataEditorAttribute)}");
return new DataValueEditor(Current.Services.DataTypeService, Current.Services.LocalizationService, Attribute);
}
@@ -175,7 +176,7 @@ namespace Umbraco.Core.PropertyEditors
{
var editor = new ConfigurationEditor();
// pass the default configuration if this is not a property value editor
if((Type & EditorType.PropertyValue) == 0)
if ((Type & EditorType.PropertyValue) == 0)
{
editor.DefaultConfiguration = _defaultConfiguration;
}

View File

@@ -273,12 +273,14 @@ namespace Umbraco.Core.PropertyEditors
}
}
// TODO: the methods below should be replaced by proper property value convert ToXPath usage!
/// <summary>
/// Converts a property to Xml fragments.
/// </summary>
public IEnumerable<XElement> ConvertDbToXml(IProperty property, bool published)
public IEnumerable<XElement> ConvertDbToXml(IProperty property, IDataTypeService dataTypeService,
ILocalizationService localizationService, bool published)
{
published &= property.PropertyType.SupportsPublishing;
@@ -296,7 +298,7 @@ namespace Umbraco.Core.PropertyEditors
if (pvalue.Segment != null)
xElement.Add(new XAttribute("segment", pvalue.Segment));
var xValue = ConvertDbToXml(property.PropertyType, value);
var xValue = ConvertDbToXml(property.PropertyType, value, dataTypeService);
xElement.Add(xValue);
yield return xElement;
@@ -312,12 +314,12 @@ namespace Umbraco.Core.PropertyEditors
/// <para>Returns an XText or XCData instance which must be wrapped in a element.</para>
/// <para>If the value is empty we will not return as CDATA since that will just take up more space in the file.</para>
/// </remarks>
public XNode ConvertDbToXml(IPropertyType propertyType, object value)
public XNode ConvertDbToXml(IPropertyType propertyType, object value, IDataTypeService dataTypeService)
{
//check for null or empty value, we don't want to return CDATA if that is the case
if (value == null || value.ToString().IsNullOrWhiteSpace())
{
return new XText(ConvertDbToString(propertyType, value));
return new XText(ConvertDbToString(propertyType, value, dataTypeService));
}
switch (ValueTypes.ToStorageType(ValueType))
@@ -325,11 +327,11 @@ namespace Umbraco.Core.PropertyEditors
case ValueStorageType.Date:
case ValueStorageType.Integer:
case ValueStorageType.Decimal:
return new XText(ConvertDbToString(propertyType, value));
return new XText(ConvertDbToString(propertyType, value, dataTypeService));
case ValueStorageType.Nvarchar:
case ValueStorageType.Ntext:
//put text in cdata
return new XCData(ConvertDbToString(propertyType, value));
return new XCData(ConvertDbToString(propertyType, value, dataTypeService));
default:
throw new ArgumentOutOfRangeException();
}
@@ -338,7 +340,7 @@ namespace Umbraco.Core.PropertyEditors
/// <summary>
/// Converts a property value to a string.
/// </summary>
public virtual string ConvertDbToString(IPropertyType propertyType, object value)
public virtual string ConvertDbToString(IPropertyType propertyType, object value, IDataTypeService dataTypeService)
{
if (value == null)
return string.Empty;

View File

@@ -0,0 +1,50 @@
using System.Collections.Generic;
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
namespace Umbraco.Core.PropertyEditors
{
public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataValueReferenceFactory>
{
public DataValueReferenceFactoryCollection(IEnumerable<IDataValueReferenceFactory> items)
: base(items)
{ }
public IEnumerable<UmbracoEntityReference> GetAllReferences(IPropertyCollection properties, PropertyEditorCollection propertyEditors)
{
var trackedRelations = new List<UmbracoEntityReference>();
foreach (var p in properties)
{
if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out var editor)) continue;
//TODO: Support variants/segments! This is not required for this initial prototype which is why there is a check here
if (!p.PropertyType.VariesByNothing()) continue;
var val = p.GetValue(); // get the invariant value
var valueEditor = editor.GetValueEditor();
if (valueEditor is IDataValueReference reference)
{
var refs = reference.GetReferences(val);
trackedRelations.AddRange(refs);
}
// Loop over collection that may be add to existing property editors
// implementation of GetReferences in IDataValueReference.
// Allows developers to add support for references by a
// package /property editor that did not implement IDataValueReference themselves
foreach (var item in this)
{
// Check if this value reference is for this datatype/editor
// Then call it's GetReferences method - to see if the value stored
// in the dataeditor/property has referecnes to media/content items
if (item.IsForEditor(editor))
trackedRelations.AddRange(item.GetDataValueReference().GetReferences(val));
}
}
return trackedRelations;
}
}
}

View File

@@ -0,0 +1,9 @@
using Umbraco.Core.Composing;
namespace Umbraco.Core.PropertyEditors
{
public class DataValueReferenceFactoryCollectionBuilder : OrderedCollectionBuilderBase<DataValueReferenceFactoryCollectionBuilder, DataValueReferenceFactoryCollection, IDataValueReferenceFactory>
{
protected override DataValueReferenceFactoryCollectionBuilder This => this;
}
}

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
using Umbraco.Core.Models.Editors;
namespace Umbraco.Core.PropertyEditors
{
/// <summary>
/// Resolve references from <see cref="IDataValueEditor"/> values
/// </summary>
public interface IDataValueReference
{
/// <summary>
/// Returns any references contained in the value
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
IEnumerable<UmbracoEntityReference> GetReferences(object value);
}
}

View File

@@ -0,0 +1,18 @@
namespace Umbraco.Core.PropertyEditors
{
public interface IDataValueReferenceFactory
{
/// <summary>
/// Gets a value indicating whether the DataValueReference lookup supports a datatype (data editor).
/// </summary>
/// <param name="dataType">The datatype.</param>
/// <returns>A value indicating whether the converter supports a datatype.</returns>
bool IsForEditor(IDataEditor dataEditor);
/// <summary>
///
/// </summary>
/// <returns></returns>
IDataValueReference GetDataValueReference();
}
}

View File

@@ -47,7 +47,7 @@ namespace Umbraco.Core.Runtime
// register persistence mappers - required by database factory so needs to be done here
// means the only place the collection can be modified is in a runtime - afterwards it
// has been frozen and it is too late
composition.WithCollectionBuilder<MapperCollectionBuilder>().AddCoreMappers();
composition.Mappers().AddCoreMappers();
// register the scope provider
composition.RegisterUnique<ScopeProvider>(); // implements both IScopeProvider and IScopeAccessor
@@ -76,11 +76,15 @@ namespace Umbraco.Core.Runtime
composition.ManifestFilters();
// properties and parameters derive from data editors
composition.WithCollectionBuilder<DataEditorCollectionBuilder>()
composition.DataEditors()
.Add(() => composition.TypeLoader.GetDataEditors());
composition.RegisterUnique<PropertyEditorCollection>();
composition.RegisterUnique<ParameterEditorCollection>();
// Used to determine if a datatype/editor should be storing/tracking
// references to media item/s
composition.DataValueReferenceFactories();
// register a server registrar, by default it's the db registrar
composition.RegisterUnique<IServerRegistrar>(f =>
{
@@ -109,13 +113,13 @@ namespace Umbraco.Core.Runtime
factory.GetInstance<IHostingEnvironment>()
));
composition.WithCollectionBuilder<CacheRefresherCollectionBuilder>()
composition.CacheRefreshers()
.Add(() => composition.TypeLoader.GetCacheRefreshers());
composition.WithCollectionBuilder<PackageActionCollectionBuilder>()
composition.PackageActions()
.Add(() => composition.TypeLoader.GetPackageActions());
composition.WithCollectionBuilder<PropertyValueConverterCollectionBuilder>()
composition.PropertyValueConverters()
.Append(composition.TypeLoader.GetTypes<IPropertyValueConverter>());
composition.RegisterUnique<IPublishedContentTypeFactory, PublishedContentTypeFactory>();
@@ -123,7 +127,7 @@ namespace Umbraco.Core.Runtime
composition.RegisterUnique<IShortStringHelper>(factory
=> new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance<IUmbracoSettingsSection>())));
composition.WithCollectionBuilder<UrlSegmentProviderCollectionBuilder>()
composition.UrlSegmentProviders()
.Append<DefaultUrlSegmentProvider>();
composition.RegisterUnique<IMigrationBuilder>(factory => new MigrationBuilder(factory));

View File

@@ -564,7 +564,7 @@ namespace Umbraco.Core.Services.Implement
var propertyEditor = Current.PropertyEditors[propertyType.PropertyEditorAlias];
return propertyEditor == null
? Array.Empty<XElement>()
: propertyEditor.GetValueEditor().ConvertDbToXml(property, published);
: propertyEditor.GetValueEditor().ConvertDbToXml(property, _dataTypeService, _localizationService, published);
}
// exports an IContent item descendants.

View File

@@ -25,11 +25,7 @@ namespace Umbraco.Core.Services.Implement
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
}
/// <summary>
/// Gets a <see cref="Relation"/> by its Id
/// </summary>
/// <param name="id">Id of the <see cref="Relation"/></param>
/// <returns>A <see cref="Relation"/> object</returns>
/// <inheritdoc />
public IRelation GetById(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -38,11 +34,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets a <see cref="RelationType"/> by its Id
/// </summary>
/// <param name="id">Id of the <see cref="RelationType"/></param>
/// <returns>A <see cref="RelationType"/> object</returns>
/// <inheritdoc />
public IRelationType GetRelationTypeById(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -51,11 +43,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets a <see cref="RelationType"/> by its Id
/// </summary>
/// <param name="id">Id of the <see cref="RelationType"/></param>
/// <returns>A <see cref="RelationType"/> object</returns>
/// <inheritdoc />
public IRelationType GetRelationTypeById(Guid id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -64,25 +52,10 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets a <see cref="RelationType"/> by its Alias
/// </summary>
/// <param name="alias">Alias of the <see cref="RelationType"/></param>
/// <returns>A <see cref="RelationType"/> object</returns>
public IRelationType GetRelationTypeByAlias(string alias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IRelationType>().Where(x => x.Alias == alias);
return _relationTypeRepository.Get(query).FirstOrDefault();
}
}
/// <inheritdoc />
public IRelationType GetRelationTypeByAlias(string alias) => GetRelationType(alias);
/// <summary>
/// Gets all <see cref="Relation"/> objects
/// </summary>
/// <param name="ids">Optional array of integer ids to return relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetAllRelations(params int[] ids)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -91,21 +64,13 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets all <see cref="Relation"/> objects by their <see cref="RelationType"/>
/// </summary>
/// <param name="relationType"><see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetAllRelationsByRelationType(RelationType relationType)
/// <inheritdoc />
public IEnumerable<IRelation> GetAllRelationsByRelationType(IRelationType relationType)
{
return GetAllRelationsByRelationType(relationType.Id);
}
/// <summary>
/// Gets all <see cref="Relation"/> objects by their <see cref="RelationType"/>'s Id
/// </summary>
/// <param name="relationTypeId">Id of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetAllRelationsByRelationType(int relationTypeId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -115,11 +80,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets all <see cref="Relation"/> objects
/// </summary>
/// <param name="ids">Optional array of integer ids to return relationtypes for</param>
/// <returns>An enumerable list of <see cref="RelationType"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelationType> GetAllRelationTypes(params int[] ids)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -128,82 +89,65 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their parent Id
/// </summary>
/// <param name="id">Id of the parent to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetByParentId(int id)
/// <inheritdoc />
public IEnumerable<IRelation> GetByParentId(int id) => GetByParentId(id, null);
/// <inheritdoc />
public IEnumerable<IRelation> GetByParentId(int id, string relationTypeAlias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IRelation>().Where(x => x.ParentId == id);
return _relationRepository.Get(query);
if (relationTypeAlias.IsNullOrWhiteSpace())
{
var qry1 = Query<IRelation>().Where(x => x.ParentId == id);
return _relationRepository.Get(qry1);
}
var relationType = GetRelationType(relationTypeAlias);
if (relationType == null)
return Enumerable.Empty<IRelation>();
var qry2 = Query<IRelation>().Where(x => x.ParentId == id && x.RelationTypeId == relationType.Id);
return _relationRepository.Get(qry2);
}
}
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their parent entity
/// </summary>
/// <param name="parent">Parent Entity to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetByParent(IUmbracoEntity parent)
{
return GetByParentId(parent.Id);
}
/// <inheritdoc />
public IEnumerable<IRelation> GetByParent(IUmbracoEntity parent) => GetByParentId(parent.Id);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their parent entity
/// </summary>
/// <param name="parent">Parent Entity to retrieve relations for</param>
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetByParent(IUmbracoEntity parent, string relationTypeAlias)
{
return GetByParent(parent).Where(relation => relation.RelationType.Alias == relationTypeAlias);
}
/// <inheritdoc />
public IEnumerable<IRelation> GetByParent(IUmbracoEntity parent, string relationTypeAlias) => GetByParentId(parent.Id, relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child Id
/// </summary>
/// <param name="id">Id of the child to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetByChildId(int id)
/// <inheritdoc />
public IEnumerable<IRelation> GetByChildId(int id) => GetByChildId(id, null);
/// <inheritdoc />
public IEnumerable<IRelation> GetByChildId(int id, string relationTypeAlias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IRelation>().Where(x => x.ChildId == id);
return _relationRepository.Get(query);
if (relationTypeAlias.IsNullOrWhiteSpace())
{
var qry1 = Query<IRelation>().Where(x => x.ChildId == id);
return _relationRepository.Get(qry1);
}
var relationType = GetRelationType(relationTypeAlias);
if (relationType == null)
return Enumerable.Empty<IRelation>();
var qry2 = Query<IRelation>().Where(x => x.ChildId == id && x.RelationTypeId == relationType.Id);
return _relationRepository.Get(qry2);
}
}
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child Entity
/// </summary>
/// <param name="child">Child Entity to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetByChild(IUmbracoEntity child)
{
return GetByChildId(child.Id);
}
/// <inheritdoc />
public IEnumerable<IRelation> GetByChild(IUmbracoEntity child) => GetByChildId(child.Id);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child Entity
/// </summary>
/// <param name="child">Child Entity to retrieve relations for</param>
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetByChild(IUmbracoEntity child, string relationTypeAlias)
{
return GetByChild(child).Where(relation => relation.RelationType.Alias == relationTypeAlias);
}
/// <inheritdoc />
public IEnumerable<IRelation> GetByChild(IUmbracoEntity child, string relationTypeAlias) => GetByChildId(child.Id, relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child or parent Id.
/// Using this method will get you all relations regards of it being a child or parent relation.
/// </summary>
/// <param name="id">Id of the child or parent to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetByParentOrChildId(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -217,8 +161,7 @@ namespace Umbraco.Core.Services.Implement
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var rtQuery = Query<IRelationType>().Where(x => x.Alias == relationTypeAlias);
var relationType = _relationTypeRepository.Get(rtQuery).FirstOrDefault();
var relationType = GetRelationType(relationTypeAlias);
if (relationType == null)
return Enumerable.Empty<IRelation>();
@@ -227,16 +170,13 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by the Name of the <see cref="RelationType"/>
/// </summary>
/// <param name="relationTypeName">Name of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetByRelationTypeName(string relationTypeName)
{
List<int> relationTypeIds;
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
//This is a silly query - but i guess it's needed in case someone has more than one relation with the same Name (not alias), odd.
var query = Query<IRelationType>().Where(x => x.Name == relationTypeName);
var relationTypes = _relationTypeRepository.Get(query);
relationTypeIds = relationTypes.Select(x => x.Id).ToList();
@@ -247,31 +187,17 @@ namespace Umbraco.Core.Services.Implement
: GetRelationsByListOfTypeIds(relationTypeIds);
}
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by the Alias of the <see cref="RelationType"/>
/// </summary>
/// <param name="relationTypeAlias">Alias of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetByRelationTypeAlias(string relationTypeAlias)
{
List<int> relationTypeIds;
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IRelationType>().Where(x => x.Alias == relationTypeAlias);
var relationTypes = _relationTypeRepository.Get(query);
relationTypeIds = relationTypes.Select(x => x.Id).ToList();
}
return relationTypeIds.Count == 0
var relationType = GetRelationType(relationTypeAlias);
return relationType == null
? Enumerable.Empty<IRelation>()
: GetRelationsByListOfTypeIds(relationTypeIds);
: GetRelationsByListOfTypeIds(new[] { relationType.Id });
}
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by the Id of the <see cref="RelationType"/>
/// </summary>
/// <param name="relationTypeId">Id of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetByRelationTypeId(int relationTypeId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -281,37 +207,35 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets the Child object from a Relation as an <see cref="IUmbracoEntity"/>
/// </summary>
/// <param name="relation">Relation to retrieve child object from</param>
/// <returns>An <see cref="IUmbracoEntity"/></returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering ordering = null)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IRelation>().Where(x => x.RelationTypeId == relationTypeId);
return _relationRepository.GetPagedRelationsByQuery(query, pageIndex, pageSize, out totalRecords, ordering);
}
}
/// <inheritdoc />
public IUmbracoEntity GetChildEntityFromRelation(IRelation relation)
{
var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
var objectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType);
return _entityService.Get(relation.ChildId, objectType);
}
/// <summary>
/// Gets the Parent object from a Relation as an <see cref="IUmbracoEntity"/>
/// </summary>
/// <param name="relation">Relation to retrieve parent object from</param>
/// <returns>An <see cref="IUmbracoEntity"/></returns>
/// <inheritdoc />
public IUmbracoEntity GetParentEntityFromRelation(IRelation relation)
{
var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
var objectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType);
return _entityService.Get(relation.ParentId, objectType);
}
/// <summary>
/// Gets the Parent and Child objects from a Relation as a <see cref="Tuple"/>"/> with <see cref="IUmbracoEntity"/>.
/// </summary>
/// <param name="relation">Relation to retrieve parent and child object from</param>
/// <returns>Returns a Tuple with Parent (item1) and Child (item2)</returns>
/// <inheritdoc />
public Tuple<IUmbracoEntity, IUmbracoEntity> GetEntitiesFromRelation(IRelation relation)
{
var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType);
var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType);
var child = _entityService.Get(relation.ChildId, childObjectType);
var parent = _entityService.Get(relation.ParentId, parentObjectType);
@@ -319,45 +243,63 @@ namespace Umbraco.Core.Services.Implement
return new Tuple<IUmbracoEntity, IUmbracoEntity>(parent, child);
}
/// <summary>
/// Gets the Child objects from a list of Relations as a list of <see cref="IUmbracoEntity"/> objects.
/// </summary>
/// <param name="relations">List of relations to retrieve child objects from</param>
/// <returns>An enumerable list of <see cref="IUmbracoEntity"/></returns>
/// <inheritdoc />
public IEnumerable<IUmbracoEntity> GetChildEntitiesFromRelations(IEnumerable<IRelation> relations)
{
foreach (var relation in relations)
// Trying to avoid full N+1 lookups, so we'll group by the object type and then use the GetAll
// method to lookup batches of entities for each parent object type
foreach (var groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ChildObjectType)))
{
var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
yield return _entityService.Get(relation.ChildId, objectType);
var objectType = groupedRelations.Key;
var ids = groupedRelations.Select(x => x.ChildId).ToArray();
foreach (var e in _entityService.GetAll(objectType, ids))
yield return e;
}
}
/// <summary>
/// Gets the Parent objects from a list of Relations as a list of <see cref="IUmbracoEntity"/> objects.
/// </summary>
/// <param name="relations">List of relations to retrieve parent objects from</param>
/// <returns>An enumerable list of <see cref="IUmbracoEntity"/></returns>
/// <inheritdoc />
public IEnumerable<IUmbracoEntity> GetParentEntitiesFromRelations(IEnumerable<IRelation> relations)
{
foreach (var relation in relations)
// Trying to avoid full N+1 lookups, so we'll group by the object type and then use the GetAll
// method to lookup batches of entities for each parent object type
foreach (var groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ParentObjectType)))
{
var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
yield return _entityService.Get(relation.ParentId, objectType);
var objectType = groupedRelations.Key;
var ids = groupedRelations.Select(x => x.ParentId).ToArray();
foreach (var e in _entityService.GetAll(objectType, ids))
yield return e;
}
}
/// <summary>
/// Gets the Parent and Child objects from a list of Relations as a list of <see cref="IUmbracoEntity"/> objects.
/// </summary>
/// <param name="relations">List of relations to retrieve parent and child objects from</param>
/// <returns>An enumerable list of <see cref="Tuple"/> with <see cref="IUmbracoEntity"/></returns>
/// <inheritdoc />
public IEnumerable<IUmbracoEntity> GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _relationRepository.GetPagedParentEntitiesByChildId(id, pageIndex, pageSize, out totalChildren, entityTypes.Select(x => x.GetGuid()).ToArray());
}
}
/// <inheritdoc />
public IEnumerable<IUmbracoEntity> GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _relationRepository.GetPagedChildEntitiesByParentId(id, pageIndex, pageSize, out totalChildren, entityTypes.Select(x => x.GetGuid()).ToArray());
}
}
/// <inheritdoc />
public IEnumerable<Tuple<IUmbracoEntity, IUmbracoEntity>> GetEntitiesFromRelations(IEnumerable<IRelation> relations)
{
//TODO: Argh! N+1
foreach (var relation in relations)
{
var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType);
var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType);
var child = _entityService.Get(relation.ChildId, childObjectType);
var parent = _entityService.Get(relation.ParentId, parentObjectType);
@@ -366,19 +308,15 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Relates two objects by their entity Ids.
/// </summary>
/// <param name="parentId">Id of the parent</param>
/// <param name="childId">Id of the child</param>
/// <param name="relationType">The type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <inheritdoc />
public IRelation Relate(int parentId, int childId, IRelationType relationType)
{
// Ensure that the RelationType has an identity before using it to relate two entities
if (relationType.HasIdentity == false)
Save(relationType);
//TODO: We don't check if this exists first, it will throw some sort of data integrity exception if it already exists, is that ok?
var relation = new Relation(parentId, childId, relationType);
using (var scope = ScopeProvider.CreateScope())
@@ -398,25 +336,13 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Relates two objects that are based on the <see cref="IUmbracoEntity"/> interface.
/// </summary>
/// <param name="parent">Parent entity</param>
/// <param name="child">Child entity</param>
/// <param name="relationType">The type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <inheritdoc />
public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType)
{
return Relate(parent.Id, child.Id, relationType);
}
/// <summary>
/// Relates two objects by their entity Ids.
/// </summary>
/// <param name="parentId">Id of the parent</param>
/// <param name="childId">Id of the child</param>
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <inheritdoc />
public IRelation Relate(int parentId, int childId, string relationTypeAlias)
{
var relationType = GetRelationTypeByAlias(relationTypeAlias);
@@ -426,13 +352,7 @@ namespace Umbraco.Core.Services.Implement
return Relate(parentId, childId, relationType);
}
/// <summary>
/// Relates two objects that are based on the <see cref="IUmbracoEntity"/> interface.
/// </summary>
/// <param name="parent">Parent entity</param>
/// <param name="child">Child entity</param>
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <inheritdoc />
public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias)
{
var relationType = GetRelationTypeByAlias(relationTypeAlias);
@@ -442,11 +362,7 @@ namespace Umbraco.Core.Services.Implement
return Relate(parent.Id, child.Id, relationType);
}
/// <summary>
/// Checks whether any relations exists for the passed in <see cref="RelationType"/>.
/// </summary>
/// <param name="relationType"><see cref="RelationType"/> to check for relations</param>
/// <returns>Returns <c>True</c> if any relations exists for the given <see cref="RelationType"/>, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool HasRelations(IRelationType relationType)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -456,11 +372,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Checks whether any relations exists for the passed in Id.
/// </summary>
/// <param name="id">Id of an object to check relations for</param>
/// <returns>Returns <c>True</c> if any relations exists with the given Id, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool IsRelated(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -470,12 +382,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Checks whether two items are related
/// </summary>
/// <param name="parentId">Id of the Parent relation</param>
/// <param name="childId">Id of the Child relation</param>
/// <returns>Returns <c>True</c> if any relations exists with the given Ids, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool AreRelated(int parentId, int childId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -485,13 +392,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Checks whether two items are related with a given relation type alias
/// </summary>
/// <param name="parentId">Id of the Parent relation</param>
/// <param name="childId">Id of the Child relation</param>
/// <param name="relationTypeAlias">Alias of the relation type</param>
/// <returns>Returns <c>True</c> if any relations exists with the given Ids and relation type, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool AreRelated(int parentId, int childId, string relationTypeAlias)
{
var relType = GetRelationTypeByAlias(relationTypeAlias);
@@ -502,13 +403,7 @@ namespace Umbraco.Core.Services.Implement
}
/// <summary>
/// Checks whether two items are related with a given relation type
/// </summary>
/// <param name="parentId">Id of the Parent relation</param>
/// <param name="childId">Id of the Child relation</param>
/// <param name="relationType">Type of relation</param>
/// <returns>Returns <c>True</c> if any relations exists with the given Ids and relation type, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool AreRelated(int parentId, int childId, IRelationType relationType)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -518,34 +413,20 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Checks whether two items are related
/// </summary>
/// <param name="parent">Parent entity</param>
/// <param name="child">Child entity</param>
/// <returns>Returns <c>True</c> if any relations exist between the entities, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child)
{
return AreRelated(parent.Id, child.Id);
}
/// <summary>
/// Checks whether two items are related
/// </summary>
/// <param name="parent">Parent entity</param>
/// <param name="child">Child entity</param>
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
/// <returns>Returns <c>True</c> if any relations exist between the entities, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias)
{
return AreRelated(parent.Id, child.Id, relationTypeAlias);
}
/// <summary>
/// Saves a <see cref="Relation"/>
/// </summary>
/// <param name="relation">Relation to save</param>
/// <inheritdoc />
public void Save(IRelation relation)
{
using (var scope = ScopeProvider.CreateScope())
@@ -564,10 +445,25 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Saves a <see cref="RelationType"/>
/// </summary>
/// <param name="relationType">RelationType to Save</param>
public void Save(IEnumerable<IRelation> relations)
{
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<IRelation>(relations);
if (scope.Events.DispatchCancelable(SavingRelation, this, saveEventArgs))
{
scope.Complete();
return;
}
_relationRepository.Save(relations);
scope.Complete();
saveEventArgs.CanCancel = false;
scope.Events.Dispatch(SavedRelation, this, saveEventArgs);
}
}
/// <inheritdoc />
public void Save(IRelationType relationType)
{
using (var scope = ScopeProvider.CreateScope())
@@ -586,10 +482,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Deletes a <see cref="Relation"/>
/// </summary>
/// <param name="relation">Relation to Delete</param>
/// <inheritdoc />
public void Delete(IRelation relation)
{
using (var scope = ScopeProvider.CreateScope())
@@ -608,10 +501,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Deletes a <see cref="RelationType"/>
/// </summary>
/// <param name="relationType">RelationType to Delete</param>
/// <inheritdoc />
public void Delete(IRelationType relationType)
{
using (var scope = ScopeProvider.CreateScope())
@@ -630,10 +520,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Deletes all <see cref="Relation"/> objects based on the passed in <see cref="RelationType"/>
/// </summary>
/// <param name="relationType"><see cref="RelationType"/> to Delete Relations for</param>
/// <inheritdoc />
public void DeleteRelationsOfType(IRelationType relationType)
{
var relations = new List<IRelation>();
@@ -642,6 +529,8 @@ namespace Umbraco.Core.Services.Implement
var query = Query<IRelation>().Where(x => x.RelationTypeId == relationType.Id);
relations.AddRange(_relationRepository.Get(query).ToList());
//TODO: N+1, we should be able to do this in a single call
foreach (var relation in relations)
_relationRepository.Delete(relation);
@@ -653,6 +542,15 @@ namespace Umbraco.Core.Services.Implement
#region Private Methods
private IRelationType GetRelationType(string relationTypeAlias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IRelationType>().Where(x => x.Alias == relationTypeAlias);
return _relationTypeRepository.Get(query).FirstOrDefault();
}
}
private IEnumerable<IRelation> GetRelationsByListOfTypeIds(IEnumerable<int> relationTypeIds)
{
var relations = new List<IRelation>();

View File

@@ -161,8 +161,13 @@
<Compile Include="Composing\LightInject\MixedLightInjectScopeManagerProvider.cs" />
<Compile Include="IO\IOHelper.cs" />
<Compile Include="Logging\Viewer\LogTimePeriod.cs" />
<Compile Include="Manifest\DashboardAccessRuleConverter.cs" />
<Compile Include="Manifest\DataEditorConverter.cs" />
<Compile Include="Manifest\IPackageManifest.cs" />
<Compile Include="Manifest\ManifestContentAppFactory.cs" />
<Compile Include="Manifest\ManifestParser.cs" />
<Compile Include="Manifest\ManifestWatcher.cs" />
<Compile Include="Manifest\ValueValidatorConverter.cs" />
<Compile Include="Migrations\IMigrationBuilder.cs" />
<Compile Include="Migrations\Upgrade\Common\DeleteKeysAndIndexes.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\ContentPickerPreValueMigrator.cs" />
@@ -180,6 +185,9 @@
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\RichTextPreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\DropDownFlexiblePreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\MergeDateAndDateTimePropertyEditor.cs" />
<Compile Include="Migrations\Upgrade\V_8_6_0\AddNewRelationTypes.cs" />
<Compile Include="Migrations\Upgrade\V_8_6_0\UpdateRelationTypeTable.cs" />
<Compile Include="Models\Editors\UmbracoEntityReference.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueMigratorBase.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueDto.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\PropertyEditorsMigrationBase.cs" />
@@ -225,6 +233,10 @@
<Compile Include="PropertyEditors\ValueConverters\SliderValueConverter.cs" />
<Compile Include="PropertyEditors\ValueConverters\TagsValueConverter.cs" />
<Compile Include="PropertyEditors\ValueConverters\TinyMceValueConverter.cs" />
<Compile Include="PropertyEditors\DataValueReferenceFactoryCollection.cs" />
<Compile Include="PropertyEditors\DataValueReferenceFactoryCollectionBuilder.cs" />
<Compile Include="PropertyEditors\IDataValueReference.cs" />
<Compile Include="PropertyEditors\IDataValueReferenceFactory.cs" />
<Compile Include="Persistence\Dtos\PropertyTypeCommonDto.cs" />
<Compile Include="Persistence\Factories\MacroFactory.cs" />
<Compile Include="Persistence\Repositories\Implement\ContentTypeCommonRepository.cs" />
@@ -239,7 +251,6 @@
<Compile Include="StringExtensions.cs" />
<Compile Include="Strings\DefaultUrlSegmentProvider.cs" />
<Compile Include="Sync\RefreshInstructionEnvelope.cs" />
<Compile Include="TypeExtensions.cs" />
<Compile Include="TypeLoaderExtensions.cs" />
<Compile Include="Logging\Viewer\CountingFilter.cs" />
<Compile Include="Logging\Viewer\ErrorCounterFilter.cs" />
@@ -261,8 +272,6 @@
<Compile Include="Logging\Serilog\Enrichers\Log4NetLevelMapperEnricher.cs" />
<Compile Include="Logging\Viewer\MessageTemplateFilter.cs" />
<Compile Include="Logging\Viewer\SavedLogSearch.cs" />
<Compile Include="Manifest\DashboardAccessRuleConverter.cs" />
<Compile Include="Manifest\ManifestContentAppFactory.cs" />
<Compile Include="Migrations\MergeBuilder.cs" />
<Compile Include="Migrations\MigrationBase_Extra.cs" />
<Compile Include="Migrations\PostMigrations\IPublishedSnapshotRebuilder.cs" />
@@ -348,9 +357,6 @@
<Compile Include="Logging\OwinLogger.cs" />
<Compile Include="Logging\OwinLoggerFactory.cs" />
<Compile Include="MainDom.cs" />
<Compile Include="Manifest\ValueValidatorConverter.cs" />
<Compile Include="Manifest\ManifestWatcher.cs" />
<Compile Include="Manifest\DataEditorConverter.cs" />
<Compile Include="Migrations\MigrationBuilder.cs" />
<Compile Include="Migrations\MigrationPlan.cs" />
<Compile Include="Migrations\Upgrade\UmbracoPlan.cs" />
@@ -794,6 +800,7 @@
<Compile Include="Sync\DatabaseServerMessenger.cs" />
<Compile Include="Sync\RefreshInstruction.cs" />
<Compile Include="Sync\ServerMessengerBase.cs" />
<Compile Include="TypeExtensions.cs" />
<Compile Include="UriExtensions.cs" />
<Compile Include="Persistence\FaultHandling\RetryDbConnection.cs">
<SubType>Component</SubType>

View File

@@ -13,7 +13,7 @@ namespace Umbraco.Tests.Models
[Test]
public void Can_Deep_Clone()
{
var item = new Relation(9, 8, new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test")
var item = new Relation(9, 8, new RelationType("test", "test", false, Guid.NewGuid(), Guid.NewGuid())
{
Id = 66
})
@@ -51,7 +51,7 @@ namespace Umbraco.Tests.Models
[Test]
public void Can_Serialize_Without_Error()
{
var item = new Relation(9, 8, new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test")
var item = new Relation(9, 8, new RelationType("test", "test", false, Guid.NewGuid(), Guid.NewGuid())
{
Id = 66
})

View File

@@ -13,7 +13,7 @@ namespace Umbraco.Tests.Models
[Test]
public void Can_Deep_Clone()
{
var item = new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test")
var item = new RelationType("test", "test", false, Guid.NewGuid(), Guid.NewGuid())
{
Id = 66,
CreateDate = DateTime.Now,
@@ -47,7 +47,7 @@ namespace Umbraco.Tests.Models
[Test]
public void Can_Serialize_Without_Error()
{
var item = new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test")
var item = new RelationType("test", "test", false, Guid.NewGuid(), Guid.NewGuid())
{
Id = 66,
CreateDate = DateTime.Now,

View File

@@ -26,22 +26,22 @@ namespace Umbraco.Tests.Packaging
public class PackageDataInstallationTests : TestWithSomeContentBase
{
[HideFromTypeFinder]
[DataEditor("7e062c13-7c41-4ad9-b389-41d88aeef87c", "Editor1", "editor1")]
public class Editor1 : DataEditor
{
public Editor1(ILogger logger)
: base(logger)
{
Alias = "7e062c13-7c41-4ad9-b389-41d88aeef87c";
}
}
[HideFromTypeFinder]
[DataEditor("d15e1281-e456-4b24-aa86-1dda3e4299d5", "Editor2", "editor2")]
public class Editor2 : DataEditor
{
public Editor2(ILogger logger)
: base(logger)
{
Alias = "d15e1281-e456-4b24-aa86-1dda3e4299d5";
}
}

View File

@@ -8,6 +8,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -35,7 +36,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(scopeAccessor);
var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}

View File

@@ -69,7 +69,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches);
var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(scopeAccessor);
var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Moq;
@@ -6,6 +7,7 @@ using NUnit.Framework;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -25,7 +27,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
languageRepository = new LanguageRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository, languageRepository);
documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
return domainRepository;
}

View File

@@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
namespace Umbraco.Tests.Persistence.Repositories
{
[TestFixture]
[UmbracoTest(Mapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class EntityRepositoryTest : TestWithDatabaseBase
{
private EntityRepository CreateRepository(IScopeAccessor scopeAccessor)
{
var entityRepository = new EntityRepository(scopeAccessor);
return entityRepository;
}
[Test]
public void Get_Paged_Mixed_Entities_By_Ids()
{
//Create content
var createdContent = new List<IContent>();
var contentType = MockedContentTypes.CreateBasicContentType("blah");
ServiceContext.ContentTypeService.Save(contentType);
for (int i = 0; i < 10; i++)
{
var c1 = MockedContent.CreateBasicContent(contentType);
ServiceContext.ContentService.Save(c1);
createdContent.Add(c1);
}
//Create media
var createdMedia = new List<IMedia>();
var imageType = MockedContentTypes.CreateImageMediaType("myImage");
ServiceContext.MediaTypeService.Save(imageType);
for (int i = 0; i < 10; i++)
{
var c1 = MockedMedia.CreateMediaImage(imageType, -1);
ServiceContext.MediaService.Save(c1);
createdMedia.Add(c1);
}
// Create members
var memberType = MockedContentTypes.CreateSimpleMemberType("simple");
ServiceContext.MemberTypeService.Save(memberType);
var createdMembers = MockedMember.CreateSimpleMember(memberType, 10).ToList();
ServiceContext.MemberService.Save(createdMembers);
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repo = CreateRepository((IScopeAccessor)provider);
var ids = createdContent.Select(x => x.Id).Concat(createdMedia.Select(x => x.Id)).Concat(createdMembers.Select(x => x.Id));
var objectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member };
var query = SqlContext.Query<IUmbracoEntity>()
.WhereIn(e => e.Id, ids);
var entities = repo.GetPagedResultsByQuery(query, objectTypes, 0, 20, out var totalRecords, null, null).ToList();
Assert.AreEqual(20, entities.Count);
Assert.AreEqual(30, totalRecords);
//add the next page
entities.AddRange(repo.GetPagedResultsByQuery(query, objectTypes, 1, 20, out totalRecords, null, null));
Assert.AreEqual(30, entities.Count);
Assert.AreEqual(30, totalRecords);
var contentEntities = entities.OfType<IDocumentEntitySlim>().ToList();
var mediaEntities = entities.OfType<IMediaEntitySlim>().ToList();
var memberEntities = entities.OfType<IMemberEntitySlim>().ToList();
Assert.AreEqual(10, contentEntities.Count);
Assert.AreEqual(10, mediaEntities.Count);
Assert.AreEqual(10, memberEntities.Count);
}
}
}
}

View File

@@ -15,6 +15,7 @@ using Umbraco.Core.Scoping;
using Umbraco.Tests.Testing;
using Umbraco.Core.Services;
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Tests.Persistence.Repositories
{
@@ -39,7 +40,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger);
var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(scopeAccessor);
var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}

View File

@@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -35,7 +36,12 @@ namespace Umbraco.Tests.Persistence.Repositories
memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
memberGroupRepository = new MemberGroupRepository(accessor, AppCaches.Disabled, Logger);
var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger);
var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of<ILanguageRepository>());
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of<ILanguageRepository>(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}

View File

@@ -7,6 +7,7 @@ using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -310,7 +311,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
@@ -6,6 +7,7 @@ using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
@@ -31,7 +33,8 @@ namespace Umbraco.Tests.Persistence.Repositories
{
var accessor = (IScopeAccessor) provider;
relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Mock.Of<ILogger>());
var repository = new RelationRepository(accessor, Mock.Of<ILogger>(), relationTypeRepository);
var entityRepository = new EntityRepository(accessor);
var repository = new RelationRepository(accessor, Mock.Of<ILogger>(), relationTypeRepository, entityRepository);
return repository;
}
@@ -168,6 +171,156 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
[Test]
public void Get_Paged_Parent_Entities_By_Child_Id()
{
CreateTestDataForPagingTests(out var createdContent, out var createdMembers, out var createdMedia);
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider, out var relationTypeRepository);
// Get parent entities for child id
var parents = repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 11, out var totalRecords).ToList();
Assert.AreEqual(20, totalRecords);
Assert.AreEqual(11, parents.Count);
//add the next page
parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 1, 11, out totalRecords));
Assert.AreEqual(20, totalRecords);
Assert.AreEqual(20, parents.Count);
var contentEntities = parents.OfType<IDocumentEntitySlim>().ToList();
var mediaEntities = parents.OfType<IMediaEntitySlim>().ToList();
var memberEntities = parents.OfType<IMemberEntitySlim>().ToList();
Assert.AreEqual(10, contentEntities.Count);
Assert.AreEqual(0, mediaEntities.Count);
Assert.AreEqual(10, memberEntities.Count);
//only of a certain type
parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Document.GetGuid()));
Assert.AreEqual(10, totalRecords);
parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Member.GetGuid()));
Assert.AreEqual(10, totalRecords);
parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Media.GetGuid()));
Assert.AreEqual(0, totalRecords);
}
}
[Test]
public void Get_Paged_Parent_Child_Entities_With_Same_Entity_Relation()
{
//Create a media item and create a relationship between itself (parent -> child)
var imageType = MockedContentTypes.CreateImageMediaType("myImage");
ServiceContext.MediaTypeService.Save(imageType);
var media = MockedMedia.CreateMediaImage(imageType, -1);
ServiceContext.MediaService.Save(media);
var relType = ServiceContext.RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias);
ServiceContext.RelationService.Relate(media.Id, media.Id, relType);
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider, out var relationTypeRepository);
// Get parent entities for child id
var parents = repository.GetPagedParentEntitiesByChildId(media.Id, 0, 10, out var totalRecords).ToList();
Assert.AreEqual(1, totalRecords);
Assert.AreEqual(1, parents.Count);
// Get child entities for parent id
var children = repository.GetPagedChildEntitiesByParentId(media.Id, 0, 10, out totalRecords).ToList();
Assert.AreEqual(1, totalRecords);
Assert.AreEqual(1, children.Count);
}
}
[Test]
public void Get_Paged_Child_Entities_By_Parent_Id()
{
CreateTestDataForPagingTests(out var createdContent, out var createdMembers, out var createdMedia);
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider, out var relationTypeRepository);
// Get parent entities for child id
var parents = repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 6, out var totalRecords).ToList();
Assert.AreEqual(10, totalRecords);
Assert.AreEqual(6, parents.Count);
//add the next page
parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 1, 6, out totalRecords));
Assert.AreEqual(10, totalRecords);
Assert.AreEqual(10, parents.Count);
var contentEntities = parents.OfType<IDocumentEntitySlim>().ToList();
var mediaEntities = parents.OfType<IMediaEntitySlim>().ToList();
var memberEntities = parents.OfType<IMemberEntitySlim>().ToList();
Assert.AreEqual(0, contentEntities.Count);
Assert.AreEqual(10, mediaEntities.Count);
Assert.AreEqual(0, memberEntities.Count);
//only of a certain type
parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Media.GetGuid()));
Assert.AreEqual(10, totalRecords);
parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdMembers[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Media.GetGuid()));
Assert.AreEqual(10, totalRecords);
parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Member.GetGuid()));
Assert.AreEqual(0, totalRecords);
}
}
private void CreateTestDataForPagingTests(out List<IContent> createdContent, out List<IMember> createdMembers, out List<IMedia> createdMedia)
{
//Create content
createdContent = new List<IContent>();
var contentType = MockedContentTypes.CreateBasicContentType("blah");
ServiceContext.ContentTypeService.Save(contentType);
for (int i = 0; i < 10; i++)
{
var c1 = MockedContent.CreateBasicContent(contentType);
ServiceContext.ContentService.Save(c1);
createdContent.Add(c1);
}
//Create media
createdMedia = new List<IMedia>();
var imageType = MockedContentTypes.CreateImageMediaType("myImage");
ServiceContext.MediaTypeService.Save(imageType);
for (int i = 0; i < 10; i++)
{
var c1 = MockedMedia.CreateMediaImage(imageType, -1);
ServiceContext.MediaService.Save(c1);
createdMedia.Add(c1);
}
// Create members
var memberType = MockedContentTypes.CreateSimpleMemberType("simple");
ServiceContext.MemberTypeService.Save(memberType);
createdMembers = MockedMember.CreateSimpleMember(memberType, 10).ToList();
ServiceContext.MemberService.Save(createdMembers);
var relType = ServiceContext.RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias);
// Relate content to media
foreach (var content in createdContent)
foreach (var media in createdMedia)
ServiceContext.RelationService.Relate(content.Id, media.Id, relType);
// Relate members to media
foreach (var member in createdMembers)
foreach (var media in createdMedia)
ServiceContext.RelationService.Relate(member.Id, media.Id, relType);
}
[Test]
public void Can_Perform_Exists_On_RelationRepository()
{
@@ -260,14 +413,24 @@ namespace Umbraco.Tests.Persistence.Repositories
public void CreateTestData()
{
var relateContent = new RelationType(Constants.ObjectTypes.Document, new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"), "relateContentOnCopy") { IsBidirectional = true, Name = "Relate Content on Copy" };
var relateContentType = new RelationType(Constants.ObjectTypes.DocumentType, new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"), "relateContentTypeOnCopy") { IsBidirectional = true, Name = "Relate ContentType on Copy" };
var relateContent = new RelationType(
"Relate Content on Copy", "relateContentOnCopy", true,
Constants.ObjectTypes.Document,
new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"));
var relateContentType = new RelationType("Relate ContentType on Copy",
"relateContentTypeOnCopy",
true,
Constants.ObjectTypes.DocumentType,
new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"));
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var relationTypeRepository = new RelationTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of<ILogger>());
var relationRepository = new RelationRepository((IScopeAccessor) provider, Mock.Of<ILogger>(), relationTypeRepository);
var accessor = (IScopeAccessor)provider;
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Mock.Of<ILogger>());
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Mock.Of<ILogger>(), relationTypeRepository, entityRepository);
relationTypeRepository.Save(relateContent);
relationTypeRepository.Save(relateContentType);

View File

@@ -42,9 +42,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var repository = CreateRepository(provider);
// Act
var relateMemberToContent = new RelationType(Constants.ObjectTypes.Member,
Constants.ObjectTypes.Document,
"relateMemberToContent") { IsBidirectional = true, Name = "Relate Member to Content" };
var relateMemberToContent = new RelationType("Relate Member to Content", "relateMemberToContent", true, Constants.ObjectTypes.Member, Constants.ObjectTypes.Document);
repository.Save(relateMemberToContent);
@@ -135,7 +133,7 @@ namespace Umbraco.Tests.Persistence.Repositories
Assert.That(relationTypes, Is.Not.Null);
Assert.That(relationTypes.Any(), Is.True);
Assert.That(relationTypes.Any(x => x == null), Is.False);
Assert.That(relationTypes.Count(), Is.EqualTo(5));
Assert.That(relationTypes.Count(), Is.EqualTo(7));
}
}
@@ -226,8 +224,8 @@ namespace Umbraco.Tests.Persistence.Repositories
public void CreateTestData()
{
var relateContent = new RelationType(Constants.ObjectTypes.Document, new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"), "relateContentOnCopy") { IsBidirectional = true, Name = "Relate Content on Copy" };
var relateContentType = new RelationType(Constants.ObjectTypes.DocumentType, new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"), "relateContentTypeOnCopy") { IsBidirectional = true, Name = "Relate ContentType on Copy" };
var relateContent = new RelationType("Relate Content on Copy", "relateContentOnCopy", true, Constants.ObjectTypes.Document, new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"));
var relateContentType = new RelationType("Relate ContentType on Copy", "relateContentTypeOnCopy", true, Constants.ObjectTypes.DocumentType, new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"));
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = ScopeProvider.CreateScope())

View File

@@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core.Cache;
@@ -7,6 +8,7 @@ using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -74,7 +76,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
// create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -104,7 +106,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -143,7 +145,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -185,7 +187,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -225,7 +227,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -261,7 +263,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -305,7 +307,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -349,7 +351,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -394,7 +396,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -429,7 +431,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -469,7 +471,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -513,7 +515,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -557,7 +559,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -601,7 +603,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -646,7 +648,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
var mediaRepository = CreateMediaRepository(provider, out var mediaTypeRepository);
//create data to relate to
@@ -703,7 +705,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
var mediaRepository = CreateMediaRepository(provider, out var mediaTypeRepository);
//create data to relate to
@@ -755,7 +757,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -791,7 +793,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
var mediaRepository = CreateMediaRepository(provider, out var mediaTypeRepository);
//create data to relate to
@@ -871,7 +873,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
var mediaRepository = CreateMediaRepository(provider, out var mediaTypeRepository);
//create data to relate to
@@ -950,7 +952,7 @@ namespace Umbraco.Tests.Persistence.Repositories
return new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
}
private DocumentRepository CreateContentRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository)
private DocumentRepository CreateDocumentRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository)
{
var accessor = (IScopeAccessor) provider;
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock(), IOHelper);
@@ -958,7 +960,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
@@ -970,7 +977,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
}

View File

@@ -13,6 +13,7 @@ using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -238,11 +239,16 @@ namespace Umbraco.Tests.Persistence.Repositories
{
var templateRepository = CreateRepository(ScopeProvider);
var tagRepository = new TagRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
var tagRepository = new TagRepository(ScopeProvider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(ScopeProvider, templateRepository, AppCaches);
var languageRepository = new LanguageRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var languageRepository = new LanguageRepository(ScopeProvider, AppCaches.Disabled, Logger);
var contentTypeRepository = new ContentTypeRepository(ScopeProvider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(ScopeProvider, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(ScopeProvider);
var relationRepository = new RelationRepository(ScopeProvider, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var contentRepo = new DocumentRepository(ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage");
ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType!

View File

@@ -14,6 +14,8 @@ using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
using Umbraco.Core.Persistence;
using Umbraco.Core.PropertyEditors;
using System;
using Umbraco.Core.Configuration;
namespace Umbraco.Tests.Persistence.Repositories
@@ -30,7 +32,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of<ILogger>(), commonRepository, languageRepository);
var tagRepository = new TagRepository(accessor, AppCaches, Mock.Of<ILogger>());
var repository = new MediaRepository(accessor, AppCaches, Mock.Of<ILogger>(), mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new MediaRepository(accessor, AppCaches, Mock.Of<ILogger>(), mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
@@ -48,7 +55,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}

View File

@@ -24,6 +24,7 @@ using Umbraco.Web.PropertyEditors;
namespace Umbraco.Tests.PropertyEditors
{
[TestFixture]
public class ImageCropperTest
{

View File

@@ -51,7 +51,7 @@ namespace Umbraco.Tests.PropertyEditors
var valueEditor = dataType.Editor.GetValueEditor();
((DataValueEditor) valueEditor).Configuration = dataType.Configuration;
var result = valueEditor.ConvertDbToString(prop.PropertyType, prop.GetValue());
var result = valueEditor.ConvertDbToString(prop.PropertyType, prop.GetValue(), dataTypeService);
Assert.AreEqual("Value 1,Value 2,Value 3", result);
}
@@ -78,7 +78,7 @@ namespace Umbraco.Tests.PropertyEditors
var prop = new Property(1, new PropertyType(dataType));
prop.SetValue("Value 2");
var result = dataType.Editor.GetValueEditor().ConvertDbToString(prop.PropertyType, prop.GetValue());
var result = dataType.Editor.GetValueEditor().ConvertDbToString(prop.PropertyType, prop.GetValue(), dataTypeService);
Assert.AreEqual("Value 2", result);
}

View File

@@ -35,7 +35,7 @@ namespace Umbraco.Tests.Published
var localizationService = Mock.Of<ILocalizationService>();
PropertyEditorCollection editors = null;
var editor = new NestedContentPropertyEditor(logger, new Lazy<PropertyEditorCollection>(() => editors), Mock.Of<IDataTypeService>(), localizationService);
var editor = new NestedContentPropertyEditor(logger, new Lazy<PropertyEditorCollection>(() => editors), Mock.Of<IDataTypeService>(), Mock.Of<IContentTypeService>(), localizationService);
editors = new PropertyEditorCollection(new DataEditorCollection(new DataEditor[] { editor }));
var dataType1 = new DataType(editor)

View File

@@ -11,6 +11,7 @@ using Umbraco.Core.Models;
using Umbraco.Web.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.Templates;
namespace Umbraco.Tests.PublishedContent
{
@@ -38,7 +39,12 @@ namespace Umbraco.Tests.PublishedContent
base.Initialize();
var converters = Factory.GetInstance<PropertyValueConverterCollection>();
var umbracoContextAccessor = Mock.Of<IUmbracoContextAccessor>();
var logger = Mock.Of<ILogger>();
var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor);
var pastedImages = new RichTextEditorPastedImages(umbracoContextAccessor, logger, IOHelper, Mock.Of<IMediaService>(), Mock.Of<IContentTypeBaseServiceProvider>());
var localLinkParser = new HtmlLocalLinkParser(umbracoContextAccessor);
var dataTypeService = new TestObjects.TestDataTypeService(
new DataType(new RichTextPropertyEditor(
Mock.Of<ILogger>(),
@@ -46,7 +52,9 @@ namespace Umbraco.Tests.PublishedContent
Mock.Of<IContentTypeBaseServiceProvider>(),
Mock.Of<IUmbracoContextAccessor>(),
Mock.Of<IDataTypeService>(),
Mock.Of<ILocalizationService>())) { Id = 1 });
Mock.Of<ILocalizationService>(),
imageSourceParser, localLinkParser, pastedImages)) { Id = 1 });
var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), converters, dataTypeService);

View File

@@ -20,11 +20,13 @@ using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
using Umbraco.Core.Strings;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
using Umbraco.Web.Models.PublishedContent;
using Umbraco.Web.PropertyEditors;
using Umbraco.Web.Templates;
namespace Umbraco.Tests.PublishedContent
{
@@ -49,15 +51,18 @@ namespace Umbraco.Tests.PublishedContent
var mediaService = Mock.Of<IMediaService>();
var contentTypeBaseServiceProvider = Mock.Of<IContentTypeBaseServiceProvider>();
var umbracoContextAccessor = Mock.Of<IUmbracoContextAccessor>();
var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor);
var pastedImages = new RichTextEditorPastedImages(umbracoContextAccessor, logger, IOHelper, mediaService, contentTypeBaseServiceProvider);
var linkParser = new HtmlLocalLinkParser(umbracoContextAccessor);
var localizationService = Mock.Of<ILocalizationService>();
var dataTypeService = new TestObjects.TestDataTypeService(
new DataType(new VoidEditor(logger)) { Id = 1 },
new DataType(new TrueFalsePropertyEditor(logger)) { Id = 1001 },
new DataType(new RichTextPropertyEditor(logger, mediaService, contentTypeBaseServiceProvider, umbracoContextAccessor, Mock.Of<IDataTypeService>(), localizationService)) { Id = 1002 },
new DataType(new RichTextPropertyEditor(logger, mediaService, contentTypeBaseServiceProvider, umbracoContextAccessor, Mock.Of<IDataTypeService>(), localizationService, imageSourceParser, linkParser, pastedImages)) { Id = 1002 },
new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 },
new DataType(new TextboxPropertyEditor(logger, Mock.Of<IDataTypeService>(), localizationService)) { Id = 1004 },
new DataType(new MediaPickerPropertyEditor(logger)) { Id = 1005 });
new DataType(new MediaPickerPropertyEditor(logger, Mock.Of<IDataTypeService>(), localizationService)) { Id = 1005 });
Composition.RegisterUnique<IDataTypeService>(f => dataTypeService);
}

View File

@@ -14,6 +14,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -39,6 +40,23 @@ namespace Umbraco.Tests.Services
Composition.Register<IProfiler, TestProfiler>();
}
private DocumentRepository CreateDocumentRepository(IScopeProvider provider)
{
var accessor = (IScopeAccessor)provider;
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock(), IOHelper);
var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, tRepository, AppCaches);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
[Test]
public void Profiler()
{
@@ -163,12 +181,7 @@ namespace Umbraco.Tests.Services
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock(), IOHelper);
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
var repository = CreateDocumentRepository(provider);
// Act
Stopwatch watch = Stopwatch.StartNew();
@@ -197,12 +210,7 @@ namespace Umbraco.Tests.Services
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock(), IOHelper);
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
var repository = CreateDocumentRepository(provider);
// Act
Stopwatch watch = Stopwatch.StartNew();
@@ -229,12 +237,7 @@ namespace Umbraco.Tests.Services
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock(), IOHelper);
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor) provider, tRepository, AppCaches);
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
var repository = CreateDocumentRepository(provider);
// Act
var contents = repository.GetMany();
@@ -264,12 +267,7 @@ namespace Umbraco.Tests.Services
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock(), IOHelper);
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
var repository = CreateDocumentRepository(provider);
// Act
var contents = repository.GetMany();

View File

@@ -448,6 +448,47 @@ namespace Umbraco.Tests.Services
Assert.That(content.HasIdentity, Is.False);
}
[Test]
public void Automatically_Track_Relations()
{
var mt = MockedContentTypes.CreateSimpleMediaType("testMediaType", "Test Media Type");
ServiceContext.MediaTypeService.Save(mt);
var m1 = MockedMedia.CreateSimpleMedia(mt, "hello 1", -1);
var m2 = MockedMedia.CreateSimpleMedia(mt, "hello 1", -1);
ServiceContext.MediaService.Save(m1);
ServiceContext.MediaService.Save(m2);
var ct = MockedContentTypes.CreateTextPageContentType("richTextTest");
ct.AllowedTemplates = Enumerable.Empty<ITemplate>();
ServiceContext.ContentTypeService.Save(ct);
var c1 = MockedContent.CreateTextpageContent(ct, "my content 1", -1);
ServiceContext.ContentService.Save(c1);
var c2 = MockedContent.CreateTextpageContent(ct, "my content 2", -1);
//'bodyText' is a property with a RTE property editor which we knows tracks relations
c2.Properties["bodyText"].SetValue(@"<p>
<img src='/media/12312.jpg' data-udi='umb://media/" + m1.Key.ToString("N") + @"' />
</p><p><img src='/media/234234.jpg' data-udi=""umb://media/" + m2.Key.ToString("N") + @""" />
</p>
<p>
<a href=""{locallink:umb://document/" + c1.Key.ToString("N") + @"}"">hello</a>
</p>");
ServiceContext.ContentService.Save(c2);
var relations = ServiceContext.RelationService.GetByParentId(c2.Id).ToList();
Assert.AreEqual(3, relations.Count);
Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMediaAlias, relations[0].RelationType.Alias);
Assert.AreEqual(m1.Id, relations[0].ChildId);
Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMediaAlias, relations[1].RelationType.Alias);
Assert.AreEqual(m2.Id, relations[1].ChildId);
Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedDocumentAlias, relations[2].RelationType.Alias);
Assert.AreEqual(c1.Id, relations[2].ChildId);
}
[Test]
public void Can_Create_Content_Without_Explicitly_Set_User()
{
@@ -1778,7 +1819,7 @@ namespace Umbraco.Tests.Services
admin.StartContentIds = new[] {content1.Id};
ServiceContext.UserService.Save(admin);
ServiceContext.RelationService.Save(new RelationType(Constants.ObjectTypes.Document, Constants.ObjectTypes.Document, "test"));
ServiceContext.RelationService.Save(new RelationType("test", "test", false, Constants.ObjectTypes.Document, Constants.ObjectTypes.Document));
Assert.IsNotNull(ServiceContext.RelationService.Relate(content1, content2, "test"));
ServiceContext.PublicAccessService.Save(new PublicAccessEntry(content1, content2, content2, new List<PublicAccessRule>
@@ -3166,7 +3207,12 @@ namespace Umbraco.Tests.Services
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}

View File

@@ -1,27 +1,247 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
namespace Umbraco.Tests.Services
{
[TestFixture]
[Apartment(ApartmentState.STA)]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class RelationServiceTests : TestWithSomeContentBase
{
[Test]
public void Get_Paged_Relations_By_Relation_Type()
{
//Create content
var createdContent = new List<IContent>();
var contentType = MockedContentTypes.CreateBasicContentType("blah");
ServiceContext.ContentTypeService.Save(contentType);
for (int i = 0; i < 10; i++)
{
var c1 = MockedContent.CreateBasicContent(contentType);
ServiceContext.ContentService.Save(c1);
createdContent.Add(c1);
}
//Create media
var createdMedia = new List<IMedia>();
var imageType = MockedContentTypes.CreateImageMediaType("myImage");
ServiceContext.MediaTypeService.Save(imageType);
for (int i = 0; i < 10; i++)
{
var c1 = MockedMedia.CreateMediaImage(imageType, -1);
ServiceContext.MediaService.Save(c1);
createdMedia.Add(c1);
}
var relType = ServiceContext.RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias);
// Relate content to media
foreach (var content in createdContent)
foreach (var media in createdMedia)
ServiceContext.RelationService.Relate(content.Id, media.Id, relType);
var paged = ServiceContext.RelationService.GetPagedByRelationTypeId(relType.Id, 0, 51, out var totalRecs).ToList();
Assert.AreEqual(100, totalRecs);
Assert.AreEqual(51, paged.Count);
//next page
paged.AddRange(ServiceContext.RelationService.GetPagedByRelationTypeId(relType.Id, 1, 51, out totalRecs));
Assert.AreEqual(100, totalRecs);
Assert.AreEqual(100, paged.Count);
Assert.IsTrue(createdContent.Select(x => x.Id).ContainsAll(paged.Select(x => x.ParentId)));
Assert.IsTrue(createdMedia.Select(x => x.Id).ContainsAll(paged.Select(x => x.ChildId)));
}
[Test]
public void Return_List_Of_Content_Items_Where_Media_Item_Referenced()
{
var mt = MockedContentTypes.CreateSimpleMediaType("testMediaType", "Test Media Type");
ServiceContext.MediaTypeService.Save(mt);
var m1 = MockedMedia.CreateSimpleMedia(mt, "hello 1", -1);
ServiceContext.MediaService.Save(m1);
var ct = MockedContentTypes.CreateTextPageContentType("richTextTest");
ct.AllowedTemplates = Enumerable.Empty<ITemplate>();
ServiceContext.ContentTypeService.Save(ct);
void createContentWithMediaRefs()
{
var content = MockedContent.CreateTextpageContent(ct, "my content 2", -1);
//'bodyText' is a property with a RTE property editor which we knows automatically tracks relations
content.Properties["bodyText"].SetValue(@"<p>
<img src='/media/12312.jpg' data-udi='umb://media/" + m1.Key.ToString("N") + @"' />
</p>");
ServiceContext.ContentService.Save(content);
}
for (var i = 0; i < 6; i++)
createContentWithMediaRefs(); //create 6 content items referencing the same media
var relations = ServiceContext.RelationService.GetByChildId(m1.Id, Constants.Conventions.RelationTypes.RelatedMediaAlias).ToList();
Assert.AreEqual(6, relations.Count);
var entities = ServiceContext.RelationService.GetParentEntitiesFromRelations(relations).ToList();
Assert.AreEqual(6, entities.Count);
}
[Test]
public void Can_Create_RelationType_Without_Name()
{
var rs = ServiceContext.RelationService;
var rt = new RelationType(Constants.ObjectTypes.Document, Constants.ObjectTypes.Document, "repeatedEventOccurence");
IRelationType rt = new RelationType("Test", "repeatedEventOccurence", false, Constants.ObjectTypes.Document, Constants.ObjectTypes.Media);
Assert.DoesNotThrow(() => rs.Save(rt));
Assert.AreEqual(rt.Name, "repeatedEventOccurence");
//re-get
rt = ServiceContext.RelationService.GetRelationTypeById(rt.Id);
Assert.AreEqual("Test", rt.Name);
Assert.AreEqual("repeatedEventOccurence", rt.Alias);
Assert.AreEqual(false, rt.IsBidirectional);
Assert.AreEqual(Constants.ObjectTypes.Document, rt.ChildObjectType.Value);
Assert.AreEqual(Constants.ObjectTypes.Media, rt.ParentObjectType.Value);
}
[Test]
public void Create_Relation_Type_Without_Object_Types()
{
var rs = ServiceContext.RelationService;
IRelationType rt = new RelationType("repeatedEventOccurence", "repeatedEventOccurence", false, null, null);
Assert.DoesNotThrow(() => rs.Save(rt));
//re-get
rt = ServiceContext.RelationService.GetRelationTypeById(rt.Id);
Assert.IsNull(rt.ChildObjectType);
Assert.IsNull(rt.ParentObjectType);
}
[Test]
public void Relation_Returns_Parent_Child_Object_Types_When_Creating()
{
var r = CreateAndSaveRelation("Test", "test");
Assert.AreEqual(Constants.ObjectTypes.Document, r.ParentObjectType);
Assert.AreEqual(Constants.ObjectTypes.Media, r.ChildObjectType);
}
[Test]
public void Relation_Returns_Parent_Child_Object_Types_When_Getting()
{
var r = CreateAndSaveRelation("Test", "test");
// re-get
r = ServiceContext.RelationService.GetById(r.Id);
Assert.AreEqual(Constants.ObjectTypes.Document, r.ParentObjectType);
Assert.AreEqual(Constants.ObjectTypes.Media, r.ChildObjectType);
}
[Test]
public void Insert_Bulk_Relations()
{
var rs = ServiceContext.RelationService;
var newRelations = CreateRelations(10);
Assert.IsTrue(newRelations.All(x => !x.HasIdentity));
ServiceContext.RelationService.Save(newRelations);
Assert.IsTrue(newRelations.All(x => x.HasIdentity));
}
[Test]
public void Update_Bulk_Relations()
{
var rs = ServiceContext.RelationService;
var date = DateTime.Now.AddDays(-10);
var newRelations = CreateRelations(10);
foreach (var r in newRelations)
{
r.CreateDate = date;
r.UpdateDate = date;
}
//insert
ServiceContext.RelationService.Save(newRelations);
Assert.IsTrue(newRelations.All(x => x.UpdateDate == date));
var newDate = DateTime.Now.AddDays(-5);
foreach (var r in newRelations)
r.UpdateDate = newDate;
//update
ServiceContext.RelationService.Save(newRelations);
Assert.IsTrue(newRelations.All(x => x.UpdateDate == newDate));
}
private IRelation CreateAndSaveRelation(string name, string alias)
{
var rs = ServiceContext.RelationService;
var rt = new RelationType(name, alias, false, null, null);
rs.Save(rt);
var ct = MockedContentTypes.CreateBasicContentType();
ServiceContext.ContentTypeService.Save(ct);
var mt = MockedContentTypes.CreateImageMediaType("img");
ServiceContext.MediaTypeService.Save(mt);
var c1 = MockedContent.CreateBasicContent(ct);
var c2 = MockedMedia.CreateMediaImage(mt, -1);
ServiceContext.ContentService.Save(c1);
ServiceContext.MediaService.Save(c2);
var r = new Relation(c1.Id, c2.Id, rt);
ServiceContext.RelationService.Save(r);
return r;
}
/// <summary>
/// Creates a bunch of content/media items return relation objects for them (unsaved)
/// </summary>
/// <param name="count"></param>
/// <returns></returns>
private IEnumerable<IRelation> CreateRelations(int count)
{
var rs = ServiceContext.RelationService;
var rtName = Guid.NewGuid().ToString();
var rt = new RelationType(rtName, rtName, false, null, null);
rs.Save(rt);
var ct = MockedContentTypes.CreateBasicContentType();
ServiceContext.ContentTypeService.Save(ct);
var mt = MockedContentTypes.CreateImageMediaType("img");
ServiceContext.MediaTypeService.Save(mt);
return Enumerable.Range(1, count).Select(index =>
{
var c1 = MockedContent.CreateBasicContent(ct);
var c2 = MockedMedia.CreateMediaImage(mt, -1);
ServiceContext.ContentService.Save(c1);
ServiceContext.MediaService.Save(c2);
return new Relation(c1.Id, c2.Id, rt);
}).ToList();
}
//TODO: Create a relation for entities of the wrong Entity Type (GUID) based on the Relation Type's defined parent/child object types
}
}

View File

@@ -0,0 +1,120 @@
using Umbraco.Core.Logging;
using Moq;
using NUnit.Framework;
using Umbraco.Core.Services;
using Umbraco.Tests.Testing.Objects.Accessors;
using Umbraco.Web.Templates;
using Umbraco.Web;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Routing;
using Umbraco.Tests.Testing.Objects;
using System.Web;
using System;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Core;
using Umbraco.Web.PropertyEditors;
namespace Umbraco.Tests.Templates
{
[TestFixture]
public class HtmlImageSourceParserTests
{
[Test]
public void Returns_Udis_From_Data_Udi_Html_Attributes()
{
var input = @"<p>
<div>
<img src='/media/12312.jpg' data-udi='umb://media/D4B18427A1544721B09AC7692F35C264' />
</div>
</p><p><img src='/media/234234.jpg' data-udi=""umb://media-type/B726D735E4C446D58F703F3FBCFC97A5"" /></p>";
var logger = Mock.Of<ILogger>();
var umbracoContextAccessor = new TestUmbracoContextAccessor();
var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor);
var result = imageSourceParser.FindUdisFromDataAttributes(input).ToList();
Assert.AreEqual(2, result.Count);
Assert.AreEqual(UdiParser.Parse("umb://media/D4B18427A1544721B09AC7692F35C264"), result[0]);
Assert.AreEqual(UdiParser.Parse("umb://media-type/B726D735E4C446D58F703F3FBCFC97A5"), result[1]);
}
[Test]
public void Remove_Image_Sources()
{
var logger = Mock.Of<ILogger>();
var umbracoContextAccessor = new TestUmbracoContextAccessor();
var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor);
var result = imageSourceParser.RemoveImageSources(@"<p>
<div>
<img src=""/media/12354/test.jpg"" />
</div></p>
<p>
<div><img src=""/media/987645/test.jpg"" data-udi=""umb://media/81BB2036-034F-418B-B61F-C7160D68DCD4"" /></div>
</p>");
Assert.AreEqual(@"<p>
<div>
<img src=""/media/12354/test.jpg"" />
</div></p>
<p>
<div><img src="""" data-udi=""umb://media/81BB2036-034F-418B-B61F-C7160D68DCD4"" /></div>
</p>", result);
}
[Test]
public void Ensure_Image_Sources()
{
//setup a mock url provider which we'll use for testing
var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var media = new Mock<IPublishedContent>();
media.Setup(x => x.ContentType).Returns(mediaType);
var mediaUrlProvider = new Mock<IMediaUrlProvider>();
mediaUrlProvider.Setup(x => x.GetMediaUrl(It.IsAny<UmbracoContext>(), It.IsAny<IPublishedContent>(), It.IsAny<string>(), It.IsAny<UrlMode>(), It.IsAny<string>(), It.IsAny<Uri>()))
.Returns(UrlInfo.Url("/media/1001/my-image.jpg"));
var umbracoContextAccessor = new TestUmbracoContextAccessor();
var umbracoContextFactory = TestUmbracoContextFactory.Create(
mediaUrlProvider: mediaUrlProvider.Object,
umbracoContextAccessor: umbracoContextAccessor);
using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>()))
{
var mediaCache = Mock.Get(reference.UmbracoContext.Media);
mediaCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(media.Object);
var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor);
var result = imageSourceParser.EnsureImageSources(@"<p>
<div>
<img src="""" />
</div></p>
<p>
<div><img src="""" data-udi=""umb://media/81BB2036-034F-418B-B61F-C7160D68DCD4"" /></div>
</p>
<p>
<div><img src=""?width=100"" data-udi=""umb://media/81BB2036-034F-418B-B61F-C7160D68DCD4"" /></div>
</p>");
Assert.AreEqual(@"<p>
<div>
<img src="""" />
</div></p>
<p>
<div><img src=""/media/1001/my-image.jpg"" data-udi=""umb://media/81BB2036-034F-418B-B61F-C7160D68DCD4"" /></div>
</p>
<p>
<div><img src=""/media/1001/my-image.jpg?width=100"" data-udi=""umb://media/81BB2036-034F-418B-B61F-C7160D68DCD4"" /></div>
</p>", result);
}
}
}
}

View File

@@ -0,0 +1,94 @@
using Moq;
using NUnit.Framework;
using System;
using System.Linq;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Tests.Testing.Objects;
using Umbraco.Tests.Testing.Objects.Accessors;
using Umbraco.Web;
using Umbraco.Web.Routing;
using Umbraco.Web.Templates;
namespace Umbraco.Tests.Templates
{
[TestFixture]
public class HtmlLocalLinkParserTests
{
[Test]
public void Returns_Udis_From_LocalLinks()
{
var input = @"<p>
<div>
<img src='/media/12312.jpg' data-udi='umb://media/D4B18427A1544721B09AC7692F35C264' />
<a href=""{locallink:umb://document/C093961595094900AAF9170DDE6AD442}"">hello</a>
</div>
</p><p><img src='/media/234234.jpg' data-udi=""umb://media-type/B726D735E4C446D58F703F3FBCFC97A5"" />
<a href=""{locallink:umb://document-type/2D692FCB070B4CDA92FB6883FDBFD6E2}"">hello</a>
</p>";
var umbracoContextAccessor = new TestUmbracoContextAccessor();
var parser = new HtmlLocalLinkParser(umbracoContextAccessor);
var result = parser.FindUdisFromLocalLinks(input).ToList();
Assert.AreEqual(2, result.Count);
Assert.AreEqual(UdiParser.Parse("umb://document/C093961595094900AAF9170DDE6AD442"), result[0]);
Assert.AreEqual(UdiParser.Parse("umb://document-type/2D692FCB070B4CDA92FB6883FDBFD6E2"), result[1]);
}
[TestCase("", "")]
[TestCase("hello href=\"{localLink:1234}\" world ", "hello href=\"/my-test-url\" world ")]
[TestCase("hello href=\"{localLink:umb://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")]
[TestCase("hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")]
[TestCase("hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/1001/my-image.jpg\" world ")]
//this one has an invalid char so won't match
[TestCase("hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")]
[TestCase("hello href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"#\" world ")]
public void ParseLocalLinks(string input, string result)
{
//setup a mock url provider which we'll use for testing
var contentUrlProvider = new Mock<IUrlProvider>();
contentUrlProvider
.Setup(x => x.GetUrl(It.IsAny<UmbracoContext>(), It.IsAny<IPublishedContent>(), It.IsAny<UrlMode>(), It.IsAny<string>(), It.IsAny<Uri>()))
.Returns(UrlInfo.Url("/my-test-url"));
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var publishedContent = new Mock<IPublishedContent>();
publishedContent.Setup(x => x.Id).Returns(1234);
publishedContent.Setup(x => x.ContentType).Returns(contentType);
var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var media = new Mock<IPublishedContent>();
media.Setup(x => x.ContentType).Returns(mediaType);
var mediaUrlProvider = new Mock<IMediaUrlProvider>();
mediaUrlProvider.Setup(x => x.GetMediaUrl(It.IsAny<UmbracoContext>(), It.IsAny<IPublishedContent>(), It.IsAny<string>(), It.IsAny<UrlMode>(), It.IsAny<string>(), It.IsAny<Uri>()))
.Returns(UrlInfo.Url("/media/1001/my-image.jpg"));
var umbracoContextAccessor = new TestUmbracoContextAccessor();
var umbracoContextFactory = TestUmbracoContextFactory.Create(
urlProvider: contentUrlProvider.Object,
mediaUrlProvider: mediaUrlProvider.Object,
umbracoContextAccessor: umbracoContextAccessor);
using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>()))
{
var contentCache = Mock.Get(reference.UmbracoContext.Content);
contentCache.Setup(x => x.GetById(It.IsAny<int>())).Returns(publishedContent.Object);
contentCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(publishedContent.Object);
var mediaCache = Mock.Get(reference.UmbracoContext.Media);
mediaCache.Setup(x => x.GetById(It.IsAny<int>())).Returns(media.Object);
mediaCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(media.Object);
var linkParser = new HtmlLocalLinkParser(umbracoContextAccessor);
var output = linkParser.EnsureInternalLinks(input);
Assert.AreEqual(result, output);
}
}
}
}

View File

@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Scoping;
using Umbraco.Web;
using Umbraco.Web.PublishedCache.NuCache;
using Umbraco.Web.PublishedCache.NuCache.DataSource;
namespace Umbraco.Tests.Testing.Objects
{
internal class TestDataSource : IDataSource
{
public TestDataSource(params ContentNodeKit[] kits)

View File

@@ -0,0 +1,49 @@
using Moq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing.Objects.Accessors;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
namespace Umbraco.Tests.Testing.Objects
{
/// <summary>
/// Simplify creating test UmbracoContext's
/// </summary>
public class TestUmbracoContextFactory
{
public static IUmbracoContextFactory Create(IGlobalSettings globalSettings = null, IUrlProvider urlProvider = null,
IMediaUrlProvider mediaUrlProvider = null,
IUmbracoContextAccessor umbracoContextAccessor = null)
{
if (globalSettings == null) globalSettings = SettingsForTests.GenerateMockGlobalSettings();
if (urlProvider == null) urlProvider = Mock.Of<IUrlProvider>();
if (mediaUrlProvider == null) mediaUrlProvider = Mock.Of<IMediaUrlProvider>();
if (umbracoContextAccessor == null) umbracoContextAccessor = new TestUmbracoContextAccessor();
var contentCache = new Mock<IPublishedContentCache>();
var mediaCache = new Mock<IPublishedMediaCache>();
var snapshot = new Mock<IPublishedSnapshot>();
snapshot.Setup(x => x.Content).Returns(contentCache.Object);
snapshot.Setup(x => x.Media).Returns(mediaCache.Object);
var snapshotService = new Mock<IPublishedSnapshotService>();
snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny<string>())).Returns(snapshot.Object);
var umbracoContextFactory = new UmbracoContextFactory(
umbracoContextAccessor,
snapshotService.Object,
new TestVariationContextAccessor(),
new TestDefaultCultureAccessor(),
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == Mock.Of<IWebRoutingSection>(routingSection => routingSection.UrlProviderMode == "Auto")),
globalSettings,
new UrlProviderCollection(new[] { urlProvider }),
new MediaUrlProviderCollection(new[] { mediaUrlProvider }),
Mock.Of<IUserService>());
return umbracoContextFactory;
}
}
}

View File

@@ -44,6 +44,8 @@ using Umbraco.Web.Hosting;
using Umbraco.Web.Sections;
using Current = Umbraco.Core.Composing.Current;
using FileSystems = Umbraco.Core.IO.FileSystems;
using Umbraco.Web.Templates;
using Umbraco.Web.PropertyEditors;
using Umbraco.Core.Dictionary;
using Umbraco.Net;
@@ -244,6 +246,9 @@ namespace Umbraco.Tests.Testing
Composition.RegisterUnique(_ => Umbraco.Web.Composing.Current.UmbracoContextAccessor);
Composition.RegisterUnique<IPublishedRouter, PublishedRouter>();
Composition.WithCollectionBuilder<ContentFinderCollectionBuilder>();
Composition.DataValueReferenceFactories();
Composition.RegisterUnique<IContentLastChanceFinder, TestLastChanceFinder>();
Composition.RegisterUnique<IVariationContextAccessor, TestVariationContextAccessor>();
Composition.RegisterUnique<IPublishedSnapshotAccessor, TestPublishedSnapshotAccessor>();
@@ -260,6 +265,11 @@ namespace Umbraco.Tests.Testing
.Append<TranslationSection>();
Composition.RegisterUnique<ISectionService, SectionService>();
Composition.RegisterUnique<HtmlLocalLinkParser>();
Composition.RegisterUnique<HtmlUrlParser>();
Composition.RegisterUnique<HtmlImageSourceParser>();
Composition.RegisterUnique<RichTextEditorPastedImages>();
}
protected virtual void ComposeMisc()

View File

@@ -144,6 +144,7 @@
<Compile Include="Models\VariationTests.cs" />
<Compile Include="Persistence\Mappers\MapperTestBase.cs" />
<Compile Include="Persistence\Repositories\DocumentRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\EntityRepositoryTest.cs" />
<Compile Include="PublishedContent\NuCacheChildrenTests.cs" />
<Compile Include="PublishedContent\PublishedContentLanguageVariantTests.cs" />
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />
@@ -164,6 +165,7 @@
<Compile Include="Services\MemberGroupServiceTests.cs" />
<Compile Include="Services\MediaTypeServiceTests.cs" />
<Compile Include="Services\PropertyValidationServiceTests.cs" />
<Compile Include="Templates\HtmlLocalLinkParserTests.cs" />
<Compile Include="TestHelpers\RandomIdRamDirectory.cs" />
<Compile Include="Testing\Objects\TestDataSource.cs" />
<Compile Include="Published\PublishedSnapshotTestObjects.cs" />
@@ -217,6 +219,7 @@
<Compile Include="Testing\Objects\Accessors\TestVariationContextAccessor.cs" />
<Compile Include="Testing\ContentBaseExtensions.cs" />
<Compile Include="Testing\Objects\Accessors\TestDefaultCultureAccessor.cs" />
<Compile Include="Testing\Objects\TestUmbracoContextFactory.cs" />
<Compile Include="Testing\TestDatabase.cs" />
<Compile Include="Testing\TestingTests\NUnitTests.cs" />
<Compile Include="Persistence\LocksTests.cs" />
@@ -258,6 +261,7 @@
<Compile Include="Web\Controllers\UsersControllerTests.cs" />
<Compile Include="Web\HealthChecks\HealthCheckResultsTests.cs" />
<Compile Include="Web\HttpCookieExtensionsTests.cs" />
<Compile Include="Templates\HtmlImageSourceParserTests.cs" />
<Compile Include="Web\Mvc\HtmlStringUtilitiesTests.cs" />
<Compile Include="Web\ModelStateExtensionsTests.cs" />
<Compile Include="Web\Mvc\RenderIndexActionSelectorAttributeTests.cs" />
@@ -503,7 +507,6 @@
<Compile Include="Composing\TypeFinderTests.cs" />
<Compile Include="Routing\UmbracoModuleTests.cs" />
<Compile Include="CoreThings\VersionExtensionTests.cs" />
<Compile Include="Web\TemplateUtilitiesTests.cs" />
<Compile Include="Web\UrlHelperExtensionTests.cs" />
<Compile Include="Web\WebExtensionMethodTests.cs" />
<Compile Include="CoreThings\XmlExtensionsTests.cs" />

View File

@@ -1,84 +0,0 @@
using System;
using System.Linq;
using System.Web;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing.Objects.Accessors;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Templates;
namespace Umbraco.Tests.Web
{
[TestFixture]
public class TemplateUtilitiesTests
{
[SetUp]
public void SetUp()
{
UdiParser.ResetUdiTypes();
}
[TestCase("", "")]
[TestCase("hello href=\"{localLink:1234}\" world ", "hello href=\"/my-test-url\" world ")]
[TestCase("hello href=\"{localLink:umb://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")]
[TestCase("hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")]
[TestCase("hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/1001/my-image.jpg\" world ")]
//this one has an invalid char so won't match
[TestCase("hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")]
[TestCase("hello href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"#\" world ")]
public void ParseLocalLinks(string input, string result)
{
//setup a mock url provider which we'll use for testing
var testUrlProvider = new Mock<IUrlProvider>();
testUrlProvider
.Setup(x => x.GetUrl(It.IsAny<UmbracoContext>(), It.IsAny<IPublishedContent>(), It.IsAny<UrlMode>(), It.IsAny<string>(), It.IsAny<Uri>()))
.Returns((UmbracoContext umbCtx, IPublishedContent content, UrlMode mode, string culture, Uri url) => UrlInfo.Url("/my-test-url"));
var globalSettings = SettingsForTests.GenerateMockGlobalSettings();
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var publishedContent = Mock.Of<IPublishedContent>();
Mock.Get(publishedContent).Setup(x => x.Id).Returns(1234);
Mock.Get(publishedContent).Setup(x => x.ContentType).Returns(contentType);
var contentCache = Mock.Of<IPublishedContentCache>();
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<int>())).Returns(publishedContent);
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<Guid>())).Returns(publishedContent);
var snapshot = Mock.Of<IPublishedSnapshot>();
Mock.Get(snapshot).Setup(x => x.Content).Returns(contentCache);
var snapshotService = Mock.Of<IPublishedSnapshotService>();
Mock.Get(snapshotService).Setup(x => x.CreatePublishedSnapshot(It.IsAny<string>())).Returns(snapshot);
var media = Mock.Of<IPublishedContent>();
Mock.Get(media).Setup(x => x.Url).Returns("/media/1001/my-image.jpg");
var mediaCache = Mock.Of<IPublishedMediaCache>();
Mock.Get(mediaCache).Setup(x => x.GetById(It.IsAny<Guid>())).Returns(media);
var umbracoContextAccessor = new TestUmbracoContextAccessor();
var umbracoContextFactory = new UmbracoContextFactory(
umbracoContextAccessor,
snapshotService,
new TestVariationContextAccessor(),
new TestDefaultCultureAccessor(),
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == Mock.Of<IWebRoutingSection>(routingSection => routingSection.UrlProviderMode == "Auto")),
globalSettings,
new UrlProviderCollection(new[] { testUrlProvider.Object }),
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
Mock.Of<IUserService>());
using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>()))
{
var output = TemplateUtilities.ParseInternalLinks(input, reference.UmbracoContext.UrlProvider, mediaCache);
Assert.AreEqual(result, output);
}
}
}
}

View File

@@ -1,13 +1,30 @@
(function () {
'use strict';
function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper) {
function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper, mediaResource, $q) {
function link(scope, element, attrs, ctrl) {
var evts = [];
scope.allowChangeMediaType = false;
scope.loading = true;
scope.changeContentPageNumber = changeContentPageNumber;
scope.contentOptions = {};
scope.contentOptions.entityType = "DOCUMENT";
scope.hasContentReferences = false;
scope.changeMediaPageNumber = changeMediaPageNumber;
scope.mediaOptions = {};
scope.mediaOptions.entityType = "MEDIA";
scope.hasMediaReferences = false;
scope.changeMemberPageNumber = changeMemberPageNumber;
scope.memberOptions = {};
scope.memberOptions.entityType = "MEMBER";
scope.hasMemberReferences = false;
function onInit() {
@@ -94,6 +111,45 @@
setMediaExtension();
});
function changeContentPageNumber(pageNumber) {
scope.contentOptions.pageNumber = pageNumber;
loadContentRelations();
}
function changeMediaPageNumber(pageNumber) {
scope.mediaOptions.pageNumber = pageNumber;
loadMediaRelations();
}
function changeMemberPageNumber(pageNumber) {
scope.memberOptions.pageNumber = pageNumber;
loadMemberRelations();
}
function loadContentRelations() {
return mediaResource.getPagedReferences(scope.node.id, scope.contentOptions)
.then(function (data) {
scope.contentReferences = data;
scope.hasContentReferences = data.items.length > 0;
});
}
function loadMediaRelations() {
return mediaResource.getPagedReferences(scope.node.id, scope.mediaOptions)
.then(function (data) {
scope.mediaReferences = data;
scope.hasMediaReferences = data.items.length > 0;
});
}
function loadMemberRelations() {
return mediaResource.getPagedReferences(scope.node.id, scope.memberOptions)
.then(function (data) {
scope.memberReferences = data;
scope.hasMemberReferences = data.items.length > 0;
});
}
//ensure to unregister from all events!
scope.$on('$destroy', function () {
for (var e in evts) {
@@ -102,6 +158,18 @@
});
onInit();
// load media type references when the 'info' tab is first activated/switched to
evts.push(eventsService.on("app.tabChange", function (event, args) {
$timeout(function () {
if (args.alias === "umbInfo") {
$q.all([loadContentRelations(), loadMediaRelations(), loadMemberRelations()]).then(function () {
scope.loading = false;
});
}
});
}));
}
var directive = {

View File

@@ -552,6 +552,36 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
"Search",
args)),
'Failed to retrieve media items for search: ' + query);
},
getPagedReferences: function (id, options) {
var defaults = {
pageSize: 25,
pageNumber: 1,
entityType: "DOCUMENT"
};
if (options === undefined) {
options = {};
}
//overwrite the defaults if there are any specified
angular.extend(defaults, options);
//now copy back to the options we will use
options = defaults;
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"mediaApiBaseUrl",
"GetPagedReferences",
{
id: id,
entityType: options.entityType,
pageNumber: options.pageNumber,
pageSize: options.pageSize
}
)),
"Failed to retrieve usages for media of id " + id);
}
};

View File

@@ -114,6 +114,34 @@ function relationTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
$http.post(umbRequestHelper.getApiUrl("relationTypeApiBaseUrl", "DeleteById", [{ id: id }])),
"Failed to delete item " + id
);
},
getPagedResults: function (id, options) {
var defaults = {
pageSize: 25,
pageNumber: 1
};
if (options === undefined) {
options = {};
}
//overwrite the defaults if there are any specified
angular.extend(defaults, options);
//now copy back to the options we will use
options = defaults;
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"relationTypeApiBaseUrl",
"GetPagedResults",
{
id: id,
pageNumber: options.pageNumber,
pageSize: options.pageSize
}
)),
'Failed to get paged relations for id ' + id);
}
};

View File

@@ -1,20 +1,25 @@
<div class="umb-package-details">
<div class="umb-package-details__main-content">
<umb-box data-element="node-info-urls">
<umb-load-indicator ng-if="loading === true"></umb-load-indicator>
<!-- Main Column -->
<div class="umb-package-details__main-content" ng-if="loading === false">
<!-- Links -->
<umb-box data-element="node-info-urls" class="mb4">
<umb-box-header title-key="general_links"></umb-box-header>
<umb-box-content class="block-form">
<umb-empty-state
ng-if="!nodeUrl"
size="small">
<umb-empty-state ng-if="!nodeUrl"
size="small">
<localize key="content_noMediaLink"></localize>
</umb-empty-state>
<ul ng-if="nodeUrl" class="nav nav-stacked" style="margin-bottom: 0;">
<li>
<a ng-attr-href="{{node.extension !== 'svg' ? nodeUrl : undefined}}" ng-click="node.extension === 'svg' && openSVG()" target="_blank">
<i class="icon icon-out"></i>
<span>{{nodeFileName}}</span>
<i class="icon icon-out"></i>
<span>{{nodeFileName}}</span>
</a>
</li>
@@ -23,9 +28,132 @@
</umb-box-content>
</umb-box>
<!-- Media Tracking (NO Items) -->
<umb-box ng-if="(hasContentReferences === false) && (hasMediaReferences === false) && (hasMemberReferences === false) && loading === false">
<umb-box-header title-key="references_tabName"></umb-box-header>
<umb-box-content>
<umb-empty-state size="small">
This Media item has no references.
</umb-empty-state>
</umb-box-content>
</umb-box>
<!-- Media Tracking (With Items) -->
<div ng-if="loading === false">
<!-- Content -->
<div ng-if="contentReferences.items.length > 0">
<h5 class="mt0" style="margin-bottom: 20px;">
<localize key="references_labelUsedByDocuments">Used in Documents</localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in contentReferences.items">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a ng-href="#/content/content/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
<!-- Pagination -->
<div class="flex justify-center">
<umb-pagination ng-if="contentReferences.totalPages"
page-number="contentReferences.pageNumber"
total-pages="contentReferences.totalPages"
on-change="changeContentPageNumber(pageNumber)">
</umb-pagination>
</div>
</div>
<!-- Members -->
<div ng-if="memberReferences.items.length > 0">
<h5 class="mt0" style="margin-bottom: 20px;">
<localize key="references_labelUsedByMembers">Used in Members</localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in memberReferences.items">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/member/member/edit/{{::reference.key}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
<!-- Pagination -->
<div class="flex justify-center">
<umb-pagination ng-if="memberReferences.totalPages"
page-number="memberReferences.pageNumber"
total-pages="memberReferences.totalPages"
on-change="changeMemberPageNumber(pageNumber)">
</umb-pagination>
</div>
</div>
<!-- Media -->
<div ng-if="mediaReferences.items.length > 0">
<h5 class="mt0" style="margin-bottom: 20px;">
<localize key="references_labelUsedByMedia">Used in Media</localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in mediaReferences.items">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/media/media/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
<!-- Pagination -->
<div class="flex justify-center">
<umb-pagination ng-if="mediaReferences.totalPages"
page-number="mediaReferences.pageNumber"
total-pages="mediaReferences.totalPages"
on-change="changeMediaPageNumber(pageNumber)">
</umb-pagination>
</div>
</div>
</div>
</div>
<div class="umb-package-details__sidebar">
<!-- Sidebar -->
<div class="umb-package-details__sidebar" ng-if="loading === false">
<!-- General Info -->
<umb-box data-element="node-info-general">
<umb-box-header title-key="general_general"></umb-box-header>
<umb-box-content class="block-form">
@@ -39,12 +167,11 @@
</umb-control-group>
<umb-control-group data-element="node-info-media-type" label="@content_mediatype">
<umb-node-preview
style="max-width: 100%; margin-bottom: 0px;"
icon="node.icon"
name="node.contentTypeName"
allow-open="allowChangeMediaType"
on-open="openMediaType(mediaType)">
<umb-node-preview style="max-width: 100%; margin-bottom: 0px;"
icon="node.icon"
name="node.contentTypeName"
allow-open="allowChangeMediaType"
on-open="openMediaType(mediaType)">
</umb-node-preview>
</umb-control-group>

View File

@@ -31,8 +31,7 @@
<umb-control-group label="@relationType_parent">
<select name="relationType-parent"
ng-model="vm.relationType.parentObjectType"
class="umb-property-editor umb-dropdown"
required>
class="umb-property-editor umb-dropdown">
<option ng-repeat="objectType in vm.objectTypes" value="{{objectType.id}}">{{objectType.name}}</option>
</select>
</umb-control-group>
@@ -41,8 +40,7 @@
<umb-control-group label="@relationType_child">
<select name="relationType-child"
ng-model="vm.relationType.childObjectType"
class="umb-property-editor umb-dropdown"
required>
class="umb-property-editor umb-dropdown">
<option ng-repeat="objectType in vm.objectTypes" value="{{objectType.id}}">{{objectType.name}}</option>
</select>
</umb-control-group>

View File

@@ -6,7 +6,7 @@
* @description
* The controller for editing relation types.
*/
function RelationTypeEditController($scope, $routeParams, relationTypeResource, editorState, navigationService, dateHelper, userService, entityResource, formHelper, contentEditingHelper, localizationService) {
function RelationTypeEditController($scope, $routeParams, relationTypeResource, editorState, navigationService, dateHelper, userService, entityResource, formHelper, contentEditingHelper, localizationService, eventsService) {
var vm = this;
@@ -21,6 +21,10 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource,
function init() {
vm.page.loading = true;
vm.relationsLoading = true;
vm.changePageNumber = changePageNumber;
vm.options = {};
var labelKeys = [
"relationType_tabRelationType",
@@ -45,17 +49,39 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource,
];
});
// load references when the 'relations' tab is first activated/switched to
eventsService.on("app.tabChange", function (event, args) {
if (args.alias === "relations") {
loadRelations();
}
});
// Inital page/overview API call of relation type
relationTypeResource.getById($routeParams.id)
.then(function(data) {
.then(function (data) {
bindRelationType(data);
vm.page.loading = false;
});
}
function bindRelationType(relationType) {
formatDates(relationType.relations);
getRelationNames(relationType);
function changePageNumber(pageNumber) {
vm.options.pageNumber = pageNumber;
loadRelations();
}
/** Loads in the references one time when content app changed */
function loadRelations() {
relationTypeResource.getPagedResults($routeParams.id, vm.options)
.then(function (data) {
formatDates(data.items);
vm.relationsLoading = false;
vm.relations = data;
});
}
function bindRelationType(relationType) {
// Convert property value to string, since the umb-radiobutton component at the moment only handle string values.
// Sometime later the umb-radiobutton might be able to handle value as boolean.
relationType.isBidirectional = (relationType.isBidirectional || false).toString();
@@ -70,7 +96,7 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource,
}
function formatDates(relations) {
if(relations) {
if (relations) {
userService.getCurrentUser().then(function (currentUser) {
angular.forEach(relations, function (relation) {
relation.timestampFormatted = dateHelper.getLocalDate(relation.createDate, currentUser.locale, 'LLL');
@@ -79,41 +105,6 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource,
}
}
function getRelationNames(relationType) {
if (relationType.relations) {
// can we grab app entity types in one go?
if (relationType.parentObjectType === relationType.childObjectType) {
// yep, grab the distinct list of parent and child entities
var entityIds = _.uniq(_.union(_.pluck(relationType.relations, "parentId"), _.pluck(relationType.relations, "childId")));
entityResource.getByIds(entityIds, relationType.parentObjectTypeName).then(function (entities) {
updateRelationNames(relationType, entities);
});
} else {
// nope, grab the parent and child entities individually
var parentEntityIds = _.uniq(_.pluck(relationType.relations, "parentId"));
var childEntityIds = _.uniq(_.pluck(relationType.relations, "childId"));
entityResource.getByIds(parentEntityIds, relationType.parentObjectTypeName).then(function (entities) {
updateRelationNames(relationType, entities);
});
entityResource.getByIds(childEntityIds, relationType.childObjectTypeName).then(function (entities) {
updateRelationNames(relationType, entities);
});
}
}
}
function updateRelationNames(relationType, entities) {
var entitiesById = _.indexBy(entities, "id");
_.each(relationType.relations, function(relation) {
if (entitiesById[relation.parentId]) {
relation.parentName = entitiesById[relation.parentId].name;
}
if (entitiesById[relation.childId]) {
relation.childName = entitiesById[relation.childId].name;
}
});
}
function saveRelationType() {
if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) {

View File

@@ -1,28 +1,39 @@
<umb-box>
<umb-load-indicator ng-if="model.relationsLoading"></umb-load-indicator>
<umb-box ng-if="model.relationsLoading === false">
<umb-box-content class="block-form">
<umb-empty-state size="small" ng-if="model.relationType.relations.length === 0">
<umb-empty-state size="small" ng-if="model.relations.totalItems === 0">
<localize key="relationType_noRelations">No relations for this relation type.</localize>
</umb-empty-state>
<!-- Relations -->
<umb-control-group label="@relationType_relations" ng-if="model.relationType.relations.length > 0">
<div>
<table class="table">
<tr>
<th><localize key="relationType_parent">Parent</localize></th>
<th><localize key="relationType_child">Child</localize></th>
<th><localize key="relationType_created">Created</localize></th>
<th><localize key="relationType_comment">Comment</localize></th>
</tr>
<tr ng-repeat="relation in model.relationType.relations">
<td>{{relation.parentName}}</td>
<td>{{relation.childName}}</td>
<td>{{relation.timestampFormatted}}</td>
<td>{{relation.comment}}</td>
</tr>
</table>
</div>
</umb-control-group>
<umb-control-group label="@relationType_relations" ng-if="model.relations.items.length > 0">
<div>
<table class="table">
<tr>
<th><localize key="relationType_parent">Parent</localize></th>
<th><localize key="relationType_child">Child</localize></th>
<th><localize key="relationType_created">Created</localize></th>
<th><localize key="relationType_comment">Comment</localize></th>
</tr>
<tr ng-repeat="relation in model.relations.items">
<td>{{relation.parentName}}</td>
<td>{{relation.childName}}</td>
<td>{{relation.timestampFormatted}}</td>
<td>{{relation.comment}}</td>
</tr>
</table>
</div>
<!-- Pagination -->
<div class="flex justify-center">
<umb-pagination ng-if="model.relations.totalPages"
page-number="model.relations.pageNumber"
total-pages="model.relations.totalPages"
on-change="model.changePageNumber(pageNumber)">
</umb-pagination>
</div>
</umb-control-group>
</umb-box-content>
</umb-box>

View File

@@ -2189,6 +2189,9 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="labelUsedByMemberTypes">Used in Member Types</key>
<key alias="noMemberTypes">No references to Member Types.</key>
<key alias="usedByProperties">Used by</key>
<key alias="labelUsedByDocuments">Used in Documents</key>
<key alias="labelUsedByMembers">Used in Members</key>
<key alias="labelUsedByMedia">Used in Media</key>
</area>
<area alias="logViewer">
<key alias="logLevels">Log Levels</key>

View File

@@ -2205,6 +2205,9 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="labelUsedByMemberTypes">Used in Member Types</key>
<key alias="noMemberTypes">No references to Member Types.</key>
<key alias="usedByProperties">Used by</key>
<key alias="labelUsedByDocuments">Used in Documents</key>
<key alias="labelUsedByMembers">Used in Members</key>
<key alias="labelUsedByMedia">Used in Media</key>
</area>
<area alias="logViewer">
<key alias="logLevels">Log Levels</key>

View File

@@ -182,6 +182,8 @@ namespace Umbraco.Web.Composing
public static DataEditorCollection DataEditors => CoreCurrent.DataEditors;
public static DataValueReferenceFactoryCollection DataValueReferenceFactories => CoreCurrent.DataValueReferenceFactories;
public static PropertyEditorCollection PropertyEditors => CoreCurrent.PropertyEditors;
public static ParameterEditorCollection ParameterEditors => CoreCurrent.ParameterEditors;

View File

@@ -663,6 +663,9 @@ namespace Umbraco.Web.Editors
if (pageSize <= 0)
throw new HttpResponseException(HttpStatusCode.NotFound);
// re-normalize since NULL can be passed in
filter = filter ?? string.Empty;
var objectType = ConvertToObjectType(type);
if (objectType.HasValue)
{

View File

@@ -35,6 +35,7 @@ using Umbraco.Core.PropertyEditors;
using Umbraco.Web.ContentApps;
using Umbraco.Web.Editors.Binders;
using Umbraco.Web.Editors.Filters;
using Umbraco.Core.Models.Entities;
using Constants = Umbraco.Core.Constants;
using Umbraco.Core.Dictionary;
@@ -943,5 +944,31 @@ namespace Umbraco.Web.Editors
return hasPathAccess;
}
public PagedResult<EntityBasic> GetPagedReferences(int id, string entityType, int pageNumber = 1, int pageSize = 100)
{
if (pageNumber <= 0 || pageSize <= 0)
{
throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero");
}
var objectType = ObjectTypes.GetUmbracoObjectType(entityType);
var udiType = ObjectTypes.GetUdiType(objectType);
var relations = Services.RelationService.GetPagedParentEntitiesByChildId(id, pageNumber - 1, pageSize, out var totalRecords, objectType);
return new PagedResult<EntityBasic>(totalRecords, pageNumber, pageSize)
{
Items = relations.Cast<ContentEntitySlim>().Select(rel => new EntityBasic
{
Id = rel.Id,
Key = rel.Key,
Udi = Udi.Create(udiType, rel.Key),
Icon = rel.ContentTypeIcon,
Name = rel.Name,
Alias = rel.ContentTypeAlias
})
};
}
}
}

View File

@@ -3,12 +3,14 @@ using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
@@ -45,14 +47,28 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var relations = Services.RelationService.GetByRelationTypeId(relationType.Id);
var display = Mapper.Map<IRelationType, RelationTypeDisplay>(relationType);
display.Relations = Mapper.MapEnumerable<IRelation, RelationDisplay>(relations);
return display;
}
public PagedResult<RelationDisplay> GetPagedResults(int id, int pageNumber = 1, int pageSize = 100)
{
if (pageNumber <= 0 || pageSize <= 0)
{
throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero");
}
// Ordering do we need to pass through?
var relations = Services.RelationService.GetPagedByRelationTypeId(id, pageNumber -1, pageSize, out long totalRecords);
return new PagedResult<RelationDisplay>(totalRecords, pageNumber, pageSize)
{
Items = relations.Select(x => Mapper.Map<RelationDisplay>(x))
};
}
/// <summary>
/// Gets a list of object types which can be associated via relations.
/// </summary>
@@ -84,11 +100,7 @@ namespace Umbraco.Web.Editors
/// <returns>A <see cref="HttpResponseMessage"/> containing the persisted relation type's ID.</returns>
public HttpResponseMessage PostCreate(RelationTypeSave relationType)
{
var relationTypePersisted = new RelationType(relationType.ChildObjectType, relationType.ParentObjectType, relationType.Name.ToSafeAlias(true))
{
Name = relationType.Name,
IsBidirectional = relationType.IsBidirectional
};
var relationTypePersisted = new RelationType(relationType.Name, relationType.Name.ToSafeAlias(true), relationType.IsBidirectional, relationType.ChildObjectType, relationType.ParentObjectType);
try
{

View File

@@ -24,7 +24,7 @@ namespace Umbraco.Web.Models.ContentEditing
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember(Name = "parentObjectType", IsRequired = true)]
public Guid ParentObjectType { get; set; }
public Guid? ParentObjectType { get; set; }
/// <summary>
/// Gets or sets the Parent's object type name.
@@ -38,7 +38,7 @@ namespace Umbraco.Web.Models.ContentEditing
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember(Name = "childObjectType", IsRequired = true)]
public Guid ChildObjectType { get; set; }
public Guid? ChildObjectType { get; set; }
/// <summary>
/// Gets or sets the Child's object type name.
@@ -47,13 +47,6 @@ namespace Umbraco.Web.Models.ContentEditing
[ReadOnly(true)]
public string ChildObjectTypeName { get; set; }
/// <summary>
/// Gets or sets the relations associated with this relation type.
/// </summary>
[DataMember(Name = "relations")]
[ReadOnly(true)]
public IEnumerable<RelationDisplay> Relations { get; set; }
/// <summary>
/// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes.
/// </summary>

View File

@@ -16,12 +16,12 @@ namespace Umbraco.Web.Models.ContentEditing
/// Gets or sets the parent object type ID.
/// </summary>
[DataMember(Name = "parentObjectType", IsRequired = false)]
public Guid ParentObjectType { get; set; }
public Guid? ParentObjectType { get; set; }
/// <summary>
/// Gets or sets the child object type ID.
/// </summary>
[DataMember(Name = "childObjectType", IsRequired = false)]
public Guid ChildObjectType { get; set; }
public Guid? ChildObjectType { get; set; }
}
}

View File

@@ -234,11 +234,11 @@ namespace Umbraco.Web.Models.Mapping
{
switch (entity)
{
case ContentEntitySlim contentEntity:
// NOTE: this case covers both content and media entities
return contentEntity.ContentTypeIcon;
case MemberEntitySlim memberEntity:
case IMemberEntitySlim memberEntity:
return memberEntity.ContentTypeIcon.IfNullOrWhiteSpace(Constants.Icons.Member);
case IContentEntitySlim contentEntity:
// NOTE: this case covers both content and media entities
return contentEntity.ContentTypeIcon;
}
return null;

View File

@@ -1,12 +1,23 @@
using Umbraco.Core;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models.Mapping
{
internal class RelationMapDefinition : IMapDefinition
{
private readonly IEntityService _entityService;
private readonly IRelationService _relationService;
public RelationMapDefinition(IEntityService entityService, IRelationService relationService)
{
_entityService = entityService;
_relationService = relationService;
}
public void DefineMaps(UmbracoMapper mapper)
{
mapper.Define<IRelationType, RelationTypeDisplay>((source, context) => new RelationTypeDisplay(), Map);
@@ -15,8 +26,8 @@ namespace Umbraco.Web.Models.Mapping
}
// Umbraco.Code.MapAll -Icon -Trashed -AdditionalData
// Umbraco.Code.MapAll -Relations -ParentId -Notifications
private static void Map(IRelationType source, RelationTypeDisplay target, MapperContext context)
// Umbraco.Code.MapAll -ParentId -Notifications
private void Map(IRelationType source, RelationTypeDisplay target, MapperContext context)
{
target.ChildObjectType = source.ChildObjectType;
target.Id = source.Id;
@@ -28,18 +39,32 @@ namespace Umbraco.Web.Models.Mapping
target.Udi = Udi.Create(Constants.UdiEntityType.RelationType, source.Key);
target.Path = "-1," + source.Id;
// Set the "friendly" names for the parent and child object types
target.ParentObjectTypeName = ObjectTypes.GetUmbracoObjectType(source.ParentObjectType).GetFriendlyName();
target.ChildObjectTypeName = ObjectTypes.GetUmbracoObjectType(source.ChildObjectType).GetFriendlyName();
// Set the "friendly" and entity names for the parent and child object types
if (source.ParentObjectType.HasValue)
{
var objType = ObjectTypes.GetUmbracoObjectType(source.ParentObjectType.Value);
target.ParentObjectTypeName = objType.GetFriendlyName();
}
if (source.ChildObjectType.HasValue)
{
var objType = ObjectTypes.GetUmbracoObjectType(source.ChildObjectType.Value);
target.ChildObjectTypeName = objType.GetFriendlyName();
}
}
// Umbraco.Code.MapAll -ParentName -ChildName
private static void Map(IRelation source, RelationDisplay target, MapperContext context)
private void Map(IRelation source, RelationDisplay target, MapperContext context)
{
target.ChildId = source.ChildId;
target.Comment = source.Comment;
target.CreateDate = source.CreateDate;
target.ParentId = source.ParentId;
var entities = _relationService.GetEntitiesFromRelation(source);
target.ParentName = entities.Item1.Name;
target.ChildName = entities.Item2.Name;
}
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate

View File

@@ -380,8 +380,8 @@ namespace Umbraco.Web.Models.Mapping
.ToDictionary(x => x.Key, x => (IEnumerable<Permission>)x.ToArray());
}
private static string MapContentTypeIcon(EntitySlim entity)
=> entity is ContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null;
private static string MapContentTypeIcon(IEntitySlim entity)
=> entity is IContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null;
private IEnumerable<EntityBasic> GetStartNodes(int[] startNodeIds, UmbracoObjectTypes objectType, string localizedKey, MapperContext context)
{

View File

@@ -1,7 +1,9 @@
using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
namespace Umbraco.Web.PropertyEditors
{
@@ -17,13 +19,38 @@ namespace Umbraco.Web.PropertyEditors
Group = Constants.PropertyEditors.Groups.Pickers)]
public class ContentPickerPropertyEditor : DataEditor
{
public ContentPickerPropertyEditor(ILogger logger)
private readonly IDataTypeService _dataTypeService;
private readonly ILocalizationService _localizationService;
public ContentPickerPropertyEditor(IDataTypeService dataTypeService, ILocalizationService localizationService, ILogger logger)
: base(logger)
{ }
{
_dataTypeService = dataTypeService;
_localizationService = localizationService;
}
protected override IConfigurationEditor CreateConfigurationEditor()
{
return new ContentPickerConfigurationEditor();
}
protected override IDataValueEditor CreateValueEditor() => new ContentPickerPropertyValueEditor(_dataTypeService, _localizationService, Attribute);
internal class ContentPickerPropertyValueEditor : DataValueEditor, IDataValueReference
{
public ContentPickerPropertyValueEditor(IDataTypeService dataTypeService, ILocalizationService localizationService, DataEditorAttribute attribute) : base(dataTypeService, localizationService, attribute)
{
}
public IEnumerable<UmbracoEntityReference> GetReferences(object value)
{
var asString = value is string str ? str : value?.ToString();
if (string.IsNullOrEmpty(asString)) yield break;
if (UdiParser.TryParse(asString, out var udi))
yield return new UmbracoEntityReference(udi);
}
}
}
}

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