diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8893f4c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,141 @@ +# Contributing to this project + +Please take a moment to review this document in order to make the contribution +process easy and effective for everyone involved. + +Following these guidelines helps to communicate that you respect the time of +the developers managing and developing this open source project. In return, +they should reciprocate that respect in addressing your issue or assessing +patches and features. + + +## Using the issue tracker + +The issue tracker is the preferred channel for [bug reports](#bugs), +[features requests](#features) and [submitting pull +requests](#pull-requests), but please respect the following restrictions: + +* Please **do not** use the issue tracker for personal support requests (use + [Our Umbraco](https://our.umbraco.org/projects/backoffice-extensions/nexu/) or Twitter). + +* Please **do not** derail or troll issues. Keep the discussion on topic and + respect the opinions of others. + + + +## Bug reports + +A bug is a _demonstrable problem_ that is caused by the code in the repository. +Good bug reports are extremely helpful - thank you! + +Guidelines for bug reports: + +1. **Use the GitHub issue search** — check if the issue has already been + reported. + +2. **Check if the issue has been fixed** — try to reproduce it using the + latest `master` or development branch in the repository. + +3. **Isolate the problem** — create a reduced test case and a live example. + +A good bug report shouldn't leave others needing to chase you up for more +information. Please try to be as detailed as possible in your report. What is +your environment? What steps will reproduce the issue? What browser(s) and OS +experience the problem? What would you expect to be the outcome? All these +details will help people to fix any potential bugs. + +Example: + +> Short and descriptive example bug report title +> +> A summary of the issue and the browser/OS environment in which it occurs. If +> suitable, include the steps required to reproduce the bug. +> +> 1. This is the first step +> 2. This is the second step +> 3. Further steps, etc. +> +> `` - a link to the reduced test case +> +> Any other information you want to share that is relevant to the issue being +> reported. This might include the lines of code that you have identified as +> causing the bug, and potential solutions (and your opinions on their +> merits). + + + +## Feature requests + +Feature requests are welcome. But take a moment to find out whether your idea +fits with the scope and aims of the project. It's up to *you* to make a strong +case to convince the project's developers of the merits of this feature. Please +provide as much detail and context as possible. + + + +## Pull requests + +Good pull requests - patches, improvements, new features - are a fantastic +help. They should remain focused in scope and avoid containing unrelated +commits. + +**Please ask first** before embarking on any significant pull request (e.g. +implementing features, refactoring code, porting to a different language), +otherwise you risk spending a lot of time working on something that the +project's developers might not want to merge into the project. + +Please adhere to the coding conventions used throughout a project (indentation, +accurate comments, etc.) and any other requirements (such as test coverage). + +Follow this process if you'd like your work considered for inclusion in the +project: + +1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, + and configure the remotes: + + ```bash + # Clone your fork of the repo into the current directory + git clone https://github.com// + # Navigate to the newly cloned directory + cd + # Assign the original repo to a remote called "upstream" + git remote add upstream https://github.com// + ``` + +2. If you cloned a while ago, get the latest changes from upstream: + + ```bash + git checkout develop + git pull upstream develop + ``` + +3. Create a new topic branch (off the main project `develop` branch) to + contain your feature, change, or fix: + + ```bash + git checkout -b + ``` + +4. Commit your changes in logical chunks. Please adhere to these [git commit + message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) + or your code is unlikely be merged into the main project. Use Git's + [interactive rebase](https://help.github.com/articles/interactive-rebase) + feature to tidy up your commits before making them public. + +5. Locally merge (or rebase) the upstream development branch into your topic branch: + + ```bash + git pull [--rebase] upstream develop + ``` + +6. Push your topic branch up to your fork: + + ```bash + git push origin + ``` + +7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) + with a clear title and description. + +**IMPORTANT**: By submitting a patch, you agree to allow the project owner to +license your work under the same license as that used by the project. \ No newline at end of file diff --git a/README.md b/README.md index 35ade84..83417a4 100644 --- a/README.md +++ b/README.md @@ -21,4 +21,6 @@ The documentation for this package can be found [here](docs/index.md) ## Special thanks ## -[Jeavon Leopold](https://twitter.com/crumpled_jeavon) for his help in setting up the build automation. Sorry for bugging you on Slack :-) \ No newline at end of file +[Jeavon Leopold](https://twitter.com/crumpled_jeavon) for his help in setting up the build automation. Sorry for bugging you on Slack :-) + +And of course all the [contributors](https://github.com/dawoe/umbraco-nexu/graphs/contributors) \ No newline at end of file diff --git a/Source/Our.Umbraco.Nexu.Core.Tests/NexuApiControllerTests.cs b/Source/Our.Umbraco.Nexu.Core.Tests/NexuApiControllerTests.cs index 578a248..98e3979 100644 --- a/Source/Our.Umbraco.Nexu.Core.Tests/NexuApiControllerTests.cs +++ b/Source/Our.Umbraco.Nexu.Core.Tests/NexuApiControllerTests.cs @@ -48,6 +48,11 @@ public class NexuApiControllerTests : BaseRoutingTest /// private Mock contentServiceMock; + /// + /// The media service. + /// + private Mock mediaServiceMock; + /// /// The controller. /// @@ -106,6 +111,7 @@ public override void Initialize() this.nexuServiceMock = new Mock(); this.mappingEngineMock = new Mock(); this.contentServiceMock = new Mock(); + this.mediaServiceMock = new Mock(); // Mocked settings are now necessary SettingsForTests.ConfigureSettings(SettingsForTests.GenerateMockSettings()); @@ -126,7 +132,8 @@ public override void Initialize() this.UmbracoContext, this.nexuServiceMock.Object, this.mappingEngineMock.Object, - this.contentServiceMock.Object) + this.contentServiceMock.Object, + this.mediaServiceMock.Object) { Request = new HttpRequestMessage { @@ -195,6 +202,445 @@ public void TestGetIncomingLinks() Assert.IsNotNull(model); } + /// + /// The check descendants for incoming links should return false when there are no children. + /// + [Test] + [Category("Api")] + public void CheckContentDescendantsForIncomingLinksShouldReturnFalseWhenThereAreNoChildren() + { + // arrange + var contentId = 123; + + var children = new List(); + + this.contentServiceMock.Setup(x => x.GetChildren(contentId)).Returns(children); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(It.IsAny(), false)) + .Returns(new List()); + + // act + var result = this.controller.CheckContentDescendantsForIncomingLinks(contentId); + + // arrange + Assert.IsFalse(result); + + this.contentServiceMock.Verify(x => x.GetChildren(contentId), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(It.IsAny(), false), Times.Never); + } + + /// + /// The check descendants for incoming links should return false when they have no incoming links. + /// + [Test] + [Category("Api")] + public void CheckContentDescendantsForIncomingLinksShouldReturnFalseWhenTheyHaveNoIncomingLinks() + { + // arrange + var contentId = 123; + + var children = new List(); + + var child1 = new Mock(); + child1.Setup(x => x.Id).Returns(456); + + children.Add(child1.Object); + + var child2 = new Mock(); + child2.Setup(x => x.Id).Returns(789); + + children.Add(child2.Object); + + var descendants = new List(); + + var descendant1 = new Mock(); + descendant1.Setup(x => x.Id).Returns(000); + + this.contentServiceMock.Setup(x => x.GetChildren(contentId)).Returns(children); + this.contentServiceMock.Setup(x => x.GetChildren(child1.Object.Id)).Returns(new List()); + this.contentServiceMock.Setup(x => x.GetChildren(child2.Object.Id)).Returns(descendants); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(It.IsAny(), false)) + .Returns(new List()); + + // act + var result = this.controller.CheckContentDescendantsForIncomingLinks(contentId); + + // arrange + Assert.IsFalse(result); + + this.contentServiceMock.Verify(x => x.GetChildren(contentId), Times.Once); + this.contentServiceMock.Verify(x => x.GetChildren(child1.Object.Id), Times.Once); + this.contentServiceMock.Verify(x => x.GetChildren(child2.Object.Id), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(It.IsAny(), false), Times.Exactly(children.Count + descendants.Count)); + } + + /// + /// The check descendants for incoming links should return true when one has incoming links. + /// + [Test] + [Category("Api")] + public void CheckContentDescendantsForIncomingLinksShouldReturnTrueWhenIncomingLinksForFirstLevel() + { + // arrange + var contentId = 123; + + var children = new List(); + + var child1 = new Mock(); + child1.Setup(x => x.Id).Returns(456); + + children.Add(child1.Object); + + var child2 = new Mock(); + child2.Setup(x => x.Id).Returns(000); + + children.Add(child2.Object); + + var child3 = new Mock(); + child3.Setup(x => x.Id).Returns(789); + + children.Add(child3.Object); + + this.contentServiceMock.Setup(x => x.GetChildren(contentId)).Returns(children); + + var relations = new List(); + + relations.Add(Mock.Of()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(456, false)) + .Returns(new List()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(000, false)) + .Returns(relations); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(789, false)) + .Returns(new List()); + + // act + var result = this.controller.CheckContentDescendantsForIncomingLinks(contentId); + + // arrange + Assert.IsTrue(result); + + this.contentServiceMock.Verify(x => x.GetChildren(contentId), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(456, false), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(000, false), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(789, false), Times.Never); + } + + /// + /// The check content descendants for incoming links should return true when incoming links for deeper level. + /// + [Test] + [Category("Api")] + public void CheckContentDescendantsForIncomingLinksShouldReturnTrueWhenIncomingLinksForDeeperLevel() + { + // arrange + var contentId = 123; + + var children = new List(); + + var child1 = new Mock(); + child1.Setup(x => x.Id).Returns(456); + + children.Add(child1.Object); + + var child2 = new Mock(); + child2.Setup(x => x.Id).Returns(000); + + children.Add(child2.Object); + + var child3 = new Mock(); + child3.Setup(x => x.Id).Returns(789); + + children.Add(child3.Object); + + var descendants_1 = new List(); + + var descendant1 = new Mock(); + descendant1.Setup(x => x.Id).Returns(006); + + descendants_1.Add(descendant1.Object); + + var descendants_2 = new List(); + + var descendant2 = new Mock(); + descendant2.Setup(x => x.Id).Returns(007); + + descendants_2.Add(descendant2.Object); + + this.contentServiceMock.Setup(x => x.GetChildren(contentId)).Returns(children); + this.contentServiceMock.Setup(x => x.GetChildren(child1.Object.Id)).Returns(descendants_1); + this.contentServiceMock.Setup(x => x.GetChildren(child2.Object.Id)).Returns(descendants_2); + this.contentServiceMock.Setup(x => x.GetChildren(child3.Object.Id)).Returns(new List()); + + var relations = new List(); + + relations.Add(Mock.Of()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(456, false)) + .Returns(new List()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(000, false)) + .Returns(new List()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(789, false)) + .Returns(new List()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(006, false)) + .Returns(new List()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(007, false)) + .Returns(relations); + + // act + var result = this.controller.CheckContentDescendantsForIncomingLinks(contentId); + + // arrange + Assert.IsTrue(result); + + this.contentServiceMock.Verify(x => x.GetChildren(contentId), Times.Once); + this.contentServiceMock.Verify(x => x.GetChildren(child1.Object.Id), Times.Once); + this.contentServiceMock.Verify(x => x.GetChildren(child2.Object.Id), Times.Once); + this.contentServiceMock.Verify(x => x.GetChildren(child3.Object.Id), Times.Never); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(456, false), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(000, false), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(789, false), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(006, false), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(007, false), Times.Once); + } + + /// + /// The check media descendants for incoming links should return false when there are no children. + /// + [Test] + [Category("Api")] + public void CheckMediaDescendantsForIncomingLinksShouldReturnFalseWhenThereAreNoChildren() + { + // arrange + var mediaId = 123; + + var children = new List(); + + this.mediaServiceMock.Setup(x => x.GetChildren(mediaId)).Returns(children); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(It.IsAny(), false)) + .Returns(new List()); + + // act + var result = this.controller.CheckMediaDescendantsForIncomingLinks(mediaId); + + // arrange + Assert.IsFalse(result); + + this.mediaServiceMock.Verify(x => x.GetChildren(mediaId), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(It.IsAny(), false), Times.Never); + } + + /// + /// The check media descendants for incoming links should return false when they have no incoming links. + /// + [Test] + [Category("Api")] + public void CheckMediaDescendantsForIncomingLinksShouldReturnFalseWhenTheyHaveNoIncomingLinks() + { + // arrange + var mediaId = 123; + + var children = new List(); + + var child1 = new Mock(); + child1.Setup(x => x.Id).Returns(456); + + children.Add(child1.Object); + + var child2 = new Mock(); + child2.Setup(x => x.Id).Returns(789); + + children.Add(child2.Object); + + var descendants = new List(); + + var descendant1 = new Mock(); + descendant1.Setup(x => x.Id).Returns(000); + + this.mediaServiceMock.Setup(x => x.GetChildren(mediaId)).Returns(children); + this.mediaServiceMock.Setup(x => x.GetChildren(child1.Object.Id)).Returns(new List()); + this.mediaServiceMock.Setup(x => x.GetChildren(child2.Object.Id)).Returns(descendants); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(It.IsAny(), false)) + .Returns(new List()); + + // act + var result = this.controller.CheckMediaDescendantsForIncomingLinks(mediaId); + + // arrange + Assert.IsFalse(result); + + this.mediaServiceMock.Verify(x => x.GetChildren(mediaId), Times.Once); + this.mediaServiceMock.Verify(x => x.GetChildren(child1.Object.Id), Times.Once); + this.mediaServiceMock.Verify(x => x.GetChildren(child2.Object.Id), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(It.IsAny(), false), Times.Exactly(children.Count + descendants.Count)); + } + + /// + /// The check media descendants for incoming links should return true when one has incoming links. + /// + [Test] + [Category("Api")] + public void CheckMediaDescendantsForIncomingLinksShouldReturnTrueWhenOneHasIncomingLinksForFirstLevel() + { + // arrange + var mediaId = 123; + + var children = new List(); + + var child1 = new Mock(); + child1.Setup(x => x.Id).Returns(456); + + children.Add(child1.Object); + + var child2 = new Mock(); + child2.Setup(x => x.Id).Returns(000); + + children.Add(child2.Object); + + var child3 = new Mock(); + child3.Setup(x => x.Id).Returns(789); + + children.Add(child3.Object); + + this.mediaServiceMock.Setup(x => x.GetChildren(mediaId)).Returns(children); + + var relations = new List(); + + relations.Add(Mock.Of()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(456, false)) + .Returns(new List()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(000, false)) + .Returns(relations); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(789, false)) + .Returns(new List()); + + // act + var result = this.controller.CheckMediaDescendantsForIncomingLinks(mediaId); + + // arrange + Assert.IsTrue(result); + + this.mediaServiceMock.Verify(x => x.GetChildren(mediaId), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(456, false), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(000, false), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(789, false), Times.Never); + } + + /// + /// The check media descendants for incoming links should return true when incoming links for deeper level. + /// + [Test] + [Category("Api")] + public void CheckMediaDescendantsForIncomingLinksShouldReturnTrueWhenIncomingLinksForDeeperLevel() + { + // arrange + var contentId = 123; + + var children = new List(); + + var child1 = new Mock(); + child1.Setup(x => x.Id).Returns(456); + + children.Add(child1.Object); + + var child2 = new Mock(); + child2.Setup(x => x.Id).Returns(000); + + children.Add(child2.Object); + + var child3 = new Mock(); + child3.Setup(x => x.Id).Returns(789); + + children.Add(child3.Object); + + var descendants_1 = new List(); + + var descendant1 = new Mock(); + descendant1.Setup(x => x.Id).Returns(006); + + descendants_1.Add(descendant1.Object); + + var descendants_2 = new List(); + + var descendant2 = new Mock(); + descendant2.Setup(x => x.Id).Returns(007); + + descendants_2.Add(descendant2.Object); + + this.mediaServiceMock.Setup(x => x.GetChildren(contentId)).Returns(children); + this.mediaServiceMock.Setup(x => x.GetChildren(child1.Object.Id)).Returns(descendants_1); + this.mediaServiceMock.Setup(x => x.GetChildren(child2.Object.Id)).Returns(descendants_2); + this.mediaServiceMock.Setup(x => x.GetChildren(child3.Object.Id)).Returns(new List()); + + var relations = new List(); + + relations.Add(Mock.Of()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(456, false)) + .Returns(new List()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(000, false)) + .Returns(new List()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(789, false)) + .Returns(new List()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(006, false)) + .Returns(new List()); + + this.nexuServiceMock.Setup(x => x.GetNexuRelationsForContent(007, false)) + .Returns(relations); + + // act + var result = this.controller.CheckMediaDescendantsForIncomingLinks(contentId); + + // arrange + Assert.IsTrue(result); + + this.mediaServiceMock.Verify(x => x.GetChildren(contentId), Times.Once); + this.mediaServiceMock.Verify(x => x.GetChildren(child1.Object.Id), Times.Once); + this.mediaServiceMock.Verify(x => x.GetChildren(child2.Object.Id), Times.Once); + this.mediaServiceMock.Verify(x => x.GetChildren(child3.Object.Id), Times.Never); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(456, false), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(000, false), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(789, false), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(006, false), Times.Once); + + this.nexuServiceMock.Verify(x => x.GetNexuRelationsForContent(007, false), Times.Once); + } + + /// /// Test getting rebuild status. /// diff --git a/Source/Our.Umbraco.Nexu.Core.Tests/NexuMappingTests.cs b/Source/Our.Umbraco.Nexu.Core.Tests/NexuMappingTests.cs index 9487be3..d6b9baa 100644 --- a/Source/Our.Umbraco.Nexu.Core.Tests/NexuMappingTests.cs +++ b/Source/Our.Umbraco.Nexu.Core.Tests/NexuMappingTests.cs @@ -1,20 +1,18 @@ namespace Our.Umbraco.Nexu.Core.Tests { - using System; using System.Collections.Generic; using System.Linq; + using System.Threading; using AutoMapper; using global::Umbraco.Core.Models; - using global::Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero; using global::Umbraco.Core.Services; using Moq; using NUnit.Framework; - using Our.Umbraco.Nexu.Core.Mapping.Profiles; using Our.Umbraco.Nexu.Core.Mapping.TypeConverters; using Our.Umbraco.Nexu.Core.Models; @@ -29,6 +27,11 @@ public class NexuMappingTests /// private Mock contentServiceMock; + /// + /// The localization service mock. + /// + private Mock localizationServiceMock; + /// /// Test setup. /// @@ -36,8 +39,9 @@ public class NexuMappingTests public void Setup() { this.contentServiceMock = new Mock(); + this.localizationServiceMock = new Mock(); - Mapper.CreateMap, IEnumerable>().ConvertUsing(new RelationsToRelatedDocumentsConverter(this.contentServiceMock.Object)); + Mapper.CreateMap, IEnumerable>().ConvertUsing(new RelationsToRelatedDocumentsConverter(this.contentServiceMock.Object, this.localizationServiceMock.Object)); Mapper.CreateMap() .ForMember(x => x.Properties, opt => opt.Ignore()) @@ -59,12 +63,35 @@ public void TearDown() /// [Test] [Category("Mappings")] - public void TestMapRelationsToRelatedDocuments() + public void TestMapRelationsToRelatedDocumentssWithDictionaryItemsInPropAndTabnames() { // arrange + this.localizationServiceMock.Setup(x => x.DictionaryItemExists(It.IsAny())).Returns(true); + this.localizationServiceMock.Setup(x => x.GetDictionaryItemByKey(It.IsAny())) + .Returns( + (string s) => + { + var language = Mock.Of(); + + Mock.Get(language).SetupGet(x => x.IsoCode) + .Returns(Thread.CurrentThread.CurrentCulture.Name); + + var translation = Mock.Of(); + + Mock.Get(translation).SetupGet(x => x.Value).Returns(s); + Mock.Get(translation).SetupGet(x => x.Language).Returns(language); + + var dictionary = Mock.Of(); + + Mock.Get(dictionary).SetupGet(x => x.ItemKey).Returns(s); + Mock.Get(dictionary).SetupGet(x => x.Translations) + .Returns(new List { translation }); + return dictionary; + }); + var relation123Mock = new Mock(); relation123Mock.SetupGet(x => x.ParentId).Returns(123); - relation123Mock.SetupGet(x => x.Comment).Returns("media [[Images]] || rte [[Content]]"); + relation123Mock.SetupGet(x => x.Comment).Returns("#media [[#Images]] || rte [[Content]]"); var relation456Mock = new Mock(); relation456Mock.SetupGet(x => x.ParentId).Returns(456); @@ -124,6 +151,7 @@ public void TestMapRelationsToRelatedDocuments() var destination = Mapper.Map>(input).ToList(); // verify + this.localizationServiceMock.Verify(x => x.GetDictionaryItemByKey(It.IsAny()), Times.Exactly(2)); this.contentServiceMock.Verify(x => x.GetByIds(It.IsAny>()), Times.Once); Assert.AreEqual(input.Select(x => x.ParentId), actualContentIds); @@ -157,6 +185,106 @@ public void TestMapRelationsToRelatedDocuments() } + [Test] + [Category("Mappings")] + public void TestMapRelationsToRelatedDocuments() + { + // arrange + var relation123Mock = new Mock(); + relation123Mock.SetupGet(x => x.ParentId).Returns(123); + relation123Mock.SetupGet(x => x.Comment).Returns("media [[Images]] || rte [[Content]]"); + + var relation456Mock = new Mock(); + relation456Mock.SetupGet(x => x.ParentId).Returns(456); + relation456Mock.SetupGet(x => x.Comment).Returns("picker [[Links]] || rte [[Content]]"); + + var relation789Mock = new Mock(); + relation789Mock.SetupGet(x => x.ParentId).Returns(789); + relation789Mock.SetupGet(x => x.Comment).Returns("picker [[Images]] || media [[Images]]"); + + var input = new List + { + relation123Mock.Object, + relation456Mock.Object, + relation789Mock.Object + }; + + IEnumerable actualContentIds = new List(); + + var contentTypeMock = new Mock(); + contentTypeMock.SetupGet(x => x.Icon).Returns("page"); + + var contentItems = new List(); + + var content123Mock = new Mock(); + content123Mock.SetupGet(x => x.Id).Returns(123); + content123Mock.SetupGet(x => x.Name).Returns("Content 123"); + content123Mock.SetupGet(x => x.Published).Returns(true); + content123Mock.SetupGet(x => x.Trashed).Returns(false); + content123Mock.SetupGet(x => x.ContentType).Returns(contentTypeMock.Object); + + var content456Mock = new Mock(); + content456Mock.SetupGet(x => x.Id).Returns(456); + content456Mock.SetupGet(x => x.Name).Returns("Content 456"); + content456Mock.SetupGet(x => x.Published).Returns(false); + content456Mock.SetupGet(x => x.Trashed).Returns(false); + content456Mock.SetupGet(x => x.ContentType).Returns(contentTypeMock.Object); + + var content789MOck = new Mock(); + content789MOck.SetupGet(x => x.Id).Returns(789); + content789MOck.SetupGet(x => x.Name).Returns("Content 789"); + content789MOck.SetupGet(x => x.Published).Returns(false); + content789MOck.SetupGet(x => x.Trashed).Returns(true); + content789MOck.SetupGet(x => x.ContentType).Returns(contentTypeMock.Object); + + contentItems.Add(content123Mock.Object); + contentItems.Add(content456Mock.Object); + contentItems.Add(content789MOck.Object); + + this.contentServiceMock.Setup(x => x.GetByIds(It.IsAny>())) + .Callback( + (IEnumerable ids) => + { + actualContentIds = ids; + }).Returns(contentItems); + + // act + var destination = Mapper.Map>(input).ToList(); + + // verify + this.contentServiceMock.Verify(x => x.GetByIds(It.IsAny>()), Times.Once); + + Assert.AreEqual(input.Select(x => x.ParentId), actualContentIds); + + Assert.IsNotNull(destination); + Assert.AreEqual(input.Count, destination.Count()); + + // properties + var related123Props = destination.First(x => x.Id == 123).Properties; + + Assert.IsTrue(related123Props.Keys.Contains("Images")); + Assert.IsTrue(related123Props.Keys.Contains("Content")); + + Assert.IsTrue(related123Props["Images"].Exists(x => x.Trim() == "media")); + Assert.IsTrue(related123Props["Content"].Exists(x => x.Trim() == "rte")); + + var related456Props = destination.First(x => x.Id == 456).Properties; + + Assert.IsTrue(related456Props.Keys.Contains("Links")); + Assert.IsTrue(related456Props.Keys.Contains("Content")); + + Assert.IsTrue(related456Props["Links"].Exists(x => x.Trim() == "picker")); + Assert.IsTrue(related456Props["Content"].Exists(x => x.Trim() == "rte")); + + var related789Props = destination.First(x => x.Id == 789).Properties; + + Assert.IsTrue(related789Props.Keys.Contains("Images")); + + Assert.IsTrue(related789Props["Images"].Exists(x => x.Trim() == "picker")); + Assert.IsTrue(related789Props["Images"].Exists(x => x.Trim() == "media")); + + } + /// /// The test mapping from content to related document. /// diff --git a/Source/Our.Umbraco.Nexu.Core.Tests/Properties/AssemblyInfo.cs b/Source/Our.Umbraco.Nexu.Core.Tests/Properties/AssemblyInfo.cs index 1d76501..e5e25bb 100644 --- a/Source/Our.Umbraco.Nexu.Core.Tests/Properties/AssemblyInfo.cs +++ b/Source/Our.Umbraco.Nexu.Core.Tests/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.3.0.0")] -[assembly: AssemblyFileVersion("1.3.0.0")] +[assembly: AssemblyVersion("1.4.0.0")] +[assembly: AssemblyFileVersion("1.4.0.0")] diff --git a/Source/Our.Umbraco.Nexu.Core/BootStrapper.cs b/Source/Our.Umbraco.Nexu.Core/BootStrapper.cs index 5518c4b..8bce50f 100644 --- a/Source/Our.Umbraco.Nexu.Core/BootStrapper.cs +++ b/Source/Our.Umbraco.Nexu.Core/BootStrapper.cs @@ -8,9 +8,12 @@ using System.Web.Routing; using AutoMapper; - + using ObjectResolution; + using Our.Umbraco.Nexu.Core.Mapping.Profiles; + using Our.Umbraco.Nexu.Core.WebApi; + using global::Umbraco.Core; using global::Umbraco.Core.Events; using global::Umbraco.Core.Models; @@ -18,9 +21,6 @@ using global::Umbraco.Web; using global::Umbraco.Web.UI.JavaScript; - using Our.Umbraco.Nexu.Core.Mapping.Profiles; - using Our.Umbraco.Nexu.Core.WebApi; - /// /// Bootstrapper to handle umbraco startup events /// @@ -28,7 +28,7 @@ internal class BootStrapper : ApplicationEventHandler { /// protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { + { using (ApplicationContext.Current.ProfilingLogger.TraceDuration("Begin ApplicationStarted", "End ApplicationStarted")) { // set up mappings @@ -53,7 +53,7 @@ protected override void ApplicationInitialized(UmbracoApplicationBase umbracoApp // resolve property parsers PropertyParserResolver.Current = new PropertyParserResolver(PluginManager.Current.ResolvePropertyParsers()); - + // resolve grid editor parsers GridEditorParserResolver.Current = new GridEditorParserResolver(PluginManager.Current.ResolveGridEditorParsers()); } @@ -98,6 +98,8 @@ private void ServerVariablesParserParsing(object sender, Dictionary(c => c.GetIncomingLinks(-1))); urlDictionairy.Add("GetIncomingLinks", urlHelper.GetUmbracoApiService("GetIncomingLinks", null)); + urlDictionairy.Add("CheckContentDescendantsForIncomingLinks", urlHelper.GetUmbracoApiService("CheckContentDescendantsForIncomingLinks", null)); + urlDictionairy.Add("CheckMediaDescendantsForIncomingLinks", urlHelper.GetUmbracoApiService("CheckMediaDescendantsForIncomingLinks", null)); urlDictionairy.Add("GetRebuildStatus", urlHelper.GetUmbracoApiService("GetRebuildStatus")); urlDictionairy.Add("Rebuild", urlHelper.GetUmbracoApiService("Rebuild", null)); diff --git a/Source/Our.Umbraco.Nexu.Core/Client/controllers/base-delete-controller.js b/Source/Our.Umbraco.Nexu.Core/Client/controllers/base-delete-controller.js new file mode 100644 index 0000000..2795530 --- /dev/null +++ b/Source/Our.Umbraco.Nexu.Core/Client/controllers/base-delete-controller.js @@ -0,0 +1,24 @@ +angular.module('umbraco').controller('Our.Umbraco.Nexu.BaseDeleteController', + ['$scope', '$controller', 'Our.Umbraco.Nexu.Resource', + function ($scope, $controller, nexuResource) { + // inherit core delete controller + angular.extend(this, $controller('Umbraco.Editors.Media.DeleteController', { $scope: $scope })); + + $scope.links = {}; + $scope.descendantsHaveLinks = false; + $scope.isLoading = true; + + nexuResource.getIncomingLinks($scope.currentNode.id).then(function (result) { + $scope.links = result.data; + + if (result.data.length == 0) { + nexuResource.checkDescendants($scope.currentNode.id, $scope.isMedia).then(function (result) { + $scope.descendantsHaveLinks = result.data; + $scope.isLoading = false; + }); + } else { + $scope.isLoading = false; + } + + }); + }]); \ No newline at end of file diff --git a/Source/Our.Umbraco.Nexu.Core/Client/controllers/content-delete-controller.js b/Source/Our.Umbraco.Nexu.Core/Client/controllers/content-delete-controller.js index 31cf9af..69824a7 100644 --- a/Source/Our.Umbraco.Nexu.Core/Client/controllers/content-delete-controller.js +++ b/Source/Our.Umbraco.Nexu.Core/Client/controllers/content-delete-controller.js @@ -1,14 +1,8 @@ angular.module('umbraco').controller('Our.Umbraco.Nexu.ContentDeleteController', - ['$scope', '$controller', 'Our.Umbraco.Nexu.Resource', - function ($scope, $controller, nexuResource) { - // inherit core delete controller - angular.extend(this, $controller('Umbraco.Editors.Content.DeleteController', { $scope: $scope })); + ['$scope', '$controller', + function ($scope, $controller) { + $scope.isMedia = false; - $scope.links = {}; - $scope.isLoading = true; - - nexuResource.getIncomingLinks($scope.currentNode.id).then(function (result) { - $scope.links = result.data; - $scope.isLoading = false; - }); + // inherit base delete controller + angular.extend(this, $controller('Our.Umbraco.Nexu.BaseDeleteController', { $scope: $scope })); }]); \ No newline at end of file diff --git a/Source/Our.Umbraco.Nexu.Core/Client/controllers/dashboard-controller.js b/Source/Our.Umbraco.Nexu.Core/Client/controllers/dashboard-controller.js index 0511c55..c6ef528 100644 --- a/Source/Our.Umbraco.Nexu.Core/Client/controllers/dashboard-controller.js +++ b/Source/Our.Umbraco.Nexu.Core/Client/controllers/dashboard-controller.js @@ -17,11 +17,11 @@ if ($scope.RebuildStatus.IsProcessing && $scope.autoRefresh) { $timeout(function() { $scope.getRebuildStatus() }, 5000, true); - } + } }); }; - $scope.rebuild = function() { + $scope.rebuild = function() { nexuResource.rebuild(-1) .then(function(result) { $scope.getRebuildStatus(); diff --git a/Source/Our.Umbraco.Nexu.Core/Client/controllers/media-delete-controller.js b/Source/Our.Umbraco.Nexu.Core/Client/controllers/media-delete-controller.js index af382ca..f960b11 100644 --- a/Source/Our.Umbraco.Nexu.Core/Client/controllers/media-delete-controller.js +++ b/Source/Our.Umbraco.Nexu.Core/Client/controllers/media-delete-controller.js @@ -1,14 +1,9 @@ angular.module('umbraco').controller('Our.Umbraco.Nexu.MediaDeleteController', - ['$scope', '$controller', 'Our.Umbraco.Nexu.Resource', - function ($scope, $controller, nexuResource) { - // inherit core delete controller - angular.extend(this, $controller('Umbraco.Editors.Media.DeleteController', { $scope: $scope })); + ['$scope', '$controller', + function ($scope, $controller) { + $scope.isMedia = true; - $scope.links = {}; - $scope.isLoading = true; - - nexuResource.getIncomingLinks($scope.currentNode.id).then(function (result) { - $scope.links = result.data; - $scope.isLoading = false; - }); + // inherit base delete controller + angular.extend(this, $controller('Our.Umbraco.Nexu.BaseDeleteController', { $scope: $scope })); + }]); \ No newline at end of file diff --git a/Source/Our.Umbraco.Nexu.Core/Client/controllers/unpublish-confirmation-controller.js b/Source/Our.Umbraco.Nexu.Core/Client/controllers/unpublish-confirmation-controller.js index 6c323ce..f25596c 100644 --- a/Source/Our.Umbraco.Nexu.Core/Client/controllers/unpublish-confirmation-controller.js +++ b/Source/Our.Umbraco.Nexu.Core/Client/controllers/unpublish-confirmation-controller.js @@ -1,17 +1,19 @@ angular.module('umbraco').controller('Our.Umbraco.Nexu.UnPublishConfirmationController', - ['$scope','notificationsService', + ['$scope','notificationsService', function ($scope, notificationsService) { $scope.links = $scope.notification.args.links; - $scope.publish = function (notification) { + $scope.descendantsHaveLinks = $scope.notification.args.descendantsHaveLinks; + + $scope.publish = function (notification) { notificationsService.remove(notification); // execute the deferred unpublish request - notification.args.deferredPromise.resolve(notification.args.originalRequest); + notification.args.deferredPromise.resolve(notification.args.originalRequest); }; - $scope.cancel = function (notification) { + $scope.cancel = function (notification) { notificationsService.remove(notification); }; diff --git a/Source/Our.Umbraco.Nexu.Core/Client/directives/nexuDescendantsWarning.js b/Source/Our.Umbraco.Nexu.Core/Client/directives/nexuDescendantsWarning.js new file mode 100644 index 0000000..134c8e1 --- /dev/null +++ b/Source/Our.Umbraco.Nexu.Core/Client/directives/nexuDescendantsWarning.js @@ -0,0 +1,9 @@ +angular.module("umbraco.directives") + .directive('nexuDescendantsWarning', function () { + return { + restrict: "E", // restrict to an element + replace: true, // replace the html element with the template + transclude: true, + templateUrl: '/App_Plugins/Nexu/views/nexu-descendants-warning.html' + }; + }); \ No newline at end of file diff --git a/Source/Our.Umbraco.Nexu.Core/Client/interceptor.js b/Source/Our.Umbraco.Nexu.Core/Client/interceptor.js index d501348..5b41d62 100644 --- a/Source/Our.Umbraco.Nexu.Core/Client/interceptor.js +++ b/Source/Our.Umbraco.Nexu.Core/Client/interceptor.js @@ -8,7 +8,7 @@ // Redirect any requests to built in content delete to our custom delete if (request.url.indexOf("views/content/delete.html") === 0) { - request.url = '/App_Plugins/Nexu/views/content-delete.html'; + request.url = '/App_Plugins/Nexu/views/content-delete.html'; } // Redirect any requests to built in media delete to our custom delete @@ -19,15 +19,15 @@ var unpublishUrlRegex = /^\/umbraco\/backoffice\/UmbracoApi\/Content\/PostUnPublish\?id=(\d*)$/i; // check if unpublished api call is made - if(unpublishUrlRegex.test(request.url)) { + if(unpublishUrlRegex.test(request.url)) { // get the id from the url var id = unpublishUrlRegex.exec(request.url)[1]; - // get nexuResource + // get nexuResource var nexuService = $injector.get('Our.Umbraco.Nexu.Resource'); // create deferred request - var deferred = $q.defer(); + var deferred = $q.defer(); // get incoming links nexuService.getIncomingLinks(id) @@ -41,20 +41,38 @@ args: { links: result.data, deferredPromise: deferred, - originalRequest : request + originalRequest: request, + descendantsHaveLinks : false } - }); + }); } else { - // execute request as normal - deferred.resolve(request); + nexuService.checkDescendants(id, false).then(function(result) { + if (result.data) { + notificationsService.add({ + // the path of our custom notification view + view: "/App_Plugins/Nexu/views/unpublish-confirmation.html", + // arguments object we want to pass to our custom notification + args: { + links: [], + deferredPromise: deferred, + originalRequest: request, + descendantsHaveLinks: true + } + }); + } else { + // execute request as normal + deferred.resolve(request); + } + }); + } }); // return deferred promise return deferred.promise; - + } - + return request || $q.when(request); } }; diff --git a/Source/Our.Umbraco.Nexu.Core/Client/lang/en-GB.xml b/Source/Our.Umbraco.Nexu.Core/Client/lang/en-GB.xml index 25940ab..0fb66f1 100644 --- a/Source/Our.Umbraco.Nexu.Core/Client/lang/en-GB.xml +++ b/Source/Our.Umbraco.Nexu.Core/Client/lang/en-GB.xml @@ -11,5 +11,6 @@ This page is linked to from following items This media item is linked to from following items Are you sure you want to unpublish + One of the sub items is linked from another content item \ No newline at end of file diff --git a/Source/Our.Umbraco.Nexu.Core/Client/lang/en-US.xml b/Source/Our.Umbraco.Nexu.Core/Client/lang/en-US.xml index 4a42869..0de07d1 100644 --- a/Source/Our.Umbraco.Nexu.Core/Client/lang/en-US.xml +++ b/Source/Our.Umbraco.Nexu.Core/Client/lang/en-US.xml @@ -11,5 +11,6 @@ This page is linked to from following items This media item is linked to from following items Are you sure you want to unpublish + One of the sub items is linked from another content item \ No newline at end of file diff --git a/Source/Our.Umbraco.Nexu.Core/Client/lang/nl-NL.xml b/Source/Our.Umbraco.Nexu.Core/Client/lang/nl-NL.xml index 7cfc5eb..6158f39 100644 --- a/Source/Our.Umbraco.Nexu.Core/Client/lang/nl-NL.xml +++ b/Source/Our.Umbraco.Nexu.Core/Client/lang/nl-NL.xml @@ -11,5 +11,6 @@ Er wordt naar deze pagina gelinkt vanuit deze paginas Er wordt naar dit bestand gelinkt vanuit deze paginas Bent u zeker dat u deze pagina wil depubliceren + Eén van de onderliggende items is gelinkt in een andere pagina \ No newline at end of file diff --git a/Source/Our.Umbraco.Nexu.Core/Client/package.manifest b/Source/Our.Umbraco.Nexu.Core/Client/package.manifest index 8056271..5cce275 100644 --- a/Source/Our.Umbraco.Nexu.Core/Client/package.manifest +++ b/Source/Our.Umbraco.Nexu.Core/Client/package.manifest @@ -1,14 +1,16 @@ -{ - javascript: [ - '~/App_Plugins/Nexu/interceptor.js', - '~/App_Plugins/Nexu/resource.js', - '~/App_Plugins/Nexu/controllers/content-delete-controller.js', - '~/App_Plugins/Nexu/controllers/media-delete-controller.js', - '~/App_Plugins/Nexu/controllers/unpublish-confirmation-controller.js', - '~/App_Plugins/Nexu/controllers/dashboard-controller.js', - '~/App_Plugins/Nexu/directives/nexuLinks.js' - ], - css: [ - '~/App_Plugins/Nexu/nexu.css' - ] +{ + javascript: [ + '~/App_Plugins/Nexu/interceptor.js', + '~/App_Plugins/Nexu/resource.js', + '~/App_Plugins/Nexu/controllers/base-delete-controller.js', + '~/App_Plugins/Nexu/controllers/content-delete-controller.js', + '~/App_Plugins/Nexu/controllers/media-delete-controller.js', + '~/App_Plugins/Nexu/controllers/unpublish-confirmation-controller.js', + '~/App_Plugins/Nexu/controllers/dashboard-controller.js', + '~/App_Plugins/Nexu/directives/nexuLinks.js', + '~/App_Plugins/Nexu/directives/nexuDescendantsWarning.js' + ], + css: [ + '~/App_Plugins/Nexu/nexu.css' + ] } \ No newline at end of file diff --git a/Source/Our.Umbraco.Nexu.Core/Client/resource.js b/Source/Our.Umbraco.Nexu.Core/Client/resource.js index 12a6f85..3673c05 100644 --- a/Source/Our.Umbraco.Nexu.Core/Client/resource.js +++ b/Source/Our.Umbraco.Nexu.Core/Client/resource.js @@ -4,6 +4,13 @@ getIncomingLinks: function (id) { return $http.get(Umbraco.Sys.ServerVariables.Nexu.GetIncomingLinks + "?contentId=" + id); }, + checkDescendants: function (id, isMedia) { + if (isMedia) { + return $http.get(Umbraco.Sys.ServerVariables.Nexu.CheckMediaDescendantsForIncomingLinks + "?mediaId=" + id); + } + + return $http.get(Umbraco.Sys.ServerVariables.Nexu.CheckContentDescendantsForIncomingLinks + "?contentId=" + id); + }, getRebuildStatus : function() { return $http.get(Umbraco.Sys.ServerVariables.Nexu.GetRebuildStatus); }, diff --git a/Source/Our.Umbraco.Nexu.Core/Client/views/content-delete.html b/Source/Our.Umbraco.Nexu.Core/Client/views/content-delete.html index 99ce7b2..88cd75a 100644 --- a/Source/Our.Umbraco.Nexu.Core/Client/views/content-delete.html +++ b/Source/Our.Umbraco.Nexu.Core/Client/views/content-delete.html @@ -1,7 +1,12 @@ 
+ + +
- + + +

Are you sure you want to delete {{currentNode.name}} ? diff --git a/Source/Our.Umbraco.Nexu.Core/Client/views/media-delete.html b/Source/Our.Umbraco.Nexu.Core/Client/views/media-delete.html index 8d11bb6..5b0735a 100644 --- a/Source/Our.Umbraco.Nexu.Core/Client/views/media-delete.html +++ b/Source/Our.Umbraco.Nexu.Core/Client/views/media-delete.html @@ -1,14 +1,19 @@ 

-
+ + - +
-

- Are you sure you want to delete {{currentNode.name}} ? -

+ - - + -
+

+ Are you sure you want to delete {{currentNode.name}} ? +

+ + + + +
\ No newline at end of file diff --git a/Source/Our.Umbraco.Nexu.Core/Client/views/nexu-descendants-warning.html b/Source/Our.Umbraco.Nexu.Core/Client/views/nexu-descendants-warning.html new file mode 100644 index 0000000..4eaecdd --- /dev/null +++ b/Source/Our.Umbraco.Nexu.Core/Client/views/nexu-descendants-warning.html @@ -0,0 +1,3 @@ +
+

One of the sub items is linked from another content item

+
\ No newline at end of file diff --git a/Source/Our.Umbraco.Nexu.Core/Client/views/nexu-links.html b/Source/Our.Umbraco.Nexu.Core/Client/views/nexu-links.html index 6b1bd65..cbc8320 100644 --- a/Source/Our.Umbraco.Nexu.Core/Client/views/nexu-links.html +++ b/Source/Our.Umbraco.Nexu.Core/Client/views/nexu-links.html @@ -1,11 +1,11 @@ -
+