├── icon.png ├── docs ├── extending.md ├── images │ ├── content-app.png │ ├── dashboard.png │ ├── delete_dialog.png │ └── unpublish_dialog.png ├── index.md ├── installation.md ├── configuration.md └── introduction.md ├── Packaging ├── Tools │ ├── NuGet.exe │ ├── MSBuildNugetTasks │ │ ├── MSBuild.NuGet.Tasks.dll │ │ └── MSBuild.NuGet.Tasks.Targets │ ├── MSBuildUmbracoTasks │ │ ├── MSBuild.Umbraco.Tasks.dll │ │ ├── ICSharpCode.SharpZipLib.dll │ │ └── MSBuild.Umbraco.Tasks.Targets │ ├── MSBuildCommunityTasks │ │ ├── ICSharpCode.SharpZipLib.dll │ │ ├── MSBuild.Community.Tasks.chm │ │ ├── MSBuild.Community.Tasks.dll │ │ └── Sample.proj │ └── AppVeyorUmbraco │ │ └── AppVeyorUmbraco.targets ├── transforms │ ├── Dashboard.config.uninstall.xdt │ └── Dashboard.config.install.xdt ├── build-appveyor.bat ├── build.bat ├── package.core.nuspec ├── Package.xml ├── package.parsers.nuspec ├── package.nuspec └── github-package.xml ├── src ├── Our.Umbraco.Nexu.Web │ ├── App_Plugins │ │ └── Nexu │ │ │ ├── styles │ │ │ └── nexu.css │ │ │ ├── controllers │ │ │ ├── content-delete-controller.js │ │ │ ├── media-delete-controller.js │ │ │ ├── related-links-app-controller.js │ │ │ ├── unpublish-controller.js │ │ │ ├── base-delete-controller.js │ │ │ ├── dashboard-controller.js │ │ │ ├── listview-controller.js │ │ │ └── listview-dialog-controller.js │ │ │ ├── views │ │ │ ├── related-links-app.html │ │ │ ├── dashboard.html │ │ │ ├── media-delete.html │ │ │ ├── content-delete.html │ │ │ ├── listview-dialog.html │ │ │ ├── relation-list-component.html │ │ │ └── unpublish.html │ │ │ ├── resources │ │ │ ├── rebuild-resource.js │ │ │ └── relation-check-resource.js │ │ │ ├── package.manifest │ │ │ ├── lang │ │ │ ├── sv-SE.xml │ │ │ └── en-us.xml │ │ │ ├── services │ │ │ └── relation-check-service.js │ │ │ ├── decorators │ │ │ └── controller-decorator.js │ │ │ ├── interceptors │ │ │ └── interceptors.js │ │ │ └── components │ │ │ └── relation-list-component.js │ ├── Models │ │ └── RebuildStatus.cs │ ├── Composing │ │ ├── Components │ │ │ ├── RebuildDashboard.cs │ │ │ ├── RelatedLinksContentAppFactory.cs │ │ │ └── ServerVariablesComponent.cs │ │ └── Composers │ │ │ └── NexuWebComposer.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── app.config │ ├── Api │ │ └── RelationCheckApiController.cs │ └── packages.config ├── Our.Umbraco.Nexu.Parsers │ ├── Core │ │ ├── BlockListEditorParser.cs │ │ ├── GridParser.cs │ │ ├── MultiUrlPickerParser.cs │ │ ├── MediaPickerParser.cs │ │ ├── NestedContentParser.cs │ │ ├── RichTextEditorParser.cs │ │ ├── MultiNodeTreePickerParser.cs │ │ ├── ContentPickerParser.cs │ │ └── MediaPicker3.cs │ ├── app.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── BaseTextParser.cs │ ├── Helpers │ │ └── ParserUtilities.cs │ ├── Community │ │ └── SEOCheckerParser.cs │ └── packages.config ├── Our.Umbraco.Nexu.Core.Tests │ ├── Models │ │ ├── NexuRelationTests.Nexu_Relation_Udi_Should_Match_Id.approved.json │ │ └── NexuRelationTests.cs │ ├── app.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Services │ │ └── NexuEntityParsingService │ │ │ ├── SaveRelationsForContentItem_Tests.cs │ │ │ ├── NexuEntityParsingServiceBaseTest.cs │ │ │ ├── GetParserForPropertyEditor_Tests.cs │ │ │ ├── GetRelatedEntitiesFromContent_Tests.cs │ │ │ ├── GetRelatedEntitiesFromProperty_Tests.cs │ │ │ └── GetRelatedEntitiesFromPropertyEditorValue_Tests.cs │ ├── NexuRelationRepository │ │ ├── GetIncomingRelationsForItem_Tests.cs │ │ ├── GetUsedItemsFromList_Tests.cs │ │ └── RepositoryBaseTest.cs │ └── packages.config ├── Our.Umbraco.Nexu.Common │ ├── app.config │ ├── Constants │ │ ├── AppSettings.cs │ │ ├── RelationTypes.cs │ │ └── DatabaseConstants.cs │ ├── Models │ │ ├── NexuRelationPropertyDisplay.cs │ │ ├── RelatedMediaEntity.cs │ │ ├── RelatedDocumentEntity.cs │ │ ├── NexuRelationDisplayModel.cs │ │ └── NexuRelation.cs │ ├── Interfaces │ │ ├── Services │ │ │ ├── IEntityParsingService.cs │ │ │ └── IEntityRelationService.cs │ │ ├── Models │ │ │ ├── IRelatedEntity.cs │ │ │ └── IPropertyValueParser.cs │ │ └── Repositories │ │ │ └── IRelationRepository.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── packages.config │ └── NexuContext.cs ├── Our.Umbraco.Nexu.Core │ ├── app.config │ ├── Composing │ │ ├── Collections │ │ │ ├── PropertyValueParserCollectionBuilder.cs │ │ │ └── PropertyValueParserCollection.cs │ │ ├── Extensions.cs │ │ ├── Composers │ │ │ ├── CollectionsComposer.cs │ │ │ └── NexuComposer.cs │ │ └── Components │ │ │ ├── ContentServiceEventsComponent.cs │ │ │ └── MigrationComponent.cs │ ├── Migrations │ │ ├── NexuMigrationPlan.cs │ │ └── Version_2_0_0 │ │ │ └── CreateRelationTableMigration.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── packages.config │ └── Repositories │ │ └── NexuRelationRepository.cs ├── Our.Umbraco.Nexu.Parsers.Tests │ ├── app.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Core │ │ ├── ContentPickerParserTests.cs │ │ ├── MediaPickerParserTests.cs │ │ ├── MultiNodeTreePickerParserTests.cs │ │ ├── MultiUrlPickerParserTests.cs │ │ ├── NestedContentParserTests.cs │ │ └── RichTextEditorParserTests.cs │ ├── Community │ │ └── SEOCheckerParserTests.cs │ └── packages.config ├── Our.Umbraco.Nexu.Web.Tests │ ├── Properties │ │ └── AssemblyInfo.cs │ └── app.config └── Our.Umbraco.Nexu.sln ├── .github └── FUNDING.yml ├── .gitignore ├── README.md ├── LICENSE └── appveyor.yml /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawoe/umbraco-nexu/HEAD/icon.png -------------------------------------------------------------------------------- /docs/extending.md: -------------------------------------------------------------------------------- 1 | # Extending # 2 | 3 | ### Coming soon ### 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Packaging/Tools/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawoe/umbraco-nexu/HEAD/Packaging/Tools/NuGet.exe -------------------------------------------------------------------------------- /docs/images/content-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawoe/umbraco-nexu/HEAD/docs/images/content-app.png -------------------------------------------------------------------------------- /docs/images/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawoe/umbraco-nexu/HEAD/docs/images/dashboard.png -------------------------------------------------------------------------------- /docs/images/delete_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawoe/umbraco-nexu/HEAD/docs/images/delete_dialog.png -------------------------------------------------------------------------------- /docs/images/unpublish_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawoe/umbraco-nexu/HEAD/docs/images/unpublish_dialog.png -------------------------------------------------------------------------------- /Packaging/Tools/MSBuildNugetTasks/MSBuild.NuGet.Tasks.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawoe/umbraco-nexu/HEAD/Packaging/Tools/MSBuildNugetTasks/MSBuild.NuGet.Tasks.dll -------------------------------------------------------------------------------- /Packaging/Tools/MSBuildUmbracoTasks/MSBuild.Umbraco.Tasks.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawoe/umbraco-nexu/HEAD/Packaging/Tools/MSBuildUmbracoTasks/MSBuild.Umbraco.Tasks.dll -------------------------------------------------------------------------------- /Packaging/Tools/MSBuildUmbracoTasks/ICSharpCode.SharpZipLib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawoe/umbraco-nexu/HEAD/Packaging/Tools/MSBuildUmbracoTasks/ICSharpCode.SharpZipLib.dll -------------------------------------------------------------------------------- /Packaging/Tools/MSBuildCommunityTasks/ICSharpCode.SharpZipLib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawoe/umbraco-nexu/HEAD/Packaging/Tools/MSBuildCommunityTasks/ICSharpCode.SharpZipLib.dll -------------------------------------------------------------------------------- /Packaging/Tools/MSBuildCommunityTasks/MSBuild.Community.Tasks.chm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawoe/umbraco-nexu/HEAD/Packaging/Tools/MSBuildCommunityTasks/MSBuild.Community.Tasks.chm -------------------------------------------------------------------------------- /Packaging/Tools/MSBuildCommunityTasks/MSBuild.Community.Tasks.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawoe/umbraco-nexu/HEAD/Packaging/Tools/MSBuildCommunityTasks/MSBuild.Community.Tasks.dll -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/styles/nexu.css: -------------------------------------------------------------------------------- 1 | .nexu-table { 2 | box-shadow: none !important; 3 | } 4 | 5 | .nexu-table .umb-table-row { 6 | cursor: pointer; 7 | } -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Nexu documentation # 2 | 3 | 1. [Introduction](introduction.md "Introduction") 4 | 2. [Installation](installation.md) 5 | 3. [Configuration](configuration.md) 6 | 4. [Extending](extending.md "Extending") 7 | -------------------------------------------------------------------------------- /Packaging/transforms/Dashboard.config.uninstall.xdt: -------------------------------------------------------------------------------- 1 | 2 |
5 | -------------------------------------------------------------------------------- /Packaging/build-appveyor.bat: -------------------------------------------------------------------------------- 1 | ECHO APPVEYOR_REPO_BRANCH: %APPVEYOR_REPO_BRANCH% 2 | ECHO APPVEYOR_REPO_TAG: %APPVEYOR_REPO_TAG% 3 | ECHO APPVEYOR_BUILD_NUMBER : %APPVEYOR_BUILD_NUMBER% 4 | ECHO APPVEYOR_BUILD_VERSION : %APPVEYOR_BUILD_VERSION% 5 | 6 | CALL "%programfiles(x86)%\MSBuild\14.0\Bin\MsBuild.exe" build.xml -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/Core/BlockListEditorParser.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Core 2 | { 3 | public class BlockListEditorParser : BaseTextParser 4 | { 5 | public override bool IsParserFor(string propertyEditorAlias) 6 | { 7 | return propertyEditorAlias.Equals("Umbraco.BlockList"); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/Models/NexuRelationTests.Nexu_Relation_Udi_Should_Match_Id.approved.json: -------------------------------------------------------------------------------- 1 | { 2 | "Id": "9a935375-6c89-4dda-ae83-b9207e19a3ee", 3 | "ParentUdi": null, 4 | "ChildUdi": null, 5 | "RelationType": "00000000-0000-0000-0000-000000000000", 6 | "PropertyAlias": null, 7 | "Culture": null, 8 | "Udi": "umb://nexurelation/9a9353756c894ddaae83b9207e19a3ee" 9 | } -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/controllers/content-delete-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('umbraco').controller('Our.Umbraco.Nexu.ContentDeleteController', 2 | ['$scope', '$controller', 3 | function ($scope, $controller) { 4 | $scope.isMedia = false; 5 | 6 | // inherit base delete controller 7 | angular.extend(this, $controller('Our.Umbraco.Nexu.BaseDeleteController', { $scope: $scope })); 8 | }]); -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/controllers/media-delete-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('umbraco').controller('Our.Umbraco.Nexu.MediaDeleteController', 2 | ['$scope', '$controller', 3 | function ($scope, $controller) { 4 | $scope.isMedia = true; 5 | 6 | // inherit base delete controller 7 | angular.extend(this, $controller('Our.Umbraco.Nexu.BaseDeleteController', { $scope: $scope })); 8 | 9 | }]); -------------------------------------------------------------------------------- /Packaging/transforms/Dashboard.config.install.xdt: -------------------------------------------------------------------------------- 1 | 2 | 3 |
6 | 7 | developer 8 | 9 | 10 | /App_Plugins/Nexu/views/dashboard.html 11 | 12 |
13 |
-------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers.Tests/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/Core/GridParser.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Core 2 | { 3 | using global::Umbraco.Core; 4 | 5 | /// 6 | /// Represents the parser for the grid editor 7 | /// 8 | public class GridParser : BaseTextParser 9 | { 10 | /// 11 | public override bool IsParserFor(string propertyEditorAlias) 12 | { 13 | return propertyEditorAlias.Equals(Constants.PropertyEditors.Aliases.Grid); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/Core/MultiUrlPickerParser.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Core 2 | { 3 | using global::Umbraco.Core; 4 | 5 | /// 6 | /// Represents the multi url picker 7 | /// 8 | public class MultiUrlPickerParser : BaseTextParser 9 | { 10 | /// 11 | public override bool IsParserFor(string propertyEditorAlias) 12 | { 13 | return propertyEditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiUrlPicker); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/Core/MediaPickerParser.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Core 2 | { 3 | using global::Umbraco.Core; 4 | 5 | /// 6 | /// Represents the media picker parser. 7 | /// 8 | public class MediaPickerParser : BaseTextParser 9 | { 10 | /// 11 | public override bool IsParserFor(string propertyEditorAlias) 12 | { 13 | return propertyEditorAlias.Equals(Constants.PropertyEditors.Aliases.MediaPicker); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/Core/NestedContentParser.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Core 2 | { 3 | using global::Umbraco.Core; 4 | 5 | /// 6 | /// Represents the nested content parser. 7 | /// 8 | public class NestedContentParser : BaseTextParser 9 | { 10 | /// 11 | public override bool IsParserFor(string propertyEditorAlias) 12 | { 13 | return propertyEditorAlias.Equals(Constants.PropertyEditors.Aliases.NestedContent); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/Core/RichTextEditorParser.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Core 2 | { 3 | using global::Umbraco.Core; 4 | 5 | /// 6 | /// Represents the rich text editor parser 7 | /// 8 | public class RichTextEditorParser : BaseTextParser 9 | { 10 | /// 11 | public override bool IsParserFor(string propertyEditorAlias) 12 | { 13 | return propertyEditorAlias.Equals(Constants.PropertyEditors.Aliases.TinyMce); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/views/related-links-app.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
-------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/Core/MultiNodeTreePickerParser.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Core 2 | { 3 | using global::Umbraco.Core; 4 | 5 | /// 6 | /// Represents the multi node tree picker parser 7 | /// 8 | public class MultiNodeTreePickerParser : BaseTextParser 9 | { 10 | /// 11 | public override bool IsParserFor(string propertyEditorAlias) 12 | { 13 | return propertyEditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Constants/AppSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Common.Constants 2 | { 3 | /// 4 | /// The app settings constants 5 | /// 6 | internal class AppSettings 7 | { 8 | /// 9 | /// Prevent delete app setting 10 | /// 11 | public const string PreventDelete = "nexu:PreventDelete"; 12 | 13 | /// 14 | /// Prevent unpublish app setting 15 | /// 16 | public const string PreventUnpublish = "nexu:PreventUnpublish"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Models/NexuRelationPropertyDisplay.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Common.Models 2 | { 3 | /// 4 | /// Represents the nexu relation property display model 5 | /// 6 | public class NexuRelationPropertyDisplay 7 | { 8 | /// 9 | /// Gets or sets the property name. 10 | /// 11 | public string PropertyName { get; set; } 12 | 13 | /// 14 | /// Gets or sets the tab name. 15 | /// 16 | public string TabName { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Interfaces/Services/IEntityParsingService.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Common.Interfaces.Services 2 | { 3 | using global::Umbraco.Core.Models; 4 | 5 | /// 6 | /// Represents the entity parsing service 7 | /// 8 | public interface IEntityParsingService 9 | { 10 | /// 11 | /// Parses a content item for related entities 12 | /// 13 | /// 14 | /// The content item that needs to be parsed 15 | /// 16 | void ParseContent(IContent content); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Interfaces/Models/IRelatedEntity.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Common.Interfaces.Models 2 | { 3 | using System; 4 | 5 | using global::Umbraco.Core; 6 | 7 | /// 8 | /// Represents the RelatedEntity interface. 9 | /// 10 | public interface IRelatedEntity 11 | { 12 | /// 13 | /// Gets or sets the related entity udi. 14 | /// 15 | Udi RelatedEntityUdi { get; set; } 16 | 17 | /// 18 | /// Gets the relation type. 19 | /// 20 | Guid RelationType { get; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Models/RelatedMediaEntity.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Common.Models 2 | { 3 | using System; 4 | 5 | using global::Umbraco.Core; 6 | 7 | using Our.Umbraco.Nexu.Common.Constants; 8 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 9 | 10 | /// 11 | /// Represents a related media entity 12 | /// 13 | public class RelatedMediaEntity : IRelatedEntity 14 | { 15 | /// 16 | public Udi RelatedEntityUdi { get; set; } 17 | 18 | /// 19 | public Guid RelationType => RelationTypes.DocumentToMedia; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Models/RelatedDocumentEntity.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Common.Models 2 | { 3 | using System; 4 | 5 | using global::Umbraco.Core; 6 | 7 | using Our.Umbraco.Nexu.Common.Constants; 8 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 9 | 10 | /// 11 | /// Represents a related document entity 12 | /// 13 | public class RelatedDocumentEntity : IRelatedEntity 14 | { 15 | /// 16 | public Udi RelatedEntityUdi { get; set; } 17 | 18 | /// 19 | public Guid RelationType => RelationTypes.DocumentToDocument; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Packaging/Tools/MSBuildNugetTasks/MSBuild.NuGet.Tasks.Targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $(MSBuildProjectDirectory)\MSBuildTasks 6 | $(MSBuildNuGetTasksPath)\MSBuild.NuGet.Tasks.dll 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core/Composing/Collections/PropertyValueParserCollectionBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Composing.Collections 2 | { 3 | using global::Umbraco.Core.Composing; 4 | 5 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 6 | 7 | /// 8 | /// Represents the property value parser collection builder 9 | /// 10 | public class PropertyValueParserCollectionBuilder 11 | : OrderedCollectionBuilderBase 12 | { 13 | /// 14 | protected override PropertyValueParserCollectionBuilder This => this; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation # 2 | 3 | ## Umbraco package ## 4 | 5 | Download and install the package found here : [https://our.umbraco.org/projects/backoffice-extensions/nexu](https://our.umbraco.org/projects/backoffice-extensions/nexu) 6 | 7 | ## Nuget package ## 8 | 9 | [https://www.nuget.org/packages/Our.Umbraco.Nexu/](https://www.nuget.org/packages/Our.Umbraco.Nexu/) 10 | 11 | # Steps after installation # 12 | 13 | Go to the nexu dashboard in the settings section and click the "Rebuild nexu relations" button. This will crawl all your content and rebuild the link relations. As this process happens in the background you can keep working without a problem and come back to recheck the status. 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Constants/RelationTypes.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Common.Constants 2 | { 3 | using System; 4 | 5 | /// 6 | /// Represents the relation type guids 7 | /// 8 | public static class RelationTypes 9 | { 10 | /// 11 | /// Gets the document to document relation type id 12 | /// 13 | public static Guid DocumentToDocument => new Guid("F1E181D1-BAE7-4B33-9504-2C111E2A2245"); 14 | 15 | /// 16 | /// Gets the document to document relation type id 17 | /// 18 | public static Guid DocumentToMedia => new Guid("9EE85524-DE88-42C4-901A-B24C1C777723"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/Models/RebuildStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Web.Models 2 | { 3 | /// 4 | /// Represents the rebuild status. 5 | /// 6 | public class RebuildStatus 7 | { 8 | /// 9 | /// Gets or sets a value indicating whether is processing. 10 | /// 11 | public bool IsProcessing { get; set; } 12 | 13 | /// 14 | /// Gets or sets the item name. 15 | /// 16 | public string ItemName { get; set; } 17 | 18 | /// 19 | /// Gets or sets the items processed. 20 | /// 21 | public int ItemsProcessed { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration # 2 | 3 | ## Prevent deleting (v2.2.0+) ## 4 | 5 | To prevent that editors can delete a content or media item that is linked to from other items you can add the following key to your appSettings in the web.config 6 | 7 | 8 | 9 | Removing this setting or setting it to false allows editors to delete the used item 10 | 11 | ## Prevent unpublishing (v2.2.0+) ## 12 | 13 | To prevent that editors can unpublish a content item that is linked to from other items you can add the following key to your appSettings in the web.config 14 | 15 | 16 | 17 | Removing this setting or setting it to false allows editors to unpublish the used item -------------------------------------------------------------------------------- /Packaging/build.bat: -------------------------------------------------------------------------------- 1 | ECHO off 2 | 3 | SET /P APPVEYOR_BUILD_NUMBER=Please enter a build number (e.g. 134): 4 | SET /P PACKAGE_VERISON=Please enter your package version (e.g. 1.0.5): 5 | SET /P UMBRACO_PACKAGE_PRERELEASE_SUFFIX=Please enter your package release suffix or leave empty (e.g. beta): 6 | 7 | SET /P APPVEYOR_REPO_TAG=If you want to simulate a GitHub tag for a release (e.g. true): 8 | 9 | if "%APPVEYOR_BUILD_NUMBER%" == "" ( 10 | SET APPVEYOR_BUILD_NUMBER=100 11 | ) 12 | if "%PACKAGE_VERISON%" == "" ( 13 | SET PACKAGE_VERISON=0.1.0 14 | ) 15 | 16 | SET APPVEYOR_BUILD_VERSION=%PACKAGE_VERISON%.%APPVEYOR_BUILD_NUMBER% 17 | SET BUILDCONFIG=Debug 18 | build-appveyor.bat 19 | 20 | @IF %ERRORLEVEL% NEQ 0 GOTO err 21 | @EXIT /B 0 22 | :err 23 | @PAUSE 24 | @EXIT /B 1 -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Our.Umbraco.Nexu.Core.Tests")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("Our.Umbraco.Nexu.Core.Tests")] 10 | [assembly: AssemblyCopyright("Copyright © 2019")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("a675fcde-4b42-480e-8299-91a270071ea6")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Our.Umbraco.Nexu.Web.Tests")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("Our.Umbraco.Nexu.Web.Tests")] 10 | [assembly: AssemblyCopyright("Copyright © 2019")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("7dedd335-c295-4f35-ac92-721a6e6b05ca")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | 4 | # Folder config file 5 | Desktop.ini 6 | 7 | # Mac OS X Finder Desktop Services Stores 8 | .DS_Store 9 | 10 | # git merge files 11 | *.orig 12 | *.BACKUP.* 13 | *.BASE.* 14 | *.LOCAL.* 15 | *.REMOTE.* 16 | 17 | # User-specific files 18 | *.suo 19 | *.user 20 | *.cache 21 | *.orig 22 | *.udt 23 | 24 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 25 | [Oo]bj/ 26 | [Bb]in/ 27 | [Dd]ebug/ 28 | [Bb]uild/ 29 | [Rr]elease/ 30 | 31 | #Project related 32 | packages/ 33 | App_Data/ 34 | testsites/ 35 | 36 | Source/.vs/config/applicationhost.config 37 | Packaging/NugetBuild/ 38 | Packaging/Package/ 39 | Packaging/UmbracoBuild/ 40 | artifacts/ 41 | .vs 42 | Packaging/NugetCoreBuild/ 43 | Packaging/NugetParserBuild/ 44 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Our.Umbraco.Nexu.Parsers.Tests")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("Our.Umbraco.Nexu.Parsers.Tests")] 10 | [assembly: AssemblyCopyright("Copyright © 2019")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("e5026f75-7c2b-4a90-adea-8d79d191405f")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /Packaging/package.core.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 0.0.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Packaging/Tools/MSBuildUmbracoTasks/MSBuild.Umbraco.Tasks.Targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $(MSBuildProjectDirectory)\MSBuildTasks 6 | $(MSBuildUmbracoTasksPath)\MSBuild.Umbraco.Tasks.dll 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/Composing/Components/RebuildDashboard.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Web.Composing.Components 2 | { 3 | using global::Umbraco.Core.Dashboards; 4 | 5 | /// 6 | /// Represents the rebuild dashboard. 7 | /// 8 | public class RebuildDashboard : IDashboard 9 | { 10 | /// 11 | public string Alias => "Our.Umbraco.Nexu.RebuildDashboard"; 12 | 13 | /// 14 | public string View => "/App_Plugins/Nexu/views/dashboard.html"; 15 | 16 | /// 17 | public string[] Sections => new string[] { new global::Umbraco.Web.Sections.SettingsSection().Alias}; 18 | 19 | /// 20 | public IAccessRule[] AccessRules => new IAccessRule[0]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Packaging/Package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0.0.0 7 | 8 | 9 | 10 | 0 11 | 0 12 | 0 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/Composing/Composers/NexuWebComposer.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Web.Composing.Composers 2 | { 3 | using global::Umbraco.Core; 4 | using global::Umbraco.Core.Composing; 5 | using global::Umbraco.Web; 6 | 7 | using Our.Umbraco.Nexu.Core.Composing.Composers; 8 | using Our.Umbraco.Nexu.Web.Composing.Components; 9 | 10 | /// 11 | /// Represents the nexu web composer. 12 | /// 13 | [ComposeAfter(typeof(NexuComposer))] 14 | internal class NexuWebComposer : IUserComposer 15 | { 16 | /// 17 | public void Compose(Composition composition) 18 | { 19 | composition.Components().Append(); 20 | composition.ContentApps().Append(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Packaging/package.parsers.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 0.0.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core/Composing/Collections/PropertyValueParserCollection.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Composing.Collections 2 | { 3 | using System.Collections.Generic; 4 | 5 | using global::Umbraco.Core.Composing; 6 | 7 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 8 | 9 | /// 10 | /// Represents property value parser collection. 11 | /// 12 | public class PropertyValueParserCollection : BuilderCollectionBase 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// 18 | /// The items. 19 | /// 20 | public PropertyValueParserCollection(IEnumerable items) 21 | : base(items) 22 | { 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core/Composing/Extensions.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Composing 2 | { 3 | using global::Umbraco.Core.Composing; 4 | 5 | using Our.Umbraco.Nexu.Core.Composing.Collections; 6 | 7 | /// 8 | /// Represents extension methods used in composing 9 | /// 10 | public static class Extensions 11 | { 12 | /// 13 | /// Gets the property value parsers collection builder 14 | /// 15 | /// 16 | /// The composition. 17 | /// 18 | /// 19 | /// The . 20 | /// 21 | public static PropertyValueParserCollectionBuilder PropertyValueParsers(this Composition composition) 22 | => composition.WithCollectionBuilder(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Packaging/package.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 0.0.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Interfaces/Services/IEntityRelationService.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Common.Interfaces.Services 2 | { 3 | using System.Collections.Generic; 4 | 5 | using global::Umbraco.Core; 6 | 7 | using Our.Umbraco.Nexu.Common.Models; 8 | 9 | /// 10 | /// Represents the entity relation service 11 | /// 12 | public interface IEntityRelationService 13 | { 14 | /// 15 | /// Gets relations for a item. 16 | /// 17 | /// 18 | /// The udi. 19 | /// 20 | /// 21 | /// The . 22 | /// 23 | IList GetRelationsForItem(Udi udi); 24 | 25 | 26 | IList GetUsedItemsFromList(IList udis); 27 | 28 | bool CheckLinksInDescendants(GuidUdi rootId); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/views/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 7 | 8 | 9 | 18 | 19 |

{{vm.status.Status}}

20 |
21 |
22 |
-------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core/Composing/Composers/CollectionsComposer.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Composing.Composers 2 | { 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | 6 | using global::Umbraco.Core; 7 | using global::Umbraco.Core.Composing; 8 | 9 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 10 | 11 | /// 12 | /// Represents the collections composer. 13 | /// 14 | [ComposeAfter(typeof(NexuComposer))] 15 | [RuntimeLevel(MinLevel = RuntimeLevel.Run)] 16 | public class CollectionsComposer : IUserComposer 17 | { 18 | /// 19 | public void Compose(Composition composition) 20 | { 21 | var assembly = Assembly.Load("Our.Umbraco.Nexu.Parsers"); 22 | composition.PropertyValueParsers().Append( 23 | composition.TypeLoader.GetTypes( 24 | specificAssemblies: new List { assembly })); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/resources/rebuild-resource.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function RebuildResource($http, umbRequestHelper) { 5 | 6 | var apiUrl = Umbraco.Sys.ServerVariables.Nexu.RebuildApi; 7 | 8 | var resource = { 9 | getStatus: getStatus, 10 | startRebuild: startRebuild 11 | }; 12 | 13 | return resource; 14 | 15 | function startRebuild() { 16 | 17 | return umbRequestHelper.resourcePromise( 18 | $http.get(apiUrl + 'Rebuild'), 19 | 'Failed starting rebuild' 20 | ); 21 | }; 22 | 23 | function getStatus() { 24 | 25 | return umbRequestHelper.resourcePromise( 26 | $http.get(apiUrl + 'GetRebuildStatus'), 27 | 'Failed getting rebuild status' 28 | ); 29 | }; 30 | 31 | } 32 | 33 | angular.module('umbraco.resources').factory('Our.Umbraco.Nexu.Resources.RebuildResource', RebuildResource); 34 | 35 | })(); -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/controllers/related-links-app-controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function RelatedLinksAppController($scope, appState) { 5 | var vm = this; 6 | 7 | vm.showLanguage = appState.getSectionState("currentSection") === 'media'; 8 | 9 | vm.relations = $scope.model.viewModel; 10 | var currentVariant = _.find($scope.content.variants, function (v) { return v.active }); 11 | 12 | if (currentVariant && currentVariant.language) { 13 | vm.culture = currentVariant.language.culture; 14 | vm.cultureRelations = _.filter(vm.relations, 15 | function(r) { return r.Culture.toLowerCase() === vm.culture.toLowerCase() }); 16 | } else { 17 | vm.cultureRelations = vm.relations; 18 | } 19 | 20 | } 21 | 22 | angular.module('umbraco').controller('Our.Umbraco.Nexu.Controllers.RelatedLinksAppController', 23 | [ 24 | '$scope', 25 | 'appState', 26 | RelatedLinksAppController 27 | ]); 28 | 29 | })(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nexu # 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/vqk2mxw245qxnnf8?svg=true)](https://ci.appveyor.com/project/dawoe/umbraco-nexu) 4 | 5 | 6 | 7 | |NuGet Packages |Version | 8 | |:-----------------|:-----------------| 9 | |**Release**|[![NuGet download](http://img.shields.io/nuget/v/Our.Umbraco.Nexu.svg)](https://www.nuget.org/packages/Our.Umbraco.Nexu/) 10 | |**Pre-release**|[![MyGet Pre Release](https://img.shields.io/myget/dawoe-umbraco/vpre/Our.Umbraco.Nexu.svg)](https://www.myget.org/feed/dawoe-umbraco/package/nuget/Our.Umbraco.Nexu) 11 | 12 | |Umbraco Packages | | 13 | |:-----------------|:-----------------| 14 | |**Release**|[![Our Umbraco project page](https://img.shields.io/badge/our-umbraco-orange.svg)](https://our.umbraco.org/projects/backoffice-extensions/nexu/) 15 | |**Pre-release**| [![AppVeyor Artifacts](https://img.shields.io/badge/appveyor-umbraco-orange.svg)](https://ci.appveyor.com/project/dawoe/umbraco-nexu/build/artifacts) 16 | 17 | 18 | ## Documentation ## 19 | 20 | The documentation for this package can be found [here](docs/index.md) 21 | 22 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core/Migrations/NexuMigrationPlan.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Migrations 2 | { 3 | using global::Umbraco.Core.Migrations; 4 | 5 | using Our.Umbraco.Nexu.Core.Migrations.Version_2_0_0; 6 | 7 | /// 8 | /// Represents the nexu migration plan 9 | /// 10 | internal class NexuMigrationPlan : MigrationPlan 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | public NexuMigrationPlan() 16 | : base("Our.Umbraco.Nexu") 17 | { 18 | this.InitialInstall(); 19 | } 20 | 21 | /// 22 | /// Gets the initial state. 23 | /// 24 | public override string InitialState => string.Empty; 25 | 26 | /// 27 | /// Runs the migration for a initial install 28 | /// 29 | private void InitialInstall() 30 | { 31 | this.From(this.InitialState).To("2.0.0-Initial"); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Dave Woestenborghs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/views/media-delete.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | 6 |
7 | 8 |
9 | 10 |

11 | Are you sure you want to delete {{currentNode.name}} ? 12 |

13 | 14 |
15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Interfaces/Models/IPropertyValueParser.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Common.Interfaces.Models 2 | { 3 | using System.Collections.Generic; 4 | 5 | using global::Umbraco.Core.Composing; 6 | 7 | /// 8 | /// Represents the PropertyValueParser interface. 9 | /// 10 | public interface IPropertyValueParser : IDiscoverable 11 | { 12 | /// 13 | /// Checks if this is the parser for the current property editor 14 | /// 15 | /// 16 | /// The property Editor Alias. 17 | /// 18 | /// 19 | /// The . 20 | /// 21 | bool IsParserFor(string propertyEditorAlias); 22 | 23 | /// 24 | /// Gets the related entities from the property value 25 | /// 26 | /// 27 | /// The property value 28 | /// 29 | /// 30 | /// The . 31 | /// 32 | IEnumerable GetRelatedEntities(string value); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/package.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "javascript": [ 3 | "~/App_Plugins/Nexu/resources/rebuild-resource.js", 4 | "~/App_Plugins/Nexu/resources/relation-check-resource.js", 5 | "~/App_Plugins/Nexu/services/relation-check-service.js", 6 | "~/App_Plugins/Nexu/controllers/dashboard-controller.js", 7 | "~/App_Plugins/Nexu/controllers/related-links-app-controller.js", 8 | "~/App_Plugins/Nexu/controllers/unpublish-controller.js", 9 | "~/App_Plugins/Nexu/controllers/dashboard-controller.js", 10 | "~/App_Plugins/Nexu/controllers/related-links-app-controller.js", 11 | "~/App_Plugins/Nexu/controllers/unpublish-controller.js", 12 | "~/App_Plugins/Nexu/controllers/base-delete-controller.js", 13 | "~/App_Plugins/Nexu/controllers/content-delete-controller.js", 14 | "~/App_Plugins/Nexu/controllers/media-delete-controller.js", 15 | "~/App_Plugins/Nexu/controllers/listview-controller.js", 16 | "~/App_Plugins/Nexu/controllers/listview-dialog-controller.js", 17 | "~/App_Plugins/Nexu/components/relation-list-component.js", 18 | "~/App_Plugins/Nexu/interceptors/interceptors.js", 19 | "~/App_Plugins/Nexu/decorators/controller-decorator.js" 20 | ], 21 | "css": [ 22 | "~/App_Plugins/Nexu/styles/nexu.css" 23 | ] 24 | } -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core/Composing/Composers/NexuComposer.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Composing.Composers 2 | { 3 | using global::Umbraco.Core; 4 | using global::Umbraco.Core.Composing; 5 | 6 | using Our.Umbraco.Nexu.Common.Interfaces.Repositories; 7 | using Our.Umbraco.Nexu.Common.Interfaces.Services; 8 | using Our.Umbraco.Nexu.Core.Composing.Components; 9 | using Our.Umbraco.Nexu.Core.Repositories; 10 | using Our.Umbraco.Nexu.Core.Services; 11 | 12 | /// 13 | /// Represents the composer to handle all registrations for nexu 14 | /// 15 | [RuntimeLevel(MinLevel = RuntimeLevel.Run)] 16 | public class NexuComposer : IUserComposer 17 | { 18 | /// 19 | public void Compose(Composition composition) 20 | { 21 | composition.Register(); 22 | composition.Register(); 23 | composition.Register(); 24 | 25 | 26 | composition.Components().Append(); 27 | composition.Components().Append(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/lang/sv-SE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Byggom nexu relationer 5 | Ombyggnad av nexu relationer är pågående 6 | noder bearbetade 7 | Laddar om var 5:e sekund 8 | Ladda om 9 | Visa egenskaper 10 | Göm egenskaper 11 | Denna sidan är länkad till av följande noder 12 | Denna bild är länkad till följande 13 | Är du säker på att du vill avpublicera 14 | En av under noderna är länkad av en annan nod 15 | Borttagning av använda noder är 16 | Avpublicering av noder är 17 | tillåtet 18 | förbjudet 19 | 20 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/lang/en-us.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Nexu 5 | 6 | 7 | Rebuild relations 8 | Rebuild entire relations database. Depending on the number of content items this may take a while. 9 | Rebuild 10 | Idle 11 | Currently processing item %0%. %1% total items processed 12 | 13 | 14 | The following items you are trying to delete are in use by other content. 15 | The following items you are trying to unpublish are in use by other content. 16 | The current item is linked from one of the items below. Unpublishing or deleting this item will cause your site to have broken links or images. 17 | One of the underlying items is in use. Deleting or unpublishing this page can cause broken links,pages or images on your website 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/Models/NexuRelationTests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Tests.Models 2 | { 3 | using System; 4 | 5 | using ApprovalTests; 6 | using ApprovalTests.Reporters; 7 | using ApprovalTests.Reporters.ContinuousIntegration; 8 | 9 | using Newtonsoft.Json; 10 | 11 | using NUnit.Framework; 12 | 13 | using Our.Umbraco.Nexu.Common.Models; 14 | 15 | /// 16 | /// Represents nexu relation model tests. 17 | /// 18 | [TestFixture] 19 | [UseReporter(typeof(DiffReporter), typeof(AppVeyorReporter))] 20 | public class NexuRelationTests 21 | { 22 | [Test] 23 | public void When_NexuRelation_Instance_Is_Created_Id_Should_Be_A_Guid() 24 | { 25 | // act 26 | var result = new NexuRelation(); 27 | 28 | // assert 29 | Assert.That(result.Id != Guid.Empty); 30 | } 31 | 32 | [Test] 33 | public void Nexu_Relation_Udi_Should_Match_Id() 34 | { 35 | // act 36 | var result = new NexuRelation 37 | { 38 | Id= new Guid("9a935375-6c89-4dda-ae83-b9207e19a3ee") 39 | }; 40 | 41 | // assert 42 | Approvals.VerifyJson(JsonConvert.SerializeObject(result)); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Packaging/github-package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Our.Umbraco.Nexu 9 | 10 | MIT license 11 | https://github.com/dawoe/umbraco-nexu 12 | 13 | 8 14 | 1 15 | 0 16 | 17 | 18 | 19 | Dave Woestenborghs 20 | https://github.com/dawoe/umbraco-nexu 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/views/content-delete.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | 7 |
8 | 9 |
10 | 11 |

12 | Are you sure you want to delete {{currentNode.name}}? 13 |

14 | 15 |
16 | 17 |
18 | 19 |
20 | This will delete the node and all its languages. If you only want to delete one language go and unpublish it instead. 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction # 2 | 3 | After installing this package all your internal links will be tracked in Umbraco. This way a content editor can see where a page or media items is used when trying to delete or unpublishing a page and will prevent broken links 4 | 5 | ## How does it work ## 6 | 7 | When content is saved the package will parse all the properties that have a parser configured and store the related items as a relation in Umbraco. 8 | 9 | ### Delete warnings and unpublish warnings ### 10 | 11 | When you try to delete or unpublish a item that is linked to from another item you will get a warning that it is used by other items. You can also see in which property the link is created. 12 | 13 | Delete dialog 14 | 15 | ![Delete dialog](images/delete_dialog.png) 16 | 17 | Unpublish dialog 18 | 19 | ![Unpublish dialog](images/unpublish_dialog.png) 20 | 21 | ### Viewing incoming links ### 22 | 23 | When a item has incoming links these can be viewed in the Related links content app. 24 | 25 | ![Content app](images/content-app.png) 26 | 27 | 28 | 29 | ### Nexu dashboard ### 30 | 31 | In the settins section a dashboard is added that allows you to rebuild all the relations. This happens in a background thread, so once you have pushed the button you can continue working in the backoffice. You can go back to the dashboard to check the status. 32 | 33 | 34 | 35 | ![Dashboard](images/dashboard.png) 36 | 37 | ## Supported property editors ## 38 | 39 | All built-in property editors that store links are supported. 40 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/services/relation-check-service.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function RelationCheckServive($q, resource) { 5 | 6 | var service = { 7 | checkRelations: getIncheckRelationscomingLinks 8 | }; 9 | 10 | return service; 11 | 12 | function getIncheckRelationscomingLinks(udi) { 13 | 14 | var deferred = $q.defer(); 15 | 16 | var result = { 17 | relations :[], 18 | descendantsUsed : false 19 | }; 20 | 21 | resource.getIncomingLinks(udi).then(function (data) { 22 | 23 | if (data.length > 0) { 24 | result.relations = data; 25 | deferred.resolve(result); 26 | } 27 | else { 28 | resource.checkDescendants(udi).then(function (data) { 29 | result.descendantsUsed = data; 30 | deferred.resolve(result); 31 | }) 32 | } 33 | 34 | 35 | }, 36 | function () { 37 | deferred.reject(); 38 | }); 39 | 40 | return deferred.promise; 41 | }; 42 | 43 | } 44 | 45 | angular.module('umbraco.resources').factory('Our.Umbraco.Nexu.Services.RelationCheckServive', 46 | ['$q', 'Our.Umbraco.Nexu.Resources.RelationCheckResource', RelationCheckServive] 47 | ); 48 | 49 | })(); -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/Core/ContentPickerParser.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Core 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | using global::Umbraco.Core; 8 | 9 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 10 | using Our.Umbraco.Nexu.Common.Models; 11 | 12 | /// 13 | /// Represents content picker parser. 14 | /// 15 | public class ContentPickerParser : IPropertyValueParser 16 | { 17 | /// 18 | public bool IsParserFor(string propertyEditorAlias) 19 | { 20 | return propertyEditorAlias.Equals(Constants.PropertyEditors.Aliases.ContentPicker); 21 | } 22 | 23 | /// 24 | public IEnumerable GetRelatedEntities(string value) 25 | { 26 | if (!string.IsNullOrWhiteSpace(value)) 27 | { 28 | var relatedEntities = new List 29 | { 30 | new RelatedDocumentEntity 31 | { 32 | RelatedEntityUdi = new StringUdi(new Uri(value)) 33 | } 34 | }; 35 | 36 | return relatedEntities; 37 | } 38 | 39 | return Enumerable.Empty(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/controllers/unpublish-controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | function UnpublishController($scope, $controller, editorState, service) { 5 | var vm = this; 6 | angular.extend(this, $controller('Umbraco.Overlays.UnpublishController', { $scope: $scope })); 7 | 8 | vm.loading = true; 9 | vm.relations = []; 10 | vm.descendantsHaveLinks = false; 11 | vm.showLanguageColumn = false; 12 | $scope.model.disableSubmitButton = true; 13 | 14 | function init() { 15 | service.checkRelations(editorState.current.udi).then(function (data) { 16 | vm.relations = data.relations; 17 | vm.descendantsHaveLinks = data.descendantsUsed; 18 | $scope.model.disableSubmitButton = false; 19 | 20 | if (Umbraco.Sys.ServerVariables.Nexu.PreventUnPublish) { 21 | if (vm.relations.length > 0 || vm.descendantsHaveLinks) { 22 | $scope.model.disableSubmitButton = true; 23 | } 24 | } 25 | 26 | vm.loading = false; 27 | }); 28 | 29 | } 30 | 31 | init(); 32 | } 33 | 34 | angular.module("umbraco").controller("Our.Umbraco.Nexu.Controllers.UnpublishController", 35 | [ 36 | '$scope', 37 | '$controller', 38 | 'editorState', 39 | 'Our.Umbraco.Nexu.Services.RelationCheckServive', 40 | UnpublishController]); 41 | 42 | })(); 43 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Our.Umbraco.Nexu.Web")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Our.Umbraco.Nexu.Web")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("cd53b833-3bdd-4dd1-86b3-27e5e173bca8")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Our.Umbraco.Nexu.Parsers")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Our.Umbraco.Nexu.Parsers")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6797e59c-5528-4f3c-baf4-b2ac32185101")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/Services/NexuEntityParsingService/SaveRelationsForContentItem_Tests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Tests.Services.NexuEntityParsingService 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Web.WebSockets; 6 | 7 | using global::Umbraco.Core; 8 | using global::Umbraco.Core.Models; 9 | 10 | using Moq; 11 | 12 | using NUnit.Framework; 13 | 14 | using Our.Umbraco.Nexu.Common.Models; 15 | 16 | /// 17 | /// Represents the tests for SaveRelationsForContentItem method on the NexuEntityParsingService 18 | /// 19 | [TestFixture] 20 | public class SaveRelationsForContentItem_Tests : NexuEntityParsingServiceBaseTest 21 | { 22 | [Test] 23 | public void When_SaveRelationsForContentItem_It_Should_Call_Repository_Method_With_Correct_Parameters() 24 | { 25 | // arrange 26 | var guid = Guid.NewGuid(); 27 | 28 | var content = Mock.Of(); 29 | content.Blueprint = false; 30 | content.Key = guid; 31 | 32 | var udi = content.GetUdi(); 33 | 34 | var relations = new List(); 35 | 36 | this.RelationRepositoryMock.Setup(x => x.PersistRelationsForContentItem(udi, relations)); 37 | 38 | // act 39 | this.Service.SaveRelationsForContentItem(content, relations); 40 | 41 | // assert 42 | this.RelationRepositoryMock.Verify(x => x.PersistRelationsForContentItem(udi, relations)); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Interfaces/Repositories/IRelationRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Common.Interfaces.Repositories 2 | { 3 | using System.Collections.Generic; 4 | 5 | using global::Umbraco.Core; 6 | 7 | using Our.Umbraco.Nexu.Common.Models; 8 | 9 | /// 10 | /// Represents the RelationRepository interface. 11 | /// 12 | public interface IRelationRepository 13 | { 14 | /// 15 | /// Persists the relations for a content item 16 | /// 17 | /// 18 | /// The content item udi. 19 | /// 20 | /// 21 | /// The relations. 22 | /// 23 | void PersistRelationsForContentItem(Udi contentItemUdi, IEnumerable relations); 24 | 25 | /// 26 | /// Gets the incoming relations for a item 27 | /// 28 | /// 29 | /// The udi. 30 | /// 31 | /// 32 | /// The . 33 | /// 34 | IEnumerable GetIncomingRelationsForItem(Udi udi); 35 | 36 | /// 37 | /// Gets the items that are in use from a list of unique doc identifier 38 | /// 39 | /// 40 | /// The list of unique doc identifier. 41 | /// 42 | /// 43 | /// The . 44 | /// 45 | IList> GetUsedItemsFromList(IList udis); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/controllers/base-delete-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('umbraco').controller('Our.Umbraco.Nexu.BaseDeleteController', 2 | ['$scope', '$controller', 'Our.Umbraco.Nexu.Services.RelationCheckServive', 3 | function ($scope, $controller, service) { 4 | // inherit core delete controller 5 | if ($scope.isMedia) { 6 | angular.extend(this, $controller('Umbraco.Editors.Media.DeleteController', { $scope: $scope })); 7 | } else { 8 | angular.extend(this, $controller('Umbraco.Editors.Content.DeleteController', { $scope: $scope })); 9 | } 10 | 11 | $scope.preventDelete = false; 12 | $scope.links = {}; 13 | $scope.descendantsHaveLinks = false; 14 | $scope.isLoading = true; 15 | $scope.showlanguage = true; 16 | $scope.itemsPerPage = 5; 17 | 18 | service.checkRelations($scope.currentNode.udi).then(function (result) { 19 | $scope.links = result.relations; 20 | $scope.descendantsHaveLinks = result.descendantsUsed; 21 | 22 | if (Umbraco.Sys.ServerVariables.Nexu.PreventDelete) { 23 | if ($scope.links.length > 0 || $scope.descendantsHaveLinks) { 24 | $scope.preventDelete = true; 25 | } 26 | } 27 | 28 | $scope.isLoading = false; 29 | 30 | }); 31 | 32 | $scope.$watch('success', function (newValue, oldValue) { 33 | if (newValue === true) { 34 | $scope.close(); 35 | } 36 | }); 37 | }]); -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/resources/relation-check-resource.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function RelationCheckResource($http, umbRequestHelper) { 5 | 6 | var apiUrl = Umbraco.Sys.ServerVariables.Nexu.RelationCheckApi; 7 | 8 | var resource = { 9 | getIncomingLinks: getIncomingLinks, 10 | checkLinkedItems: checkLinkedItems, 11 | checkDescendants : checkDescendants, 12 | }; 13 | 14 | return resource; 15 | 16 | function getIncomingLinks(udi) { 17 | 18 | return umbRequestHelper.resourcePromise( 19 | $http.get(apiUrl + 'GetIncomingLinks?udi=' + udi), 20 | 'Failed to get incoming links' 21 | ); 22 | }; 23 | 24 | function checkLinkedItems(udis, isMedia) { 25 | 26 | var udiList = _.map(udis, 27 | function (u) { 28 | return 'umb://' + (isMedia ? 'media' : 'document') + '/' + u.key.replace(/-/g,''); 29 | }); 30 | 31 | return umbRequestHelper.resourcePromise( 32 | $http.post(apiUrl + 'CheckLinkedItems', JSON.stringify(udiList)), 33 | 'Failed to checked linked items' 34 | ); 35 | } 36 | 37 | function checkDescendants(udi) { 38 | 39 | return umbRequestHelper.resourcePromise( 40 | $http.get(apiUrl + 'CheckDescendants?udi=' + udi), 41 | 'Failed to check descendants' 42 | ); 43 | }; 44 | 45 | } 46 | 47 | angular.module('umbraco.resources').factory('Our.Umbraco.Nexu.Resources.RelationCheckResource', RelationCheckResource); 48 | 49 | })(); -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/Core/MediaPicker3.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 6 | using Our.Umbraco.Nexu.Common.Models; 7 | using Umbraco.Core; 8 | 9 | namespace Our.Umbraco.Nexu.Parsers.Core 10 | { 11 | public class MediaPicker3 : IPropertyValueParser 12 | { 13 | public bool IsParserFor(string propertyEditorAlias) => 14 | propertyEditorAlias.Equals("Umbraco.MediaPicker3"); 15 | 16 | public IEnumerable GetRelatedEntities(string value) 17 | { 18 | var entities = new List(); 19 | 20 | if (!string.IsNullOrWhiteSpace(value)) 21 | { 22 | try 23 | { 24 | var jsonValues = JsonConvert.DeserializeObject(value); 25 | 26 | foreach (var item in jsonValues) 27 | { 28 | if (item["mediaKey"] != null) 29 | { 30 | var mediaKey = item.Value("mediaKey"); 31 | 32 | var udi = Udi.Create("media", Guid.Parse(mediaKey)); 33 | 34 | entities.Add(new RelatedMediaEntity 35 | { 36 | RelatedEntityUdi = udi, 37 | }); 38 | } 39 | } 40 | } 41 | catch 42 | { 43 | // swallow it 44 | } 45 | } 46 | 47 | return entities; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Models/NexuRelationDisplayModel.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Common.Models 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | /// 7 | /// Represents the nexu relation display model. 8 | /// 9 | public class NexuRelationDisplayModel 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public NexuRelationDisplayModel() 15 | { 16 | this.Properties = new List(); 17 | } 18 | 19 | /// 20 | /// Gets or sets the id. 21 | /// 22 | public int Id { get; set; } 23 | 24 | /// 25 | /// Gets or sets the key. 26 | /// 27 | public Guid Key { get; set; } 28 | 29 | /// 30 | /// Gets or sets the name. 31 | /// 32 | public string Name { get; set; } 33 | 34 | /// 35 | /// Gets or sets the culture. 36 | /// 37 | public string Culture { get; set; } 38 | 39 | /// 40 | /// Gets or sets a value indicating whether is published. 41 | /// 42 | public bool IsPublished { get; set; } 43 | 44 | /// 45 | /// Gets or sets a value indicating whether is trashed. 46 | /// 47 | public bool IsTrashed { get; set; } 48 | 49 | /// 50 | /// Gets or sets the properties. 51 | /// 52 | public IList Properties { get; set; } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/NexuRelationRepository/GetIncomingRelationsForItem_Tests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Tests.NexuRelationRepository 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | using global::Umbraco.Core; 7 | using global::Umbraco.Core.Persistence; 8 | 9 | using Moq; 10 | 11 | using NPoco; 12 | 13 | using NUnit.Framework; 14 | 15 | using Our.Umbraco.Nexu.Common.Models; 16 | using Our.Umbraco.Nexu.Core.Tests.Repositories; 17 | 18 | [TestFixture] 19 | public class GetIncomingRelationsForItem_Tests : RepositoryBaseTest 20 | { 21 | [Test] 22 | public void 23 | When_GetIncomingRelationsForItem_Called_We_Should_Fetch_All_Items_Where_ChildUdi_Matches_The_Item_Udi() 24 | { 25 | // arrange 26 | var udi = new GuidUdi("foo", Guid.NewGuid()); 27 | var relations = Mock.Of>(); 28 | 29 | Sql actualSql = null; 30 | 31 | this.UmbracoDatabaseMock.Setup(x => x.Fetch(It.IsAny>())).Callback((Sql sql) => 32 | { 33 | actualSql = sql; 34 | }).Returns(relations); 35 | 36 | 37 | // act 38 | var result = this.Repository.GetIncomingRelationsForItem(udi); 39 | 40 | // assert 41 | Assert.IsNotNull(result); 42 | Assert.That(actualSql.SQL == "WHERE ((Nexu_Relations.child_udi = @0))"); 43 | Assert.That(actualSql.Arguments[0].ToString() == udi.ToString()); 44 | 45 | this.UmbracoDatabaseMock.Verify(x => x.Fetch(It.IsAny>()), Times.Once); 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/decorators/controller-decorator.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | 'use strict'; 4 | 5 | // Umbraco overrides 6 | function nexuUmbracoOverrides($provide) { 7 | 8 | $provide.decorator('$controller', function ($delegate) { 9 | 10 | 11 | 12 | return function (constructor, locals, later, ident) { 13 | var ctrl = $delegate(constructor, locals, later, ident); 14 | 15 | // Check for an ngController attribute 16 | if (locals.$attrs && locals.$attrs.ngController) { 17 | 18 | // Override content edit controller method 19 | if (locals.$attrs.ngController.match(/^Umbraco\.Editors\.Content\.EditController\b/i)) { 20 | //var controller = $delegate.apply(null, arguments); 21 | 22 | return angular.extend(function () { 23 | 24 | var ctrlInstance = ctrl(); 25 | 26 | var infiniteMode = locals.$scope.model && locals.$scope.model.infiniteMode; 27 | 28 | if (infiniteMode && locals.$scope.model.nexuCulture) { 29 | locals.$scope.culture = locals.$scope.model.nexuCulture; 30 | } 31 | 32 | return ctrlInstance; 33 | }, ctrl); 34 | } 35 | } 36 | 37 | return ctrl; 38 | } 39 | 40 | }); 41 | 42 | } 43 | 44 | angular.module("umbraco").config(['$provide', nexuUmbracoOverrides]); 45 | 46 | })(); -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Our.Umbraco.Nexu.Core")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Our.Umbraco.Nexu.Core")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("dfe4ee20-8a4c-4f9f-92bd-bb94cbf60940")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | [assembly: InternalsVisibleTo("Our.Umbraco.Nexu.Core.Tests")] 38 | [assembly: InternalsVisibleTo("Our.Umbraco.Nexu.Web")] 39 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/BaseTextParser.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 7 | using Our.Umbraco.Nexu.Common.Models; 8 | using Our.Umbraco.Nexu.Parsers.Helpers; 9 | 10 | /// 11 | /// Represents the base text parser class 12 | /// 13 | public abstract class BaseTextParser : IPropertyValueParser 14 | { 15 | /// 16 | public abstract bool IsParserFor(string propertyEditorAlias); 17 | 18 | /// 19 | public IEnumerable GetRelatedEntities(string value) 20 | { 21 | if (string.IsNullOrWhiteSpace(value)) 22 | { 23 | return Enumerable.Empty(); 24 | } 25 | 26 | var relatedEntities = new List(); 27 | 28 | foreach (var documentUdi in ParserUtilities.GetDocumentUdiFromText(value).ToList()) 29 | { 30 | relatedEntities.Add(new RelatedDocumentEntity 31 | { 32 | RelatedEntityUdi = documentUdi 33 | }); 34 | } 35 | 36 | foreach (var mediaUdi in ParserUtilities.GetMediaUdiFromText(value).ToList()) 37 | { 38 | relatedEntities.Add(new RelatedMediaEntity() 39 | { 40 | RelatedEntityUdi = mediaUdi 41 | }); 42 | } 43 | 44 | return relatedEntities; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/views/listview-dialog.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | {{vm.intro}} 4 |

5 |
6 |
7 |
8 |
9 | 10 |
11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 | {{item.Name}} 22 |
23 |
24 | 25 |
26 |
27 | 28 |
29 | 42 |
43 |
-------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/interceptors/interceptors.js: -------------------------------------------------------------------------------- 1 | angular.module('umbraco.services').config([ 2 | '$httpProvider', 3 | function ($httpProvider) { 4 | 5 | $httpProvider.interceptors.push(function () { 6 | return { 7 | 'request': function (request) { 8 | 9 | 10 | // Redirect any requests to built in content delete to our custom delete 11 | if (request.url.indexOf("views/content/delete.html") === 0) { 12 | request.url = '/App_Plugins/Nexu/views/content-delete.html'; 13 | } 14 | 15 | // Redirect any requests to built in media delete to our custom delete 16 | if (request.url.indexOf("views/media/delete.html") === 0) { 17 | request.url = '/App_Plugins/Nexu/views/media-delete.html'; 18 | } 19 | 20 | // Redirect requests to unpublish confirmation to our own 21 | if (request.url.indexOf("views/content/overlays/unpublish.html") === 0) { 22 | request.url = '/App_Plugins/Nexu/views/unpublish.html'; 23 | } 24 | 25 | 26 | return request; 27 | }, 28 | 'response': function(response) { 29 | // Change the controller of the list view 30 | if (response.config.url.indexOf("views/propertyeditors/listview") === 0) { 31 | response.data = response.data.replace('Umbraco.PropertyEditors.ListViewController', 32 | 'Our.Umbraco.Nexu.ListViewController'); 33 | } 34 | 35 | return response; 36 | } 37 | }; 38 | }); 39 | 40 | }]); -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Our.Umbraco.Nexu.Common")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Our.Umbraco.Nexu.Common")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f16a67cc-0f13-45e6-b493-d6c10f742214")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | 38 | [assembly:InternalsVisibleTo("Our.Umbraco.Nexu.Core")] 39 | [assembly: InternalsVisibleTo("Our.Umbraco.Nexu.Web")] 40 | [assembly: InternalsVisibleTo("Our.Umbraco.Nexu.Core.Tests")] 41 | [assembly: InternalsVisibleTo("Our.Umbraco.Nexu.Web.Tests")] 42 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 43 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Constants/DatabaseConstants.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Common.Constants 2 | { 3 | /// 4 | /// Represents the constants used in the database table 5 | /// 6 | public static class DatabaseConstants 7 | { 8 | /// 9 | /// The table name. 10 | /// 11 | public const string TableName = "Nexu_Relations"; 12 | 13 | /// 14 | /// The id column. 15 | /// 16 | public const string IdColumn = "id"; 17 | 18 | /// 19 | /// The primary key. 20 | /// 21 | public const string PrimaryKey = "PK_" + TableName; 22 | 23 | /// 24 | /// The parent udi column. 25 | /// 26 | public const string ParentUdiColumn = "parent_udi"; 27 | 28 | /// 29 | /// The child udi column. 30 | /// 31 | public const string ChildUdiColumn = "child_udi"; 32 | 33 | /// 34 | /// The relation type column. 35 | /// 36 | public const string RelationTypeColumn = "relation_type"; 37 | 38 | /// 39 | /// The property id column. 40 | /// 41 | public const string PropertyAlias = "property_alias"; 42 | 43 | /// 44 | /// The culture column. 45 | /// 46 | public const string CultureColumn = "culture_column"; 47 | 48 | /// 49 | /// The parent udi index. 50 | /// 51 | public const string ParentUdiIndex = "IX_" + TableName + "_" + ParentUdiColumn; 52 | 53 | /// 54 | /// The child udi index. 55 | /// 56 | public const string ChildUdiIndex = "IX_" + TableName + "_" + ChildUdiColumn; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web.Tests/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | 3 | # Version format 4 | version: 2.4.0.{build} 5 | 6 | branches: 7 | only: 8 | - develop 9 | - master 10 | 11 | init: 12 | - set UMBRACO_PACKAGE_PRERELEASE_SUFFIX=beta 13 | 14 | assembly_info: 15 | patch: true 16 | file: AssemblyInfo.* 17 | assembly_version: "{version}" 18 | assembly_file_version: "{version}" 19 | assembly_informational_version: "{version}" 20 | 21 | cache: 22 | - src\packages -> **\packages.config # preserve "packages" directory in the root of build folder but will reset it if packages.config is modified 23 | 24 | platform: Any CPU 25 | 26 | configuration: Release 27 | 28 | before_build: 29 | - nuget restore src 30 | 31 | build: 32 | parallel: true 33 | project: src\Our.Umbraco.Nexu.sln 34 | verbosity: minimal 35 | 36 | after_build: 37 | - cd Packaging 38 | - build-appveyor.bat 39 | 40 | artifacts: 41 | - path: artifacts\*.nupkg 42 | - path: artifacts\*.zip 43 | 44 | 45 | 46 | deploy: 47 | # MyGet Deployment for beta releases 48 | - provider: NuGet 49 | server: https://www.myget.org/F/dawoe-umbraco/api/v2/package 50 | api_key: 51 | secure: xuhyqGpLpjeK0tUansB6IVWy89Fw+yqGqCmCpVuAY1fnDjfYrxA/gVGNBh8oU0JA 52 | artifact: /.*\.nupkg/ 53 | on: 54 | branch: develop 55 | 56 | # GitHub Deployment for releases 57 | - provider: GitHub 58 | auth_token: 59 | secure: 2NNEN9lVF0/cA40PZrm64BMMK34Y26A8xK22eUdWcN/7Nn5KyyRH2+THumy91tcV 60 | artifact: /.*\.zip/ # upload all Zip packages to release assets 61 | draft: false 62 | prerelease: false 63 | on: 64 | branch: master 65 | appveyor_repo_tag: true # deploy on tag push only 66 | 67 | # NuGet Deployment for releases 68 | - provider: NuGet 69 | server: 70 | api_key: 71 | secure: f5ijMJdo8hygOHm+RRD4pCBcRqj6uw4AWqDgG4MvA1x5sgdmcuRxvdtN1Cn2NB6I 72 | artifact: /.*\.nupkg/ 73 | on: 74 | branch: master 75 | appveyor_repo_tag: true 76 | 77 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core/Migrations/Version_2_0_0/CreateRelationTableMigration.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Migrations.Version_2_0_0 2 | { 3 | using global::Umbraco.Core.Migrations; 4 | 5 | using Our.Umbraco.Nexu.Common.Constants; 6 | 7 | /// 8 | /// Represents the create relation table migration. 9 | /// 10 | internal class CreateRelationTableMigration : MigrationBase 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// 16 | /// The context. 17 | /// 18 | public CreateRelationTableMigration(IMigrationContext context) 19 | : base(context) 20 | { 21 | } 22 | 23 | /// 24 | public override void Migrate() 25 | { 26 | this.Create.Table(DatabaseConstants.TableName) 27 | .WithColumn(DatabaseConstants.IdColumn).AsGuid().PrimaryKey(DatabaseConstants.PrimaryKey).NotNullable() 28 | .WithColumn(DatabaseConstants.ParentUdiColumn).AsString(100).NotNullable() 29 | .WithColumn(DatabaseConstants.ChildUdiColumn).AsString(100).NotNullable() 30 | .WithColumn(DatabaseConstants.RelationTypeColumn).AsGuid().NotNullable() 31 | .WithColumn(DatabaseConstants.PropertyAlias).AsString(100).NotNullable() 32 | .WithColumn(DatabaseConstants.CultureColumn).AsString(32).Nullable() 33 | .Do(); 34 | 35 | this.Create.Index(DatabaseConstants.ParentUdiIndex).OnTable(DatabaseConstants.TableName).WithOptions().NonClustered() 36 | .OnColumn(DatabaseConstants.ParentUdiColumn).Ascending().Do(); 37 | 38 | this.Create.Index(DatabaseConstants.ChildUdiIndex).OnTable(DatabaseConstants.TableName).WithOptions().NonClustered() 39 | .OnColumn(DatabaseConstants.ChildUdiColumn).Ascending().Do(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Packaging/Tools/AppVeyorUmbraco/AppVeyorUmbraco.targets: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core/Composing/Components/ContentServiceEventsComponent.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Composing.Components 2 | { 3 | using global::Umbraco.Core.Composing; 4 | using global::Umbraco.Core.Events; 5 | using global::Umbraco.Core.Services; 6 | using global::Umbraco.Core.Services.Implement; 7 | 8 | using Our.Umbraco.Nexu.Common.Interfaces.Services; 9 | 10 | /// 11 | /// Represents a component to hook in to the content service events 12 | /// 13 | internal class ContentServiceEventsComponent : IComponent 14 | { 15 | /// 16 | /// Represents entity parsing service. 17 | /// 18 | private readonly IEntityParsingService entityParsingService; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// 24 | /// The entity parsing service. 25 | /// 26 | public ContentServiceEventsComponent(IEntityParsingService entityParsingService) 27 | { 28 | this.entityParsingService = entityParsingService; 29 | } 30 | 31 | /// 32 | public void Initialize() 33 | { 34 | ContentService.Saved += this.ContentServiceOnSaved; 35 | } 36 | 37 | /// 38 | public void Terminate() 39 | { 40 | ContentService.Saved -= this.ContentServiceOnSaved; 41 | } 42 | 43 | /// 44 | /// Event handler for content service saved event 45 | /// 46 | /// 47 | /// The sender. 48 | /// 49 | /// 50 | /// The e. 51 | /// 52 | private void ContentServiceOnSaved(IContentService sender, ContentSavedEventArgs e) 53 | { 54 | foreach (var contentItem in e.SavedEntities) 55 | { 56 | this.entityParsingService.ParseContent(contentItem); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/Services/NexuEntityParsingService/NexuEntityParsingServiceBaseTest.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Tests.Services.NexuEntityParsingService 2 | { 3 | using System.Collections.Generic; 4 | 5 | using global::Umbraco.Core.Logging; 6 | 7 | using Moq; 8 | 9 | using NUnit.Framework; 10 | 11 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 12 | using Our.Umbraco.Nexu.Common.Interfaces.Repositories; 13 | using Our.Umbraco.Nexu.Core.Composing.Collections; 14 | using Our.Umbraco.Nexu.Core.Services; 15 | 16 | /// 17 | /// Represents nexu entity parsing service base test. 18 | /// 19 | public abstract class NexuEntityParsingServiceBaseTest 20 | { 21 | /// 22 | /// The service instance used in all tests 23 | /// 24 | internal NexuEntityParsingService Service { get; set; } 25 | 26 | /// 27 | /// The logger mock. 28 | /// 29 | internal Mock LoggerMock { get; set; } 30 | 31 | /// 32 | /// The relation repository mock. 33 | /// 34 | internal Mock RelationRepositoryMock { get; set; } 35 | 36 | /// 37 | /// The setup that is run for all tests 38 | /// 39 | [SetUp] 40 | public void SetUp() 41 | { 42 | this.LoggerMock = new Mock(); 43 | this.RelationRepositoryMock = new Mock(); 44 | 45 | var serviceMock = new Mock( 46 | new PropertyValueParserCollection(this.GetParsers()), this.LoggerMock.Object, this.RelationRepositoryMock.Object) 47 | { 48 | CallBase = true 49 | }; 50 | 51 | 52 | this.Service = serviceMock.Object; 53 | } 54 | 55 | public virtual List GetParsers() 56 | { 57 | return new List(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/Composing/Components/RelatedLinksContentAppFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Web.Composing.Components 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | using global::Umbraco.Core; 7 | using global::Umbraco.Core.Models; 8 | using global::Umbraco.Core.Models.ContentEditing; 9 | using global::Umbraco.Core.Models.Membership; 10 | 11 | using Our.Umbraco.Nexu.Common.Interfaces.Services; 12 | 13 | /// 14 | /// Represents the related links content app factory. 15 | /// 16 | public class RelatedLinksContentAppFactory : IContentAppFactory 17 | { 18 | private readonly IEntityRelationService entityRelationService; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | public RelatedLinksContentAppFactory(IEntityRelationService entityRelationService) 24 | { 25 | this.entityRelationService = entityRelationService; 26 | } 27 | 28 | /// 29 | public ContentApp GetContentAppFor(object source, IEnumerable userGroups) 30 | { 31 | var contentBase = source as IContentBase; 32 | 33 | if (contentBase == null) 34 | { 35 | return null; 36 | } 37 | 38 | if (contentBase.Id == 0) 39 | { 40 | return null; 41 | } 42 | 43 | var nexuRelationDisplayModels = this.entityRelationService.GetRelationsForItem(contentBase.GetUdi()).ToList(); 44 | 45 | if (!nexuRelationDisplayModels.Any()) 46 | { 47 | return null; 48 | } 49 | 50 | return new ContentApp 51 | { 52 | Alias = "nexuRelatedLinksApp", 53 | Icon = "icon-link", 54 | Name = "Related links", 55 | View = "/App_Plugins/Nexu/views/related-links-app.html", 56 | ViewModel = nexuRelationDisplayModels 57 | }; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/NexuRelationRepository/GetUsedItemsFromList_Tests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Tests.NexuRelationRepository 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | using global::Umbraco.Core; 7 | using global::Umbraco.Core.Persistence; 8 | 9 | using Moq; 10 | 11 | using NPoco; 12 | 13 | using NUnit.Framework; 14 | 15 | using Our.Umbraco.Nexu.Common.Models; 16 | using Our.Umbraco.Nexu.Core.Tests.Repositories; 17 | 18 | [TestFixture] 19 | public class GetUsedItemsFromList_Tests : RepositoryBaseTest 20 | { 21 | [Test] 22 | public void 23 | When_GetGetUsedItemsFromList_Testsm_Called_We_Should_Fetch_All_Items_Where_ChildUdi_Matches_The_Udis_In_TheList() 24 | { 25 | // arrange 26 | var fooUdi = new GuidUdi("foo", Guid.NewGuid()); 27 | var barUdi = new GuidUdi("bar", Guid.NewGuid()); 28 | var listUdis = new List 29 | { 30 | fooUdi, 31 | barUdi 32 | }; 33 | var relations = Mock.Of>(); 34 | 35 | Sql actualSql = null; 36 | 37 | this.UmbracoDatabaseMock.Setup(x => x.Fetch(It.IsAny>())).Callback((Sql sql) => 38 | { 39 | actualSql = sql; 40 | }).Returns(relations); 41 | 42 | 43 | // act 44 | var result = this.Repository.GetUsedItemsFromList(listUdis); 45 | 46 | // assert 47 | Assert.Multiple( 48 | () => 49 | { 50 | Assert.IsNotNull(result); 51 | Assert.That(actualSql.SQL == "WHERE (Nexu_Relations.child_udi IN (@0,@1))"); 52 | Assert.That(actualSql.Arguments[0].ToString() == fooUdi.ToString()); 53 | Assert.That(actualSql.Arguments[1].ToString() == barUdi.ToString()); 54 | }); 55 | 56 | 57 | this.UmbracoDatabaseMock.Verify(x => x.Fetch(It.IsAny>()), Times.Once); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/Helpers/ParserUtilities.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Helpers 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text.RegularExpressions; 6 | 7 | using global::Umbraco.Core; 8 | 9 | /// 10 | /// Represents the parser utilities. This objects has useful methods for helping to parse content 11 | /// 12 | internal static class ParserUtilities 13 | { 14 | /// 15 | /// Gets all the document udi from a text 16 | /// 17 | /// 18 | /// The text. 19 | /// 20 | /// 21 | /// The . 22 | /// 23 | public static IEnumerable GetDocumentUdiFromText(string text) 24 | { 25 | return GetUdiFromText(text, "umb://document/(.{32})"); 26 | } 27 | 28 | /// 29 | /// Gets all the media udi from a text 30 | /// 31 | /// 32 | /// The text. 33 | /// 34 | /// 35 | /// The . 36 | /// 37 | public static IEnumerable GetMediaUdiFromText(string text) 38 | { 39 | return GetUdiFromText(text, "umb://media/(.{32})"); 40 | } 41 | 42 | /// 43 | /// Get all udi from a text that match the regex 44 | /// 45 | /// 46 | /// The text. 47 | /// 48 | /// 49 | /// The regex. 50 | /// 51 | /// 52 | /// The . 53 | /// 54 | private static IEnumerable GetUdiFromText(string text, string regex) 55 | { 56 | var udiList = new List(); 57 | 58 | var udiMatches = Regex.Matches(text, regex); 59 | 60 | foreach (Match match in udiMatches) 61 | { 62 | udiList.Add(new StringUdi(new Uri(match.Value))); 63 | } 64 | 65 | return udiList.DistinctBy(x => x.ToString()); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/controllers/dashboard-controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function DashboardController($scope, $timeout, localizationService, resource) { 5 | var vm = this; 6 | 7 | var idleLabel = ''; 8 | 9 | vm.buttonState = 'init'; 10 | vm.status = { 11 | IsRunning: true, 12 | Status: idleLabel 13 | } 14 | 15 | function getStatus() { 16 | resource.getStatus().then(function (result) { 17 | vm.status.IsRunning = result.IsProcessing; 18 | 19 | if (vm.status.IsRunning) { 20 | localizationService 21 | .localize('nexuDashboard_rebuildProcessingStatusLabelFormat', [result.ItemName, result.ItemsProcessed]) 22 | .then(function(data) { 23 | vm.status.Status = data; 24 | }); 25 | $timeout(function() { getStatus() }, 1000, true); 26 | } else { 27 | vm.status.Status = idleLabel; 28 | } 29 | }); 30 | } 31 | 32 | 33 | function startRebuild() { 34 | vm.buttonState = 'busy'; 35 | resource.startRebuild().then(function () { 36 | vm.buttonState = 'success'; 37 | vm.status.IsRunning = true; 38 | getStatus(); 39 | }, 40 | function () { 41 | vm.buttonState = 'error'; 42 | }); 43 | 44 | }; 45 | 46 | vm.startRebuild = startRebuild; 47 | 48 | function init() { 49 | localizationService.localize('nexuDashboard_rebuildIdleStatusLabel').then(function(data) { 50 | idleLabel = data; 51 | }); 52 | getStatus(); 53 | } 54 | 55 | init(); 56 | 57 | } 58 | 59 | angular.module('umbraco').controller('Our.Umbraco.Nexu.Controllers.DashboardController', 60 | [ 61 | '$scope', 62 | '$timeout', 63 | 'localizationService', 64 | 'Our.Umbraco.Nexu.Resources.RebuildResource', 65 | DashboardController 66 | ]); 67 | 68 | })(); -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/Community/SEOCheckerParser.cs: -------------------------------------------------------------------------------- 1 | using Our.Umbraco.Nexu.Parsers.Helpers; 2 | 3 | namespace Our.Umbraco.Nexu.Parsers.Core 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Xml.Linq; 9 | using global::Umbraco.Core; 10 | 11 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 12 | using Our.Umbraco.Nexu.Common.Models; 13 | 14 | /// 15 | /// Represents content picker parser. 16 | /// 17 | public class SEOCheckerParser : IPropertyValueParser 18 | { 19 | /// 20 | public bool IsParserFor(string propertyEditorAlias) 21 | { 22 | return propertyEditorAlias.Equals("SEOChecker.SEOCheckerSocialPropertyEditor"); 23 | } 24 | 25 | /// 26 | public IEnumerable GetRelatedEntities(string value) 27 | { 28 | if (!string.IsNullOrWhiteSpace(value)) 29 | { 30 | var entities = new List(); 31 | var model = XDocument.Parse(value).Root; 32 | if (model != null) 33 | { 34 | try 35 | { 36 | var socialImage = model.Descendants("socialImage").FirstOrDefault(); 37 | if (socialImage == null) 38 | { 39 | return Enumerable.Empty(); 40 | } 41 | 42 | if (string.IsNullOrEmpty(socialImage.Value)) 43 | { 44 | return Enumerable.Empty(); 45 | } 46 | 47 | foreach (var documentUdi in ParserUtilities.GetMediaUdiFromText(socialImage.Value).ToList()) 48 | { 49 | entities.Add(new RelatedMediaEntity 50 | { 51 | RelatedEntityUdi = documentUdi, 52 | }); 53 | } 54 | 55 | return entities; 56 | } 57 | catch { } 58 | } 59 | } 60 | 61 | return Enumerable.Empty(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers.Tests/Core/ContentPickerParserTests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Tests.Core 2 | { 3 | using System.Linq; 4 | 5 | using global::Umbraco.Core; 6 | 7 | using NUnit.Framework; 8 | 9 | using Our.Umbraco.Nexu.Parsers.Core; 10 | 11 | /// 12 | /// The rich text editor parser tests. 13 | /// 14 | [TestFixture] 15 | public class ContentPickerParserTests 16 | { 17 | [Test] 18 | public void When_EditorAlias_Is_Not_Correct_IsParserFor_Should_Return_False() 19 | { 20 | // arrange 21 | var parser = new ContentPickerParser(); 22 | 23 | // act 24 | var result = parser.IsParserFor(Constants.PropertyEditors.Aliases.Boolean); 25 | 26 | // assert 27 | Assert.IsFalse(result); 28 | } 29 | 30 | [Test] 31 | public void When_EditorAlias_Is_Correct_IsParserFor_Should_Return_True() 32 | { 33 | // arrange 34 | var parser = new ContentPickerParser(); 35 | 36 | // act 37 | var result = parser.IsParserFor(Constants.PropertyEditors.Aliases.ContentPicker); 38 | 39 | // assert 40 | Assert.IsTrue(result); 41 | } 42 | 43 | [Test] 44 | public void When_Value_Is_Not_Set_GetRelatedEntities_Return_Empty_List() 45 | { 46 | // arrange 47 | var parser = new ContentPickerParser(); 48 | 49 | // act 50 | var result = parser.GetRelatedEntities(null); 51 | 52 | // assert 53 | Assert.IsNotNull(result); 54 | Assert.AreEqual(0, result.Count()); 55 | } 56 | 57 | [Test] 58 | public void When_Value_Is_Set_GetRelatedEntities_Return_List_With_Related_Entities() 59 | { 60 | // arrange 61 | var contentUdi = "umb://document/ca4249ed2b234337b52263cabe5587d1"; 62 | 63 | var parser = new ContentPickerParser(); 64 | 65 | // act 66 | var result = parser.GetRelatedEntities(contentUdi).ToList(); 67 | 68 | // assert 69 | Assert.IsNotNull(result); 70 | Assert.AreEqual(1, result.Count); 71 | Assert.IsTrue(result.Exists(x => x.RelatedEntityUdi.ToString() == contentUdi)); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/Services/NexuEntityParsingService/GetParserForPropertyEditor_Tests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Tests.Services.NexuEntityParsingService 2 | { 3 | using System.Collections.Generic; 4 | 5 | using global::Umbraco.Core; 6 | 7 | using Moq; 8 | 9 | using NUnit.Framework; 10 | 11 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 12 | 13 | /// 14 | /// Represents the tests for GetParserForPropertyEditor method on the NexuEntityParsingService 15 | /// 16 | public class GetParserForPropertyEditor_Tests : NexuEntityParsingServiceBaseTest 17 | { 18 | public override List GetParsers() 19 | { 20 | var parsers = new List(); 21 | 22 | var contentPickerParser = new Mock(); 23 | contentPickerParser.Setup(x => x.IsParserFor(Constants.PropertyEditors.Aliases.ContentPicker)) 24 | .Returns(true); 25 | 26 | parsers.Add(contentPickerParser.Object); 27 | 28 | var rteParser = new Mock(); 29 | rteParser.Setup(x => x.IsParserFor(Constants.PropertyEditors.Aliases.TinyMce)) 30 | .Returns(true); 31 | 32 | parsers.Add(rteParser.Object); 33 | 34 | return parsers; 35 | } 36 | 37 | 38 | [Test] 39 | [TestCase(Constants.PropertyEditors.Aliases.ContentPicker)] 40 | [TestCase(Constants.PropertyEditors.Aliases.TinyMce)] 41 | public void When_Parser_Exists_In_Collection_GetParserForPropertyEditor_Should_Return_It(string editorAlias) 42 | { 43 | // act 44 | var parser = this.Service.GetParserForPropertyEditor(editorAlias); 45 | 46 | // assert 47 | Assert.IsNotNull(parser); 48 | Assert.IsTrue(parser.IsParserFor(editorAlias)); 49 | 50 | } 51 | 52 | [Test] 53 | public void When_Parser_Does_Not_Exists_In_Collection_GetParserForPropertyEditor_Should_Return_Null() 54 | { 55 | // arrange 56 | var editorAlias = Constants.PropertyEditors.Aliases.MediaPicker; 57 | 58 | // act 59 | var parser = this.Service.GetParserForPropertyEditor(editorAlias); 60 | 61 | // assert 62 | Assert.IsNull(parser); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/views/relation-list-component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 |

5 |
6 | 7 |
8 |
9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 |
27 | 28 |
29 | 30 |
31 |
32 | {{item.name}} 33 |
34 |
35 | {{item.propertyname}} 36 |
37 |
38 | {{item.tabname}} 39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 | 62 |
-------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/Composing/Components/ServerVariablesComponent.cs: -------------------------------------------------------------------------------- 1 | using Our.Umbraco.Nexu.Common; 2 | 3 | namespace Our.Umbraco.Nexu.Web.Composing.Components 4 | { 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Web; 8 | using System.Web.Mvc; 9 | using System.Web.Routing; 10 | 11 | using global::Umbraco.Core.Composing; 12 | using global::Umbraco.Web; 13 | using global::Umbraco.Web.JavaScript; 14 | 15 | using Our.Umbraco.Nexu.Web.Api; 16 | 17 | /// 18 | /// Represents the component for registering server variables 19 | /// 20 | internal class ServerVariablesComponent : IComponent 21 | { 22 | /// 23 | public void Initialize() 24 | { 25 | ServerVariablesParser.Parsing += this.ServerVariablesParserParsing; 26 | } 27 | 28 | /// 29 | public void Terminate() 30 | { 31 | ServerVariablesParser.Parsing -= this.ServerVariablesParserParsing; 32 | } 33 | 34 | /// 35 | /// Event handler for the ServerVariablesParsing parsing event 36 | /// 37 | /// 38 | /// The sender. 39 | /// 40 | /// 41 | /// The e. 42 | /// 43 | private void ServerVariablesParserParsing(object sender, Dictionary e) 44 | { 45 | if (HttpContext.Current == null) 46 | { 47 | return; 48 | } 49 | 50 | var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); 51 | 52 | var urlDictionairy = new Dictionary(); 53 | 54 | urlDictionairy.Add("RebuildApi", urlHelper.GetUmbracoApiServiceBaseUrl(c => c.GetRebuildStatus())); 55 | urlDictionairy.Add("RelationCheckApi", urlHelper.GetUmbracoApiServiceBaseUrl(c => c.GetIncomingLinks(null))); 56 | urlDictionairy.Add("PreventDelete", NexuContext.Current.PreventDelete); 57 | urlDictionairy.Add("PreventUnPublish", NexuContext.Current.PreventUnPublish); 58 | 59 | if (!e.Keys.Contains("Nexu")) 60 | { 61 | e.Add("Nexu", urlDictionairy); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/controllers/listview-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('umbraco').controller('Our.Umbraco.Nexu.ListViewController', 2 | ['$scope', '$controller', 'localizationService', 'overlayService', 'appState', 'Our.Umbraco.Nexu.Resources.RelationCheckResource', 3 | function ($scope, $controller, localizationService, overlayService, appState, relationCheckResource) { 4 | // inherit core delete controller 5 | angular.extend(this, $controller('Umbraco.PropertyEditors.ListViewController', { $scope: $scope })); 6 | 7 | var oldDelete = $scope.delete; 8 | 9 | var oldUnpublish = $scope.unpublish; 10 | 11 | var section = appState.getSectionState("currentSection"); 12 | 13 | var dialog = { 14 | view: '/App_Plugins/Nexu/views/listview-dialog.html', 15 | close: function () { 16 | overlayService.close(); 17 | } 18 | }; 19 | 20 | $scope.delete = function () { 21 | 22 | if (section === 'content' || section === 'media') { 23 | openDialog('general_delete', 'nexu_listviewDeleteIntro', oldDelete); 24 | } else { 25 | oldDelete(); 26 | } 27 | 28 | }; 29 | 30 | $scope.unpublish = function () { 31 | 32 | if (section === 'content' || section === 'media') { 33 | openDialog('content_unpublish', 'nexu_listviewUnpublishIntro', oldUnpublish); 34 | } else { 35 | oldUnpublish(); 36 | } 37 | } 38 | 39 | function openDialog(titleKey, introKey, callBack) { 40 | relationCheckResource.checkLinkedItems($scope.selection, section === 'media').then(function (data) { 41 | if (data.length > 0) { 42 | localizationService.localizeMany([titleKey, introKey]).then(value => { 43 | dialog.title = value[0]; 44 | dialog.intro = value[1]; 45 | dialog.items = data; 46 | overlayService.open(dialog); 47 | }); 48 | } else { 49 | callBack(); 50 | } 51 | }); 52 | } 53 | 54 | }]); -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core/Composing/Components/MigrationComponent.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Composing.Components 2 | { 3 | using global::Umbraco.Core.Composing; 4 | using global::Umbraco.Core.Logging; 5 | using global::Umbraco.Core.Migrations; 6 | using global::Umbraco.Core.Migrations.Upgrade; 7 | using global::Umbraco.Core.Scoping; 8 | using global::Umbraco.Core.Services; 9 | 10 | using Our.Umbraco.Nexu.Core.Migrations; 11 | 12 | /// 13 | /// Represents the migration component. 14 | /// 15 | internal class MigrationComponent : IComponent 16 | { 17 | /// 18 | /// The scope provider. 19 | /// 20 | private readonly IScopeProvider scopeProvider; 21 | 22 | /// 23 | /// The migration builder. 24 | /// 25 | private readonly IMigrationBuilder migrationBuilder; 26 | 27 | /// 28 | /// The key value service. 29 | /// 30 | private readonly IKeyValueService keyValueService; 31 | 32 | /// 33 | /// The logger. 34 | /// 35 | private readonly ILogger logger; 36 | 37 | /// 38 | /// Initializes a new instance of the class. 39 | /// 40 | /// 41 | /// The scope provider. 42 | /// 43 | /// 44 | /// The migration builder. 45 | /// 46 | /// 47 | /// The key value service. 48 | /// 49 | /// 50 | /// The logger. 51 | /// 52 | public MigrationComponent(IScopeProvider scopeProvider, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, ILogger logger) 53 | { 54 | this.scopeProvider = scopeProvider; 55 | this.migrationBuilder = migrationBuilder; 56 | this.keyValueService = keyValueService; 57 | this.logger = logger; 58 | } 59 | 60 | /// 61 | public void Initialize() 62 | { 63 | var upgrader = new Upgrader(new NexuMigrationPlan()); 64 | 65 | upgrader.Execute(this.scopeProvider, this.migrationBuilder, this.keyValueService, this.logger); 66 | } 67 | 68 | /// 69 | public void Terminate() 70 | { 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/Models/NexuRelation.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Common.Models 2 | { 3 | using System; 4 | 5 | using global::Umbraco.Core; 6 | using global::Umbraco.Core.Persistence.DatabaseAnnotations; 7 | 8 | using NPoco; 9 | 10 | using Our.Umbraco.Nexu.Common.Constants; 11 | 12 | /// 13 | /// Represents a nexu relation 14 | /// 15 | [TableName(DatabaseConstants.TableName)] 16 | [PrimaryKey(DatabaseConstants.IdColumn, AutoIncrement = false)] 17 | public class NexuRelation 18 | { 19 | private Udi udi; 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | public NexuRelation() 25 | { 26 | this.Id = Guid.NewGuid(); 27 | } 28 | 29 | /// 30 | /// Gets or sets the id. 31 | /// 32 | [Column(DatabaseConstants.IdColumn)] 33 | [PrimaryKeyColumn(AutoIncrement = false, Clustered = false, Name = DatabaseConstants.PrimaryKey)] 34 | public Guid Id { get; set; } 35 | 36 | /// 37 | /// Gets or sets the parent udi 38 | /// 39 | [Column(DatabaseConstants.ParentUdiColumn)] 40 | public string ParentUdi { get; set; } 41 | 42 | /// 43 | /// Gets or sets the child udi 44 | /// 45 | [Column(DatabaseConstants.ChildUdiColumn)] 46 | public string ChildUdi { get; set; } 47 | 48 | /// 49 | /// Gets or sets the relation type. 50 | /// 51 | [Column(DatabaseConstants.RelationTypeColumn)] 52 | public Guid RelationType { get; set; } 53 | 54 | /// 55 | /// Gets or sets the property id. 56 | /// 57 | [Column(DatabaseConstants.PropertyAlias)] 58 | public string PropertyAlias { get; set; } 59 | 60 | /// 61 | /// Gets or sets the culture. 62 | /// 63 | [Column(DatabaseConstants.CultureColumn)] 64 | public string Culture { get; set; } 65 | 66 | /// 67 | /// Gets the udi. 68 | /// 69 | [Ignore] 70 | public Udi Udi 71 | { 72 | get 73 | { 74 | if (this.udi != null) 75 | { 76 | return this.udi; 77 | } 78 | 79 | this.udi = new GuidUdi("nexurelation", this.Id); 80 | 81 | return this.udi; 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/controllers/listview-dialog-controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function ListViewDialogController($scope, languageResource, editorService, overlayService) { 5 | var vm = this; 6 | 7 | vm.languages = []; 8 | vm.relations = $scope.model.items; 9 | vm.pageSize = 5; 10 | vm.pagedListItems = []; 11 | vm.currentPage = 1; 12 | vm.totalPages = 1; 13 | vm.intro = $scope.model.intro; 14 | 15 | vm.openContent = function (item) { 16 | var editor = { 17 | id: item.Id, 18 | nexuCulture: item.Culture, 19 | submit: function (model) { 20 | editorService.close(); 21 | }, 22 | close: function () { 23 | editorService.close(); 24 | } 25 | }; 26 | 27 | overlayService.close(); 28 | editorService.contentEditor(editor); 29 | } 30 | 31 | vm.getLanguageLabel = function (culture) { 32 | 33 | if (vm.languages.length > 0) { 34 | 35 | var lang = _.find(vm.languages, 36 | function (l) { 37 | return l.culture.toLowerCase() === culture.toLowerCase(); 38 | }); 39 | 40 | if (lang) { 41 | return lang.name; 42 | } 43 | } 44 | 45 | return culture; 46 | } 47 | 48 | vm.nextPage = function () { 49 | vm.goToPage(vm.currentPage + 1); 50 | }; 51 | 52 | vm.prevPage = function () { 53 | vm.goToPage(vm.currentPage - 1); 54 | }; 55 | vm.goToPage = function (pageNumber) { 56 | vm.currentPage = pageNumber; 57 | setPagedList(); 58 | }; 59 | 60 | 61 | function setPagedList() { 62 | vm.pagedListItems = vm.relations.slice((vm.currentPage - 1) * vm.pageSize, vm.currentPage * vm.pageSize); 63 | 64 | } 65 | 66 | function init() { 67 | languageResource.getAll().then(function (data) { 68 | vm.languages = data; 69 | vm.totalPages = Math.ceil(vm.relations.length / vm.pageSize); 70 | setPagedList(); 71 | }); 72 | } 73 | 74 | init(); 75 | } 76 | 77 | angular.module('umbraco').controller('Our.Umbraco.Nexu.Controllers.ListViewDialogController', 78 | [ 79 | '$scope', 80 | 'languageResource', 81 | 'editorService', 82 | 'overlayService', 83 | ListViewDialogController 84 | ]); 85 | 86 | })(); -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers.Tests/Community/SEOCheckerParserTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Our.Umbraco.Nexu.Parsers.Tests.Community 8 | { 9 | using System.Linq; 10 | 11 | using global::Umbraco.Core; 12 | 13 | using NUnit.Framework; 14 | 15 | using Our.Umbraco.Nexu.Common.Constants; 16 | using Our.Umbraco.Nexu.Parsers.Core; 17 | 18 | [TestFixture] 19 | public class SEOCheckerParserTests 20 | { 21 | [Test] 22 | public void When_EditorAlias_Is_Not_Correct_IsParserFor_Should_Return_False() 23 | { 24 | // arrange 25 | var parser = new SEOCheckerParser(); 26 | 27 | // act 28 | var result = parser.IsParserFor(Constants.PropertyEditors.Aliases.Boolean); 29 | 30 | // assert 31 | Assert.IsFalse(result); 32 | } 33 | 34 | [Test] 35 | public void When_EditorAlias_Is_Correct_IsParserFor_Should_Return_True() 36 | { 37 | // arrange 38 | var parser = new SEOCheckerParser(); 39 | 40 | // act 41 | var result = parser.IsParserFor("SEOChecker.SEOCheckerSocialPropertyEditor"); 42 | 43 | // assert 44 | Assert.IsTrue(result); 45 | } 46 | 47 | [Test] 48 | public void When_Value_Is_Not_Set_GetRelatedEntities_Return_Empty_List() 49 | { 50 | // arrange 51 | var parser = new SEOCheckerParser(); 52 | 53 | // act 54 | var result = parser.GetRelatedEntities(null).ToList(); 55 | 56 | // assert 57 | Assert.IsNotNull(result); 58 | Assert.That(result.Count == 0); 59 | } 60 | 61 | [Test] 62 | public void When_Value_Is_Set_GetRelatedEntities_Return_List_With_Related_Entities() 63 | { 64 | // arrange 65 | var seoCheckerXML = 66 | "umb://media/6f2d6d1d13a8438789b3cf0cced47344 "; 67 | 68 | var parser = new SEOCheckerParser(); 69 | 70 | // act 71 | var result = parser.GetRelatedEntities(seoCheckerXML).ToList(); 72 | 73 | // assert 74 | Assert.IsNotNull(result); 75 | Assert.That(result.Count == 1); 76 | Assert.That(result.Count(x => x.RelationType == RelationTypes.DocumentToMedia) == 1); 77 | 78 | Assert.That(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://media/6f2d6d1d13a8438789b3cf0cced47344")); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers.Tests/Core/MediaPickerParserTests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Tests.Core 2 | { 3 | using System.Linq; 4 | 5 | using global::Umbraco.Core; 6 | 7 | using NUnit.Framework; 8 | 9 | using Our.Umbraco.Nexu.Common.Constants; 10 | using Our.Umbraco.Nexu.Parsers.Core; 11 | 12 | [TestFixture] 13 | public class MediaPickerParserTests 14 | { 15 | [Test] 16 | public void When_EditorAlias_Is_Not_Correct_IsParserFor_Should_Return_False() 17 | { 18 | // arrange 19 | var parser = new MediaPickerParser(); 20 | 21 | // act 22 | var result = parser.IsParserFor(Constants.PropertyEditors.Aliases.Boolean); 23 | 24 | // assert 25 | Assert.IsFalse(result); 26 | } 27 | 28 | [Test] 29 | public void When_EditorAlias_Is_Correct_IsParserFor_Should_Return_True() 30 | { 31 | // arrange 32 | var parser = new MediaPickerParser(); 33 | 34 | // act 35 | var result = parser.IsParserFor(Constants.PropertyEditors.Aliases.MediaPicker); 36 | 37 | // assert 38 | Assert.IsTrue(result); 39 | } 40 | 41 | [Test] 42 | public void When_Value_Is_Not_Set_GetRelatedEntities_Return_Empty_List() 43 | { 44 | // arrange 45 | var parser = new MediaPickerParser(); 46 | 47 | // act 48 | var result = parser.GetRelatedEntities(null).ToList(); 49 | 50 | // assert 51 | Assert.IsNotNull(result); 52 | Assert.That(result.Count == 0); 53 | } 54 | 55 | [Test] 56 | public void When_Value_Is_Set_GetRelatedEntities_Return_List_With_Related_Entities() 57 | { 58 | // arrange 59 | var contentUdi = 60 | "umb://media/ca4249ed2b234337b52263cabe5587d1,umb://media/ca4249ed2b234337b52263cabe5587d2,umb://media/ca4249ed2b234337b52263cabe5587d8"; 61 | 62 | var parser = new MediaPickerParser(); 63 | 64 | // act 65 | var result = parser.GetRelatedEntities(contentUdi).ToList(); 66 | 67 | // assert 68 | Assert.IsNotNull(result); 69 | Assert.That(result.Count == 3); 70 | Assert.That(result.Count(x => x.RelationType == RelationTypes.DocumentToMedia) == 3); 71 | 72 | Assert.That(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://media/ca4249ed2b234337b52263cabe5587d1")); 73 | Assert.That(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://media/ca4249ed2b234337b52263cabe5587d2")); 74 | Assert.That(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://media/ca4249ed2b234337b52263cabe5587d8")); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers.Tests/Core/MultiNodeTreePickerParserTests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Tests.Core 2 | { 3 | using System.Linq; 4 | 5 | using global::Umbraco.Core; 6 | 7 | using NUnit.Framework; 8 | 9 | using Our.Umbraco.Nexu.Common.Constants; 10 | using Our.Umbraco.Nexu.Parsers.Core; 11 | 12 | [TestFixture] 13 | public class MultiNodeTreePickerParserTests 14 | { 15 | [Test] 16 | public void When_EditorAlias_Is_Not_Correct_IsParserFor_Should_Return_False() 17 | { 18 | // arrange 19 | var parser = new MultiNodeTreePickerParser(); 20 | 21 | // act 22 | var result = parser.IsParserFor(Constants.PropertyEditors.Aliases.Boolean); 23 | 24 | // assert 25 | Assert.IsFalse(result); 26 | } 27 | 28 | [Test] 29 | public void When_EditorAlias_Is_Correct_IsParserFor_Should_Return_True() 30 | { 31 | // arrange 32 | var parser = new MultiNodeTreePickerParser(); 33 | 34 | // act 35 | var result = parser.IsParserFor(Constants.PropertyEditors.Aliases.MultiNodeTreePicker); 36 | 37 | // assert 38 | Assert.IsTrue(result); 39 | } 40 | 41 | [Test] 42 | public void When_Value_Is_Not_Set_GetRelatedEntities_Return_Empty_List() 43 | { 44 | // arrange 45 | var parser = new MultiNodeTreePickerParser(); 46 | 47 | // act 48 | var result = parser.GetRelatedEntities(null).ToList(); 49 | 50 | // assert 51 | Assert.IsNotNull(result); 52 | Assert.That(result.Count == 0); 53 | } 54 | 55 | [Test] 56 | public void When_Value_Is_Set_GetRelatedEntities_Return_List_With_Related_Entities() 57 | { 58 | // arrange 59 | var contentUdi = 60 | "umb://document/ca4249ed2b234337b52263cabe5587d1,umb://document/ca4249ed2b234337b52263cabe5587d2,umb://media/ca4249ed2b234337b52263cabe5587d1"; 61 | 62 | var parser = new MultiNodeTreePickerParser(); 63 | 64 | // act 65 | var result = parser.GetRelatedEntities(contentUdi).ToList(); 66 | 67 | // assert 68 | Assert.IsNotNull(result); 69 | Assert.That(result.Count == 3); 70 | Assert.That(result.Count(x => x.RelationType == RelationTypes.DocumentToDocument) == 2); 71 | Assert.That(result.Count(x => x.RelationType == RelationTypes.DocumentToMedia) == 1); 72 | 73 | Assert.That(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://document/ca4249ed2b234337b52263cabe5587d1" && x.RelationType == RelationTypes.DocumentToDocument)); 74 | Assert.That(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://document/ca4249ed2b234337b52263cabe5587d2" && x.RelationType == RelationTypes.DocumentToDocument)); 75 | Assert.That(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://media/ca4249ed2b234337b52263cabe5587d1" && x.RelationType == RelationTypes.DocumentToMedia)); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/Services/NexuEntityParsingService/GetRelatedEntitiesFromContent_Tests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Tests.Services.NexuEntityParsingService 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | using global::Umbraco.Core; 8 | using global::Umbraco.Core.Models; 9 | 10 | using Moq; 11 | 12 | using NUnit.Framework; 13 | 14 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 15 | using Our.Umbraco.Nexu.Common.Models; 16 | 17 | /// 18 | /// Represents the tests for GetRelatedEntitiesFromContent method on the NexuEntityParsingService 19 | /// 20 | [TestFixture] 21 | public class GetRelatedEntitiesFromContent_Tests : NexuEntityParsingServiceBaseTest 22 | { 23 | [Test] 24 | public void When_GetRelatedEntitiesFromContent_Is_Called_It_Should_Get_RelatedEntities_For_Each_Property() 25 | { 26 | // arrange 27 | var contentKey = Guid.NewGuid(); 28 | 29 | var content = Mock.Of(); 30 | content.Blueprint = false; 31 | content.Key = contentKey; 32 | 33 | var editorAlias = "alias"; 34 | 35 | var propertyType = 36 | new PropertyType(editorAlias, ValueStorageType.Ntext) 37 | { 38 | Variations = ContentVariation.Culture 39 | }; 40 | 41 | var property1 = new Property(propertyType); 42 | property1.Id = 123; 43 | 44 | var property2 = new Property(propertyType); 45 | property2.Id = 456; 46 | 47 | content.Properties = new PropertyCollection(new[] { property1, property2 }); 48 | 49 | var entityGuid = Guid.NewGuid(); 50 | 51 | var prop1Entities = new Dictionary>(); 52 | prop1Entities.Add("nl", new List { new RelatedDocumentEntity { RelatedEntityUdi = Udi.Create("document", entityGuid )}}); 53 | 54 | var prop2Entities = new Dictionary>(); 55 | prop2Entities.Add("nl", new List { new RelatedDocumentEntity { RelatedEntityUdi = Udi.Create("document", entityGuid) } }); 56 | prop2Entities.Add("en", new List { new RelatedDocumentEntity { RelatedEntityUdi = Udi.Create("document", entityGuid) } }); 57 | 58 | Mock.Get(this.Service).Setup(x => x.GetRelatedEntitiesFromProperty(property1)).Returns(prop1Entities); 59 | Mock.Get(this.Service).Setup(x => x.GetRelatedEntitiesFromProperty(property2)).Returns(prop2Entities); 60 | 61 | // act 62 | var result = this.Service.GetRelatedEntitiesFromContent(content).ToList(); 63 | 64 | // assert 65 | Assert.IsNotNull(result); 66 | 67 | Assert.That(result.Count == 3); 68 | 69 | Mock.Get(this.Service).Verify(x => x.GetRelatedEntitiesFromProperty(property1), Times.Once); 70 | Mock.Get(this.Service).Verify(x => x.GetRelatedEntitiesFromProperty(property2), Times.Once); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers.Tests/Core/MultiUrlPickerParserTests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Tests.Core 2 | { 3 | using System.Linq; 4 | 5 | using global::Umbraco.Core; 6 | 7 | using NUnit.Framework; 8 | 9 | using Our.Umbraco.Nexu.Common.Constants; 10 | using Our.Umbraco.Nexu.Parsers.Core; 11 | 12 | [TestFixture] 13 | public class MultiUrlPickerParserTests 14 | { 15 | [Test] 16 | public void When_EditorAlias_Is_Not_Correct_IsParserFor_Should_Return_False() 17 | { 18 | // arrange 19 | var parser = new MultiUrlPickerParser(); 20 | 21 | // act 22 | var result = parser.IsParserFor(Constants.PropertyEditors.Aliases.Boolean); 23 | 24 | // assert 25 | Assert.IsFalse(result); 26 | } 27 | 28 | [Test] 29 | public void When_EditorAlias_Is_Correct_IsParserFor_Should_Return_True() 30 | { 31 | // arrange 32 | var parser = new MultiUrlPickerParser(); 33 | 34 | // act 35 | var result = parser.IsParserFor(Constants.PropertyEditors.Aliases.MultiUrlPicker); 36 | 37 | // assert 38 | Assert.IsTrue(result); 39 | } 40 | 41 | [Test] 42 | public void When_Value_Is_Not_Set_GetRelatedEntities_Return_Empty_List() 43 | { 44 | // arrange 45 | var parser = new MultiUrlPickerParser(); 46 | 47 | // act 48 | var result = parser.GetRelatedEntities(null).ToList(); 49 | 50 | // assert 51 | Assert.IsNotNull(result); 52 | Assert.That(result.Count == 0); 53 | } 54 | 55 | [Test] 56 | public void When_Value_Is_Set_GetRelatedEntities_Return_List_With_Related_Entities() 57 | { 58 | // arrange 59 | var contentUdi = @"[{""name"":""Home"",""udi"":""umb://document/ca4249ed2b234337b52263cabe5587d1""},{""name"":""About Us"",""udi"":""umb://document/3cce2545e3ac44ecbf55a52cc5965db3""},{""name"":""Umbraco Campari Meeting Room"",""udi"":""umb://media/662af6ca411a4c93a6c722c4845698e7""}]"; 60 | 61 | var parser = new MultiUrlPickerParser(); 62 | 63 | // act 64 | var result = parser.GetRelatedEntities(contentUdi).ToList(); 65 | 66 | // assert 67 | Assert.IsNotNull(result); 68 | Assert.That(result.Count == 3); 69 | Assert.That(result.Count(x => x.RelationType == RelationTypes.DocumentToDocument) == 2); 70 | Assert.That(result.Count(x => x.RelationType == RelationTypes.DocumentToMedia) == 1); 71 | 72 | Assert.That(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://document/ca4249ed2b234337b52263cabe5587d1" && x.RelationType == RelationTypes.DocumentToDocument)); 73 | Assert.That(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://document/3cce2545e3ac44ecbf55a52cc5965db3" && x.RelationType == RelationTypes.DocumentToDocument)); 74 | Assert.That(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://media/662af6ca411a4c93a6c722c4845698e7" && x.RelationType == RelationTypes.DocumentToMedia)); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/Api/RelationCheckApiController.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Web.Api 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Net; 6 | using System.Net.Http; 7 | using System.Web.Http; 8 | 9 | using global::Umbraco.Core; 10 | using global::Umbraco.Web.Editors; 11 | 12 | using Our.Umbraco.Nexu.Common.Interfaces.Services; 13 | 14 | /// 15 | /// Represents the relation check api controller 16 | /// 17 | public class RelationCheckApiController : UmbracoAuthorizedJsonController 18 | { 19 | /// 20 | /// The entity relation service. 21 | /// 22 | private readonly IEntityRelationService entityRelationService; 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// 28 | /// The entity relation service. 29 | /// 30 | public RelationCheckApiController(IEntityRelationService entityRelationService) 31 | { 32 | this.entityRelationService = entityRelationService; 33 | } 34 | 35 | /// 36 | /// Gets the incoming links 37 | /// 38 | /// 39 | /// The udi. 40 | /// 41 | /// 42 | /// The . 43 | /// 44 | [HttpGet] 45 | public HttpResponseMessage GetIncomingLinks(string udi) 46 | { 47 | var relations = this.entityRelationService.GetRelationsForItem(new GuidUdi(new Uri(udi))); 48 | 49 | return this.Request.CreateResponse(HttpStatusCode.OK, relations); 50 | } 51 | 52 | /// 53 | /// Checks a list of udi's for linked items 54 | /// 55 | /// 56 | /// The udis. 57 | /// 58 | /// 59 | /// The . 60 | /// 61 | [HttpPost] 62 | public HttpResponseMessage CheckLinkedItems([FromBody] string[] udis) 63 | { 64 | var listUdis = new List(); 65 | 66 | foreach (var stringUdi in udis) 67 | { 68 | GuidUdi guidUdi; 69 | 70 | if (GuidUdi.TryParse(stringUdi, out guidUdi)) 71 | { 72 | listUdis.Add(guidUdi); 73 | } 74 | } 75 | 76 | return this.Request.CreateResponse(HttpStatusCode.OK, this.entityRelationService.GetUsedItemsFromList(listUdis)); 77 | } 78 | 79 | [HttpGet] 80 | public HttpResponseMessage CheckDescendants(string udi) 81 | { 82 | var result = false; 83 | GuidUdi guidUdi; 84 | 85 | if (GuidUdi.TryParse(udi, out guidUdi)) 86 | { 87 | result = this.entityRelationService.CheckLinksInDescendants(guidUdi); 88 | } 89 | 90 | return this.Request.CreateResponse(HttpStatusCode.OK, result); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/views/unpublish.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 | 10 |
11 | 12 |
13 | 14 | 15 |
16 |

17 |
18 | 19 | 20 |
21 |
22 |

23 |
24 | 25 |
26 | 27 |
28 | 29 |
30 | 31 | 37 | 38 |
39 | 47 |
48 |
49 | 50 |
51 |
52 |
53 |
54 | 55 |
56 |
57 |

58 |
59 | 60 |
61 |
62 |
63 | {{ variant.language.name }} 64 | * 65 |
66 | 67 |
68 | 69 |
70 |
71 |
72 |
73 | 74 |
75 | 76 |
77 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core/Repositories/NexuRelationRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Repositories 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | using global::Umbraco.Core; 7 | using global::Umbraco.Core.Persistence; 8 | using global::Umbraco.Core.Scoping; 9 | 10 | using NPoco; 11 | 12 | using Our.Umbraco.Nexu.Common.Interfaces.Repositories; 13 | using Our.Umbraco.Nexu.Common.Models; 14 | 15 | /// 16 | /// Represents nexu relation repository. 17 | /// 18 | internal class NexuRelationRepository : IRelationRepository 19 | { 20 | /// 21 | /// The scope provider. 22 | /// 23 | private readonly IScopeProvider scopeProvider; 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// 29 | /// The scope provider. 30 | /// 31 | public NexuRelationRepository(IScopeProvider scopeProvider) 32 | { 33 | this.scopeProvider = scopeProvider; 34 | } 35 | 36 | /// 37 | public void PersistRelationsForContentItem(Udi contentItemUdi, IEnumerable relations) 38 | { 39 | using (var scope = this.scopeProvider.CreateScope(autoComplete:true)) 40 | { 41 | using (var transaction = scope.Database.GetTransaction()) 42 | { 43 | var db = scope.Database; 44 | 45 | var udiString = contentItemUdi.ToString(); 46 | 47 | var deleteSql = new Sql(scope.SqlContext); 48 | deleteSql.Where(x => x.ParentUdi == udiString); 49 | 50 | db.Delete(deleteSql); 51 | 52 | db.BulkInsertRecords(relations); 53 | 54 | transaction.Complete(); 55 | } 56 | } 57 | } 58 | 59 | /// 60 | public IEnumerable GetIncomingRelationsForItem(Udi udi) 61 | { 62 | using (var scope = this.scopeProvider.CreateScope(autoComplete: true)) 63 | { 64 | var db = scope.Database; 65 | 66 | var udiString = udi.ToString(); 67 | 68 | var sql = new Sql(scope.SqlContext); 69 | sql.Where(x => x.ChildUdi == udiString); 70 | 71 | return db.Fetch(sql); 72 | } 73 | } 74 | 75 | /// 76 | public IList> GetUsedItemsFromList(IList udis) 77 | { 78 | var usedUdis = new List>(); 79 | 80 | using (var scope = this.scopeProvider.CreateScope(autoComplete: true)) 81 | { 82 | var db = scope.Database; 83 | 84 | var udiStrings = udis.Select(x => x.ToString()).ToList(); 85 | 86 | var sql = new Sql(scope.SqlContext); 87 | sql.Where(x => udiStrings.Contains(x.ChildUdi)); 88 | 89 | var relations = db.Fetch(sql).ToList(); 90 | 91 | foreach (var rel in relations) 92 | { 93 | usedUdis.Add(new KeyValuePair(rel.ChildUdi, rel.Culture)); 94 | } 95 | } 96 | 97 | return usedUdis; 98 | 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers.Tests/Core/NestedContentParserTests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Tests.Core 2 | { 3 | using System.Linq; 4 | 5 | using global::Umbraco.Core; 6 | 7 | using NUnit.Framework; 8 | 9 | using Our.Umbraco.Nexu.Common.Constants; 10 | using Our.Umbraco.Nexu.Parsers.Core; 11 | 12 | [TestFixture] 13 | public class NestedContentParserTests 14 | { 15 | [Test] 16 | public void When_EditorAlias_Is_Not_Correct_IsParserFor_Should_Return_False() 17 | { 18 | // arrange 19 | var parser = new NestedContentParser(); 20 | 21 | // act 22 | var result = parser.IsParserFor(Constants.PropertyEditors.Aliases.Boolean); 23 | 24 | // assert 25 | Assert.IsFalse(result); 26 | } 27 | 28 | [Test] 29 | public void When_EditorAlias_Is_Correct_IsParserFor_Should_Return_True() 30 | { 31 | // arrange 32 | var parser = new NestedContentParser(); 33 | 34 | // act 35 | var result = parser.IsParserFor(Constants.PropertyEditors.Aliases.NestedContent); 36 | 37 | // assert 38 | Assert.IsTrue(result); 39 | } 40 | 41 | [Test] 42 | public void When_Value_Is_Not_Set_GetRelatedEntities_Return_Empty_List() 43 | { 44 | // arrange 45 | var parser = new NestedContentParser(); 46 | 47 | // act 48 | var result = parser.GetRelatedEntities(null).ToList(); 49 | 50 | // assert 51 | Assert.IsNotNull(result); 52 | Assert.That(result.Count == 0); 53 | } 54 | 55 | [Test] 56 | public void When_Value_Is_Set_GetRelatedEntities_Return_List_With_Related_Entities() 57 | { 58 | // arrange 59 | var contentUdi = @"[[ 60 | { 61 | ""key"":""69d6cf63-f0e5-463d-9441-e1b4ad6fb25e"", 62 | ""name"":""Item 1"", 63 | ""ncContentTypeAlias"":""nestedItem"", 64 | ""text"":""

asdfasfa as asd asd as 

"", 65 | ""callToAction"":""[{\""name\"":\""Products\"",\""udi\"":\""umb://document/ec4aafcc0c254f25a8fe705bfae1d324\""}]"" 66 | }, 67 | { 68 | ""key"":""ac61e2d2-d8e4-418d-aa91-99ead3df3f8d"", 69 | ""name"":""Item 2"", 70 | ""ncContentTypeAlias"":""nestedItem"", 71 | ""text"":""

asdfasdf a as 

"", 72 | ""callToAction"":""[{\""name\"":\""Umbraco Campari Meeting Room\"",\""udi\"":\""umb://media/662af6ca411a4c93a6c722c4845698e7\""}]"" 73 | } 74 | ]]"; 75 | 76 | var parser = new NestedContentParser(); 77 | 78 | // act 79 | var result = parser.GetRelatedEntities(contentUdi).ToList(); 80 | 81 | // assert 82 | Assert.IsNotNull(result); 83 | Assert.That(result.Count == 3); 84 | Assert.That(result.Count(x => x.RelationType == RelationTypes.DocumentToDocument) == 2); 85 | Assert.That(result.Count(x => x.RelationType == RelationTypes.DocumentToMedia) == 1); 86 | 87 | Assert.That(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://document/3cce2545e3ac44ecbf55a52cc5965db3" && x.RelationType == RelationTypes.DocumentToDocument)); 88 | Assert.That(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://document/ec4aafcc0c254f25a8fe705bfae1d324" && x.RelationType == RelationTypes.DocumentToDocument)); 89 | Assert.That(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://media/662af6ca411a4c93a6c722c4845698e7" && x.RelationType == RelationTypes.DocumentToMedia)); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/NexuRelationRepository/RepositoryBaseTest.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Tests.Repositories 2 | { 3 | using System; 4 | using System.Data; 5 | 6 | using global::Umbraco.Core.Persistence; 7 | using global::Umbraco.Core.Persistence.Mappers; 8 | using global::Umbraco.Core.Persistence.SqlSyntax; 9 | using global::Umbraco.Core.Scoping; 10 | 11 | using Moq; 12 | 13 | using NPoco; 14 | 15 | using NUnit.Framework; 16 | 17 | using Our.Umbraco.Nexu.Common.Models; 18 | using Our.Umbraco.Nexu.Core.Repositories; 19 | 20 | /// 21 | /// Represents a base class for repository tests 22 | /// 23 | public abstract class RepositoryBaseTest 24 | { 25 | /// 26 | /// Gets or sets the scope provider mock. 27 | /// 28 | internal Mock ScopeProviderMock { get; set; } 29 | 30 | /// 31 | /// Gets or sets the umbraco database mock. 32 | /// 33 | internal Mock UmbracoDatabaseMock { get; set; } 34 | 35 | /// 36 | /// Gets or sets the scope mock. 37 | /// 38 | internal Mock ScopeMock { get; set; } 39 | 40 | /// 41 | /// Gets or sets the transaction mock. 42 | /// 43 | internal Mock TransactionMock { get; set; } 44 | 45 | /// 46 | /// Gets or sets the repository. 47 | /// 48 | internal NexuRelationRepository Repository { get; set; } 49 | 50 | /// 51 | /// Sets up mocks used in derived tests 52 | /// 53 | [SetUp] 54 | public virtual void Setup() 55 | { 56 | this.UmbracoDatabaseMock = new Mock(); 57 | 58 | var sqlSyntaxMock = new Mock(); 59 | sqlSyntaxMock.Setup(x => x.GetQuotedTableName(It.IsAny())).Returns((string x) => x); 60 | sqlSyntaxMock.Setup(x => x.GetQuotedColumnName(It.IsAny())).Returns((string x) => x); 61 | 62 | var pocoMappers = new NPoco.MapperCollection { new PocoMapper() }; 63 | var pocoDataFactory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, pocoMappers).Init()); 64 | 65 | this.TransactionMock = new Mock(); 66 | 67 | this.UmbracoDatabaseMock.Setup(x => x.GetTransaction()).Returns(this.TransactionMock.Object); 68 | 69 | var sqlContextMock = new Mock(); 70 | sqlContextMock.SetupGet(x => x.SqlSyntax).Returns(sqlSyntaxMock.Object); 71 | sqlContextMock.SetupGet(x => x.PocoDataFactory).Returns(pocoDataFactory); 72 | 73 | this.ScopeMock = new Mock(); 74 | this.ScopeMock.SetupGet(x => x.Database).Returns(this.UmbracoDatabaseMock.Object); 75 | this.ScopeMock.Setup(x => x.Complete()); 76 | this.ScopeMock.SetupGet(x => x.SqlContext).Returns(sqlContextMock.Object); 77 | 78 | this.ScopeProviderMock = new Mock(); 79 | 80 | 81 | 82 | this.ScopeProviderMock 83 | .Setup( 84 | x => x.CreateScope( 85 | IsolationLevel.Unspecified, 86 | RepositoryCacheMode.Unspecified, 87 | null, 88 | null, 89 | false, 90 | true)).Returns(this.ScopeMock.Object); 91 | 92 | 93 | 94 | this.Repository = new NexuRelationRepository(this.ScopeProviderMock.Object); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Parsers.Tests/Core/RichTextEditorParserTests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Parsers.Tests.Core 2 | { 3 | using System.Linq; 4 | 5 | using global::Umbraco.Core; 6 | 7 | using NUnit.Framework; 8 | 9 | using Our.Umbraco.Nexu.Common.Constants; 10 | using Our.Umbraco.Nexu.Parsers.Core; 11 | 12 | /// 13 | /// The rich text editor parser tests. 14 | /// 15 | [TestFixture] 16 | public class RichTextEditorParserTests 17 | { 18 | [Test] 19 | public void When_EditorAlias_Is_Not_Correct_IsParserFor_Should_Return_False() 20 | { 21 | // arrange 22 | var parser = new RichTextEditorParser(); 23 | 24 | // act 25 | var result = parser.IsParserFor(Constants.PropertyEditors.Aliases.Boolean); 26 | 27 | // assert 28 | Assert.IsFalse(result); 29 | } 30 | 31 | [Test] 32 | public void When_EditorAlias_Is_Correct_IsParserFor_Should_Return_True() 33 | { 34 | // arrange 35 | var parser = new RichTextEditorParser(); 36 | 37 | // act 38 | var result = parser.IsParserFor(Constants.PropertyEditors.Aliases.TinyMce); 39 | 40 | // assert 41 | Assert.IsTrue(result); 42 | } 43 | 44 | [Test] 45 | public void When_Value_Is_Not_Set_GetRelatedEntities_Return_Empty_List() 46 | { 47 | // arrange 48 | var parser = new RichTextEditorParser(); 49 | 50 | // act 51 | var result = parser.GetRelatedEntities(null); 52 | 53 | // assert 54 | Assert.IsNotNull(result); 55 | Assert.AreEqual(0, result.Count()); 56 | } 57 | 58 | [Test] 59 | public void When_Value_Is_Set_GetRelatedEntities_Return_List_With_Related_Entities() 60 | { 61 | // arrange 62 | var rteValue = @"

Hier komt de tekst voor de contact pagina

63 |

Hier kunnen links in gezet worden 

64 |

En afbeeldingen geplaats

65 |

66 |

 

67 |

Maar we kunnen ook bestanden opladen

"; 68 | 69 | var parser = new RichTextEditorParser(); 70 | 71 | // act 72 | var result = parser.GetRelatedEntities(rteValue).ToList(); 73 | 74 | // assert 75 | Assert.IsNotNull(result); 76 | Assert.AreEqual(3, result.Count); 77 | 78 | Assert.AreEqual(1, result.Count(x => x.RelationType == RelationTypes.DocumentToDocument)); 79 | Assert.AreEqual(2, result.Count(x => x.RelationType == RelationTypes.DocumentToMedia)); 80 | 81 | Assert.IsTrue(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://document/ca4249ed2b234337b52263cabe5587d1" && x.RelationType == RelationTypes.DocumentToDocument)); 82 | Assert.IsTrue(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://media/a7e62beab9834049aaf765f5f95f2263" && x.RelationType == RelationTypes.DocumentToMedia)); 83 | Assert.IsTrue(result.Exists(x => x.RelatedEntityUdi.ToString() == "umb://media/34371d0892c84015912ebaacd002c5d0" && x.RelationType == RelationTypes.DocumentToMedia)); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Common/NexuContext.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | using Umbraco.Core; 3 | 4 | namespace Our.Umbraco.Nexu.Common 5 | { 6 | /// 7 | /// Represents the nexu context. 8 | /// 9 | public class NexuContext 10 | { 11 | private static NexuContext instance; 12 | 13 | private static readonly object padlock = new object(); 14 | 15 | private bool isProcessing; 16 | 17 | private string itemInProgress; 18 | 19 | private int itemsProcessed; 20 | 21 | /// 22 | /// Prevents a default instance of the class from being created. 23 | /// 24 | private NexuContext() 25 | { 26 | this.isProcessing = false; 27 | this.itemInProgress = string.Empty; 28 | this.itemsProcessed = 0; 29 | this.PreventDelete = this.GetAppSetting(Constants.AppSettings.PreventDelete, false); 30 | this.PreventUnPublish = this.GetAppSetting(Constants.AppSettings.PreventUnpublish, false); 31 | instance = this; 32 | } 33 | 34 | /// 35 | /// Gets the current context 36 | /// 37 | public static NexuContext Current 38 | { 39 | get 40 | { 41 | lock (padlock) 42 | { 43 | return instance ?? (instance = new NexuContext()); 44 | } 45 | } 46 | } 47 | 48 | /// 49 | /// Gets or sets a value indicating whether is processing. 50 | /// 51 | public bool IsProcessing 52 | { 53 | get => this.isProcessing; 54 | set 55 | { 56 | lock (padlock) 57 | { 58 | this.isProcessing = value; 59 | } 60 | } 61 | } 62 | 63 | /// 64 | /// Gets or sets the item in progress. 65 | /// 66 | public string ItemInProgress 67 | { 68 | get => this.itemInProgress; 69 | set 70 | { 71 | lock (padlock) 72 | { 73 | this.itemInProgress = value; 74 | } 75 | } 76 | } 77 | 78 | /// 79 | /// Gets or sets the items processed. 80 | /// 81 | public int ItemsProcessed 82 | { 83 | get => this.itemsProcessed; 84 | set 85 | { 86 | lock (padlock) 87 | { 88 | this.itemsProcessed = value; 89 | } 90 | } 91 | } 92 | 93 | /// 94 | /// Gets a value indicating whether prevent delete. 95 | /// 96 | public bool PreventDelete { get; } 97 | 98 | /// 99 | /// Gets a value indicating whether prevent un publish. 100 | /// 101 | public bool PreventUnPublish { get; set; } 102 | 103 | 104 | /// 105 | /// Gets the value of app setting 106 | /// 107 | /// 108 | /// The key. 109 | /// 110 | /// The default value when the app setting is empty or not found. 111 | /// 112 | /// The return type 113 | /// 114 | /// 115 | /// The . 116 | /// 117 | private T GetAppSetting(string key, T defaultValue) 118 | { 119 | var value = defaultValue; 120 | 121 | var setting = ConfigurationManager.AppSettings[key]; 122 | 123 | if (setting == null) 124 | { 125 | return value; 126 | } 127 | 128 | var attempConvert = setting.TryConvertTo(); 129 | 130 | if (attempConvert.Success) 131 | { 132 | value = attempConvert.Result; 133 | } 134 | 135 | return value; 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/App_Plugins/Nexu/components/relation-list-component.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function RelationListComponentController($scope, editorService, languageResource, navigationService, overlayService, localizationService) { 5 | var vm = this; 6 | 7 | vm.listitems = []; 8 | vm.languages = []; 9 | vm.pageSize = 10; 10 | vm.showLanguageColumn = false; 11 | vm.pagedListItems = []; 12 | vm.currentPage = 1; 13 | vm.totalPages = 1; 14 | 15 | vm.openContent = function (item) { 16 | var editor = { 17 | id: item.id, 18 | nexuCulture: item.language, 19 | submit: function(model) { 20 | editorService.close(); 21 | }, 22 | close: function() { 23 | editorService.close(); 24 | } 25 | }; 26 | 27 | navigationService.hideDialog(); 28 | overlayService.close(); 29 | editorService.contentEditor(editor); 30 | } 31 | 32 | vm.getLanguageLabel = function (culture) { 33 | 34 | if (vm.languages.length > 0) { 35 | 36 | var lang = _.find(vm.languages, 37 | function(l) { 38 | return l.culture.toLowerCase() === culture.toLowerCase(); 39 | }); 40 | 41 | if (lang) { 42 | return lang.name; 43 | } 44 | } 45 | 46 | return culture; 47 | } 48 | 49 | vm.nextPage = function() { 50 | vm.goToPage(vm.currentPage + 1); 51 | }; 52 | 53 | vm.prevPage = function() { 54 | vm.goToPage(vm.currentPage - 1); 55 | }; 56 | vm.goToPage = function(pageNumber) { 57 | vm.currentPage = pageNumber; 58 | setPagedList(); 59 | }; 60 | 61 | function setPagedList() { 62 | vm.pagedListItems = vm.listitems.slice((vm.currentPage - 1) * vm.pageSize, vm.currentPage * vm.pageSize); 63 | 64 | } 65 | 66 | this.$onInit = function () { 67 | 68 | if (this.showLanguage) { 69 | vm.showLanguageColumn = this.showLanguage; 70 | } 71 | 72 | if (this.itemsPerPage) { 73 | vm.pageSize = this.itemsPerPage; 74 | } 75 | 76 | if (vm.showLanguageColumn) { 77 | languageResource.getAll().then(function (data) { 78 | vm.languages = data; 79 | }); 80 | } 81 | 82 | for (var i = 0; i < this.relations.length; i++) { 83 | var relation = this.relations[i]; 84 | 85 | for (var j = 0; j < relation.Properties.length; j++) { 86 | var status = 'content_unpublished'; 87 | 88 | if (relation.IsTrashed) { 89 | status = 'contentpicker_trashed'; 90 | } 91 | else if (relation.IsPublished) { 92 | status = 'content_published'; 93 | } 94 | 95 | vm.listitems.push({ 96 | name: relation.Name, 97 | propertyname: relation.Properties[j].PropertyName, 98 | tabname: relation.Properties[j].TabName, 99 | status: status, 100 | editlink: '/content/content/edit/' + relation.Id + '?mculture=' + relation.Culture, 101 | language: relation.Culture, 102 | id : relation.Id 103 | }); 104 | 105 | 106 | } 107 | } 108 | 109 | vm.totalPages = Math.ceil(vm.listitems.length / vm.pageSize); 110 | 111 | setPagedList(); 112 | } 113 | 114 | 115 | 116 | } 117 | 118 | angular.module('umbraco').component('nexuRelationList', 119 | { 120 | controller: RelationListComponentController, 121 | controllerAs: "vm", 122 | bindings: { 123 | relations: '<', 124 | showLanguage: '<', 125 | itemsPerPage : '<' 126 | }, 127 | templateUrl: '/App_Plugins/Nexu/views/relation-list-component.html' 128 | }); 129 | 130 | })(); -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Web/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/Services/NexuEntityParsingService/GetRelatedEntitiesFromProperty_Tests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Tests.Services.NexuEntityParsingService 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | using global::Umbraco.Core; 8 | using global::Umbraco.Core.Models; 9 | 10 | using Moq; 11 | 12 | using NUnit.Framework; 13 | 14 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 15 | using Our.Umbraco.Nexu.Common.Models; 16 | 17 | /// 18 | /// Represents the tests for GetRelatedEntitiesFromProperty method on the NexuEntityParsingService 19 | /// 20 | [TestFixture] 21 | public class GetRelatedEntitiesFromProperty_Tests : NexuEntityParsingServiceBaseTest 22 | { 23 | [Test] 24 | public void GetRelatedEntitiesFromProperty_Should_Parse_All_Cultures() 25 | { 26 | // arrange 27 | var editorAlias = "editorAlias"; 28 | 29 | var cultureValues = new Dictionary(); 30 | 31 | var nlValue = "umb://document/ca4249ed2b234337b52263cabe5587d1"; 32 | 33 | var enValue = "umb://document/ec4aafcc0c254f25a8fe705bfae1d324"; 34 | 35 | cultureValues.Add("nl-NL", nlValue); 36 | cultureValues.Add("en-US", enValue); 37 | 38 | var propertyType = 39 | new PropertyType(editorAlias, ValueStorageType.Ntext) 40 | { 41 | Variations = ContentVariation.Culture 42 | }; 43 | 44 | var property = new Property(propertyType); 45 | 46 | foreach (var key in cultureValues.Keys) 47 | { 48 | property.SetValue(cultureValues[key], key); 49 | } 50 | 51 | var nlRelation = new RelatedDocumentEntity 52 | { 53 | RelatedEntityUdi = new StringUdi(new Uri(nlValue)) 54 | }; 55 | 56 | var enRelation = new RelatedDocumentEntity 57 | { 58 | RelatedEntityUdi = new StringUdi(new Uri(enValue)) 59 | }; 60 | 61 | Mock.Get(this.Service).Setup(x => x.GetRelatedEntitiesFromPropertyEditorValue(editorAlias, nlValue)).Returns(new List { nlRelation }); 62 | Mock.Get(this.Service).Setup(x => x.GetRelatedEntitiesFromPropertyEditorValue(editorAlias, enValue)).Returns(new List { enRelation}); 63 | 64 | // act 65 | var result = this.Service.GetRelatedEntitiesFromProperty(property); 66 | 67 | // assert 68 | Assert.IsNotNull(result); 69 | 70 | Assert.That(result.Keys.Count == 2); 71 | 72 | Mock.Get(this.Service).Verify(x => x.GetRelatedEntitiesFromPropertyEditorValue(editorAlias, nlValue), Times.Once); 73 | Mock.Get(this.Service).Verify(x => x.GetRelatedEntitiesFromPropertyEditorValue(editorAlias, enValue), Times.Once); 74 | 75 | } 76 | 77 | [Test] 78 | public void GetRelatedEntitiesFromProperty_For_Invariant_Property_Should_Parse_As_Invariant_Culture() 79 | { 80 | // arrange 81 | var editorAlias = "editorAlias"; 82 | 83 | var nlValue = "umb://document/ca4249ed2b234337b52263cabe5587d1"; 84 | 85 | 86 | 87 | var propertyType = 88 | new PropertyType(editorAlias, ValueStorageType.Ntext) 89 | { 90 | Variations = ContentVariation.Nothing 91 | }; 92 | 93 | var property = new Property(propertyType); 94 | 95 | property.SetValue(nlValue); 96 | 97 | 98 | var nlRelation = new RelatedDocumentEntity 99 | { 100 | RelatedEntityUdi = new StringUdi(new Uri(nlValue)) 101 | }; 102 | 103 | Mock.Get(this.Service).Setup(x => x.GetRelatedEntitiesFromPropertyEditorValue(editorAlias, nlValue)).Returns(new List { nlRelation }); 104 | 105 | // act 106 | var result = this.Service.GetRelatedEntitiesFromProperty(property); 107 | 108 | // assert 109 | Assert.IsNotNull(result); 110 | 111 | Assert.That(result.Keys.Count == 1); 112 | 113 | Assert.That(result.Keys.First() == "invariant"); 114 | 115 | Mock.Get(this.Service).Verify(x => x.GetRelatedEntitiesFromPropertyEditorValue(editorAlias, nlValue), Times.Once); 116 | 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.168 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.Umbraco.Nexu.Web", "Our.Umbraco.Nexu.Web\Our.Umbraco.Nexu.Web.csproj", "{CD53B833-3BDD-4DD1-86B3-27E5E173BCA8}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.Umbraco.Nexu.Core", "Our.Umbraco.Nexu.Core\Our.Umbraco.Nexu.Core.csproj", "{DFE4EE20-8A4C-4F9F-92BD-BB94CBF60940}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.Umbraco.Nexu.Common", "Our.Umbraco.Nexu.Common\Our.Umbraco.Nexu.Common.csproj", "{F16A67CC-0F13-45E6-B493-D6C10F742214}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.Umbraco.Nexu.Parsers", "Our.Umbraco.Nexu.Parsers\Our.Umbraco.Nexu.Parsers.csproj", "{6797E59C-5528-4F3C-BAF4-B2AC32185101}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{5DF9E272-5FA7-4ED2-AAE7-D203E271CA12}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.Umbraco.Nexu.Parsers.Tests", "Our.Umbraco.Nexu.Parsers.Tests\Our.Umbraco.Nexu.Parsers.Tests.csproj", "{E5026F75-7C2B-4A90-ADEA-8D79D191405F}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.Umbraco.Nexu.Core.Tests", "Our.Umbraco.Nexu.Core.Tests\Our.Umbraco.Nexu.Core.Tests.csproj", "{E9328CD1-BC6D-4A5E-97F7-6FCC6A41211C}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.Umbraco.Nexu.Web.Tests", "Our.Umbraco.Nexu.Web.Tests\Our.Umbraco.Nexu.Web.Tests.csproj", "{7DEDD335-C295-4F35-AC92-721A6E6B05CA}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {CD53B833-3BDD-4DD1-86B3-27E5E173BCA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {CD53B833-3BDD-4DD1-86B3-27E5E173BCA8}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {CD53B833-3BDD-4DD1-86B3-27E5E173BCA8}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {CD53B833-3BDD-4DD1-86B3-27E5E173BCA8}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {DFE4EE20-8A4C-4F9F-92BD-BB94CBF60940}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {DFE4EE20-8A4C-4F9F-92BD-BB94CBF60940}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {DFE4EE20-8A4C-4F9F-92BD-BB94CBF60940}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {DFE4EE20-8A4C-4F9F-92BD-BB94CBF60940}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {F16A67CC-0F13-45E6-B493-D6C10F742214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {F16A67CC-0F13-45E6-B493-D6C10F742214}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {F16A67CC-0F13-45E6-B493-D6C10F742214}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {F16A67CC-0F13-45E6-B493-D6C10F742214}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {6797E59C-5528-4F3C-BAF4-B2AC32185101}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {6797E59C-5528-4F3C-BAF4-B2AC32185101}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {6797E59C-5528-4F3C-BAF4-B2AC32185101}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {6797E59C-5528-4F3C-BAF4-B2AC32185101}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {E5026F75-7C2B-4A90-ADEA-8D79D191405F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {E5026F75-7C2B-4A90-ADEA-8D79D191405F}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {E5026F75-7C2B-4A90-ADEA-8D79D191405F}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {E5026F75-7C2B-4A90-ADEA-8D79D191405F}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {E9328CD1-BC6D-4A5E-97F7-6FCC6A41211C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {E9328CD1-BC6D-4A5E-97F7-6FCC6A41211C}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {E9328CD1-BC6D-4A5E-97F7-6FCC6A41211C}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {E9328CD1-BC6D-4A5E-97F7-6FCC6A41211C}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {7DEDD335-C295-4F35-AC92-721A6E6B05CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {7DEDD335-C295-4F35-AC92-721A6E6B05CA}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {7DEDD335-C295-4F35-AC92-721A6E6B05CA}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {7DEDD335-C295-4F35-AC92-721A6E6B05CA}.Release|Any CPU.Build.0 = Release|Any CPU 56 | EndGlobalSection 57 | GlobalSection(SolutionProperties) = preSolution 58 | HideSolutionNode = FALSE 59 | EndGlobalSection 60 | GlobalSection(NestedProjects) = preSolution 61 | {E5026F75-7C2B-4A90-ADEA-8D79D191405F} = {5DF9E272-5FA7-4ED2-AAE7-D203E271CA12} 62 | {E9328CD1-BC6D-4A5E-97F7-6FCC6A41211C} = {5DF9E272-5FA7-4ED2-AAE7-D203E271CA12} 63 | {7DEDD335-C295-4F35-AC92-721A6E6B05CA} = {5DF9E272-5FA7-4ED2-AAE7-D203E271CA12} 64 | EndGlobalSection 65 | GlobalSection(ExtensibilityGlobals) = postSolution 66 | SolutionGuid = {4FCEAA47-44E2-46A6-80DB-C8E2C387B924} 67 | EndGlobalSection 68 | EndGlobal 69 | -------------------------------------------------------------------------------- /src/Our.Umbraco.Nexu.Core.Tests/Services/NexuEntityParsingService/GetRelatedEntitiesFromPropertyEditorValue_Tests.cs: -------------------------------------------------------------------------------- 1 | namespace Our.Umbraco.Nexu.Core.Tests.Services.NexuEntityParsingService 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | using global::Umbraco.Core; 8 | 9 | using Moq; 10 | 11 | using NUnit.Framework; 12 | 13 | using Our.Umbraco.Nexu.Common.Interfaces.Models; 14 | 15 | /// 16 | /// Represents the tests for GetRelatedEntitiesFromPropertyEditorValue method on the NexuEntityParsingService 17 | /// 18 | [TestFixture] 19 | public class GetRelatedEntitiesFromPropertyEditorValue_Tests : NexuEntityParsingServiceBaseTest 20 | { 21 | [Test] 22 | public void When_No_Parser_Found_GetRelatedEntitiesFromPropertyEditorValue_Should_Return_Empty_List() 23 | { 24 | // arrange 25 | var editorAlias = "Bla"; 26 | 27 | Mock.Get(this.Service).Setup(x => x.GetParserForPropertyEditor(editorAlias)); 28 | 29 | // act 30 | var result = this.Service.GetRelatedEntitiesFromPropertyEditorValue(editorAlias, "bla"); 31 | 32 | // assert 33 | Assert.IsNotNull(result); 34 | Assert.That(!result.Any()); 35 | 36 | Mock.Get(this.Service).Verify(x => x.GetParserForPropertyEditor(editorAlias), Times.Once); 37 | } 38 | 39 | [Test] 40 | public void When_PropertyValue_Is_Empty_GetRelatedEntitiesFromPropertyEditorValuee_Should_Return_Empty_List() 41 | { 42 | // arrange 43 | var editorAlias = "Bla"; 44 | object propertyValue = null; 45 | 46 | Mock.Get(this.Service).Setup(x => x.GetParserForPropertyEditor(editorAlias)); 47 | 48 | // act 49 | var result = this.Service.GetRelatedEntitiesFromPropertyEditorValue(editorAlias, propertyValue); 50 | 51 | // assert 52 | Assert.IsNotNull(result); 53 | Assert.That(!result.Any()); 54 | 55 | Mock.Get(this.Service).Verify(x => x.GetParserForPropertyEditor(editorAlias), Times.Never); 56 | } 57 | 58 | [Test] 59 | public void When_Parser_Found_GetRelatedEntitiesFromPropertyEditorValue_Should_Call_GetRelatedEntities_Method_From_Parser() 60 | { 61 | // arrange 62 | var editorAlias = "Bla"; 63 | var propertyValue = "Foo"; 64 | 65 | var parser = new Mock(); 66 | parser.Setup(x => x.GetRelatedEntities(propertyValue)).Returns(new List()); 67 | 68 | Mock.Get(this.Service).Setup(x => x.GetParserForPropertyEditor(editorAlias)).Returns(parser.Object); 69 | 70 | // act 71 | var result = this.Service.GetRelatedEntitiesFromPropertyEditorValue(editorAlias, propertyValue); 72 | 73 | // assert 74 | Assert.IsNotNull(result); 75 | Assert.That(!result.Any()); 76 | 77 | Mock.Get(this.Service).Verify(x => x.GetParserForPropertyEditor(editorAlias), Times.Once); 78 | parser.Verify(x => x.GetRelatedEntities(propertyValue), Times.Once); 79 | } 80 | 81 | [Test] 82 | public void When_Parser_Found_GetRelatedEntitiesFromPropertyEditorValue_Should_Return_Unique_Relations_If_Duplicates_Are_found() 83 | { 84 | // arrange 85 | var editorAlias = "Bla"; 86 | var propertyValue = "Foo"; 87 | 88 | var relatedEntities = new List(); 89 | 90 | var entity = new Mock(); 91 | entity.SetupGet(x => x.RelationType).Returns(Common.Constants.RelationTypes.DocumentToDocument); 92 | entity.SetupGet(x => x.RelatedEntityUdi) 93 | .Returns(new StringUdi(new Uri("umb://document/ca4249ed2b234337b52263cabe5587d1"))); 94 | 95 | relatedEntities.Add(entity.Object); 96 | relatedEntities.Add(entity.Object); 97 | 98 | var parser = new Mock(); 99 | 100 | parser.Setup(x => x.GetRelatedEntities(propertyValue)).Returns(relatedEntities); 101 | 102 | Mock.Get(this.Service).Setup(x => x.GetParserForPropertyEditor(editorAlias)).Returns(parser.Object); 103 | 104 | // act 105 | var result = this.Service.GetRelatedEntitiesFromPropertyEditorValue(editorAlias, propertyValue); 106 | 107 | // assert 108 | Assert.IsNotNull(result); 109 | Assert.That(result.Count() == 1); 110 | 111 | Mock.Get(this.Service).Verify(x => x.GetParserForPropertyEditor(editorAlias), Times.Once); 112 | parser.Verify(x => x.GetRelatedEntities(propertyValue), Times.Once); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Packaging/Tools/MSBuildCommunityTasks/Sample.proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 67 | 68 | 69 | 70 | 71 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 91 | 92 | 93 | 94 | 104 | 105 | 106 | 107 | list = new List(); 110 | list.Add("Happy"); 111 | list.Add("New"); 112 | list.Add("Year"); 113 | Console.WriteLine("Hello MSBuild Community Scripting World."); 114 | foreach(string s in list) 115 | { 116 | Console.WriteLine(s); 117 | } 118 | } 119 | ]]> 120 | 121 | 122 | 123 | 124 |