diff --git a/src/Unicorn.Tests/TestingTools/Attributes/AutoSubAttribute.cs b/src/Unicorn.Tests/TestingTools/Attributes/AutoSubAttribute.cs new file mode 100644 index 00000000..7216bd09 --- /dev/null +++ b/src/Unicorn.Tests/TestingTools/Attributes/AutoSubAttribute.cs @@ -0,0 +1,13 @@ +using Ploeh.AutoFixture.AutoNSubstitute; +using Ploeh.AutoFixture.Xunit2; + +namespace Unicorn.Tests.TestingTools.Attributes +{ + public class AutoSubAttribute : AutoDataAttribute + { + public AutoSubAttribute() + { + Fixture.Customize(new AutoNSubstituteCustomization()); + } + } +} \ No newline at end of file diff --git a/src/Unicorn.Tests/UI/Gutter/TransparentSyncGutterTests.cs b/src/Unicorn.Tests/UI/Gutter/TransparentSyncGutterTests.cs new file mode 100644 index 00000000..0190931b --- /dev/null +++ b/src/Unicorn.Tests/UI/Gutter/TransparentSyncGutterTests.cs @@ -0,0 +1,39 @@ +using System; +using FluentAssertions; +using Ploeh.AutoFixture.Xunit2; +using Sitecore; +using Sitecore.Data.Items; +using Sitecore.Shell.Applications.ContentEditor.Gutters; +using Unicorn.Data.DataProvider; +using Unicorn.Tests.TestingTools.Attributes; +using Unicorn.UI.Gutter; +using Xunit; + +namespace Unicorn.Tests.UI.Gutter +{ + public class TransparentSyncGutterTests + { + [Theory, AutoSub] + public void GetIconDescriptor_WhenItemIsNull_ThrowArgumentNullException([Greedy]TestableTransparentSyncGutter sut) + { + Assert.Throws(() => sut.Public_GetIconDescriptor(null)); + } + + [Theory, AutoDbData] + public void GetIconDescriptor_WasLastUpdatedByTransparentSync_ReturnGutterIconDescriptor([Greedy]TestableTransparentSyncGutter sut, [Content] Item item) + { + using (new EditContext(item)) + { + item[FieldIDs.UpdatedBy] = UnicornDataProvider.TransparentSyncUpdatedByValue; + sut.Public_GetIconDescriptor(item).Should().BeAssignableTo(); + } + } + } + public class TestableTransparentSyncGutter : TransparentSyncGutter + { + public GutterIconDescriptor Public_GetIconDescriptor(Item item) + { + return GetIconDescriptor(item); + } + } +} diff --git a/src/Unicorn.Tests/Unicorn.Tests.csproj b/src/Unicorn.Tests/Unicorn.Tests.csproj index b26bf43e..9252017c 100644 --- a/src/Unicorn.Tests/Unicorn.Tests.csproj +++ b/src/Unicorn.Tests/Unicorn.Tests.csproj @@ -151,6 +151,8 @@ + + diff --git a/src/Unicorn/ControlPanel/Controls/Addons.cs b/src/Unicorn/ControlPanel/Controls/Addons.cs new file mode 100644 index 00000000..bff43541 --- /dev/null +++ b/src/Unicorn/ControlPanel/Controls/Addons.cs @@ -0,0 +1,47 @@ +using System.Web.UI; +using Unicorn.ControlPanel.Pipelines.UnicornControlPanelRequest; + +namespace Unicorn.ControlPanel.Controls +{ + /// + /// Addons section + /// + internal class Addons : IControlPanelControl + { + public void Render(HtmlTextWriter writer) + { + writer.Write(@" +
"); + writer.Write(@" +

Addons

"); + + writer.Write("
"); + writer.Write(""); + + writer.Write(""); + writer.Write(@""); + writer.Write("
"); + writer.Write("

Gutter Addon

"); + writer.Write("

Adds a gutter icon to any items that are included in a Unicorn Transparent Sync configuration

"); + writer.Write(@"

Note: if you have upgraded Unicorn and see a ""Verb Not Found"" message when clicking this button, make sure the following entries are present in your unicornControlPanelRequest pipeline:"); + writer.Write(@"

+<processor type=""Unicorn.ControlPanel.Pipelines.UnicornControlPanelRequest.InstallGutterVerb, Unicorn"" />
+<processor type=""Unicorn.ControlPanel.Pipelines.UnicornControlPanelRequest.RemoveGutterVerb, Unicorn"" />
+
"); + writer.Write("
"); + + if (GutterVerbBase.GetGutterItem() != null) + { + writer.Write(@"Remove Gutter Addon"); + } + else + { + writer.Write(@"Install Gutter Addon"); + } + writer.Write("
"); + writer.Write("
"); + writer.Write(@" +
"); + } + } +} diff --git a/src/Unicorn/ControlPanel/Pipelines/UnicornControlPanelRequest/GutterVerbBase.cs b/src/Unicorn/ControlPanel/Pipelines/UnicornControlPanelRequest/GutterVerbBase.cs new file mode 100644 index 00000000..93e5ff95 --- /dev/null +++ b/src/Unicorn/ControlPanel/Pipelines/UnicornControlPanelRequest/GutterVerbBase.cs @@ -0,0 +1,35 @@ +using Kamsar.WebConsole; +using Sitecore.Configuration; +using Sitecore.Data; +using Sitecore.Data.Items; +using Sitecore.Data.Managers; +using Unicorn.ControlPanel.Headings; +using Unicorn.ControlPanel.Responses; +using Unicorn.Logging; + +namespace Unicorn.ControlPanel.Pipelines.UnicornControlPanelRequest +{ + public abstract class GutterVerbBase : UnicornControlPanelRequestPipelineProcessor + { + private readonly string _consoleTitle; + protected const string GutterItemId = "{82496AF6-123F-4724-B7B6-746ED49A7747}"; + + protected GutterVerbBase(string verbHandled, string consoleTitle) : base(verbHandled) + { + _consoleTitle = consoleTitle; + } + + protected override IResponse CreateResponse(UnicornControlPanelRequestPipelineArgs args) + { + return new WebConsoleResponse(_consoleTitle, args.SecurityState.IsAutomatedTool, new HeadingService(), progress => Process(progress, new WebConsoleLogger(progress, args.Context.Request.QueryString["log"]))); + } + + protected abstract void Process(IProgressStatus process, ILogger logger); + + public static Item GetGutterItem() + { + Database coredb = Factory.GetDatabase("core"); + return coredb.DataManager.DataEngine.GetItem(new ID(GutterItemId), LanguageManager.DefaultLanguage, Version.Latest); + } + } +} \ No newline at end of file diff --git a/src/Unicorn/ControlPanel/Pipelines/UnicornControlPanelRequest/InstallGutterVerb.cs b/src/Unicorn/ControlPanel/Pipelines/UnicornControlPanelRequest/InstallGutterVerb.cs new file mode 100644 index 00000000..ae132b76 --- /dev/null +++ b/src/Unicorn/ControlPanel/Pipelines/UnicornControlPanelRequest/InstallGutterVerb.cs @@ -0,0 +1,65 @@ +using System; +using Kamsar.WebConsole; +using Sitecore; +using Sitecore.Configuration; +using Sitecore.Data; +using Sitecore.Data.Items; +using Sitecore.Data.Managers; +using Sitecore.SecurityModel; +using Unicorn.Logging; +using Version = Sitecore.Data.Version; + +namespace Unicorn.ControlPanel.Pipelines.UnicornControlPanelRequest +{ + public class InstallGutterVerb : GutterVerbBase + { + public const string VerbName = "InstallGutter"; + private const string GutterFolderId = "{59F37069-3118-4151-8C01-5DA0EF12CB4E}"; + private const string GutterRendererTemplateId = "{F5D247E0-80E6-4F31-9921-D30D00B61B3C}"; + public InstallGutterVerb() : base(VerbName, "Install Gutter") + { + } + + protected override void Process(IProgressStatus progress, ILogger logger) + { + Item gutterItem = GetGutterItem(); + Database coredb = Factory.GetDatabase("core"); + + if (gutterItem != null) + { + logger.Warn("JOB COMPLETE. Gutter icon exists. Aborting..."); + WebConsoleUtility.SetTaskProgress(progress, 1, 1, 100); + return; + } + + using (new SecurityDisabler()) + { + Item gutterFolder = coredb.DataManager.DataEngine.GetItem(new ID(GutterFolderId), LanguageManager.DefaultLanguage, + Version.Latest); + + logger.Info("Creating item"); + try + { + gutterItem = ItemManager.CreateItem("Transparent Sync", gutterFolder, new ID(GutterRendererTemplateId), + new ID(GutterItemId)); + + + gutterItem.Editing.BeginEdit(); + gutterItem[FieldIDs.DisplayName] = "Transparent Sync"; + gutterItem["Header"] = "Transparent Sync"; + gutterItem["Type"] = "Unicorn.UI.Gutter.TransparentSyncGutter, Unicorn"; + gutterItem.Editing.EndEdit(true, false); + + logger.Info("JOB COMPLETE. Gutter item created successfully!"); + WebConsoleUtility.SetTaskProgress(progress, 1, 1, 100); + } + catch (Exception ex) + { + logger.Error(ex); + logger.Error("JOB FAILED. An error has occurred. See above stack trace for details."); + WebConsoleUtility.SetTaskProgress(progress, 1, 1, 100); + } + } + } + } +} diff --git a/src/Unicorn/ControlPanel/Pipelines/UnicornControlPanelRequest/RemoveGutterVerb.cs b/src/Unicorn/ControlPanel/Pipelines/UnicornControlPanelRequest/RemoveGutterVerb.cs new file mode 100644 index 00000000..100ac748 --- /dev/null +++ b/src/Unicorn/ControlPanel/Pipelines/UnicornControlPanelRequest/RemoveGutterVerb.cs @@ -0,0 +1,42 @@ +using System; +using Kamsar.WebConsole; +using Sitecore.Data.Items; +using Sitecore.SecurityModel; +using Unicorn.Logging; + +namespace Unicorn.ControlPanel.Pipelines.UnicornControlPanelRequest +{ + public class RemoveGutterVerb : GutterVerbBase + { + public const string VerbName = "RemoveGutter"; + public RemoveGutterVerb() : base(VerbName, "Remove Gutter Addon") + { + } + protected override void Process(IProgressStatus progress, ILogger additionalLogger) + { + Item gutterItem = GetGutterItem(); + + if (gutterItem == null) + { + additionalLogger.Warn("JOB COMPLETE. Gutter icon does not exist. Aborting..."); + WebConsoleUtility.SetTaskProgress(progress, 1, 1, 100); + return; + } + using (new SecurityDisabler()) + { + try + { + gutterItem.Delete(); + additionalLogger.Info("JOB COMPLETE. Gutter item deleted successfully!"); + WebConsoleUtility.SetTaskProgress(progress, 1, 1, 100); + } + catch (Exception ex) + { + additionalLogger.Error(ex); + additionalLogger.Error("JOB FAILED. An error has occurred. See above stack trace for details."); + WebConsoleUtility.SetTaskProgress(progress, 1, 1, 100); + } + } + } + } +} diff --git a/src/Unicorn/ControlPanel/Pipelines/UnicornControlPanelRequest/RenderControlPanel.cs b/src/Unicorn/ControlPanel/Pipelines/UnicornControlPanelRequest/RenderControlPanel.cs index b34ba2a6..8a2b6b6e 100644 --- a/src/Unicorn/ControlPanel/Pipelines/UnicornControlPanelRequest/RenderControlPanel.cs +++ b/src/Unicorn/ControlPanel/Pipelines/UnicornControlPanelRequest/RenderControlPanel.cs @@ -72,6 +72,8 @@ protected virtual IEnumerable CreateBodyControls(UnicornCo "); + yield return new Addons(); + yield return new QuickReference(); } else diff --git a/src/Unicorn/ControlPanel/UnicornControlPanelPipelineProcessor.cs b/src/Unicorn/ControlPanel/UnicornControlPanelPipelineProcessor.cs index 8e9e8e7c..a20094d8 100644 --- a/src/Unicorn/ControlPanel/UnicornControlPanelPipelineProcessor.cs +++ b/src/Unicorn/ControlPanel/UnicornControlPanelPipelineProcessor.cs @@ -61,7 +61,7 @@ public virtual void ProcessRequest(HttpContext context) if (pipelineArgs.Response == null) { - pipelineArgs.Response = new PlainTextResponse("Not Found", HttpStatusCode.NotFound); + pipelineArgs.Response = new PlainTextResponse("Verb not found. Please check your unicornControlPanelRequest pipeline and compare it against the standard Unicorn.UI.config.", HttpStatusCode.NotFound); } if (securityState.IsAllowed) diff --git a/src/Unicorn/UI/Gutter/TransparentSyncGutter.cs b/src/Unicorn/UI/Gutter/TransparentSyncGutter.cs new file mode 100644 index 00000000..6e9bec61 --- /dev/null +++ b/src/Unicorn/UI/Gutter/TransparentSyncGutter.cs @@ -0,0 +1,22 @@ +using Sitecore.Data.Items; +using Sitecore.Diagnostics; +using Sitecore.Shell.Applications.ContentEditor.Gutters; +using Unicorn.Data.DataProvider; + +namespace Unicorn.UI.Gutter +{ + public class TransparentSyncGutter : GutterRenderer + { + protected override GutterIconDescriptor GetIconDescriptor(Item item) + { + Assert.ArgumentNotNull(item, "item"); + if (item.Statistics.UpdatedBy != UnicornDataProvider.TransparentSyncUpdatedByValue) return null; + var gutterIconDescriptor = new GutterIconDescriptor + { + Icon = "Office/32x32/arrow_circle2.png", + Tooltip = "This item is included by Unicorn Transparent Sync." + }; + return gutterIconDescriptor; + } + } +} diff --git a/src/Unicorn/Unicorn.csproj b/src/Unicorn/Unicorn.csproj index fc2483e2..734bdc9e 100644 --- a/src/Unicorn/Unicorn.csproj +++ b/src/Unicorn/Unicorn.csproj @@ -81,8 +81,12 @@ + + + + @@ -219,6 +223,7 @@ +