From 07229c4bd2fa6568f951d31d3ac69f0dee100d1a Mon Sep 17 00:00:00 2001 From: AndyButland Date: Fri, 3 Jan 2014 17:08:33 +0100 Subject: [PATCH 001/189] Up/down buttons for changing the order of related links --- .../propertyeditors/relatedlinks/relatedlinks.controller.js | 6 ++++++ .../views/propertyeditors/relatedlinks/relatedlinks.html | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index e5e1fa3a14..0153b96d57 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -92,6 +92,12 @@ link.isInternal = !link.isInternal; $event.preventDefault(); }; + + $scope.move = function (index, direction) { + var temp = $scope.model.value[index]; + $scope.model.value[index] = $scope.model.value[index + direction]; + $scope.model.value[index + direction] = temp; + }; function select(data) { if ($scope.currentEditLink != null) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html index 9e260fa2e0..02d5e59e8a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html @@ -40,6 +40,8 @@
+ +
From c9d1123f0ae4d70a562be27e464c533d01c2cc06 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Fri, 28 Feb 2014 14:42:45 +0100 Subject: [PATCH 002/189] Removed related links sorting up/down buttons and updated to use drag-drop --- .../relatedlinks/relatedlinks.controller.js | 41 ++++- .../relatedlinks/relatedlinks.html | 145 +++++++++--------- 2 files changed, 111 insertions(+), 75 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index 0153b96d57..d15284628c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -98,7 +98,42 @@ $scope.model.value[index] = $scope.model.value[index + direction]; $scope.model.value[index + direction] = temp; }; - + + $scope.sortableOptions = { + containment: 'parent', + cursor: 'move', + helper: function (e, ui) { + // When sorting , the cells collapse. This helper fixes that: http://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ + ui.children().each(function () { + $(this).width($(this).width()); + }); + return ui; + }, + items: '> tr', + tolerance: 'pointer', + update: function (e, ui) { + // Get the new and old index for the moved element (using the URL as the identifier) + var newIndex = ui.item.index(); + var movedLinkUrl = ui.item.attr('data-link'); + var originalIndex = getElementIndexByUrl(movedLinkUrl); + + // Move the element in the model + var movedElement = $scope.model.value[originalIndex]; + $scope.model.value.splice(originalIndex, 1); + $scope.model.value.splice(newIndex, 0, movedElement); + } + }; + + function getElementIndexByUrl(url) { + for (var i = 0; i < $scope.model.value.length; i++) { + if ($scope.model.value[i].link === url) { + return i; + } + } + + return -1; + } + function select(data) { if ($scope.currentEditLink != null) { $scope.currentEditLink.internal = data.id; @@ -107,8 +142,6 @@ $scope.newInternal = data.id; $scope.newInternalName = data.name; } - } - - + } }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html index 02d5e59e8a..4320a2f1b8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html @@ -1,80 +1,83 @@  \ No newline at end of file From a4b25c3f84ff08c51e4317852d52a172b0274f50 Mon Sep 17 00:00:00 2001 From: Matthew Sheeley Date: Wed, 4 Jun 2014 15:44:22 -0500 Subject: [PATCH 003/189] Close user dialog on logout --- .../src/views/common/dialogs/user.controller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.controller.js index aee862d9c7..5979962128 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/user.controller.js @@ -25,7 +25,9 @@ angular.module("umbraco") $scope.close(); }); + //perform the path change, if it is successful then the promise will resolve otherwise it will fail + $scope.close(); $location.path("/logout"); }; From 02eff33d295c4e10914235e4d3fc4ba9cfb230ef Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Sun, 8 Jun 2014 13:16:32 +0200 Subject: [PATCH 004/189] Fixes U4-2640 LocalizationService throws exception when getting dictionary items and adds tests for the LocalizationService --- .../Repositories/LanguageRepository.cs | 2 +- src/Umbraco.Core/Services/PackagingService.cs | 5 +- .../Services/LocalizationServiceTests.cs | 204 +++++++++++++++++- 3 files changed, 207 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs index d52a2549f4..0c891d512d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var languageDto = Database.First(sql); + var languageDto = Database.FirstOrDefault(sql); if (languageDto == null) return null; diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index b02b765a37..94360e71ad 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -773,6 +773,7 @@ namespace Umbraco.Core.Services var items = new List(); foreach (var dictionaryItemElement in dictionaryItemElementList.Elements("DictionaryItem")) items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages, parentId)); + return items; } @@ -788,6 +789,7 @@ namespace Umbraco.Core.Services dictionaryItem = CreateNewDictionaryItem(key, dictionaryItemElement, languages, parentId); _localizationService.Save(dictionaryItem); items.Add(dictionaryItem); + items.AddRange(ImportDictionaryItems(dictionaryItemElement, languages, dictionaryItem.Key)); return items; } @@ -817,7 +819,8 @@ namespace Umbraco.Core.Services private static bool DictionaryValueIsNew(IEnumerable translations, XElement valueElement) { return translations.All(t => - String.Compare(t.Language.IsoCode, valueElement.Attribute("LanguageCultureAlias").Value, StringComparison.InvariantCultureIgnoreCase) != 0 + String.Compare(t.Language.IsoCode, valueElement.Attribute("LanguageCultureAlias").Value, + StringComparison.InvariantCultureIgnoreCase) != 0 ); } diff --git a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs index 6b22da6800..3ce63bc500 100644 --- a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs +++ b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs @@ -1,4 +1,8 @@ -using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Services @@ -8,10 +12,17 @@ namespace Umbraco.Tests.Services /// This is more of an integration test as it involves multiple layers /// as well as configuration. /// - [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerFixture)] + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] [TestFixture, RequiresSTA] public class LocalizationServiceTests : BaseServiceTest { + private Guid _parentItemGuidId; + private int _parentItemIntId; + private Guid _childItemGuidId; + private int _childItemIntId; + private int _danishLangId; + private int _englishLangId; + [SetUp] public override void Initialize() { @@ -23,5 +34,194 @@ namespace Umbraco.Tests.Services { base.TearDown(); } + + [Test] + public void LocalizationService_Can_Get_Root_Dictionary_Items() + { + var rootItems = ServiceContext.LocalizationService.GetRootDictionaryItems(); + + Assert.NotNull(rootItems); + Assert.IsTrue(rootItems.Any()); + } + + [Test] + public void LocalizationService_Can_Determint_If_DictionaryItem_Exists() + { + var exists = ServiceContext.LocalizationService.DictionaryItemExists("Parent"); + Assert.IsTrue(exists); + } + + [Test] + public void LocalizationService_Can_Get_All_Languages() + { + var languages = ServiceContext.LocalizationService.GetAllLanguages(); + Assert.NotNull(languages); + Assert.IsTrue(languages.Any()); + Assert.That(languages.Count(), Is.EqualTo(3)); + } + + [Test] + public void LocalizationService_Can_Get_Dictionary_Item_By_Int_Id() + { + var parentItem = ServiceContext.LocalizationService.GetDictionaryItemById(_parentItemIntId); + Assert.NotNull(parentItem); + + var childItem = ServiceContext.LocalizationService.GetDictionaryItemById(_childItemIntId); + Assert.NotNull(childItem); + } + + [Test] + public void LocalizationService_Can_Get_Dictionary_Item_By_Guid_Id() + { + var parentItem = ServiceContext.LocalizationService.GetDictionaryItemById(_parentItemGuidId); + Assert.NotNull(parentItem); + + var childItem = ServiceContext.LocalizationService.GetDictionaryItemById(_childItemGuidId); + Assert.NotNull(childItem); + } + + [Test] + public void LocalizationService_Can_Get_Dictionary_Item_By_Key() + { + var parentItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Parent"); + Assert.NotNull(parentItem); + + var childItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); + Assert.NotNull(childItem); + } + + [Test] + public void LocalizationService_Can_Get_Dictionary_Item_Children() + { + var item = ServiceContext.LocalizationService.GetDictionaryItemChildren(_parentItemGuidId); + Assert.NotNull(item); + Assert.That(item.Count(), Is.EqualTo(1)); + + foreach (var dictionaryItem in item) + { + Assert.IsFalse(string.IsNullOrEmpty(dictionaryItem.ItemKey)); + } + } + + [Test] + public void LocalizationService_Can_Get_Language_By_Culture_Code() + { + var danish = ServiceContext.LocalizationService.GetLanguageByCultureCode("Danish"); + var english = ServiceContext.LocalizationService.GetLanguageByCultureCode("English"); + Assert.NotNull(danish); + Assert.NotNull(english); + } + + [Test] + public void LocalizationService_Can_GetLanguageById() + { + var danish = ServiceContext.LocalizationService.GetLanguageById(_danishLangId); + var english = ServiceContext.LocalizationService.GetLanguageById(_englishLangId); + Assert.NotNull(danish); + Assert.NotNull(english); + } + + [Test] + public void LocalizationService_Can_GetLanguageByIsoCode() + { + var danish = ServiceContext.LocalizationService.GetLanguageByIsoCode("da-DK"); + var english = ServiceContext.LocalizationService.GetLanguageByIsoCode("en-GB"); + Assert.NotNull(danish); + Assert.NotNull(english); + } + + [Test] + public void LocalizationService_Does_Not_Fail_When_Language_Doesnt_Exist() + { + var language = ServiceContext.LocalizationService.GetLanguageByIsoCode("sv-SE"); + Assert.Null(language); + } + + [Test] + public void LocalizationService_Does_Not_Fail_When_DictionaryItem_Doesnt_Exist() + { + var item = ServiceContext.LocalizationService.GetDictionaryItemByKey("RandomKey"); + Assert.Null(item); + } + + [Test] + public void LocalizationService_Can_Delete_Language() + { + var norwegian = new Language("nb-NO") { CultureName = "Norwegian" }; + ServiceContext.LocalizationService.Save(norwegian, 0); + Assert.That(norwegian.HasIdentity, Is.True); + var languageId = norwegian.Id; + + ServiceContext.LocalizationService.Delete(norwegian); + + var language = ServiceContext.LocalizationService.GetLanguageById(languageId); + Assert.Null(language); + } + + [Test] + public void LocalizationService_Can_Delete_DictionaryItem() + { + var item = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); + Assert.NotNull(item); + + ServiceContext.LocalizationService.Delete(item); + + var deletedItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); + Assert.Null(deletedItem); + } + + [Test] + public void LocalizationService_Can_Update_Existing_DictionaryItem() + { + var item = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); + foreach (var translation in item.Translations) + { + translation.Value = translation.Value + "UPDATED"; + } + + ServiceContext.LocalizationService.Save(item); + + var updatedItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child"); + Assert.NotNull(updatedItem); + + foreach (var translation in updatedItem.Translations) + { + Assert.That(translation.Value.EndsWith("UPDATED"), Is.True); + } + } + + public override void CreateTestData() + { + var danish = new Language("da-DK") { CultureName = "Danish" }; + var english = new Language("en-GB") { CultureName = "English" }; + ServiceContext.LocalizationService.Save(danish, 0); + ServiceContext.LocalizationService.Save(english, 0); + _danishLangId = danish.Id; + _englishLangId = english.Id; + + var parentItem = new DictionaryItem("Parent") + { + Translations = new List + { + new DictionaryTranslation(english, "ParentValue"), + new DictionaryTranslation(danish, "ForældreVærdi") + } + }; + ServiceContext.LocalizationService.Save(parentItem); + _parentItemGuidId = parentItem.Key; + _parentItemIntId = parentItem.Id; + + var childItem = new DictionaryItem(parentItem.Key, "Child") + { + Translations = new List + { + new DictionaryTranslation(english, "ChildValue"), + new DictionaryTranslation(danish, "BørnVærdi") + } + }; + ServiceContext.LocalizationService.Save(childItem); + _childItemGuidId = childItem.Key; + _childItemIntId = childItem.Id; + } } } \ No newline at end of file From 45076491cb82488f34563c75ea3b01f942067624 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 29 Nov 2013 16:24:48 +0100 Subject: [PATCH 005/189] Added new PackagingService --- src/Umbraco.Core/Models/IMacro.cs | 2 +- src/Umbraco.Core/Models/IMacroPropertyType.cs | 2 +- src/Umbraco.Core/Packaging/IUnpackHelper.cs | 9 + .../Packaging/PackageImportIssues.cs | 15 + .../Packaging/PackageInstallationSummary.cs | 21 + src/Umbraco.Core/Packaging/PackageMetaData.cs | 18 + src/Umbraco.Core/Packaging/UnpackHelper.cs | 75 +++ src/Umbraco.Core/Services/IMacroService.cs | 2 +- .../Services/IPackageInstallerService.cs | 11 + .../Services/PackageInstallerService.cs | 525 ++++++++++++++++++ src/Umbraco.Core/Services/PackagingService.cs | 32 ++ src/Umbraco.Core/Umbraco.Core.csproj | 13 + src/Umbraco.Core/packages.config | 3 +- 13 files changed, 724 insertions(+), 4 deletions(-) create mode 100644 src/Umbraco.Core/Packaging/IUnpackHelper.cs create mode 100644 src/Umbraco.Core/Packaging/PackageImportIssues.cs create mode 100644 src/Umbraco.Core/Packaging/PackageInstallationSummary.cs create mode 100644 src/Umbraco.Core/Packaging/PackageMetaData.cs create mode 100644 src/Umbraco.Core/Packaging/UnpackHelper.cs create mode 100644 src/Umbraco.Core/Services/IPackageInstallerService.cs create mode 100644 src/Umbraco.Core/Services/PackageInstallerService.cs diff --git a/src/Umbraco.Core/Models/IMacro.cs b/src/Umbraco.Core/Models/IMacro.cs index f28160c345..27435ccb5a 100644 --- a/src/Umbraco.Core/Models/IMacro.cs +++ b/src/Umbraco.Core/Models/IMacro.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; diff --git a/src/Umbraco.Core/Models/IMacroPropertyType.cs b/src/Umbraco.Core/Models/IMacroPropertyType.cs index 7c4bc0057f..a7b6d4bfa9 100644 --- a/src/Umbraco.Core/Models/IMacroPropertyType.cs +++ b/src/Umbraco.Core/Models/IMacroPropertyType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Models +namespace Umbraco.Core.Models { /// /// Defines a PropertyType (plugin) for a Macro diff --git a/src/Umbraco.Core/Packaging/IUnpackHelper.cs b/src/Umbraco.Core/Packaging/IUnpackHelper.cs new file mode 100644 index 0000000000..dbc516e4f9 --- /dev/null +++ b/src/Umbraco.Core/Packaging/IUnpackHelper.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Packaging +{ + public interface IUnpackHelper + { + void UnPack(string sourcefilePath, string destinationDirectory); + string UnPackToTempDirectory(string sourcefilePath); + string ReadSingleTextFile(string sourcefilePath, string fileToRead); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageImportIssues.cs b/src/Umbraco.Core/Packaging/PackageImportIssues.cs new file mode 100644 index 0000000000..80b8a36f3f --- /dev/null +++ b/src/Umbraco.Core/Packaging/PackageImportIssues.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Packaging +{ + public class PackageImportIssues + { + public bool ContainsUnsecureFiles { get { return UnsecureFiles != null && UnsecureFiles.Any(); } } + public IEnumerable UnsecureFiles { get; set; } + public IEnumerable> ConflictingMacroAliases { get; set; } + public IEnumerable> ConflictingTemplateAliases { get; set; } + public IEnumerable> ConflictingStylesheetNames { get; set; } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs b/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs new file mode 100644 index 0000000000..337c961c2e --- /dev/null +++ b/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Xml; + +namespace Umbraco.Core.Packaging +{ + public class PackageInstallationSummary + { + public PackageMetaData MetaData { get; set; } + public IEnumerable DataTypesInstalled { get; set; } + public IEnumerable LanguagesInstalled { get; set; } + public IEnumerable DictionaryItemsInstalled { get; set; } + public IEnumerable MacrosInstalled { get; set; } + public IEnumerable> FilesInstalled { get;set;} + public IEnumerable TemplatesInstalled { get; set; } + public IEnumerable DocumentTypesInstalled { get; set; } + public IEnumerable StylesheetsInstalled { get; set; } + public IEnumerable DocumentsInstalled { get; set; } + public Dictionary PackageInstallActions { get; set; } + public string PackageUninstallActions { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageMetaData.cs b/src/Umbraco.Core/Packaging/PackageMetaData.cs new file mode 100644 index 0000000000..e0f5354912 --- /dev/null +++ b/src/Umbraco.Core/Packaging/PackageMetaData.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Packaging +{ + public class PackageMetaData + { + public string Name { get; set; } + public string Version { get; set; } + public string Url { get; set; } + public string License { get; set; } + public string LicenseUrl { get; set; } + public int ReqMajor { get; set; } + public int ReqMinor { get; set; } + public int ReqPatch { get; set; } + public string AuthorName { get; set; } + public string AuthorUrl { get; set; } + public string Readme { get; set; } + public string Control { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/UnpackHelper.cs b/src/Umbraco.Core/Packaging/UnpackHelper.cs new file mode 100644 index 0000000000..89c462cca3 --- /dev/null +++ b/src/Umbraco.Core/Packaging/UnpackHelper.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; +using ICSharpCode.SharpZipLib.Zip; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Packaging +{ + public class UnpackHelper : IUnpackHelper + { + public string UnPackToTempDirectory(string filePath) + { + string tempDir = IOHelper.MapPath(SystemDirectories.Data) + Path.DirectorySeparatorChar + Guid.NewGuid(); + Directory.CreateDirectory(tempDir); + UnPack(filePath, tempDir); + return tempDir; + } + + public string ReadSingleTextFile(string sourcefilePath, string fileToRead) + { + using (var fs = File.OpenRead(sourcefilePath)) + { + using (var zipStream = new ZipInputStream(fs)) + { + ZipEntry theEntry; + while ((theEntry = zipStream.GetNextEntry()) != null) + { + if (theEntry.Name.EndsWith(fileToRead, StringComparison.CurrentCultureIgnoreCase)) + { + using (var reader = new StreamReader(zipStream)) + { + return reader.ReadToEnd(); + } + } + }; + } + } + + throw new FileNotFoundException("Could not find file in package file " + sourcefilePath, fileToRead); + } + + public void UnPack(string sourcefilePath, string destinationDirectory) + { + // Unzip + using (var fs = File.OpenRead(sourcefilePath)) + { + using (var s = new ZipInputStream(fs)) + { + ZipEntry theEntry; + while ((theEntry = s.GetNextEntry()) != null) + { + string fileName = Path.GetFileName(theEntry.Name); + if (fileName == String.Empty) continue; + + using ( var streamWriter = File.Create(destinationDirectory + Path.DirectorySeparatorChar + fileName)) + { + var data = new byte[2048]; + while (true) + { + var size = s.Read(data, 0, data.Length); + if (size > 0) + { + streamWriter.Write(data, 0, size); + } + else + { + break; + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMacroService.cs b/src/Umbraco.Core/Services/IMacroService.cs index a123fb063e..d88996a6fa 100644 --- a/src/Umbraco.Core/Services/IMacroService.cs +++ b/src/Umbraco.Core/Services/IMacroService.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Services diff --git a/src/Umbraco.Core/Services/IPackageInstallerService.cs b/src/Umbraco.Core/Services/IPackageInstallerService.cs new file mode 100644 index 0000000000..e22384756c --- /dev/null +++ b/src/Umbraco.Core/Services/IPackageInstallerService.cs @@ -0,0 +1,11 @@ +using Umbraco.Core.Packaging; + +namespace Umbraco.Core.Services +{ + public interface IPackageInstallerService : IService + { + PackageInstallationSummary InstallPackageFile(string packageFilePath, int userId); + PackageMetaData GetMetaData(string packageFilePath); + PackageImportIssues FindPackageImportIssues(string packageFilePath); + } +} diff --git a/src/Umbraco.Core/Services/PackageInstallerService.cs b/src/Umbraco.Core/Services/PackageInstallerService.cs new file mode 100644 index 0000000000..e7ebf1135b --- /dev/null +++ b/src/Umbraco.Core/Services/PackageInstallerService.cs @@ -0,0 +1,525 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Web.Hosting; +using System.Xml; +using System.Xml.Linq; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Packaging; +using File = System.IO.File; + +namespace Umbraco.Core.Services +{ + public class PackageInstallerService : IPackageInstallerService + { + private const string PACKAGE_XML_FILE_NAME = "package.xml"; + private readonly IFileService _fileService; + private readonly IMacroService _macroService; + private readonly IPackagingService _packagingService; + private readonly IUnpackHelper _unpackHelper; + + public PackageInstallerService(IPackagingService packagingService, IMacroService macroService, + IFileService fileService, IUnpackHelper unpackHelper) + { + _packagingService = packagingService; + if (unpackHelper != null) _unpackHelper = unpackHelper; + else throw new ArgumentNullException("unpackHelper"); + if (fileService != null) _fileService = fileService; + else throw new ArgumentNullException("fileService"); + if (macroService != null) _macroService = macroService; + else throw new ArgumentNullException("macroService"); + } + + + public PackageInstallationSummary InstallPackageFile(string packageFilePath, int userId) + { + FileInfo fi = GetPackageFileInfo(packageFilePath); + string tempDir = null; + try + { + tempDir = _unpackHelper.UnPackToTempDirectory(fi.FullName); + return InstallFromDirectory(tempDir, userId); + } + finally + { + if (string.IsNullOrEmpty(tempDir) == false && Directory.Exists(tempDir)) + { + Directory.Delete(tempDir, true); + } + } + } + + public PackageMetaData GetMetaData(string packageFilePath) + { + XmlElement documentElement = GetConfigXmlDocFromPackageFile(packageFilePath); + + return GetMetaData(documentElement); + } + + public PackageImportIssues FindPackageImportIssues(string packageFilePath) + { + XmlElement documentElement = GetConfigXmlDocFromPackageFile(packageFilePath); + return FindImportIssues(documentElement); + } + + + private FileInfo GetPackageFileInfo(string packageFilePath) + { + if (string.IsNullOrEmpty(packageFilePath)) + { + throw new ArgumentNullException("packageFilePath"); + } + + var fi = new FileInfo(packageFilePath); + + if (fi.Exists == false) + { + throw new Exception("Error - file not found. Could find file named '" + packageFilePath + "'"); + } + + + // Check if the file is a valid package + if (fi.Extension.Equals(".umb", StringComparison.InvariantCultureIgnoreCase) == false) + { + throw new Exception( + "Error - file isn't a package (doesn't have a .umb extension). Check if the file automatically got named '.zip' upon download."); + } + + return fi; + } + + + private XmlElement GetConfigXmlDocFromPackageFile(string packageFilePath) + { + FileInfo packageFileInfo = GetPackageFileInfo(packageFilePath); + + string configXmlContent = _unpackHelper.ReadSingleTextFile(packageFileInfo.FullName, PACKAGE_XML_FILE_NAME); + + var packageConfig = new XmlDocument(); + + packageConfig.LoadXml(configXmlContent); + XmlElement documentElement = packageConfig.DocumentElement; + return documentElement; + } + + + private PackageInstallationSummary InstallFromDirectory(string packageDir, int userId) + { + XmlElement configXml = GetConfigXmlDocFromPackageDirectory(packageDir); + + return new PackageInstallationSummary + { + MetaData = GetMetaData(configXml), + DataTypesInstalled = InstallDataTypes(configXml, userId), + LanguagesInstalled = InstallLanguages(configXml, userId), + DictionaryItemsInstalled = InstallDictionaryItems(configXml, userId), + MacrosInstalled = InstallMacros(configXml, userId), + FilesInstalled = InstallFiles(packageDir, configXml), + TemplatesInstalled = InstallTemplats(configXml, userId), + DocumentTypesInstalled = InstallDocumentTypes(configXml, userId), + StylesheetsInstalled = InstallStylesheets(configXml, userId), + DocumentsInstalled = InstallDocuments(configXml, userId), + PackageInstallActions = GetInstallActions(configXml), + PackageUninstallActions = GetUninstallActions(configXml) + }; + } + + private static string GetUninstallActions(XmlElement configXml) + { + var actions = new StringBuilder(); + //saving the uninstall actions untill the package is uninstalled. + XmlNodeList xmlNodeList = configXml.SelectNodes("Actions/Action [@undo != false()]"); + if (xmlNodeList != null) + { + foreach (XmlNode n in xmlNodeList) + { + actions.Append(n.OuterXml); + } + } + return actions.ToString(); + } + + private static Dictionary GetInstallActions(XmlElement configXml) + { + XmlNodeList xmlNodeList = configXml.SelectNodes("Actions/Action [@runat != 'uninstall']"); + Dictionary retVal2; + if (xmlNodeList != null) + { + retVal2 = xmlNodeList.OfType() + .Select(an => new + { + alias = + an.Attributes == null + ? null + : an.Attributes["alias"] == null ? null : an.Attributes["alias"].Value, + node = an + }).Where(x => string.IsNullOrEmpty(x.alias) == false) + .ToDictionary(x => x.alias, x => x.node); + } + else + { + retVal2 = new Dictionary(); + } + return retVal2; + } + + private IEnumerable InstallDocuments(XmlElement configXml, int userId = 0) + { + + var rootElement = configXml.GetXElement(); + var documentElement = rootElement.Descendants("DocumentSet").FirstOrDefault(); + if (documentElement != null) + { + IEnumerable content = _packagingService.ImportContent(documentElement, -1, userId); + return content.Select(c => c.Id); + + } + return Enumerable.Empty(); + } + + private IEnumerable InstallStylesheets(XmlElement configXml, int userId = 0) + { + XmlNodeList xmlNodeList = configXml.SelectNodes("Stylesheets/Stylesheet"); + if (xmlNodeList == null) + { + return Enumerable.Empty(); + } + + var retVal = new List(); + + foreach (var element in xmlNodeList.OfType().Select(n => n.GetXElement())) + { + retVal.AddRange(_packagingService.ImportStylesheets(element, userId).Select(f => f.Id)); + + } + return retVal; + } + + private IEnumerable InstallDocumentTypes(XmlElement configXml, int userId = 0) + { + XElement rootElement = configXml.GetXElement(); + //Check whether the root element is a doc type rather then a complete package + XElement docTypeElement = rootElement.Name.LocalName.Equals("DocumentType") || + rootElement.Name.LocalName.Equals("DocumentTypes") + ? rootElement + : rootElement.Descendants("DocumentTypes").FirstOrDefault(); + if (docTypeElement != null) + { + IEnumerable contentTypes = _packagingService.ImportContentTypes(docTypeElement, userId); + return contentTypes.Select(ct => ct.Id); + } + + return Enumerable.Empty(); + } + + private IEnumerable InstallTemplats(XmlElement configXml, int userId = 0) + { + XElement templateElement = configXml.GetXElement().Descendants("Templates").FirstOrDefault(); + IEnumerable templates = _packagingService.ImportTemplates(templateElement, userId); + return templates.Select(t => t.Id); + } + + + private static IEnumerable> InstallFiles(string packageDir, XmlElement configXml) + { + string basePath = HostingEnvironment.ApplicationPhysicalPath; + + XmlNodeList xmlNodeList = configXml.SelectNodes("//file"); + + var installedFiles = new List>(); + if (xmlNodeList != null) + { + foreach (XmlNode n in xmlNodeList) + { + string orgPath = XmlHelper.GetNodeValue(n.SelectSingleNode("orgPath")); + string guid = XmlHelper.GetNodeValue(n.SelectSingleNode("guid")); + string orgName = XmlHelper.GetNodeValue(n.SelectSingleNode("orgName")); + + String destPath = GetFileName(basePath, orgPath); + String sourceFile = GetFileName(packageDir, guid); + String destFile = GetFileName(destPath, orgName); + + if (Directory.Exists(destPath) == false) Directory.CreateDirectory(destPath); + + bool overrideExisting = File.Exists(destFile); + + File.Copy(sourceFile, destFile, true); + + installedFiles.Add(new KeyValuePair(orgPath + "/" + orgName, overrideExisting)); + } + } + return installedFiles; + } + + private IEnumerable InstallMacros(XmlElement configXml, int userId = 0) + { + var xmlNodeList = configXml.SelectNodes("//macro"); + if (xmlNodeList == null) + { + return Enumerable.Empty(); + } + + var retVal = new List(); + foreach (var n in xmlNodeList.OfType().Select(n => n.GetXElement())) + { + retVal.AddRange(_packagingService.ImportMacros(n, userId).Select(m => m.Id)); + } + + return retVal; + + } + + private IEnumerable InstallDictionaryItems(XmlElement configXml, int userId = 0) + { + var xmlNodeList = configXml.SelectNodes("./DictionaryItems/DictionaryItem"); + if (xmlNodeList == null) { return Enumerable.Empty(); } + + var retVal = new List(); + foreach (var n in xmlNodeList.OfType().Select(n => n.GetXElement())) + { + retVal.AddRange(_packagingService.ImportDictionaryItems(n, userId).Select(di => di.Id)); + } + + return retVal; + } + + private IEnumerable InstallLanguages(XmlElement configXml, int userId = 0) + { + XmlNodeList xmlNodeList = configXml.SelectNodes("//Language"); + if (xmlNodeList == null) { return Enumerable.Empty(); } + var retVal = new List(); + foreach (var n in xmlNodeList.OfType().Select(n => n.GetXElement())) + { + retVal.AddRange(_packagingService.ImportLanguage(n, userId).Select(l => l.Id)); + } + return retVal; + } + + private IEnumerable InstallDataTypes(XmlElement configXml, int userId = 0) + { + XElement rootElement = configXml.GetXElement(); + XElement dataTypeElement = rootElement.Descendants("DataTypes").FirstOrDefault(); + + if (dataTypeElement != null) + { + IEnumerable dataTypeDefinitions = + _packagingService.ImportDataTypeDefinitions(dataTypeElement, userId); + return dataTypeDefinitions.Select(dtd => dtd.Id); + } + return Enumerable.Empty(); + } + + private static XmlElement GetConfigXmlDocFromPackageDirectory(string packageDir) + { + string packageXmlPath = Path.Combine(packageDir, PACKAGE_XML_FILE_NAME); + + if (File.Exists(packageXmlPath) == false) + { + throw new FileNotFoundException("Could not find " + PACKAGE_XML_FILE_NAME + " in package"); + } + + var packageConfig = new XmlDocument(); + packageConfig.Load(packageXmlPath); + + if (packageConfig.DocumentElement == null) + { + throw new Exception("Invalid package.xml could not load XML"); + } + + + XmlElement xmlRoot = packageConfig.DocumentElement; + return xmlRoot; + } + + + private PackageImportIssues FindImportIssues(XmlElement documentElement) + { + XmlNodeList fileNotes = documentElement.SelectNodes("//file"); + XmlNodeList macroNotes = documentElement.SelectNodes("//macro"); + XmlNodeList templateNotes = documentElement.SelectNodes("Templates/Template"); + XmlNodeList stylesheetNotes = documentElement.SelectNodes("Stylesheets/Stylesheet"); + + var packageImportIssues = new PackageImportIssues + { + UnsecureFiles = FindUnsecureFiles(fileNotes), + ConflictingMacroAliases = FindConflictingMacroAliases(macroNotes), + ConflictingTemplateAliases = FindConflictingTemplateAliases(templateNotes), + ConflictingStylesheetNames = FindConflictingStylesheetNames(stylesheetNotes) + }; + + return packageImportIssues; + } + + private IEnumerable FindUnsecureFiles(XmlNodeList fileNotes) + { + return fileNotes == null + ? Enumerable.Empty() + : fileNotes + .OfType() + .Where(FileNodeIsUnsecure) + .Select(n => XmlHelper.GetNodeValue(n.SelectSingleNode("orgName"))); + } + + private IEnumerable> FindConflictingStylesheetNames(XmlNodeList stylesheetNotes) + { + return stylesheetNotes == null + ? Enumerable.Empty>() + : stylesheetNotes.OfType() + .Select(n => + { + string name = XmlHelper.GetNodeValue(n.SelectSingleNode("Name")); + Stylesheet existingStilesheet = _fileService.GetStylesheetByName(name); + + + // Dont know what to put in here... existing path whas the bedst i culd come up with + string existingFilePath = existingStilesheet == null ? null : existingStilesheet.Path; + + + return new KeyValuePair(name, existingFilePath); + }) + .Where(kv => kv.Value != null); + } + + private IEnumerable> FindConflictingTemplateAliases(XmlNodeList templateNotes) + { + return templateNotes == null + ? Enumerable.Empty>() + : templateNotes.OfType() + .Select(n => + { + string alias = XmlHelper.GetNodeValue(n.SelectSingleNode("Alias")); + var existingTemplate = _fileService.GetTemplate(alias) as Template; + + string existingName = existingTemplate == null ? null : existingTemplate.Name; + + return new KeyValuePair(alias, existingName); + }) + .Where(kv => kv.Value != null); + } + + private IEnumerable> FindConflictingMacroAliases(XmlNodeList macroNotes) + { + return macroNotes == null + ? Enumerable.Empty>() + : macroNotes + .OfType() + .Select(n => + { + string alias = XmlHelper.GetNodeValue(n.SelectSingleNode("alias")); + IMacro macro = _macroService.GetByAlias(alias); + string eksistingName = macro == null ? null : macro.Name; + + return new KeyValuePair(alias, eksistingName); + }) + .Where(kv => kv.Value != null); + } + + + private bool FileNodeIsUnsecure(XmlNode fileNode) + { + string basePath = HostingEnvironment.ApplicationPhysicalPath; + string destPath = GetFileName(basePath, XmlHelper.GetNodeValue(fileNode.SelectSingleNode("orgPath"))); + + if (destPath.ToLower().Contains(IOHelper.DirSepChar + "app_code")) return true; + if (destPath.ToLower().Contains(IOHelper.DirSepChar + "bin")) return true; + + string destFile = GetFileName(destPath, XmlHelper.GetNodeValue(fileNode.SelectSingleNode("orgName"))); + + return destFile.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase); + } + + + private PackageMetaData GetMetaData(XmlElement element) + { + XmlNode selectSingleNode = element.SelectSingleNode("/umbPackage/info/package/license"); + + string licenseUrl = string.Empty; + if (selectSingleNode != null && selectSingleNode.Attributes != null) + { + XmlNode attribute = selectSingleNode.Attributes.GetNamedItem("url"); + licenseUrl = attribute == null ? string.Empty : attribute.Value ?? string.Empty; + } + + string reqMajorStr = + XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/requirements/major")); + string reqMinorStr = + XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/requirements/minor")); + string reqPatchStr = + XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/requirements/patch")); + + int val; + + + return new PackageMetaData + { + Name = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/name")), + Version = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/version")), + Url = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/url")), + License = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/license")), + LicenseUrl = licenseUrl, + AuthorName = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/author/name")), + AuthorUrl = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/author/website")), + Readme = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/readme")), + ReqMajor = int.TryParse(reqMajorStr, out val) ? val : 0, + ReqMinor = int.TryParse(reqMinorStr, out val) ? val : 0, + ReqPatch = int.TryParse(reqPatchStr, out val) ? val : 0, + Control = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/control")) + }; + } + + + /// + /// Gets the name of the file in the specified path. + /// Corrects possible problems with slashes that would result from a simple concatenation. + /// Can also be used to concatenate paths. + /// + /// The path. + /// Name of the file. + /// The name of the file in the specified path. + private static String GetFileName(String path, string fileName) + { + // virtual dir support + fileName = IOHelper.FindFile(fileName); + + if (path.Contains("[$")) + { + //this is experimental and undocumented... + path = path.Replace("[$UMBRACO]", SystemDirectories.Umbraco); + path = path.Replace("[$UMBRACOCLIENT]", SystemDirectories.UmbracoClient); + path = path.Replace("[$CONFIG]", SystemDirectories.Config); + path = path.Replace("[$DATA]", SystemDirectories.Data); + } + + //to support virtual dirs we try to lookup the file... + path = IOHelper.FindFile(path); + + + Debug.Assert(path != null && path.Length >= 1); + Debug.Assert(fileName != null && fileName.Length >= 1); + + path = path.Replace('/', '\\'); + fileName = fileName.Replace('/', '\\'); + + // Does filename start with a slash? Does path end with one? + bool fileNameStartsWithSlash = (fileName[0] == Path.DirectorySeparatorChar); + bool pathEndsWithSlash = (path[path.Length - 1] == Path.DirectorySeparatorChar); + + // Path ends with a slash + if (pathEndsWithSlash) + { + if (fileNameStartsWithSlash == false) + // No double slash, just concatenate + return path + fileName; + return path + fileName.Substring(1); + } + if (fileNameStartsWithSlash) + // Required slash specified, just concatenate + return path + fileName; + return path + Path.DirectorySeparatorChar + fileName; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 475d5d185b..3ef9d2691b 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -1474,6 +1474,38 @@ namespace Umbraco.Core.Services return templates; } + public IEnumerable ImportLanguage(XElement element, int userId = 0) + { + throw new NotImplementedException(); + } + + public IEnumerable ImportStylesheets(XElement element, int userId = 0) + { + throw new NotImplementedException(); + + + foreach (XmlNode n in xmlNodeList.OfType()) + { + StyleSheet s = StyleSheet.MakeNew( + currentUser, + XmlHelper.GetNodeValue(n.SelectSingleNode("Name")), + XmlHelper.GetNodeValue(n.SelectSingleNode("FileName")), + XmlHelper.GetNodeValue(n.SelectSingleNode("Content"))); + + foreach (XmlNode prop in n.SelectNodes("Properties/Property")) + { + StylesheetProperty sp = StylesheetProperty.MakeNew( + xmlHelper.GetNodeValue(prop.SelectSingleNode("Name")), + s, + currentUser); + sp.Alias = XmlHelper.GetNodeValue(prop.SelectSingleNode("Alias")); + sp.value = XmlHelper.GetNodeValue(prop.SelectSingleNode("Value")); + } + s.saveCssToFile(); + s.Save(); + } + } + private bool IsMasterPageSyntax(string code) { return Regex.IsMatch(code, @"<%@\s*Master", RegexOptions.IgnoreCase) || diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index bc0fdff936..b567905627 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -47,6 +47,9 @@ ..\packages\HtmlAgilityPack.1.4.6\lib\Net45\HtmlAgilityPack.dll + + ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + False ..\packages\log4net-mediumtrust.2.0.0\lib\log4net.dll @@ -361,6 +364,8 @@ + + @@ -1015,6 +1020,7 @@ + @@ -1030,6 +1036,7 @@ + @@ -1041,9 +1048,12 @@ + + + @@ -1053,6 +1063,9 @@ + + + diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config index 55d4c4b8ef..510e3ce0ba 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -10,7 +10,8 @@ - + + \ No newline at end of file From 70fcd6fab75ac40526e4c4aec1fbb000a0cf8a3e Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Wed, 12 Feb 2014 14:59:14 +0100 Subject: [PATCH 006/189] more updates --- .../Packaging/PackageInstallationSummary.cs | 3 +- .../Services/PackageInstallerService.cs | 497 ++++++++---------- src/Umbraco.Core/Services/PackagingService.cs | 48 +- 3 files changed, 255 insertions(+), 293 deletions(-) diff --git a/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs b/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs index 337c961c2e..2902e09fd1 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Xml; +using System.Xml.Linq; namespace Umbraco.Core.Packaging { @@ -15,7 +16,7 @@ namespace Umbraco.Core.Packaging public IEnumerable DocumentTypesInstalled { get; set; } public IEnumerable StylesheetsInstalled { get; set; } public IEnumerable DocumentsInstalled { get; set; } - public Dictionary PackageInstallActions { get; set; } + public IEnumerable> PackageInstallActions { get; set; } public string PackageUninstallActions { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackageInstallerService.cs b/src/Umbraco.Core/Services/PackageInstallerService.cs index e7ebf1135b..d7f2b7063b 100644 --- a/src/Umbraco.Core/Services/PackageInstallerService.cs +++ b/src/Umbraco.Core/Services/PackageInstallerService.cs @@ -3,10 +3,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using System.Web.Hosting; -using System.Xml; using System.Xml.Linq; +using System.Xml.XPath; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Packaging; @@ -55,15 +54,23 @@ namespace Umbraco.Core.Services public PackageMetaData GetMetaData(string packageFilePath) { - XmlElement documentElement = GetConfigXmlDocFromPackageFile(packageFilePath); + var documentElement = GetConfigXmlDocFromPackageFile(packageFilePath); - return GetMetaData(documentElement); + var rootElement = documentElement.Element("umbPackage"); + if (rootElement == null) { throw new ArgumentException("xml does not have a root node called \"umbPackage\"", packageFilePath); } + + return GetMetaData(rootElement); } public PackageImportIssues FindPackageImportIssues(string packageFilePath) { - XmlElement documentElement = GetConfigXmlDocFromPackageFile(packageFilePath); - return FindImportIssues(documentElement); + var documentElement = GetConfigXmlDocFromPackageFile(packageFilePath); + + var rootElement = documentElement.Element("umbPackage"); + + if (rootElement == null) { throw new ArgumentException("File does not have a root node called \"umbPackage\"", packageFilePath); } + + return FindImportIssues(rootElement); } @@ -93,381 +100,325 @@ namespace Umbraco.Core.Services } - private XmlElement GetConfigXmlDocFromPackageFile(string packageFilePath) + private XDocument GetConfigXmlDocFromPackageFile(string packageFilePath) { FileInfo packageFileInfo = GetPackageFileInfo(packageFilePath); string configXmlContent = _unpackHelper.ReadSingleTextFile(packageFileInfo.FullName, PACKAGE_XML_FILE_NAME); - var packageConfig = new XmlDocument(); - - packageConfig.LoadXml(configXmlContent); - XmlElement documentElement = packageConfig.DocumentElement; - return documentElement; + var packageConfig = XDocument.Parse(configXmlContent); + return packageConfig; } private PackageInstallationSummary InstallFromDirectory(string packageDir, int userId) { - XmlElement configXml = GetConfigXmlDocFromPackageDirectory(packageDir); + var configXml = GetConfigXmlDocFromPackageDirectory(packageDir); + var rootElement = configXml.XPathSelectElement("/umbPackage"); + if (rootElement == null) { throw new ArgumentException("File does not have a root node called \"umbPackage\"", packageDir); } + + var dataTypes = rootElement.Element("DataTypes"); + var languages = rootElement.Element("Languages"); + var dictionaryItems = rootElement.Element("DictionaryItems"); + var macroes = rootElement.Element("Macros"); + var files = rootElement.Element("Files"); + var templates = rootElement.Element("Templates"); + var documentTypes = rootElement.Element("DocumentTypes"); + var styleSheets = rootElement.Element("Stylesheets"); + var documentSet = rootElement.Element("DocumentSet"); + var actions = rootElement.Element("Actions"); return new PackageInstallationSummary { - MetaData = GetMetaData(configXml), - DataTypesInstalled = InstallDataTypes(configXml, userId), - LanguagesInstalled = InstallLanguages(configXml, userId), - DictionaryItemsInstalled = InstallDictionaryItems(configXml, userId), - MacrosInstalled = InstallMacros(configXml, userId), - FilesInstalled = InstallFiles(packageDir, configXml), - TemplatesInstalled = InstallTemplats(configXml, userId), - DocumentTypesInstalled = InstallDocumentTypes(configXml, userId), - StylesheetsInstalled = InstallStylesheets(configXml, userId), - DocumentsInstalled = InstallDocuments(configXml, userId), - PackageInstallActions = GetInstallActions(configXml), - PackageUninstallActions = GetUninstallActions(configXml) + MetaData = GetMetaData(rootElement), + DataTypesInstalled = dataTypes == null ? Enumerable.Empty() : InstallDataTypes(dataTypes, userId), + LanguagesInstalled = languages == null ? Enumerable.Empty() : InstallLanguages(languages, userId), + DictionaryItemsInstalled = dictionaryItems == null ? Enumerable.Empty() : InstallDictionaryItems(dictionaryItems, userId), + MacrosInstalled = macroes == null ? Enumerable.Empty() : InstallMacros(macroes, userId), + FilesInstalled = packageDir == null ? Enumerable.Empty>() : InstallFiles(packageDir, files), + TemplatesInstalled = templates == null ? Enumerable.Empty() : InstallTemplats(templates, userId), + DocumentTypesInstalled = documentTypes == null ? Enumerable.Empty() : InstallDocumentTypes(documentTypes, userId), + StylesheetsInstalled = styleSheets == null ? Enumerable.Empty() : InstallStylesheets(styleSheets, userId), + DocumentsInstalled = documentSet == null ? Enumerable.Empty() : InstallDocuments(documentSet, userId), + PackageInstallActions = actions == null ? Enumerable.Empty>() : GetInstallActions(actions), + PackageUninstallActions = actions == null ? string.Empty : GetUninstallActions(actions) }; } - private static string GetUninstallActions(XmlElement configXml) + private static string GetUninstallActions(XElement actionsElement) { - var actions = new StringBuilder(); //saving the uninstall actions untill the package is uninstalled. - XmlNodeList xmlNodeList = configXml.SelectNodes("Actions/Action [@undo != false()]"); - if (xmlNodeList != null) - { - foreach (XmlNode n in xmlNodeList) + return actionsElement.Elements("Action").Where(e => e.HasAttributes && e.Attribute("undo") != null && e.Attribute("undo").Value.Equals("false()", StringComparison.InvariantCultureIgnoreCase) == false) // SelectNodes("Actions/Action [@undo != false()]"); + .Select(m => m.Value).Aggregate((workingSentence, next) => next + workingSentence); + } + + private static IEnumerable> GetInstallActions(XElement actionsElement) + { + if (actionsElement == null) { return Enumerable.Empty>(); } + + if ("Actions".Equals(actionsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Actions\" as root", "actionsElement"); } + + return actionsElement.Elements("Action") + .Where( + e => + e.HasAttributes && + (e.Attribute("runat") == null || + e.Attribute("runat").Value.Equals("uninstall", StringComparison.InvariantCultureIgnoreCase) == + false)) // .SelectNodes("Actions/Action [@runat != 'uninstall']") + .Select(elemet => { - actions.Append(n.OuterXml); - } - } - return actions.ToString(); + var aliasAttr = elemet.Attribute("alias"); + if (aliasAttr == null) + throw new ArgumentException("missing alias atribute in alias element", "actionsElement"); + return new {elemet, alias = aliasAttr.Value}; + }).ToDictionary(x => x.alias, x => x.elemet); } - private static Dictionary GetInstallActions(XmlElement configXml) + private IEnumerable InstallDocuments(XElement documentsElement, int userId = 0) { - XmlNodeList xmlNodeList = configXml.SelectNodes("Actions/Action [@runat != 'uninstall']"); - Dictionary retVal2; - if (xmlNodeList != null) - { - retVal2 = xmlNodeList.OfType() - .Select(an => new - { - alias = - an.Attributes == null - ? null - : an.Attributes["alias"] == null ? null : an.Attributes["alias"].Value, - node = an - }).Where(x => string.IsNullOrEmpty(x.alias) == false) - .ToDictionary(x => x.alias, x => x.node); - } - else - { - retVal2 = new Dictionary(); - } - return retVal2; + if ("DocumentSet".Equals(documentsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"DocumentSet\" as root", "documentsElement"); } + return _packagingService.ImportContent(documentsElement, -1, userId).Select(c => c.Id); } - private IEnumerable InstallDocuments(XmlElement configXml, int userId = 0) + private IEnumerable InstallStylesheets(XElement styleSheetsElement, int userId = 0) { - - var rootElement = configXml.GetXElement(); - var documentElement = rootElement.Descendants("DocumentSet").FirstOrDefault(); - if (documentElement != null) - { - IEnumerable content = _packagingService.ImportContent(documentElement, -1, userId); - return content.Select(c => c.Id); - - } - return Enumerable.Empty(); + if ("Stylesheets".Equals(styleSheetsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Stylesheets\" as root", "styleSheetsElement"); } + return _packagingService.ImportStylesheets(styleSheetsElement, userId).Select(f => f.Id); } - private IEnumerable InstallStylesheets(XmlElement configXml, int userId = 0) + private IEnumerable InstallDocumentTypes(XElement documentTypes, int userId = 0) { - XmlNodeList xmlNodeList = configXml.SelectNodes("Stylesheets/Stylesheet"); - if (xmlNodeList == null) + if ("DocumentTypes".Equals(documentTypes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { - return Enumerable.Empty(); + if ("DocumentType".Equals(documentTypes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) + throw new ArgumentException("Must be \"DocumentTypes\" as root", "documentTypes"); + + documentTypes = new XElement("DocumentTypes", documentTypes); } - var retVal = new List(); - - foreach (var element in xmlNodeList.OfType().Select(n => n.GetXElement())) - { - retVal.AddRange(_packagingService.ImportStylesheets(element, userId).Select(f => f.Id)); - - } - return retVal; + return _packagingService.ImportContentTypes(documentTypes, userId).Select(ct => ct.Id); } - private IEnumerable InstallDocumentTypes(XmlElement configXml, int userId = 0) + private IEnumerable InstallTemplats(XElement templateElement, int userId = 0) { - XElement rootElement = configXml.GetXElement(); - //Check whether the root element is a doc type rather then a complete package - XElement docTypeElement = rootElement.Name.LocalName.Equals("DocumentType") || - rootElement.Name.LocalName.Equals("DocumentTypes") - ? rootElement - : rootElement.Descendants("DocumentTypes").FirstOrDefault(); - if (docTypeElement != null) - { - IEnumerable contentTypes = _packagingService.ImportContentTypes(docTypeElement, userId); - return contentTypes.Select(ct => ct.Id); - } - - return Enumerable.Empty(); - } - - private IEnumerable InstallTemplats(XmlElement configXml, int userId = 0) - { - XElement templateElement = configXml.GetXElement().Descendants("Templates").FirstOrDefault(); - IEnumerable templates = _packagingService.ImportTemplates(templateElement, userId); - return templates.Select(t => t.Id); + if ("Templates".Equals(templateElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "templateElement"); } + return _packagingService.ImportTemplates(templateElement, userId).Select(t => t.Id); } - private static IEnumerable> InstallFiles(string packageDir, XmlElement configXml) + private static IEnumerable> InstallFiles(string packageDir, XElement filesElement) { + if ("Files".Equals(filesElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("root element must be \"Files\"", "filesElement"); } + string basePath = HostingEnvironment.ApplicationPhysicalPath; + + var xmlNodeList = filesElement.Elements("file"); - XmlNodeList xmlNodeList = configXml.SelectNodes("//file"); - - var installedFiles = new List>(); - if (xmlNodeList != null) + return xmlNodeList.Select(e => { - foreach (XmlNode n in xmlNodeList) + var orgPathElement = e.Element("orgPath"); + if (orgPathElement == null) { throw new ArgumentException("Missing element \"orgPath\"", "filesElement"); } + + var guidElement = e.Element("guid"); + if (guidElement == null) { throw new ArgumentException("Missing element \"guid\"", "filesElement"); } + + var orgNameElement = e.Element("orgName"); + if (orgNameElement == null) { throw new ArgumentException("Missing element \"orgName\"", "filesElement"); } + + + var destPath = GetFileName(basePath, orgPathElement.Value); + var sourceFile = GetFileName(packageDir, guidElement.Value); + var destFile = GetFileName(destPath, orgNameElement.Value); + + if (Directory.Exists(destPath) == false) Directory.CreateDirectory(destPath); + + var existingOverrided = File.Exists(destFile); + + File.Copy(sourceFile, destFile, true); + + return new KeyValuePair(orgPathElement.Value + "/" + orgNameElement.Value, existingOverrided); + }); + } + + private IEnumerable InstallMacros(XElement macroElements, int userId = 0) + { + if ("Macros".Equals(macroElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "macroElements"); } + return _packagingService.ImportMacros(macroElements, userId).Select(m => m.Id); + } + + private IEnumerable InstallDictionaryItems(XElement dictionaryItemsElement, int userId = 0) + { + if ("DictionaryItems".Equals(dictionaryItemsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "dictionaryItemsElement"); } + return _packagingService.ImportDictionaryItems(dictionaryItemsElement, userId).Select(di => di.Id); + } + + private IEnumerable InstallLanguages(XElement languageElement, int userId = 0) + { + if ("Languages".Equals(languageElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "languageElement"); } + return _packagingService.ImportLanguage(languageElement, userId).Select(l => l.Id); + } + + private IEnumerable InstallDataTypes(XElement dataTypeElements, int userId = 0) + { + if ("DataTypes".Equals(dataTypeElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == + false) + { + + if ("DataType".Equals(dataTypeElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == + false) { - string orgPath = XmlHelper.GetNodeValue(n.SelectSingleNode("orgPath")); - string guid = XmlHelper.GetNodeValue(n.SelectSingleNode("guid")); - string orgName = XmlHelper.GetNodeValue(n.SelectSingleNode("orgName")); - - String destPath = GetFileName(basePath, orgPath); - String sourceFile = GetFileName(packageDir, guid); - String destFile = GetFileName(destPath, orgName); - - if (Directory.Exists(destPath) == false) Directory.CreateDirectory(destPath); - - bool overrideExisting = File.Exists(destFile); - - File.Copy(sourceFile, destFile, true); - - installedFiles.Add(new KeyValuePair(orgPath + "/" + orgName, overrideExisting)); + throw new ArgumentException("Must be \"Templates\" as root", "dataTypeElements"); } } - return installedFiles; + return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId).Select(e => e.Id); } - private IEnumerable InstallMacros(XmlElement configXml, int userId = 0) - { - var xmlNodeList = configXml.SelectNodes("//macro"); - if (xmlNodeList == null) - { - return Enumerable.Empty(); - } - - var retVal = new List(); - foreach (var n in xmlNodeList.OfType().Select(n => n.GetXElement())) - { - retVal.AddRange(_packagingService.ImportMacros(n, userId).Select(m => m.Id)); - } - - return retVal; - - } - - private IEnumerable InstallDictionaryItems(XmlElement configXml, int userId = 0) - { - var xmlNodeList = configXml.SelectNodes("./DictionaryItems/DictionaryItem"); - if (xmlNodeList == null) { return Enumerable.Empty(); } - - var retVal = new List(); - foreach (var n in xmlNodeList.OfType().Select(n => n.GetXElement())) - { - retVal.AddRange(_packagingService.ImportDictionaryItems(n, userId).Select(di => di.Id)); - } - - return retVal; - } - - private IEnumerable InstallLanguages(XmlElement configXml, int userId = 0) - { - XmlNodeList xmlNodeList = configXml.SelectNodes("//Language"); - if (xmlNodeList == null) { return Enumerable.Empty(); } - var retVal = new List(); - foreach (var n in xmlNodeList.OfType().Select(n => n.GetXElement())) - { - retVal.AddRange(_packagingService.ImportLanguage(n, userId).Select(l => l.Id)); - } - return retVal; - } - - private IEnumerable InstallDataTypes(XmlElement configXml, int userId = 0) - { - XElement rootElement = configXml.GetXElement(); - XElement dataTypeElement = rootElement.Descendants("DataTypes").FirstOrDefault(); - - if (dataTypeElement != null) - { - IEnumerable dataTypeDefinitions = - _packagingService.ImportDataTypeDefinitions(dataTypeElement, userId); - return dataTypeDefinitions.Select(dtd => dtd.Id); - } - return Enumerable.Empty(); - } - - private static XmlElement GetConfigXmlDocFromPackageDirectory(string packageDir) + private static XDocument GetConfigXmlDocFromPackageDirectory(string packageDir) { string packageXmlPath = Path.Combine(packageDir, PACKAGE_XML_FILE_NAME); - - if (File.Exists(packageXmlPath) == false) - { - throw new FileNotFoundException("Could not find " + PACKAGE_XML_FILE_NAME + " in package"); - } - - var packageConfig = new XmlDocument(); - packageConfig.Load(packageXmlPath); - - if (packageConfig.DocumentElement == null) - { - throw new Exception("Invalid package.xml could not load XML"); - } - - - XmlElement xmlRoot = packageConfig.DocumentElement; - return xmlRoot; + if (File.Exists(packageXmlPath) == false) { throw new FileNotFoundException("Could not find " + PACKAGE_XML_FILE_NAME + " in package"); } + return XDocument.Load(packageXmlPath); } - private PackageImportIssues FindImportIssues(XmlElement documentElement) + private PackageImportIssues FindImportIssues(XElement rootElement) { - XmlNodeList fileNotes = documentElement.SelectNodes("//file"); - XmlNodeList macroNotes = documentElement.SelectNodes("//macro"); - XmlNodeList templateNotes = documentElement.SelectNodes("Templates/Template"); - XmlNodeList stylesheetNotes = documentElement.SelectNodes("Stylesheets/Stylesheet"); - + var files = rootElement.Element("Files"); + var styleSheets = rootElement.Element("Stylesheets"); + var templates = rootElement.Element("Templates"); + var alias = rootElement.Element("Macros"); var packageImportIssues = new PackageImportIssues { - UnsecureFiles = FindUnsecureFiles(fileNotes), - ConflictingMacroAliases = FindConflictingMacroAliases(macroNotes), - ConflictingTemplateAliases = FindConflictingTemplateAliases(templateNotes), - ConflictingStylesheetNames = FindConflictingStylesheetNames(stylesheetNotes) + UnsecureFiles = files == null ? Enumerable.Empty() : FindUnsecureFiles(files), + ConflictingMacroAliases = alias == null ? Enumerable.Empty>() : FindConflictingMacroAliases(alias), + ConflictingTemplateAliases = templates == null ? Enumerable.Empty>() : FindConflictingTemplateAliases(templates), + ConflictingStylesheetNames = styleSheets == null ? Enumerable.Empty>() : FindConflictingStylesheetNames(styleSheets) }; return packageImportIssues; } - private IEnumerable FindUnsecureFiles(XmlNodeList fileNotes) + private IEnumerable FindUnsecureFiles(XElement fileElement) { - return fileNotes == null - ? Enumerable.Empty() - : fileNotes - .OfType() - .Where(FileNodeIsUnsecure) - .Select(n => XmlHelper.GetNodeValue(n.SelectSingleNode("orgName"))); - } + if ("Files".Equals(fileElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("the root element must be \"Files\"", "fileElement"); } - private IEnumerable> FindConflictingStylesheetNames(XmlNodeList stylesheetNotes) - { - return stylesheetNotes == null - ? Enumerable.Empty>() - : stylesheetNotes.OfType() + return fileElement.Elements("file") + .Where(FileNodeIsUnsecure) .Select(n => { - string name = XmlHelper.GetNodeValue(n.SelectSingleNode("Name")); + var xElement = n.Element("orgName"); + if (xElement == null) { throw new ArgumentException("missing a element: orgName", "n"); } + return xElement.Value; + }); + } + + private IEnumerable> FindConflictingStylesheetNames(XElement stylesheetNotes) + { + if ("Stylesheets".Equals(stylesheetNotes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("the root element must be \"Stylesheets\"", "stylesheetNotes"); } + + return stylesheetNotes.Elements("styleSheet") + .Select(n => + { + var xElement = n.Element("Name"); + if (xElement == null) { throw new ArgumentException("Missing \"Name\" element", "stylesheetNotes"); } + + string name = xElement.Name.LocalName; Stylesheet existingStilesheet = _fileService.GetStylesheetByName(name); - - // Dont know what to put in here... existing path whas the bedst i culd come up with + // Don't know what to put in here... existing path whas the best i could come up with string existingFilePath = existingStilesheet == null ? null : existingStilesheet.Path; - return new KeyValuePair(name, existingFilePath); }) .Where(kv => kv.Value != null); } - private IEnumerable> FindConflictingTemplateAliases(XmlNodeList templateNotes) + private IEnumerable> FindConflictingTemplateAliases(XElement templateNotes) { - return templateNotes == null - ? Enumerable.Empty>() - : templateNotes.OfType() + if ("Templates".Equals(templateNotes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Node must be a Templates node", "templateNotes"); } + + return templateNotes.Elements("Template") .Select(n => { - string alias = XmlHelper.GetNodeValue(n.SelectSingleNode("Alias")); - var existingTemplate = _fileService.GetTemplate(alias) as Template; - + var alias = n.Element("Alias"); + if (alias == null) { throw new ArgumentException("missing a alias element", "templateNotes"); } + string aliasStr = alias.Value; + var existingTemplate = _fileService.GetTemplate(aliasStr) as Template; string existingName = existingTemplate == null ? null : existingTemplate.Name; - return new KeyValuePair(alias, existingName); + return new KeyValuePair(aliasStr, existingName); }) .Where(kv => kv.Value != null); } - private IEnumerable> FindConflictingMacroAliases(XmlNodeList macroNotes) + private IEnumerable> FindConflictingMacroAliases(XElement macroNodes) { - return macroNotes == null - ? Enumerable.Empty>() - : macroNotes - .OfType() + return macroNodes.Elements("macro") .Select(n => { - string alias = XmlHelper.GetNodeValue(n.SelectSingleNode("alias")); - IMacro macro = _macroService.GetByAlias(alias); + var xElement = n.Element("alias"); + if (xElement == null) { throw new ArgumentException("missing a alias element", "macroNodes"); } + string alias = xElement.Value; + IMacro macro = _macroService.GetByAlias(xElement.Value); string eksistingName = macro == null ? null : macro.Name; return new KeyValuePair(alias, eksistingName); }) - .Where(kv => kv.Value != null); + .Where(kv => kv.Key != null && kv.Value != null); } - private bool FileNodeIsUnsecure(XmlNode fileNode) + private bool FileNodeIsUnsecure(XElement fileNode) { string basePath = HostingEnvironment.ApplicationPhysicalPath; - string destPath = GetFileName(basePath, XmlHelper.GetNodeValue(fileNode.SelectSingleNode("orgPath"))); + var orgName = fileNode.Element("orgName"); + if (orgName == null) { throw new ArgumentException("Missing element \"orgName\"", "fileNode"); } + string destPath = GetFileName(basePath, orgName.Value); + + // Should be done with regex :) if (destPath.ToLower().Contains(IOHelper.DirSepChar + "app_code")) return true; if (destPath.ToLower().Contains(IOHelper.DirSepChar + "bin")) return true; - string destFile = GetFileName(destPath, XmlHelper.GetNodeValue(fileNode.SelectSingleNode("orgName"))); - - return destFile.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase); + return destPath.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase); } - private PackageMetaData GetMetaData(XmlElement element) + private PackageMetaData GetMetaData(XElement xRootElement) { - XmlNode selectSingleNode = element.SelectSingleNode("/umbPackage/info/package/license"); + XElement infoElement = xRootElement.Element("info"); + + if (infoElement == null) { throw new ArgumentException("Did not hold a \"info\" element", "xRootElement"); } - string licenseUrl = string.Empty; - if (selectSingleNode != null && selectSingleNode.Attributes != null) - { - XmlNode attribute = selectSingleNode.Attributes.GetNamedItem("url"); - licenseUrl = attribute == null ? string.Empty : attribute.Value ?? string.Empty; - } + var majorElement = infoElement.XPathSelectElement("/package/requirements/major"); + var minorElement = infoElement.XPathSelectElement("/package/requirements/minor"); + var patchElement = infoElement.XPathSelectElement("/package/requirements/patch"); + var nameElement = infoElement.XPathSelectElement("/package/name"); + var versionElement = infoElement.XPathSelectElement("/package/version"); + var urlElement = infoElement.XPathSelectElement("/package/url"); + var licenseElement = infoElement.XPathSelectElement("/package/license"); + var authorNameElement = infoElement.XPathSelectElement("/author/name"); + var authorUrlElement = infoElement.XPathSelectElement("/author/website"); + var readmeElement = infoElement.XPathSelectElement("/readme"); - string reqMajorStr = - XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/requirements/major")); - string reqMinorStr = - XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/requirements/minor")); - string reqPatchStr = - XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/requirements/patch")); + var controlElement = xRootElement.Element("control"); int val; - return new PackageMetaData { - Name = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/name")), - Version = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/version")), - Url = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/url")), - License = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/package/license")), - LicenseUrl = licenseUrl, - AuthorName = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/author/name")), - AuthorUrl = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/author/website")), - Readme = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/info/readme")), - ReqMajor = int.TryParse(reqMajorStr, out val) ? val : 0, - ReqMinor = int.TryParse(reqMinorStr, out val) ? val : 0, - ReqPatch = int.TryParse(reqPatchStr, out val) ? val : 0, - Control = XmlHelper.GetNodeValue(element.SelectSingleNode("/umbPackage/control")) + Name = nameElement == null ? string.Empty : nameElement.Value, + Version = versionElement == null ? string.Empty : versionElement.Value, + Url = urlElement == null ? string.Empty : urlElement.Value, + License = licenseElement == null ? string.Empty : licenseElement.Value, + LicenseUrl = licenseElement == null ? string.Empty : licenseElement.HasAttributes ? licenseElement.AttributeValue("url") : string.Empty, + AuthorName = authorNameElement == null ? string.Empty : authorNameElement.Value, + AuthorUrl = authorUrlElement == null ? string.Empty : authorUrlElement.Value, + Readme = readmeElement == null ? string.Empty : readmeElement.Value, + ReqMajor = majorElement == null ? 0 : int.TryParse(majorElement.Value, out val) ? val : 0, + ReqMinor = minorElement == null ? 0 : int.TryParse(minorElement.Value, out val) ? val : 0, + ReqPatch = patchElement == null ? 0 : int.TryParse(patchElement.Value, out val) ? val : 0, + Control = controlElement == null ? string.Empty : controlElement.Value }; } diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 3ef9d2691b..70ff9c09e8 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -1484,26 +1484,36 @@ namespace Umbraco.Core.Services throw new NotImplementedException(); - foreach (XmlNode n in xmlNodeList.OfType()) - { - StyleSheet s = StyleSheet.MakeNew( - currentUser, - XmlHelper.GetNodeValue(n.SelectSingleNode("Name")), - XmlHelper.GetNodeValue(n.SelectSingleNode("FileName")), - XmlHelper.GetNodeValue(n.SelectSingleNode("Content"))); + //foreach (XmlNode n in xmlNodeList.OfType()) + //{ + // StyleSheet s = StyleSheet.MakeNew( + // currentUser, + // XmlHelper.GetNodeValue(n.SelectSingleNode("Name")), + // XmlHelper.GetNodeValue(n.SelectSingleNode("FileName")), + // XmlHelper.GetNodeValue(n.SelectSingleNode("Content"))); - foreach (XmlNode prop in n.SelectNodes("Properties/Property")) - { - StylesheetProperty sp = StylesheetProperty.MakeNew( - xmlHelper.GetNodeValue(prop.SelectSingleNode("Name")), - s, - currentUser); - sp.Alias = XmlHelper.GetNodeValue(prop.SelectSingleNode("Alias")); - sp.value = XmlHelper.GetNodeValue(prop.SelectSingleNode("Value")); - } - s.saveCssToFile(); - s.Save(); - } + // foreach (XmlNode prop in n.SelectNodes("Properties/Property")) + // { + // StylesheetProperty sp = StylesheetProperty.MakeNew( + // xmlHelper.GetNodeValue(prop.SelectSingleNode("Name")), + // s, + // currentUser); + // sp.Alias = XmlHelper.GetNodeValue(prop.SelectSingleNode("Alias")); + // sp.value = XmlHelper.GetNodeValue(prop.SelectSingleNode("Value")); + // } + // s.saveCssToFile(); + // s.Save(); + //} + } + + public IEnumerable ImportMacros(XElement xElement, int userId = 0) + { + throw new NotImplementedException(); + } + + public IEnumerable ImportDictionaryItems(XElement xElement, int userId = 0) + { + throw new NotImplementedException(); } private bool IsMasterPageSyntax(string code) From d57c5721167df7993ad156fe1f90ceca8e50348d Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Thu, 13 Feb 2014 14:04:12 +0100 Subject: [PATCH 007/189] moved strings to constants --- src/Umbraco.Core/Packaging/IUnpackHelper.cs | 2 +- src/Umbraco.Core/Packaging/UnpackHelper.cs | 2 +- src/Umbraco.Core/Services/IMacroProperty.cs | 56 +++++ .../Services/PackageInstallerService.cs | 232 ++++++++++-------- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 6 files changed, 195 insertions(+), 99 deletions(-) create mode 100644 src/Umbraco.Core/Services/IMacroProperty.cs diff --git a/src/Umbraco.Core/Packaging/IUnpackHelper.cs b/src/Umbraco.Core/Packaging/IUnpackHelper.cs index dbc516e4f9..29d5b62853 100644 --- a/src/Umbraco.Core/Packaging/IUnpackHelper.cs +++ b/src/Umbraco.Core/Packaging/IUnpackHelper.cs @@ -4,6 +4,6 @@ { void UnPack(string sourcefilePath, string destinationDirectory); string UnPackToTempDirectory(string sourcefilePath); - string ReadSingleTextFile(string sourcefilePath, string fileToRead); + string ReadTextFileFromArchive(string sourcefilePath, string fileToRead); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/UnpackHelper.cs b/src/Umbraco.Core/Packaging/UnpackHelper.cs index 89c462cca3..8ddcbc7dfb 100644 --- a/src/Umbraco.Core/Packaging/UnpackHelper.cs +++ b/src/Umbraco.Core/Packaging/UnpackHelper.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Packaging return tempDir; } - public string ReadSingleTextFile(string sourcefilePath, string fileToRead) + public string ReadTextFileFromArchive(string sourcefilePath, string fileToRead) { using (var fs = File.OpenRead(sourcefilePath)) { diff --git a/src/Umbraco.Core/Services/IMacroProperty.cs b/src/Umbraco.Core/Services/IMacroProperty.cs new file mode 100644 index 0000000000..b6ac6eb3f2 --- /dev/null +++ b/src/Umbraco.Core/Services/IMacroProperty.cs @@ -0,0 +1,56 @@ +using System.Xml; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + public interface IMacroProperty + { + /// + /// The sortorder + /// + int SortOrder { get; set; } + + /// + /// The alias if of the macroproperty, this is used in the special macro element + /// + /// + /// + string Alias { get; set; } + + /// + /// The userfriendly name + /// + string Name { get; set; } + + /// + /// Gets the id. + /// + /// The id. + int Id { get; } + + /// + /// Gets or sets the macro. + /// + /// The macro. + IMacro Macro { get; set; } + + /// + /// The basetype which defines which component is used in the UI for editing content + /// + IMacroPropertyType Type { get; set; } + + /// + /// Deletes the current macroproperty + /// + void Delete(); + + void Save(); + + /// + /// Retrieve a Xmlrepresentation of the MacroProperty used for exporting the Macro to the package + /// + /// XmlDocument context + /// A xmlrepresentation of the object + XmlElement ToXml(XmlDocument xd); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackageInstallerService.cs b/src/Umbraco.Core/Services/PackageInstallerService.cs index d7f2b7063b..6be3a348ff 100644 --- a/src/Umbraco.Core/Services/PackageInstallerService.cs +++ b/src/Umbraco.Core/Services/PackageInstallerService.cs @@ -15,16 +15,60 @@ namespace Umbraco.Core.Services { public class PackageInstallerService : IPackageInstallerService { + #region consts + private const string UMBPACKAGE_NODENAME = "umbPackage"; + private const string DATA_TYPES_NODENAME = "DataTypes"; private const string PACKAGE_XML_FILE_NAME = "package.xml"; + private const string UMBRACO_PACKAGE_EXTENTION = ".umb"; + private const string DATA_TYPE_NODENAME = "DataType"; + private const string LANGUAGES_NODENAME = "Languages"; + private const string FILES_NODENAME = "Files"; + private const string STYLESHEETS_NODENAME = "Stylesheets"; + private const string TEMPLATES_NODENAME = "Templates"; + private const string ORGNAME_NODENAME = "orgName"; + private const string NAME_NODENAME = "Name"; + private const string TEMPLATE_NODENAME = "Template"; + private const string ALIAS_NODENAME = "Alias"; + private const string DICTIONARYITEMS_NODENAME = "DictionaryItems"; + private const string MACROS_NODENAME = "macros"; + private const string DOCUMENTSET_NODENAME = "DocumentSet"; + private const string DOCUMENTTYPES_NODENAME = "DocumentTypes"; + private const string DOCUMENTTYPE_NODENAME = "DocumentType"; + private const string FILE_NODENAME = "file"; + private const string ORGPATH_NODENAME = "orgPath"; + private const string GUID_NODENAME = "guid"; + private const string STYLESHEET_NODENAME = "styleSheet"; + private const string MACRO_NODENAME = "macro"; + private const string INFO_NODENAME = "info"; + private const string PACKAGE_REQUIREMENTS_MAJOR_XPATH = "/package/requirements/major"; + private const string PACKAGE_REQUIREMENTS_MINOR_XPATH = "/package/requirements/minor"; + private const string PACKAGE_REQUIREMENTS_PATCH_XPATH = "/package/requirements/patch"; + private const string PACKAGE_NAME_XPATH = "/package/name"; + private const string PACKAGE_VERSION_XPATH = "/package/version"; + private const string PACKAGE_URL_XPATH = "/package/url"; + private const string PACKAGE_LICENSE_XPATH = "/package/license"; + private const string AUTHOR_NAME_XPATH = "/author/name"; + private const string AUTHOR_WEBSITE_XPATH = "/author/website"; + private const string README_XPATH = "/readme"; + private const string CONTROL_NODENAME = "control"; + private const string ACTION_NODENAME = "Action"; + private const string ACTIONS_NODENAME = "Actions"; + private const string UNDO_NODEATTRIBUTE = "undo"; + private const string RUNAT_NODEATTRIBUTE = "runat"; + + #endregion + private readonly IFileService _fileService; private readonly IMacroService _macroService; private readonly IPackagingService _packagingService; private readonly IUnpackHelper _unpackHelper; + public PackageInstallerService(IPackagingService packagingService, IMacroService macroService, IFileService fileService, IUnpackHelper unpackHelper) { - _packagingService = packagingService; + if (packagingService != null) _packagingService = packagingService; + else throw new ArgumentNullException("packagingService"); if (unpackHelper != null) _unpackHelper = unpackHelper; else throw new ArgumentNullException("unpackHelper"); if (fileService != null) _fileService = fileService; @@ -54,22 +98,13 @@ namespace Umbraco.Core.Services public PackageMetaData GetMetaData(string packageFilePath) { - var documentElement = GetConfigXmlDocFromPackageFile(packageFilePath); - - var rootElement = documentElement.Element("umbPackage"); - if (rootElement == null) { throw new ArgumentException("xml does not have a root node called \"umbPackage\"", packageFilePath); } - + var rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); return GetMetaData(rootElement); } public PackageImportIssues FindPackageImportIssues(string packageFilePath) { - var documentElement = GetConfigXmlDocFromPackageFile(packageFilePath); - - var rootElement = documentElement.Element("umbPackage"); - - if (rootElement == null) { throw new ArgumentException("File does not have a root node called \"umbPackage\"", packageFilePath); } - + var rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); return FindImportIssues(rootElement); } @@ -90,10 +125,9 @@ namespace Umbraco.Core.Services // Check if the file is a valid package - if (fi.Extension.Equals(".umb", StringComparison.InvariantCultureIgnoreCase) == false) + if (fi.Extension.Equals(UMBRACO_PACKAGE_EXTENTION, StringComparison.InvariantCultureIgnoreCase) == false) { - throw new Exception( - "Error - file isn't a package (doesn't have a .umb extension). Check if the file automatically got named '.zip' upon download."); + throw new Exception("Error - file isn't a package (doesn't have a .umb extension). Check if the file automatically got named '.zip' upon download."); } return fi; @@ -104,29 +138,36 @@ namespace Umbraco.Core.Services { FileInfo packageFileInfo = GetPackageFileInfo(packageFilePath); - string configXmlContent = _unpackHelper.ReadSingleTextFile(packageFileInfo.FullName, PACKAGE_XML_FILE_NAME); + string configXmlContent = _unpackHelper.ReadTextFileFromArchive(packageFileInfo.FullName, PACKAGE_XML_FILE_NAME); - var packageConfig = XDocument.Parse(configXmlContent); - return packageConfig; + return XDocument.Parse(configXmlContent); + } + + + private XElement GetConfigXmlRootElementFromPackageFile(string packageFilePath) + { + var document = GetConfigXmlDocFromPackageFile(packageFilePath); + if (document.Root == null || document.Root.Name.LocalName.Equals(UMBPACKAGE_NODENAME) == false) { throw new ArgumentException("xml does not have a root node called \"umbPackage\"", packageFilePath); } + return document.Root; } private PackageInstallationSummary InstallFromDirectory(string packageDir, int userId) { var configXml = GetConfigXmlDocFromPackageDirectory(packageDir); - var rootElement = configXml.XPathSelectElement("/umbPackage"); - if (rootElement == null) { throw new ArgumentException("File does not have a root node called \"umbPackage\"", packageDir); } + var rootElement = configXml.XPathSelectElement(UMBPACKAGE_NODENAME); + if (rootElement == null) { throw new ArgumentException("File does not have a root node called \"" + UMBPACKAGE_NODENAME + "\"", packageDir); } - var dataTypes = rootElement.Element("DataTypes"); - var languages = rootElement.Element("Languages"); - var dictionaryItems = rootElement.Element("DictionaryItems"); - var macroes = rootElement.Element("Macros"); - var files = rootElement.Element("Files"); - var templates = rootElement.Element("Templates"); - var documentTypes = rootElement.Element("DocumentTypes"); - var styleSheets = rootElement.Element("Stylesheets"); - var documentSet = rootElement.Element("DocumentSet"); - var actions = rootElement.Element("Actions"); + var dataTypes = rootElement.Element(DATA_TYPES_NODENAME); + var languages = rootElement.Element(LANGUAGES_NODENAME); + var dictionaryItems = rootElement.Element(DICTIONARYITEMS_NODENAME); + var macroes = rootElement.Element(MACROS_NODENAME); + var files = rootElement.Element(FILES_NODENAME); + var templates = rootElement.Element(TEMPLATES_NODENAME); + var documentTypes = rootElement.Element(DOCUMENTTYPES_NODENAME); + var styleSheets = rootElement.Element(STYLESHEETS_NODENAME); + var documentSet = rootElement.Element(DOCUMENTSET_NODENAME); + var actions = rootElement.Element(ACTIONS_NODENAME); return new PackageInstallationSummary { @@ -148,7 +189,7 @@ namespace Umbraco.Core.Services private static string GetUninstallActions(XElement actionsElement) { //saving the uninstall actions untill the package is uninstalled. - return actionsElement.Elements("Action").Where(e => e.HasAttributes && e.Attribute("undo") != null && e.Attribute("undo").Value.Equals("false()", StringComparison.InvariantCultureIgnoreCase) == false) // SelectNodes("Actions/Action [@undo != false()]"); + return actionsElement.Elements(ACTION_NODENAME).Where(e => e.HasAttributes && e.Attribute(UNDO_NODEATTRIBUTE) != null && e.Attribute(UNDO_NODEATTRIBUTE).Value.Equals("false()", StringComparison.InvariantCultureIgnoreCase) == false) // SelectNodes("Actions/Action [@undo != false()]"); .Select(m => m.Value).Aggregate((workingSentence, next) => next + workingSentence); } @@ -156,44 +197,44 @@ namespace Umbraco.Core.Services { if (actionsElement == null) { return Enumerable.Empty>(); } - if ("Actions".Equals(actionsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Actions\" as root", "actionsElement"); } + if (string.Equals(ACTIONS_NODENAME, actionsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + ACTIONS_NODENAME + "\" as root", "actionsElement"); } - return actionsElement.Elements("Action") + return actionsElement.Elements(ACTION_NODENAME) .Where( e => e.HasAttributes && - (e.Attribute("runat") == null || - e.Attribute("runat").Value.Equals("uninstall", StringComparison.InvariantCultureIgnoreCase) == + (e.Attribute(RUNAT_NODEATTRIBUTE) == null || + e.Attribute(RUNAT_NODEATTRIBUTE).Value.Equals("uninstall", StringComparison.InvariantCultureIgnoreCase) == false)) // .SelectNodes("Actions/Action [@runat != 'uninstall']") .Select(elemet => { - var aliasAttr = elemet.Attribute("alias"); + var aliasAttr = elemet.Attribute(ALIAS_NODENAME); if (aliasAttr == null) - throw new ArgumentException("missing alias atribute in alias element", "actionsElement"); + throw new ArgumentException("missing \"" + ALIAS_NODENAME + "\" atribute in alias element", "actionsElement"); return new {elemet, alias = aliasAttr.Value}; }).ToDictionary(x => x.alias, x => x.elemet); } private IEnumerable InstallDocuments(XElement documentsElement, int userId = 0) { - if ("DocumentSet".Equals(documentsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"DocumentSet\" as root", "documentsElement"); } + if (string.Equals(DOCUMENTSET_NODENAME, documentsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"DocumentSet\" as root", "documentsElement"); } return _packagingService.ImportContent(documentsElement, -1, userId).Select(c => c.Id); } private IEnumerable InstallStylesheets(XElement styleSheetsElement, int userId = 0) { - if ("Stylesheets".Equals(styleSheetsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Stylesheets\" as root", "styleSheetsElement"); } + if (string.Equals(STYLESHEETS_NODENAME, styleSheetsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Stylesheets\" as root", "styleSheetsElement"); } return _packagingService.ImportStylesheets(styleSheetsElement, userId).Select(f => f.Id); } private IEnumerable InstallDocumentTypes(XElement documentTypes, int userId = 0) { - if ("DocumentTypes".Equals(documentTypes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) + if (string.Equals(DOCUMENTTYPES_NODENAME, documentTypes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { - if ("DocumentType".Equals(documentTypes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) - throw new ArgumentException("Must be \"DocumentTypes\" as root", "documentTypes"); + if (string.Equals(DOCUMENTTYPE_NODENAME, documentTypes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) + throw new ArgumentException("Must be \"" + DOCUMENTTYPES_NODENAME + "\" as root", "documentTypes"); - documentTypes = new XElement("DocumentTypes", documentTypes); + documentTypes = new XElement(DOCUMENTTYPES_NODENAME, documentTypes); } return _packagingService.ImportContentTypes(documentTypes, userId).Select(ct => ct.Id); @@ -201,29 +242,29 @@ namespace Umbraco.Core.Services private IEnumerable InstallTemplats(XElement templateElement, int userId = 0) { - if ("Templates".Equals(templateElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "templateElement"); } + if (string.Equals(TEMPLATES_NODENAME, templateElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + TEMPLATES_NODENAME + "\" as root", "templateElement"); } return _packagingService.ImportTemplates(templateElement, userId).Select(t => t.Id); } private static IEnumerable> InstallFiles(string packageDir, XElement filesElement) { - if ("Files".Equals(filesElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("root element must be \"Files\"", "filesElement"); } + if (string.Equals(FILES_NODENAME, filesElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("root element must be \"" + FILES_NODENAME + "\"", "filesElement"); } string basePath = HostingEnvironment.ApplicationPhysicalPath; - var xmlNodeList = filesElement.Elements("file"); + var xmlNodeList = filesElement.Elements(FILE_NODENAME); return xmlNodeList.Select(e => { - var orgPathElement = e.Element("orgPath"); - if (orgPathElement == null) { throw new ArgumentException("Missing element \"orgPath\"", "filesElement"); } + var orgPathElement = e.Element(ORGPATH_NODENAME); + if (orgPathElement == null) { throw new ArgumentException("Missing element \"" + ORGPATH_NODENAME + "\"", "filesElement"); } - var guidElement = e.Element("guid"); - if (guidElement == null) { throw new ArgumentException("Missing element \"guid\"", "filesElement"); } + var guidElement = e.Element(GUID_NODENAME); + if (guidElement == null) { throw new ArgumentException("Missing element \"" + GUID_NODENAME + "\"", "filesElement"); } - var orgNameElement = e.Element("orgName"); - if (orgNameElement == null) { throw new ArgumentException("Missing element \"orgName\"", "filesElement"); } + var orgNameElement = e.Element(ORGNAME_NODENAME); + if (orgNameElement == null) { throw new ArgumentException("Missing element \"" + ORGNAME_NODENAME + "\"", "filesElement"); } var destPath = GetFileName(basePath, orgPathElement.Value); @@ -242,30 +283,28 @@ namespace Umbraco.Core.Services private IEnumerable InstallMacros(XElement macroElements, int userId = 0) { - if ("Macros".Equals(macroElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "macroElements"); } + if (string.Equals(MACROS_NODENAME, macroElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "macroElements"); } return _packagingService.ImportMacros(macroElements, userId).Select(m => m.Id); } private IEnumerable InstallDictionaryItems(XElement dictionaryItemsElement, int userId = 0) { - if ("DictionaryItems".Equals(dictionaryItemsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "dictionaryItemsElement"); } + if (string.Equals(DICTIONARYITEMS_NODENAME, dictionaryItemsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "dictionaryItemsElement"); } return _packagingService.ImportDictionaryItems(dictionaryItemsElement, userId).Select(di => di.Id); } private IEnumerable InstallLanguages(XElement languageElement, int userId = 0) { - if ("Languages".Equals(languageElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "languageElement"); } + if (string.Equals(LANGUAGES_NODENAME, languageElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "languageElement"); } return _packagingService.ImportLanguage(languageElement, userId).Select(l => l.Id); } private IEnumerable InstallDataTypes(XElement dataTypeElements, int userId = 0) { - if ("DataTypes".Equals(dataTypeElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == - false) + if (string.Equals(DATA_TYPES_NODENAME, dataTypeElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { - if ("DataType".Equals(dataTypeElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == - false) + if (string.Equals(DATA_TYPE_NODENAME, dataTypeElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "dataTypeElements"); } @@ -283,10 +322,10 @@ namespace Umbraco.Core.Services private PackageImportIssues FindImportIssues(XElement rootElement) { - var files = rootElement.Element("Files"); - var styleSheets = rootElement.Element("Stylesheets"); - var templates = rootElement.Element("Templates"); - var alias = rootElement.Element("Macros"); + var files = rootElement.Element(FILES_NODENAME); + var styleSheets = rootElement.Element(STYLESHEETS_NODENAME); + var templates = rootElement.Element(TEMPLATES_NODENAME); + var alias = rootElement.Element(MACROS_NODENAME); var packageImportIssues = new PackageImportIssues { UnsecureFiles = files == null ? Enumerable.Empty() : FindUnsecureFiles(files), @@ -300,27 +339,27 @@ namespace Umbraco.Core.Services private IEnumerable FindUnsecureFiles(XElement fileElement) { - if ("Files".Equals(fileElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("the root element must be \"Files\"", "fileElement"); } + if (string.Equals(FILES_NODENAME, fileElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("the root element must be \"Files\"", "fileElement"); } - return fileElement.Elements("file") + return fileElement.Elements(FILE_NODENAME) .Where(FileNodeIsUnsecure) .Select(n => { - var xElement = n.Element("orgName"); - if (xElement == null) { throw new ArgumentException("missing a element: orgName", "n"); } + var xElement = n.Element(ORGNAME_NODENAME); + if (xElement == null) { throw new ArgumentException("missing a element: " + ORGNAME_NODENAME, "n"); } return xElement.Value; }); } private IEnumerable> FindConflictingStylesheetNames(XElement stylesheetNotes) { - if ("Stylesheets".Equals(stylesheetNotes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("the root element must be \"Stylesheets\"", "stylesheetNotes"); } + if (string.Equals(STYLESHEETS_NODENAME, stylesheetNotes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("the root element must be \"Stylesheets\"", "stylesheetNotes"); } - return stylesheetNotes.Elements("styleSheet") + return stylesheetNotes.Elements(STYLESHEET_NODENAME) .Select(n => { - var xElement = n.Element("Name"); - if (xElement == null) { throw new ArgumentException("Missing \"Name\" element", "stylesheetNotes"); } + var xElement = n.Element(NAME_NODENAME); + if (xElement == null) { throw new ArgumentException("Missing \"" + NAME_NODENAME + "\" element", "stylesheetNotes"); } string name = xElement.Name.LocalName; Stylesheet existingStilesheet = _fileService.GetStylesheetByName(name); @@ -335,13 +374,13 @@ namespace Umbraco.Core.Services private IEnumerable> FindConflictingTemplateAliases(XElement templateNotes) { - if ("Templates".Equals(templateNotes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Node must be a Templates node", "templateNotes"); } + if (string.Equals(TEMPLATES_NODENAME, templateNotes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Node must be a \"" + TEMPLATES_NODENAME + "\" node", "templateNotes"); } - return templateNotes.Elements("Template") + return templateNotes.Elements(TEMPLATE_NODENAME) .Select(n => { - var alias = n.Element("Alias"); - if (alias == null) { throw new ArgumentException("missing a alias element", "templateNotes"); } + var alias = n.Element(ALIAS_NODENAME); + if (alias == null) { throw new ArgumentException("missing a \"" + ALIAS_NODENAME + "\" element", "templateNotes"); } string aliasStr = alias.Value; var existingTemplate = _fileService.GetTemplate(aliasStr) as Template; string existingName = existingTemplate == null ? null : existingTemplate.Name; @@ -353,13 +392,13 @@ namespace Umbraco.Core.Services private IEnumerable> FindConflictingMacroAliases(XElement macroNodes) { - return macroNodes.Elements("macro") + return macroNodes.Elements(MACRO_NODENAME) .Select(n => { - var xElement = n.Element("alias"); - if (xElement == null) { throw new ArgumentException("missing a alias element", "macroNodes"); } + var xElement = n.Element(ALIAS_NODENAME); + if (xElement == null) { throw new ArgumentException("missing a \"" + ALIAS_NODENAME + "\" element", "macroNodes"); } string alias = xElement.Value; - IMacro macro = _macroService.GetByAlias(xElement.Value); + var macro = _macroService.GetByAlias(xElement.Value); string eksistingName = macro == null ? null : macro.Name; return new KeyValuePair(alias, eksistingName); @@ -371,8 +410,8 @@ namespace Umbraco.Core.Services private bool FileNodeIsUnsecure(XElement fileNode) { string basePath = HostingEnvironment.ApplicationPhysicalPath; - var orgName = fileNode.Element("orgName"); - if (orgName == null) { throw new ArgumentException("Missing element \"orgName\"", "fileNode"); } + var orgName = fileNode.Element(ORGNAME_NODENAME); + if (orgName == null) { throw new ArgumentException("Missing element \"" + ORGNAME_NODENAME + "\"", "fileNode"); } string destPath = GetFileName(basePath, orgName.Value); @@ -386,22 +425,22 @@ namespace Umbraco.Core.Services private PackageMetaData GetMetaData(XElement xRootElement) { - XElement infoElement = xRootElement.Element("info"); - - if (infoElement == null) { throw new ArgumentException("Did not hold a \"info\" element", "xRootElement"); } + XElement infoElement = xRootElement.Element(INFO_NODENAME); - var majorElement = infoElement.XPathSelectElement("/package/requirements/major"); - var minorElement = infoElement.XPathSelectElement("/package/requirements/minor"); - var patchElement = infoElement.XPathSelectElement("/package/requirements/patch"); - var nameElement = infoElement.XPathSelectElement("/package/name"); - var versionElement = infoElement.XPathSelectElement("/package/version"); - var urlElement = infoElement.XPathSelectElement("/package/url"); - var licenseElement = infoElement.XPathSelectElement("/package/license"); - var authorNameElement = infoElement.XPathSelectElement("/author/name"); - var authorUrlElement = infoElement.XPathSelectElement("/author/website"); - var readmeElement = infoElement.XPathSelectElement("/readme"); + if (infoElement == null) { throw new ArgumentException("Did not hold a \"" + INFO_NODENAME + "\" element", "xRootElement"); } - var controlElement = xRootElement.Element("control"); + var majorElement = infoElement.XPathSelectElement(PACKAGE_REQUIREMENTS_MAJOR_XPATH); + var minorElement = infoElement.XPathSelectElement(PACKAGE_REQUIREMENTS_MINOR_XPATH); + var patchElement = infoElement.XPathSelectElement(PACKAGE_REQUIREMENTS_PATCH_XPATH); + var nameElement = infoElement.XPathSelectElement(PACKAGE_NAME_XPATH); + var versionElement = infoElement.XPathSelectElement(PACKAGE_VERSION_XPATH); + var urlElement = infoElement.XPathSelectElement(PACKAGE_URL_XPATH); + var licenseElement = infoElement.XPathSelectElement(PACKAGE_LICENSE_XPATH); + var authorNameElement = infoElement.XPathSelectElement(AUTHOR_NAME_XPATH); + var authorUrlElement = infoElement.XPathSelectElement(AUTHOR_WEBSITE_XPATH); + var readmeElement = infoElement.XPathSelectElement(README_XPATH); + + var controlElement = xRootElement.Element(CONTROL_NODENAME); int val; @@ -431,7 +470,7 @@ namespace Umbraco.Core.Services /// The path. /// Name of the file. /// The name of the file in the specified path. - private static String GetFileName(String path, string fileName) + private static String GetFileName(string path, string fileName) { // virtual dir support fileName = IOHelper.FindFile(fileName); @@ -448,7 +487,6 @@ namespace Umbraco.Core.Services //to support virtual dirs we try to lookup the file... path = IOHelper.FindFile(path); - Debug.Assert(path != null && path.Length >= 1); Debug.Assert(fileName != null && fileName.Length >= 1); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index b567905627..3228334b5f 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1037,6 +1037,7 @@ + diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 379547049d..499ebaa336 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -632,6 +632,7 @@ ResXFileCodeGenerator ImportResources.Designer.cs + Designer ResXFileCodeGenerator From acc6d46b4b4089381aa50fe96a19b7ee5b15f701 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Thu, 13 Feb 2014 16:18:08 +0100 Subject: [PATCH 008/189] splittet into more single resposebility classes --- src/Umbraco.Core/Packaging/UnpackHelper.cs | 43 +++++++-------- .../Services/IPackageValidationHelper.cs | 11 ++++ .../Services/PackageInstallerService.cs | 54 ++++++++++--------- .../Services/PackageValidationHelper.cs | 39 ++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 2 + 5 files changed, 104 insertions(+), 45 deletions(-) create mode 100644 src/Umbraco.Core/Services/IPackageValidationHelper.cs create mode 100644 src/Umbraco.Core/Services/PackageValidationHelper.cs diff --git a/src/Umbraco.Core/Packaging/UnpackHelper.cs b/src/Umbraco.Core/Packaging/UnpackHelper.cs index 8ddcbc7dfb..aaa169f453 100644 --- a/src/Umbraco.Core/Packaging/UnpackHelper.cs +++ b/src/Umbraco.Core/Packaging/UnpackHelper.cs @@ -21,21 +21,24 @@ namespace Umbraco.Core.Packaging { using (var zipStream = new ZipInputStream(fs)) { - ZipEntry theEntry; - while ((theEntry = zipStream.GetNextEntry()) != null) + ZipEntry zipEntry; + while ((zipEntry = zipStream.GetNextEntry()) != null) { - if (theEntry.Name.EndsWith(fileToRead, StringComparison.CurrentCultureIgnoreCase)) + if (zipEntry.Name.EndsWith(fileToRead, StringComparison.CurrentCultureIgnoreCase)) { using (var reader = new StreamReader(zipStream)) { return reader.ReadToEnd(); } } - }; + } + + zipStream.Close(); } + fs.Close(); } - throw new FileNotFoundException("Could not find file in package file " + sourcefilePath, fileToRead); + throw new FileNotFoundException(string.Format("Could not find file in package file {0}", sourcefilePath), fileToRead); } public void UnPack(string sourcefilePath, string destinationDirectory) @@ -43,32 +46,30 @@ namespace Umbraco.Core.Packaging // Unzip using (var fs = File.OpenRead(sourcefilePath)) { - using (var s = new ZipInputStream(fs)) + using (var zipInputStream = new ZipInputStream(fs)) { - ZipEntry theEntry; - while ((theEntry = s.GetNextEntry()) != null) + ZipEntry zipEntry; + while ((zipEntry = zipInputStream.GetNextEntry()) != null) { - string fileName = Path.GetFileName(theEntry.Name); - if (fileName == String.Empty) continue; + string fileName = Path.GetFileName(zipEntry.Name); + if (string.IsNullOrEmpty(fileName)) continue; - using ( var streamWriter = File.Create(destinationDirectory + Path.DirectorySeparatorChar + fileName)) + using ( var streamWriter = File.Create(Path.Combine(destinationDirectory, fileName))) { var data = new byte[2048]; - while (true) + int size; + while ((size = zipInputStream.Read(data, 0, data.Length)) > 0) { - var size = s.Read(data, 0, data.Length); - if (size > 0) - { - streamWriter.Write(data, 0, size); - } - else - { - break; - } + streamWriter.Write(data, 0, size); } + + streamWriter.Close(); } } + + zipInputStream.Close(); } + fs.Close(); } } } diff --git a/src/Umbraco.Core/Services/IPackageValidationHelper.cs b/src/Umbraco.Core/Services/IPackageValidationHelper.cs new file mode 100644 index 0000000000..08c493b537 --- /dev/null +++ b/src/Umbraco.Core/Services/IPackageValidationHelper.cs @@ -0,0 +1,11 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + public interface IPackageValidationHelper + { + bool StylesheetExists(string styleSheetName, out Stylesheet existingStyleSheet); + bool TemplateExists(string templateAlias, out ITemplate existingTemplate); + bool MacroExists(string macroAlias, out IMacro existingMacro); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackageInstallerService.cs b/src/Umbraco.Core/Services/PackageInstallerService.cs index 6be3a348ff..622a2645a1 100644 --- a/src/Umbraco.Core/Services/PackageInstallerService.cs +++ b/src/Umbraco.Core/Services/PackageInstallerService.cs @@ -15,6 +15,8 @@ namespace Umbraco.Core.Services { public class PackageInstallerService : IPackageInstallerService { + + #region consts private const string UMBPACKAGE_NODENAME = "umbPackage"; private const string DATA_TYPES_NODENAME = "DataTypes"; @@ -58,23 +60,16 @@ namespace Umbraco.Core.Services #endregion - private readonly IFileService _fileService; - private readonly IMacroService _macroService; + private readonly IPackageValidationHelper _packageValidationHelper; private readonly IPackagingService _packagingService; private readonly IUnpackHelper _unpackHelper; - public PackageInstallerService(IPackagingService packagingService, IMacroService macroService, - IFileService fileService, IUnpackHelper unpackHelper) + public PackageInstallerService(IPackagingService packagingService, IUnpackHelper unpackHelper, IPackageValidationHelper packageValidationHelper) { - if (packagingService != null) _packagingService = packagingService; - else throw new ArgumentNullException("packagingService"); - if (unpackHelper != null) _unpackHelper = unpackHelper; - else throw new ArgumentNullException("unpackHelper"); - if (fileService != null) _fileService = fileService; - else throw new ArgumentNullException("fileService"); - if (macroService != null) _macroService = macroService; - else throw new ArgumentNullException("macroService"); + if (packageValidationHelper != null) _packageValidationHelper = packageValidationHelper; else throw new ArgumentNullException("packageValidationHelper"); + if (packagingService != null) _packagingService = packagingService; else throw new ArgumentNullException("packagingService"); + if (unpackHelper != null) _unpackHelper = unpackHelper; else throw new ArgumentNullException("unpackHelper"); } @@ -361,13 +356,15 @@ namespace Umbraco.Core.Services var xElement = n.Element(NAME_NODENAME); if (xElement == null) { throw new ArgumentException("Missing \"" + NAME_NODENAME + "\" element", "stylesheetNotes"); } - string name = xElement.Name.LocalName; - Stylesheet existingStilesheet = _fileService.GetStylesheetByName(name); + string name = xElement.Value; - // Don't know what to put in here... existing path whas the best i could come up with - string existingFilePath = existingStilesheet == null ? null : existingStilesheet.Path; - - return new KeyValuePair(name, existingFilePath); + Stylesheet existingStyleSheet; + if (_packageValidationHelper.StylesheetExists(name, out existingStyleSheet)) + { + // Don't know what to put in here... existing path was the best i could come up with + return new KeyValuePair(name, existingStyleSheet.Path); + } + return new KeyValuePair(name, null); }) .Where(kv => kv.Value != null); } @@ -382,10 +379,15 @@ namespace Umbraco.Core.Services var alias = n.Element(ALIAS_NODENAME); if (alias == null) { throw new ArgumentException("missing a \"" + ALIAS_NODENAME + "\" element", "templateNotes"); } string aliasStr = alias.Value; - var existingTemplate = _fileService.GetTemplate(aliasStr) as Template; - string existingName = existingTemplate == null ? null : existingTemplate.Name; - return new KeyValuePair(aliasStr, existingName); + ITemplate existingTemplate; + + if (_packageValidationHelper.TemplateExists(aliasStr, out existingTemplate)) + { + return new KeyValuePair(aliasStr, existingTemplate.Name); + } + + return new KeyValuePair(aliasStr, null); }) .Where(kv => kv.Value != null); } @@ -398,10 +400,14 @@ namespace Umbraco.Core.Services var xElement = n.Element(ALIAS_NODENAME); if (xElement == null) { throw new ArgumentException("missing a \"" + ALIAS_NODENAME + "\" element", "macroNodes"); } string alias = xElement.Value; - var macro = _macroService.GetByAlias(xElement.Value); - string eksistingName = macro == null ? null : macro.Name; - return new KeyValuePair(alias, eksistingName); + IMacro existingMacro; + if (_packageValidationHelper.MacroExists(alias, out existingMacro)) + { + return new KeyValuePair(alias, existingMacro.Name); + } + + return new KeyValuePair(alias, null); }) .Where(kv => kv.Key != null && kv.Value != null); } diff --git a/src/Umbraco.Core/Services/PackageValidationHelper.cs b/src/Umbraco.Core/Services/PackageValidationHelper.cs new file mode 100644 index 0000000000..9de54dd0f0 --- /dev/null +++ b/src/Umbraco.Core/Services/PackageValidationHelper.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + public class PackageValidationHelper : IPackageValidationHelper + { + private readonly IMacroService _macroService; + private readonly IFileService _fileService; + + public PackageValidationHelper(IMacroService macroService, + IFileService fileService) + { + if (fileService != null) _fileService = fileService; + else throw new ArgumentNullException("fileService"); + if (macroService != null) _macroService = macroService; + else throw new ArgumentNullException("macroService"); + } + + public bool StylesheetExists(string styleSheetName, out Stylesheet existingStyleSheet) + { + existingStyleSheet = _fileService.GetStylesheets(styleSheetName).SingleOrDefault(); + return existingStyleSheet != null; + } + + public bool TemplateExists(string templateAlias, out ITemplate existingTemplate) + { + existingTemplate = _fileService.GetTemplate(templateAlias); + return existingTemplate != null; + } + + public bool MacroExists(string macroAlias, out IMacro existingMacro) + { + existingMacro = _macroService.GetByAlias(macroAlias); + return macroAlias != null; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3228334b5f..ee90d31374 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1048,6 +1048,7 @@ + @@ -1067,6 +1068,7 @@ + From 9f146753b58384a7552bba37fbd3a3c21c508c4c Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 14 Feb 2014 10:28:05 +0100 Subject: [PATCH 009/189] used the right method that returns a single object --- src/Umbraco.Core/Services/PackageInstallerService.cs | 2 -- src/Umbraco.Core/Services/PackageValidationHelper.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Services/PackageInstallerService.cs b/src/Umbraco.Core/Services/PackageInstallerService.cs index 622a2645a1..745996d357 100644 --- a/src/Umbraco.Core/Services/PackageInstallerService.cs +++ b/src/Umbraco.Core/Services/PackageInstallerService.cs @@ -15,8 +15,6 @@ namespace Umbraco.Core.Services { public class PackageInstallerService : IPackageInstallerService { - - #region consts private const string UMBPACKAGE_NODENAME = "umbPackage"; private const string DATA_TYPES_NODENAME = "DataTypes"; diff --git a/src/Umbraco.Core/Services/PackageValidationHelper.cs b/src/Umbraco.Core/Services/PackageValidationHelper.cs index 9de54dd0f0..cfdb98da0c 100644 --- a/src/Umbraco.Core/Services/PackageValidationHelper.cs +++ b/src/Umbraco.Core/Services/PackageValidationHelper.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Services public bool StylesheetExists(string styleSheetName, out Stylesheet existingStyleSheet) { - existingStyleSheet = _fileService.GetStylesheets(styleSheetName).SingleOrDefault(); + existingStyleSheet = _fileService.GetStylesheetByName(styleSheetName); return existingStyleSheet != null; } From 37fbc3bab4fc5e5a1185a3dfec3efb1002b16a9b Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Mon, 7 Apr 2014 08:32:46 +0200 Subject: [PATCH 010/189] Now uses new constants --- .../Services/PackageInstallerService.cs | 201 +++++++----------- 1 file changed, 79 insertions(+), 122 deletions(-) diff --git a/src/Umbraco.Core/Services/PackageInstallerService.cs b/src/Umbraco.Core/Services/PackageInstallerService.cs index 745996d357..530d0612f2 100644 --- a/src/Umbraco.Core/Services/PackageInstallerService.cs +++ b/src/Umbraco.Core/Services/PackageInstallerService.cs @@ -15,49 +15,6 @@ namespace Umbraco.Core.Services { public class PackageInstallerService : IPackageInstallerService { - #region consts - private const string UMBPACKAGE_NODENAME = "umbPackage"; - private const string DATA_TYPES_NODENAME = "DataTypes"; - private const string PACKAGE_XML_FILE_NAME = "package.xml"; - private const string UMBRACO_PACKAGE_EXTENTION = ".umb"; - private const string DATA_TYPE_NODENAME = "DataType"; - private const string LANGUAGES_NODENAME = "Languages"; - private const string FILES_NODENAME = "Files"; - private const string STYLESHEETS_NODENAME = "Stylesheets"; - private const string TEMPLATES_NODENAME = "Templates"; - private const string ORGNAME_NODENAME = "orgName"; - private const string NAME_NODENAME = "Name"; - private const string TEMPLATE_NODENAME = "Template"; - private const string ALIAS_NODENAME = "Alias"; - private const string DICTIONARYITEMS_NODENAME = "DictionaryItems"; - private const string MACROS_NODENAME = "macros"; - private const string DOCUMENTSET_NODENAME = "DocumentSet"; - private const string DOCUMENTTYPES_NODENAME = "DocumentTypes"; - private const string DOCUMENTTYPE_NODENAME = "DocumentType"; - private const string FILE_NODENAME = "file"; - private const string ORGPATH_NODENAME = "orgPath"; - private const string GUID_NODENAME = "guid"; - private const string STYLESHEET_NODENAME = "styleSheet"; - private const string MACRO_NODENAME = "macro"; - private const string INFO_NODENAME = "info"; - private const string PACKAGE_REQUIREMENTS_MAJOR_XPATH = "/package/requirements/major"; - private const string PACKAGE_REQUIREMENTS_MINOR_XPATH = "/package/requirements/minor"; - private const string PACKAGE_REQUIREMENTS_PATCH_XPATH = "/package/requirements/patch"; - private const string PACKAGE_NAME_XPATH = "/package/name"; - private const string PACKAGE_VERSION_XPATH = "/package/version"; - private const string PACKAGE_URL_XPATH = "/package/url"; - private const string PACKAGE_LICENSE_XPATH = "/package/license"; - private const string AUTHOR_NAME_XPATH = "/author/name"; - private const string AUTHOR_WEBSITE_XPATH = "/author/website"; - private const string README_XPATH = "/readme"; - private const string CONTROL_NODENAME = "control"; - private const string ACTION_NODENAME = "Action"; - private const string ACTIONS_NODENAME = "Actions"; - private const string UNDO_NODEATTRIBUTE = "undo"; - private const string RUNAT_NODEATTRIBUTE = "runat"; - - #endregion - private readonly IPackageValidationHelper _packageValidationHelper; private readonly IPackagingService _packagingService; private readonly IUnpackHelper _unpackHelper; @@ -118,7 +75,7 @@ namespace Umbraco.Core.Services // Check if the file is a valid package - if (fi.Extension.Equals(UMBRACO_PACKAGE_EXTENTION, StringComparison.InvariantCultureIgnoreCase) == false) + if (fi.Extension.Equals(Constants.Packaging.UmbracoPackageExtention, StringComparison.InvariantCultureIgnoreCase) == false) { throw new Exception("Error - file isn't a package (doesn't have a .umb extension). Check if the file automatically got named '.zip' upon download."); } @@ -131,7 +88,7 @@ namespace Umbraco.Core.Services { FileInfo packageFileInfo = GetPackageFileInfo(packageFilePath); - string configXmlContent = _unpackHelper.ReadTextFileFromArchive(packageFileInfo.FullName, PACKAGE_XML_FILE_NAME); + string configXmlContent = _unpackHelper.ReadTextFileFromArchive(packageFileInfo.FullName, Constants.Packaging.PackageXmlFileName); return XDocument.Parse(configXmlContent); } @@ -140,7 +97,7 @@ namespace Umbraco.Core.Services private XElement GetConfigXmlRootElementFromPackageFile(string packageFilePath) { var document = GetConfigXmlDocFromPackageFile(packageFilePath); - if (document.Root == null || document.Root.Name.LocalName.Equals(UMBPACKAGE_NODENAME) == false) { throw new ArgumentException("xml does not have a root node called \"umbPackage\"", packageFilePath); } + if (document.Root == null || document.Root.Name.LocalName.Equals(Constants.Packaging.UmbPackageNodeName) == false) { throw new ArgumentException("xml does not have a root node called \"umbPackage\"", packageFilePath); } return document.Root; } @@ -148,19 +105,19 @@ namespace Umbraco.Core.Services private PackageInstallationSummary InstallFromDirectory(string packageDir, int userId) { var configXml = GetConfigXmlDocFromPackageDirectory(packageDir); - var rootElement = configXml.XPathSelectElement(UMBPACKAGE_NODENAME); - if (rootElement == null) { throw new ArgumentException("File does not have a root node called \"" + UMBPACKAGE_NODENAME + "\"", packageDir); } + var rootElement = configXml.XPathSelectElement(Constants.Packaging.UmbPackageNodeName); + if (rootElement == null) { throw new ArgumentException("File does not have a root node called \"" + Constants.Packaging.UmbPackageNodeName + "\"", packageDir); } - var dataTypes = rootElement.Element(DATA_TYPES_NODENAME); - var languages = rootElement.Element(LANGUAGES_NODENAME); - var dictionaryItems = rootElement.Element(DICTIONARYITEMS_NODENAME); - var macroes = rootElement.Element(MACROS_NODENAME); - var files = rootElement.Element(FILES_NODENAME); - var templates = rootElement.Element(TEMPLATES_NODENAME); - var documentTypes = rootElement.Element(DOCUMENTTYPES_NODENAME); - var styleSheets = rootElement.Element(STYLESHEETS_NODENAME); - var documentSet = rootElement.Element(DOCUMENTSET_NODENAME); - var actions = rootElement.Element(ACTIONS_NODENAME); + var dataTypes = rootElement.Element(Constants.Packaging.DataTypesNodeName); + var languages = rootElement.Element(Constants.Packaging.LanguagesNodeName); + var dictionaryItems = rootElement.Element(Constants.Packaging.DictionaryitemsNodeName); + var macroes = rootElement.Element(Constants.Packaging.MacrosNodeName); + var files = rootElement.Element(Constants.Packaging.FilesNodeName); + var templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); + var documentTypes = rootElement.Element(Constants.Packaging.DocumentTypesNodeName); + var styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); + var documentSet = rootElement.Element(Constants.Packaging.DocumentSetNodeName); + var actions = rootElement.Element(Constants.Packaging.ActionsNodeName); return new PackageInstallationSummary { @@ -182,7 +139,7 @@ namespace Umbraco.Core.Services private static string GetUninstallActions(XElement actionsElement) { //saving the uninstall actions untill the package is uninstalled. - return actionsElement.Elements(ACTION_NODENAME).Where(e => e.HasAttributes && e.Attribute(UNDO_NODEATTRIBUTE) != null && e.Attribute(UNDO_NODEATTRIBUTE).Value.Equals("false()", StringComparison.InvariantCultureIgnoreCase) == false) // SelectNodes("Actions/Action [@undo != false()]"); + return actionsElement.Elements(Constants.Packaging.ActionNodeName).Where(e => e.HasAttributes && e.Attribute(Constants.Packaging.UndoNodeAttribute) != null && e.Attribute(Constants.Packaging.UndoNodeAttribute).Value.Equals("false()", StringComparison.InvariantCultureIgnoreCase) == false) // SelectNodes("Actions/Action [@undo != false()]"); .Select(m => m.Value).Aggregate((workingSentence, next) => next + workingSentence); } @@ -190,44 +147,44 @@ namespace Umbraco.Core.Services { if (actionsElement == null) { return Enumerable.Empty>(); } - if (string.Equals(ACTIONS_NODENAME, actionsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + ACTIONS_NODENAME + "\" as root", "actionsElement"); } + if (string.Equals(Constants.Packaging.ActionsNodeName, actionsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.ActionsNodeName + "\" as root", "actionsElement"); } - return actionsElement.Elements(ACTION_NODENAME) + return actionsElement.Elements(Constants.Packaging.ActionNodeName) .Where( e => e.HasAttributes && - (e.Attribute(RUNAT_NODEATTRIBUTE) == null || - e.Attribute(RUNAT_NODEATTRIBUTE).Value.Equals("uninstall", StringComparison.InvariantCultureIgnoreCase) == + (e.Attribute(Constants.Packaging.RunatNodeAttribute) == null || + e.Attribute(Constants.Packaging.RunatNodeAttribute).Value.Equals("uninstall", StringComparison.InvariantCultureIgnoreCase) == false)) // .SelectNodes("Actions/Action [@runat != 'uninstall']") .Select(elemet => { - var aliasAttr = elemet.Attribute(ALIAS_NODENAME); + var aliasAttr = elemet.Attribute(Constants.Packaging.AliasNodeName); if (aliasAttr == null) - throw new ArgumentException("missing \"" + ALIAS_NODENAME + "\" atribute in alias element", "actionsElement"); + throw new ArgumentException("missing \"" + Constants.Packaging.AliasNodeName + "\" atribute in alias element", "actionsElement"); return new {elemet, alias = aliasAttr.Value}; }).ToDictionary(x => x.alias, x => x.elemet); } private IEnumerable InstallDocuments(XElement documentsElement, int userId = 0) { - if (string.Equals(DOCUMENTSET_NODENAME, documentsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"DocumentSet\" as root", "documentsElement"); } + if (string.Equals(Constants.Packaging.DocumentSetNodeName, documentsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.DocumentSetNodeName + "\" as root", "documentsElement"); } return _packagingService.ImportContent(documentsElement, -1, userId).Select(c => c.Id); } private IEnumerable InstallStylesheets(XElement styleSheetsElement, int userId = 0) { - if (string.Equals(STYLESHEETS_NODENAME, styleSheetsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Stylesheets\" as root", "styleSheetsElement"); } + if (string.Equals(Constants.Packaging.StylesheetsNodeName, styleSheetsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.StylesheetsNodeName + "\" as root", "styleSheetsElement"); } return _packagingService.ImportStylesheets(styleSheetsElement, userId).Select(f => f.Id); } private IEnumerable InstallDocumentTypes(XElement documentTypes, int userId = 0) { - if (string.Equals(DOCUMENTTYPES_NODENAME, documentTypes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) + if (string.Equals(Constants.Packaging.DocumentTypesNodeName, documentTypes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { - if (string.Equals(DOCUMENTTYPE_NODENAME, documentTypes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) - throw new ArgumentException("Must be \"" + DOCUMENTTYPES_NODENAME + "\" as root", "documentTypes"); + if (string.Equals(Constants.Packaging.DocumentTypeNodeName, documentTypes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) + throw new ArgumentException("Must be \"" + Constants.Packaging.DocumentTypesNodeName + "\" as root", "documentTypes"); - documentTypes = new XElement(DOCUMENTTYPES_NODENAME, documentTypes); + documentTypes = new XElement(Constants.Packaging.DocumentTypesNodeName, documentTypes); } return _packagingService.ImportContentTypes(documentTypes, userId).Select(ct => ct.Id); @@ -235,29 +192,29 @@ namespace Umbraco.Core.Services private IEnumerable InstallTemplats(XElement templateElement, int userId = 0) { - if (string.Equals(TEMPLATES_NODENAME, templateElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + TEMPLATES_NODENAME + "\" as root", "templateElement"); } + if (string.Equals(Constants.Packaging.TemplatesNodeName, templateElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.TemplatesNodeName + "\" as root", "templateElement"); } return _packagingService.ImportTemplates(templateElement, userId).Select(t => t.Id); } private static IEnumerable> InstallFiles(string packageDir, XElement filesElement) { - if (string.Equals(FILES_NODENAME, filesElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("root element must be \"" + FILES_NODENAME + "\"", "filesElement"); } + if (string.Equals(Constants.Packaging.FilesNodeName, filesElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("root element must be \"" + Constants.Packaging.FilesNodeName + "\"", "filesElement"); } string basePath = HostingEnvironment.ApplicationPhysicalPath; - - var xmlNodeList = filesElement.Elements(FILE_NODENAME); + + var xmlNodeList = filesElement.Elements(Constants.Packaging.FileNodeName); return xmlNodeList.Select(e => { - var orgPathElement = e.Element(ORGPATH_NODENAME); - if (orgPathElement == null) { throw new ArgumentException("Missing element \"" + ORGPATH_NODENAME + "\"", "filesElement"); } + var orgPathElement = e.Element(Constants.Packaging.OrgPathNodeName); + if (orgPathElement == null) { throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgPathNodeName + "\"", "filesElement"); } - var guidElement = e.Element(GUID_NODENAME); - if (guidElement == null) { throw new ArgumentException("Missing element \"" + GUID_NODENAME + "\"", "filesElement"); } + var guidElement = e.Element(Constants.Packaging.GuidNodeName); + if (guidElement == null) { throw new ArgumentException("Missing element \"" + Constants.Packaging.GuidNodeName + "\"", "filesElement"); } - var orgNameElement = e.Element(ORGNAME_NODENAME); - if (orgNameElement == null) { throw new ArgumentException("Missing element \"" + ORGNAME_NODENAME + "\"", "filesElement"); } + var orgNameElement = e.Element(Constants.Packaging.OrgnameNodeName); + if (orgNameElement == null) { throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgnameNodeName + "\"", "filesElement"); } var destPath = GetFileName(basePath, orgPathElement.Value); @@ -276,28 +233,28 @@ namespace Umbraco.Core.Services private IEnumerable InstallMacros(XElement macroElements, int userId = 0) { - if (string.Equals(MACROS_NODENAME, macroElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "macroElements"); } + if (string.Equals(Constants.Packaging.MacrosNodeName, macroElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.MacrosNodeName + "\" as root", "macroElements"); } return _packagingService.ImportMacros(macroElements, userId).Select(m => m.Id); } private IEnumerable InstallDictionaryItems(XElement dictionaryItemsElement, int userId = 0) { - if (string.Equals(DICTIONARYITEMS_NODENAME, dictionaryItemsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "dictionaryItemsElement"); } + if (string.Equals(Constants.Packaging.DictionaryitemsNodeName, dictionaryItemsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.DictionaryitemsNodeName + "\" as root", "dictionaryItemsElement"); } return _packagingService.ImportDictionaryItems(dictionaryItemsElement, userId).Select(di => di.Id); } private IEnumerable InstallLanguages(XElement languageElement, int userId = 0) { - if (string.Equals(LANGUAGES_NODENAME, languageElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "languageElement"); } + if (string.Equals(Constants.Packaging.LanguagesNodeName, languageElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "languageElement"); } return _packagingService.ImportLanguage(languageElement, userId).Select(l => l.Id); } private IEnumerable InstallDataTypes(XElement dataTypeElements, int userId = 0) { - if (string.Equals(DATA_TYPES_NODENAME, dataTypeElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) + if (string.Equals(Constants.Packaging.DataTypesNodeName, dataTypeElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { - if (string.Equals(DATA_TYPE_NODENAME, dataTypeElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) + if (string.Equals(Constants.Packaging.DataTypeNodeName, dataTypeElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "dataTypeElements"); } @@ -307,18 +264,18 @@ namespace Umbraco.Core.Services private static XDocument GetConfigXmlDocFromPackageDirectory(string packageDir) { - string packageXmlPath = Path.Combine(packageDir, PACKAGE_XML_FILE_NAME); - if (File.Exists(packageXmlPath) == false) { throw new FileNotFoundException("Could not find " + PACKAGE_XML_FILE_NAME + " in package"); } + string packageXmlPath = Path.Combine(packageDir, Constants.Packaging.PackageXmlFileName); + if (File.Exists(packageXmlPath) == false) { throw new FileNotFoundException("Could not find " + Constants.Packaging.PackageXmlFileName + " in package"); } return XDocument.Load(packageXmlPath); } private PackageImportIssues FindImportIssues(XElement rootElement) { - var files = rootElement.Element(FILES_NODENAME); - var styleSheets = rootElement.Element(STYLESHEETS_NODENAME); - var templates = rootElement.Element(TEMPLATES_NODENAME); - var alias = rootElement.Element(MACROS_NODENAME); + var files = rootElement.Element(Constants.Packaging.FilesNodeName); + var styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); + var templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); + var alias = rootElement.Element(Constants.Packaging.MacrosNodeName); var packageImportIssues = new PackageImportIssues { UnsecureFiles = files == null ? Enumerable.Empty() : FindUnsecureFiles(files), @@ -332,27 +289,27 @@ namespace Umbraco.Core.Services private IEnumerable FindUnsecureFiles(XElement fileElement) { - if (string.Equals(FILES_NODENAME, fileElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("the root element must be \"Files\"", "fileElement"); } + if (string.Equals(Constants.Packaging.FilesNodeName, fileElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("the root element must be \"Files\"", "fileElement"); } - return fileElement.Elements(FILE_NODENAME) + return fileElement.Elements(Constants.Packaging.FileNodeName) .Where(FileNodeIsUnsecure) .Select(n => { - var xElement = n.Element(ORGNAME_NODENAME); - if (xElement == null) { throw new ArgumentException("missing a element: " + ORGNAME_NODENAME, "n"); } + var xElement = n.Element(Constants.Packaging.OrgnameNodeName); + if (xElement == null) { throw new ArgumentException("missing a element: " + Constants.Packaging.OrgnameNodeName, "n"); } return xElement.Value; }); } private IEnumerable> FindConflictingStylesheetNames(XElement stylesheetNotes) { - if (string.Equals(STYLESHEETS_NODENAME, stylesheetNotes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("the root element must be \"Stylesheets\"", "stylesheetNotes"); } + if (string.Equals(Constants.Packaging.StylesheetsNodeName, stylesheetNotes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("the root element must be \"Stylesheets\"", "stylesheetNotes"); } - return stylesheetNotes.Elements(STYLESHEET_NODENAME) + return stylesheetNotes.Elements(Constants.Packaging.StylesheetNodeName) .Select(n => { - var xElement = n.Element(NAME_NODENAME); - if (xElement == null) { throw new ArgumentException("Missing \"" + NAME_NODENAME + "\" element", "stylesheetNotes"); } + var xElement = n.Element(Constants.Packaging.NameNodeName); + if (xElement == null) { throw new ArgumentException("Missing \"" + Constants.Packaging.NameNodeName + "\" element", "stylesheetNotes"); } string name = xElement.Value; @@ -369,13 +326,13 @@ namespace Umbraco.Core.Services private IEnumerable> FindConflictingTemplateAliases(XElement templateNotes) { - if (string.Equals(TEMPLATES_NODENAME, templateNotes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Node must be a \"" + TEMPLATES_NODENAME + "\" node", "templateNotes"); } + if (string.Equals(Constants.Packaging.TemplatesNodeName, templateNotes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Node must be a \"" + Constants.Packaging.TemplatesNodeName + "\" node", "templateNotes"); } - return templateNotes.Elements(TEMPLATE_NODENAME) + return templateNotes.Elements(Constants.Packaging.TemplateNodeName) .Select(n => { - var alias = n.Element(ALIAS_NODENAME); - if (alias == null) { throw new ArgumentException("missing a \"" + ALIAS_NODENAME + "\" element", "templateNotes"); } + var alias = n.Element(Constants.Packaging.AliasNodeName); + if (alias == null) { throw new ArgumentException("missing a \"" + Constants.Packaging.AliasNodeName + "\" element", "templateNotes"); } string aliasStr = alias.Value; ITemplate existingTemplate; @@ -392,11 +349,11 @@ namespace Umbraco.Core.Services private IEnumerable> FindConflictingMacroAliases(XElement macroNodes) { - return macroNodes.Elements(MACRO_NODENAME) + return macroNodes.Elements(Constants.Packaging.MacroNodeName) .Select(n => { - var xElement = n.Element(ALIAS_NODENAME); - if (xElement == null) { throw new ArgumentException("missing a \"" + ALIAS_NODENAME + "\" element", "macroNodes"); } + var xElement = n.Element(Constants.Packaging.AliasNodeName); + if (xElement == null) { throw new ArgumentException("missing a \"" + Constants.Packaging.AliasNodeName + "\" element", "macroNodes"); } string alias = xElement.Value; IMacro existingMacro; @@ -414,8 +371,8 @@ namespace Umbraco.Core.Services private bool FileNodeIsUnsecure(XElement fileNode) { string basePath = HostingEnvironment.ApplicationPhysicalPath; - var orgName = fileNode.Element(ORGNAME_NODENAME); - if (orgName == null) { throw new ArgumentException("Missing element \"" + ORGNAME_NODENAME + "\"", "fileNode"); } + var orgName = fileNode.Element(Constants.Packaging.OrgnameNodeName); + if (orgName == null) { throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgnameNodeName + "\"", "fileNode"); } string destPath = GetFileName(basePath, orgName.Value); @@ -429,22 +386,22 @@ namespace Umbraco.Core.Services private PackageMetaData GetMetaData(XElement xRootElement) { - XElement infoElement = xRootElement.Element(INFO_NODENAME); + XElement infoElement = xRootElement.Element(Constants.Packaging.InfoNodeName); - if (infoElement == null) { throw new ArgumentException("Did not hold a \"" + INFO_NODENAME + "\" element", "xRootElement"); } + if (infoElement == null) { throw new ArgumentException("Did not hold a \"" + Constants.Packaging.InfoNodeName + "\" element", "xRootElement"); } - var majorElement = infoElement.XPathSelectElement(PACKAGE_REQUIREMENTS_MAJOR_XPATH); - var minorElement = infoElement.XPathSelectElement(PACKAGE_REQUIREMENTS_MINOR_XPATH); - var patchElement = infoElement.XPathSelectElement(PACKAGE_REQUIREMENTS_PATCH_XPATH); - var nameElement = infoElement.XPathSelectElement(PACKAGE_NAME_XPATH); - var versionElement = infoElement.XPathSelectElement(PACKAGE_VERSION_XPATH); - var urlElement = infoElement.XPathSelectElement(PACKAGE_URL_XPATH); - var licenseElement = infoElement.XPathSelectElement(PACKAGE_LICENSE_XPATH); - var authorNameElement = infoElement.XPathSelectElement(AUTHOR_NAME_XPATH); - var authorUrlElement = infoElement.XPathSelectElement(AUTHOR_WEBSITE_XPATH); - var readmeElement = infoElement.XPathSelectElement(README_XPATH); + var majorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMajorXpath); + var minorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMinorXpath); + var patchElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsPatchXpath); + var nameElement = infoElement.XPathSelectElement(Constants.Packaging.PackageNameXpath); + var versionElement = infoElement.XPathSelectElement(Constants.Packaging.PackageVersionXpath); + var urlElement = infoElement.XPathSelectElement(Constants.Packaging.PackageUrlXpath); + var licenseElement = infoElement.XPathSelectElement(Constants.Packaging.PackageLicenseXpath); + var authorNameElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorNameXpath); + var authorUrlElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorWebsiteXpath); + var readmeElement = infoElement.XPathSelectElement(Constants.Packaging.ReadmeXpath); - var controlElement = xRootElement.Element(CONTROL_NODENAME); + var controlElement = xRootElement.Element(Constants.Packaging.ControlNodeName); int val; From a03e46339d90f618bae8604b2073aebe13d1b01c Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Mon, 7 Apr 2014 09:35:31 +0200 Subject: [PATCH 011/189] small layout stuff --- src/Umbraco.Core/Services/PackageInstallerService.cs | 6 ++++-- src/Umbraco.Core/Services/PackageValidationHelper.cs | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Services/PackageInstallerService.cs b/src/Umbraco.Core/Services/PackageInstallerService.cs index 530d0612f2..bf5cf26e00 100644 --- a/src/Umbraco.Core/Services/PackageInstallerService.cs +++ b/src/Umbraco.Core/Services/PackageInstallerService.cs @@ -139,8 +139,10 @@ namespace Umbraco.Core.Services private static string GetUninstallActions(XElement actionsElement) { //saving the uninstall actions untill the package is uninstalled. - return actionsElement.Elements(Constants.Packaging.ActionNodeName).Where(e => e.HasAttributes && e.Attribute(Constants.Packaging.UndoNodeAttribute) != null && e.Attribute(Constants.Packaging.UndoNodeAttribute).Value.Equals("false()", StringComparison.InvariantCultureIgnoreCase) == false) // SelectNodes("Actions/Action [@undo != false()]"); - .Select(m => m.Value).Aggregate((workingSentence, next) => next + workingSentence); + return actionsElement.Elements(Constants.Packaging.ActionNodeName) + .Where(e => e.HasAttributes && e.Attribute(Constants.Packaging.UndoNodeAttribute) != null && e.Attribute(Constants.Packaging.UndoNodeAttribute) + .Value.Equals("false()", StringComparison.InvariantCultureIgnoreCase) == false) // SelectNodes("Actions/Action [@undo != false()]"); + .Select(m => m.Value).Aggregate((workingSentence, next) => next + workingSentence); } private static IEnumerable> GetInstallActions(XElement actionsElement) diff --git a/src/Umbraco.Core/Services/PackageValidationHelper.cs b/src/Umbraco.Core/Services/PackageValidationHelper.cs index cfdb98da0c..e818781734 100644 --- a/src/Umbraco.Core/Services/PackageValidationHelper.cs +++ b/src/Umbraco.Core/Services/PackageValidationHelper.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Umbraco.Core.Models; namespace Umbraco.Core.Services From 889736f63bffe63f6935de080708b7431566304c Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Thu, 8 May 2014 08:43:51 +0200 Subject: [PATCH 012/189] now loosely testet. --- src/Umbraco.Core/Constants-Packaging.cs | 38 +- src/Umbraco.Core/Models/IStyleSheet.cs | 12 + src/Umbraco.Core/Models/Stylesheet.cs | 2 +- src/Umbraco.Core/Packaging/IUnpackHelper.cs | 10 +- .../Packaging/PackageImportIssues.cs | 12 +- .../Packaging/PackageInstallationSummary.cs | 18 +- src/Umbraco.Core/Packaging/UnpackHelper.cs | 92 ++- src/Umbraco.Core/Services/IMacroProperty.cs | 2 +- .../Services/IPackageValidationHelper.cs | 2 +- .../Services/IPackagingService.cs | 27 +- .../Services/PackageInstallerService.cs | 698 +++++++++++------- .../Services/PackageValidationHelper.cs | 2 +- src/Umbraco.Core/Services/ServiceContext.cs | 17 +- src/Umbraco.Core/Umbraco.Core.csproj | 16 +- src/Umbraco.Tests/MockTests.cs | 8 + .../Packages/Document_Type_Picker_1.1.umb | Bin 0 -> 6147 bytes .../Services/PackageInstallerServiceTest.cs | 82 ++ src/Umbraco.Tests/Umbraco.Tests.csproj | 2 + 18 files changed, 676 insertions(+), 364 deletions(-) create mode 100644 src/Umbraco.Core/Models/IStyleSheet.cs create mode 100644 src/Umbraco.Tests/Packages/Document_Type_Picker_1.1.umb create mode 100644 src/Umbraco.Tests/Services/PackageInstallerServiceTest.cs diff --git a/src/Umbraco.Core/Constants-Packaging.cs b/src/Umbraco.Core/Constants-Packaging.cs index a73adf10de..43c0e6f69a 100644 --- a/src/Umbraco.Core/Constants-Packaging.cs +++ b/src/Umbraco.Core/Constants-Packaging.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core +using System.Xml.Linq; + +namespace Umbraco.Core { public static partial class Constants { @@ -13,39 +15,41 @@ public const string UmbracoPackageExtention = ".umb"; public const string DataTypeNodeName = "DataType"; public const string LanguagesNodeName = "Languages"; - public const string FilesNodeName = "Files"; + public const string FilesNodeName = "files"; public const string StylesheetsNodeName = "Stylesheets"; public const string TemplatesNodeName = "Templates"; - public const string OrgnameNodeName = "orgName"; public const string NameNodeName = "Name"; public const string TemplateNodeName = "Template"; public const string AliasNodeName = "Alias"; - public const string DictionaryitemsNodeName = "DictionaryItems"; - public const string MacrosNodeName = "macros"; + public const string DictionaryItemsNodeName = "DictionaryItems"; + public const string DictionaryItemNodeName = "DictionaryItem"; + public const string MacrosNodeName = "Macros"; public const string DocumentSetNodeName = "DocumentSet"; public const string DocumentTypesNodeName = "DocumentTypes"; public const string DocumentTypeNodeName = "DocumentType"; - public const string FileNodeName = "file"; + public const string FileNodeName = "file"; + public const string OrgNameNodeName = "orgName"; public const string OrgPathNodeName = "orgPath"; public const string GuidNodeName = "guid"; public const string StylesheetNodeName = "styleSheet"; public const string MacroNodeName = "macro"; public const string InfoNodeName = "info"; - public const string PackageRequirementsMajorXpath = "/package/requirements/major"; - public const string PackageRequirementsMinorXpath = "/package/requirements/minor"; - public const string PackageRequirementsPatchXpath = "/package/requirements/patch"; - public const string PackageNameXpath = "/package/name"; - public const string PackageVersionXpath = "/package/version"; - public const string PackageUrlXpath = "/package/url"; - public const string PackageLicenseXpath = "/package/license"; - public const string AuthorNameXpath = "/author/name"; - public const string AuthorWebsiteXpath = "/author/website"; - public const string ReadmeXpath = "/readme"; + public const string PackageRequirementsMajorXpath = "./package/requirements/major"; + public const string PackageRequirementsMinorXpath = "./package/requirements/minor"; + public const string PackageRequirementsPatchXpath = "./package/requirements/patch"; + public const string PackageNameXpath = "./package/name"; + public const string PackageVersionXpath = "./package/version"; + public const string PackageUrlXpath = "./package/url"; + public const string PackageLicenseXpath = "./package/license"; + public const string AuthorNameXpath = "./author/name"; + public const string AuthorWebsiteXpath = "./author/website"; + public const string ReadmeXpath = "./readme"; public const string ControlNodeName = "control"; public const string ActionNodeName = "Action"; public const string ActionsNodeName = "Actions"; public const string UndoNodeAttribute = "undo"; - public const string RunatNodeAttribute = "runat"; + public const string RunatNodeAttribute = "runat"; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IStyleSheet.cs b/src/Umbraco.Core/Models/IStyleSheet.cs new file mode 100644 index 0000000000..08e10aedea --- /dev/null +++ b/src/Umbraco.Core/Models/IStyleSheet.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Models +{ + public interface IStylesheet : IFile + { + } +} diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs index 2bed225361..7c92b1b9a9 100644 --- a/src/Umbraco.Core/Models/Stylesheet.cs +++ b/src/Umbraco.Core/Models/Stylesheet.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class Stylesheet : File + public class Stylesheet : File, IStylesheet { public Stylesheet(string path) : base(path) { diff --git a/src/Umbraco.Core/Packaging/IUnpackHelper.cs b/src/Umbraco.Core/Packaging/IUnpackHelper.cs index 29d5b62853..c8d1b9234a 100644 --- a/src/Umbraco.Core/Packaging/IUnpackHelper.cs +++ b/src/Umbraco.Core/Packaging/IUnpackHelper.cs @@ -1,9 +1,11 @@ -namespace Umbraco.Core.Packaging +using System.Collections.Generic; + +namespace Umbraco.Core.Packaging { public interface IUnpackHelper { - void UnPack(string sourcefilePath, string destinationDirectory); - string UnPackToTempDirectory(string sourcefilePath); - string ReadTextFileFromArchive(string sourcefilePath, string fileToRead); + string ReadTextFileFromArchive(string packageFilePath, string fileToRead); + bool CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath); + IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageImportIssues.cs b/src/Umbraco.Core/Packaging/PackageImportIssues.cs index 80b8a36f3f..284ca9c3f7 100644 --- a/src/Umbraco.Core/Packaging/PackageImportIssues.cs +++ b/src/Umbraco.Core/Packaging/PackageImportIssues.cs @@ -1,15 +1,15 @@ -using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Services; namespace Umbraco.Core.Packaging { public class PackageImportIssues { public bool ContainsUnsecureFiles { get { return UnsecureFiles != null && UnsecureFiles.Any(); } } - public IEnumerable UnsecureFiles { get; set; } - public IEnumerable> ConflictingMacroAliases { get; set; } - public IEnumerable> ConflictingTemplateAliases { get; set; } - public IEnumerable> ConflictingStylesheetNames { get; set; } - + public IFileInPackageInfo[] UnsecureFiles { get; set; } + public IMacro[] ConflictingMacroAliases { get; set; } + public ITemplate[] ConflictingTemplateAliases { get; set; } + public IStylesheet[] ConflictingStylesheetNames { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs b/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs index 2902e09fd1..8ad6d36243 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs @@ -1,21 +1,21 @@ using System.Collections.Generic; -using System.Xml; using System.Xml.Linq; +using Umbraco.Core.Models; namespace Umbraco.Core.Packaging { public class PackageInstallationSummary { public PackageMetaData MetaData { get; set; } - public IEnumerable DataTypesInstalled { get; set; } - public IEnumerable LanguagesInstalled { get; set; } - public IEnumerable DictionaryItemsInstalled { get; set; } - public IEnumerable MacrosInstalled { get; set; } + public IDataTypeDefinition[] DataTypesInstalled { get; set; } + public ILanguage[] LanguagesInstalled { get; set; } + public IDictionaryItem[] DictionaryItemsInstalled { get; set; } + public IMacro[] MacrosInstalled { get; set; } public IEnumerable> FilesInstalled { get;set;} - public IEnumerable TemplatesInstalled { get; set; } - public IEnumerable DocumentTypesInstalled { get; set; } - public IEnumerable StylesheetsInstalled { get; set; } - public IEnumerable DocumentsInstalled { get; set; } + public ITemplate[] TemplatesInstalled { get; set; } + public IContentType[] DocumentTypesInstalled { get; set; } + public IStylesheet[] StylesheetsInstalled { get; set; } + public IContent[] DocumentsInstalled { get; set; } public IEnumerable> PackageInstallActions { get; set; } public string PackageUninstallActions { get; set; } } diff --git a/src/Umbraco.Core/Packaging/UnpackHelper.cs b/src/Umbraco.Core/Packaging/UnpackHelper.cs index aaa169f453..b8a7ad3fb5 100644 --- a/src/Umbraco.Core/Packaging/UnpackHelper.cs +++ b/src/Umbraco.Core/Packaging/UnpackHelper.cs @@ -1,23 +1,18 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using ICSharpCode.SharpZipLib.Zip; -using Umbraco.Core.IO; namespace Umbraco.Core.Packaging { public class UnpackHelper : IUnpackHelper { - public string UnPackToTempDirectory(string filePath) + public string ReadTextFileFromArchive(string packageFilePath, string fileToRead) { - string tempDir = IOHelper.MapPath(SystemDirectories.Data) + Path.DirectorySeparatorChar + Guid.NewGuid(); - Directory.CreateDirectory(tempDir); - UnPack(filePath, tempDir); - return tempDir; - } + CheckPackageExists(packageFilePath); - public string ReadTextFileFromArchive(string sourcefilePath, string fileToRead) - { - using (var fs = File.OpenRead(sourcefilePath)) + using (var fs = File.OpenRead(packageFilePath)) { using (var zipStream = new ZipInputStream(fs)) { @@ -38,32 +33,50 @@ namespace Umbraco.Core.Packaging fs.Close(); } - throw new FileNotFoundException(string.Format("Could not find file in package file {0}", sourcefilePath), fileToRead); + throw new FileNotFoundException(string.Format("Could not find file in package file {0}", packageFilePath), fileToRead); } - public void UnPack(string sourcefilePath, string destinationDirectory) + private static void CheckPackageExists(string packageFilePath) { - // Unzip - using (var fs = File.OpenRead(sourcefilePath)) + if (File.Exists(packageFilePath) == false) + throw new ArgumentException(string.Format("Package file: {0} could not be found", packageFilePath), + "packageFilePath"); + } + + + public bool CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath) + { + CheckPackageExists(packageFilePath); + + bool fileFoundInArchive = false; + bool fileOverwritten = false; + + using (var fs = File.OpenRead(packageFilePath)) { using (var zipInputStream = new ZipInputStream(fs)) { ZipEntry zipEntry; while ((zipEntry = zipInputStream.GetNextEntry()) != null) { - string fileName = Path.GetFileName(zipEntry.Name); - if (string.IsNullOrEmpty(fileName)) continue; - - using ( var streamWriter = File.Create(Path.Combine(destinationDirectory, fileName))) + if(zipEntry.Name.Equals(fileInPackageName)) { - var data = new byte[2048]; - int size; - while ((size = zipInputStream.Read(data, 0, data.Length)) > 0) + fileFoundInArchive = true; + + fileOverwritten = File.Exists(destinationfilePath); + + using (var streamWriter = File.Open(destinationfilePath, FileMode.Create)) { - streamWriter.Write(data, 0, size); + var data = new byte[2048]; + int size; + while ((size = zipInputStream.Read(data, 0, data.Length)) > 0) + { + streamWriter.Write(data, 0, size); + } + + streamWriter.Close(); } - streamWriter.Close(); + break; } } @@ -71,6 +84,39 @@ namespace Umbraco.Core.Packaging } fs.Close(); } + + if (fileFoundInArchive == false) throw new ArgumentException(string.Format("Could not find file: {0} in package file: {1}", fileInPackageName, packageFilePath), "fileInPackageName"); + + return fileOverwritten; + } + + public IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles) + { + CheckPackageExists(packageFilePath); + + var exp = expectedFiles.ToDictionary(k => k, v => true); + + using (var fs = File.OpenRead(packageFilePath)) + { + using (var zipInputStream = new ZipInputStream(fs)) + { + ZipEntry zipEntry; + while ((zipEntry = zipInputStream.GetNextEntry()) != null) + { + if (exp.ContainsKey(zipEntry.Name)) + { + exp[zipEntry.Name] = false; + } + } + + zipInputStream.Close(); + } + fs.Close(); + } + + + return exp.Where(kv => kv.Value).Select(kv => kv.Key); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMacroProperty.cs b/src/Umbraco.Core/Services/IMacroProperty.cs index b6ac6eb3f2..3ff879509e 100644 --- a/src/Umbraco.Core/Services/IMacroProperty.cs +++ b/src/Umbraco.Core/Services/IMacroProperty.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Services { - public interface IMacroProperty + internal interface IMacroProperty { /// /// The sortorder diff --git a/src/Umbraco.Core/Services/IPackageValidationHelper.cs b/src/Umbraco.Core/Services/IPackageValidationHelper.cs index 08c493b537..dbbbeafd61 100644 --- a/src/Umbraco.Core/Services/IPackageValidationHelper.cs +++ b/src/Umbraco.Core/Services/IPackageValidationHelper.cs @@ -4,7 +4,7 @@ namespace Umbraco.Core.Services { public interface IPackageValidationHelper { - bool StylesheetExists(string styleSheetName, out Stylesheet existingStyleSheet); + bool StylesheetExists(string styleSheetName, out IStylesheet existingStylesheet); bool TemplateExists(string templateAlias, out ITemplate existingTemplate); bool MacroExists(string macroAlias, out IMacro existingMacro); } diff --git a/src/Umbraco.Core/Services/IPackagingService.cs b/src/Umbraco.Core/Services/IPackagingService.cs index c7e68e6eca..e49f6e24ba 100644 --- a/src/Umbraco.Core/Services/IPackagingService.cs +++ b/src/Umbraco.Core/Services/IPackagingService.cs @@ -1,7 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Xml.Linq; -using Umbraco.Core.Models; - +using Umbraco.Core.Models; + namespace Umbraco.Core.Services { public interface IPackagingService : IService @@ -47,10 +47,10 @@ namespace Umbraco.Core.Services /// /// Imports and saves the 'DictionaryItems' part of the package xml as a list of /// - /// Xml to import + /// Xml to import /// Optional parameter indicating whether or not to raise events - /// An enumerable list of dictionary items - IEnumerable ImportDictionaryItems(XElement dictionaryItemElementList, bool raiseEvents = true); + /// An enumerable list of dictionary items + IEnumerable ImportDictionaryItems(XElement element, bool raiseEvents = true); /// /// Imports and saves the 'Languages' part of a package xml as a list of @@ -71,13 +71,22 @@ namespace Umbraco.Core.Services IEnumerable ImportMacros(XElement element, int userId = 0, bool raiseEvents = true); /// - /// Imports and saves package xml as + /// Imports and saves package xml as /// /// Xml to import /// Optional id of the User performing the operation. Default is zero (admin) /// Optional parameter indicating whether or not to raise events /// An enumrable list of generated Templates - IEnumerable ImportTemplates(XElement element, int userId = 0, bool raiseEvents = true); + IEnumerable ImportTemplates(XElement element, int userId = 0, bool raiseEvents = true); + + /// + /// Imports and saves package xml as + /// + /// Xml to import + /// Optional id of the User performing the operation. Default is zero (admin) + /// Optional parameter indicating whether or not to raise events + /// An enumerable list of generated stylesheets + IEnumerable ImportStylesheets(XElement element, int userId = 0, bool raiseEvents = true); /// /// Exports an to xml as an @@ -185,6 +194,6 @@ namespace Umbraco.Core.Services /// Macro to export /// Optional parameter indicating whether or not to raise events /// containing the xml representation of the IMacro object - XElement Export(IMacro macro, bool raiseEvents = true); + XElement Export(IMacro macro, bool raiseEvents = true); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackageInstallerService.cs b/src/Umbraco.Core/Services/PackageInstallerService.cs index bf5cf26e00..b172b617ca 100644 --- a/src/Umbraco.Core/Services/PackageInstallerService.cs +++ b/src/Umbraco.Core/Services/PackageInstallerService.cs @@ -1,94 +1,168 @@ using System; using System.Collections.Generic; -using System.Diagnostics; +using System.Data; using System.IO; using System.Linq; -using System.Web.Hosting; using System.Xml.Linq; using System.Xml.XPath; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Packaging; -using File = System.IO.File; namespace Umbraco.Core.Services { public class PackageInstallerService : IPackageInstallerService { - private readonly IPackageValidationHelper _packageValidationHelper; + private readonly IFileService _fileService; + private readonly IMacroService _macroService; private readonly IPackagingService _packagingService; - private readonly IUnpackHelper _unpackHelper; + private IPackageValidationHelper _packageValidationHelper; + private IUnpackHelper _unpackHelper; - - public PackageInstallerService(IPackagingService packagingService, IUnpackHelper unpackHelper, IPackageValidationHelper packageValidationHelper) + public PackageInstallerService(IPackagingService packagingService, IMacroService macroService, + IFileService fileService) { - if (packageValidationHelper != null) _packageValidationHelper = packageValidationHelper; else throw new ArgumentNullException("packageValidationHelper"); - if (packagingService != null) _packagingService = packagingService; else throw new ArgumentNullException("packagingService"); - if (unpackHelper != null) _unpackHelper = unpackHelper; else throw new ArgumentNullException("unpackHelper"); + if (macroService != null) _macroService = macroService; + else throw new ArgumentNullException("macroService"); + if (fileService != null) _fileService = fileService; + else throw new ArgumentNullException("fileService"); + if (packagingService != null) _packagingService = packagingService; + else throw new ArgumentNullException("packagingService"); } - public PackageInstallationSummary InstallPackageFile(string packageFilePath, int userId) + public IPackageValidationHelper PackageValidationHelper { - FileInfo fi = GetPackageFileInfo(packageFilePath); - string tempDir = null; - try + private get { - tempDir = _unpackHelper.UnPackToTempDirectory(fi.FullName); - return InstallFromDirectory(tempDir, userId); + return _packageValidationHelper ?? + (_packageValidationHelper = new PackageValidationHelper(_macroService, _fileService)); } - finally + set { - if (string.IsNullOrEmpty(tempDir) == false && Directory.Exists(tempDir)) + if (_packageValidationHelper != null) { - Directory.Delete(tempDir, true); + throw new PropertyConstraintException("This property allraedy have a value"); } + _packageValidationHelper = value; } } + + public IUnpackHelper UnpackHelper + { + private get { return _unpackHelper ?? (_unpackHelper = new UnpackHelper()); } + set + { + if (_unpackHelper != null) + { + throw new PropertyConstraintException("This property allraedy have a value"); + } + _unpackHelper = value; + } + } + + private string _fullpathToRoot; + public string FullpathToRoot + { + private get { return _fullpathToRoot ?? (_fullpathToRoot = GlobalSettings.FullpathToRoot); } + set + { + + if (_fullpathToRoot != null) + { + throw new PropertyConstraintException("This property allraedy have a value"); + } + + _fullpathToRoot = value; + } + } + + public PackageMetaData GetMetaData(string packageFilePath) { - var rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); + XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); return GetMetaData(rootElement); } public PackageImportIssues FindPackageImportIssues(string packageFilePath) { - var rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); + XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); return FindImportIssues(rootElement); } + public PackageInstallationSummary InstallPackageFile(string packageFile, int userId) + { + ValidateFilesExistsInPackage(packageFile); - private FileInfo GetPackageFileInfo(string packageFilePath) + + XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFile); + + XElement dataTypes = rootElement.Element(Constants.Packaging.DataTypesNodeName); + XElement languages = rootElement.Element(Constants.Packaging.LanguagesNodeName); + XElement dictionaryItems = rootElement.Element(Constants.Packaging.DictionaryItemsNodeName); + XElement macroes = rootElement.Element(Constants.Packaging.MacrosNodeName); + XElement files = rootElement.Element(Constants.Packaging.FilesNodeName); + XElement templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); + XElement documentTypes = rootElement.Element(Constants.Packaging.DocumentTypesNodeName); + XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); + XElement documentSet = rootElement.Element(Constants.Packaging.DocumentSetNodeName); + XElement actions = rootElement.Element(Constants.Packaging.ActionsNodeName); + + return new PackageInstallationSummary + { + MetaData = GetMetaData(rootElement), + DataTypesInstalled = + dataTypes == null ? new IDataTypeDefinition[0] : InstallDataTypes(dataTypes, userId), + LanguagesInstalled = languages == null ? new ILanguage[0] : InstallLanguages(languages, userId), + DictionaryItemsInstalled = + dictionaryItems == null ? new IDictionaryItem[0] : InstallDictionaryItems(dictionaryItems), + MacrosInstalled = macroes == null ? new IMacro[0] : InstallMacros(macroes, userId), + FilesInstalled = + packageFile == null + ? Enumerable.Empty>() + : InstallFiles(packageFile, files), + TemplatesInstalled = templates == null ? new ITemplate[0] : InstallTemplats(templates, userId), + DocumentTypesInstalled = + documentTypes == null ? new IContentType[0] : InstallDocumentTypes(documentTypes, userId), + StylesheetsInstalled = + styleSheets == null ? new IStylesheet[0] : InstallStylesheets(styleSheets, userId), + DocumentsInstalled = documentSet == null ? new IContent[0] : InstallDocuments(documentSet, userId), + PackageInstallActions = + actions == null ? Enumerable.Empty>() : GetInstallActions(actions), + PackageUninstallActions = actions == null ? string.Empty : GetUninstallActions(actions) + }; + } + + + private void ValidatePackageFileExists(string packageFilePath) { if (string.IsNullOrEmpty(packageFilePath)) { throw new ArgumentNullException("packageFilePath"); } - var fi = new FileInfo(packageFilePath); - - if (fi.Exists == false) + if (System.IO.File.Exists(packageFilePath) == false) { throw new Exception("Error - file not found. Could find file named '" + packageFilePath + "'"); } - // Check if the file is a valid package - if (fi.Extension.Equals(Constants.Packaging.UmbracoPackageExtention, StringComparison.InvariantCultureIgnoreCase) == false) + if (Path.GetExtension(packageFilePath).Equals(".umb", StringComparison.InvariantCultureIgnoreCase) == false) { - throw new Exception("Error - file isn't a package (doesn't have a .umb extension). Check if the file automatically got named '.zip' upon download."); + throw new Exception( + "Error - file isn't a package (doesn't have a .umb extension). Check if the file automatically got named '.zip' upon download."); } - - return fi; } private XDocument GetConfigXmlDocFromPackageFile(string packageFilePath) { - FileInfo packageFileInfo = GetPackageFileInfo(packageFilePath); + ValidatePackageFileExists(packageFilePath); - string configXmlContent = _unpackHelper.ReadTextFileFromArchive(packageFileInfo.FullName, Constants.Packaging.PackageXmlFileName); + string configXmlContent = UnpackHelper.ReadTextFileFromArchive(packageFilePath, + Constants.Packaging.PackageXmlFileName); return XDocument.Parse(configXmlContent); } @@ -96,293 +170,356 @@ namespace Umbraco.Core.Services private XElement GetConfigXmlRootElementFromPackageFile(string packageFilePath) { - var document = GetConfigXmlDocFromPackageFile(packageFilePath); - if (document.Root == null || document.Root.Name.LocalName.Equals(Constants.Packaging.UmbPackageNodeName) == false) { throw new ArgumentException("xml does not have a root node called \"umbPackage\"", packageFilePath); } + XDocument document = GetConfigXmlDocFromPackageFile(packageFilePath); + if (document.Root == null || + document.Root.Name.LocalName.Equals(Constants.Packaging.UmbPackageNodeName) == false) + { + throw new ArgumentException("xml does not have a root node called \"umbPackage\"", packageFilePath); + } return document.Root; } - - private PackageInstallationSummary InstallFromDirectory(string packageDir, int userId) + private void ValidateFilesExistsInPackage(string packageFilePath) { - var configXml = GetConfigXmlDocFromPackageDirectory(packageDir); - var rootElement = configXml.XPathSelectElement(Constants.Packaging.UmbPackageNodeName); - if (rootElement == null) { throw new ArgumentException("File does not have a root node called \"" + Constants.Packaging.UmbPackageNodeName + "\"", packageDir); } - - var dataTypes = rootElement.Element(Constants.Packaging.DataTypesNodeName); - var languages = rootElement.Element(Constants.Packaging.LanguagesNodeName); - var dictionaryItems = rootElement.Element(Constants.Packaging.DictionaryitemsNodeName); - var macroes = rootElement.Element(Constants.Packaging.MacrosNodeName); - var files = rootElement.Element(Constants.Packaging.FilesNodeName); - var templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); - var documentTypes = rootElement.Element(Constants.Packaging.DocumentTypesNodeName); - var styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); - var documentSet = rootElement.Element(Constants.Packaging.DocumentSetNodeName); - var actions = rootElement.Element(Constants.Packaging.ActionsNodeName); - - return new PackageInstallationSummary + XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); + XElement filesElement = rootElement.Element(Constants.Packaging.FilesNodeName); + if (filesElement != null) { - MetaData = GetMetaData(rootElement), - DataTypesInstalled = dataTypes == null ? Enumerable.Empty() : InstallDataTypes(dataTypes, userId), - LanguagesInstalled = languages == null ? Enumerable.Empty() : InstallLanguages(languages, userId), - DictionaryItemsInstalled = dictionaryItems == null ? Enumerable.Empty() : InstallDictionaryItems(dictionaryItems, userId), - MacrosInstalled = macroes == null ? Enumerable.Empty() : InstallMacros(macroes, userId), - FilesInstalled = packageDir == null ? Enumerable.Empty>() : InstallFiles(packageDir, files), - TemplatesInstalled = templates == null ? Enumerable.Empty() : InstallTemplats(templates, userId), - DocumentTypesInstalled = documentTypes == null ? Enumerable.Empty() : InstallDocumentTypes(documentTypes, userId), - StylesheetsInstalled = styleSheets == null ? Enumerable.Empty() : InstallStylesheets(styleSheets, userId), - DocumentsInstalled = documentSet == null ? Enumerable.Empty() : InstallDocuments(documentSet, userId), - PackageInstallActions = actions == null ? Enumerable.Empty>() : GetInstallActions(actions), - PackageUninstallActions = actions == null ? string.Empty : GetUninstallActions(actions) - }; + IEnumerable extractFileInPackageInfos = + ExtractFileInPackageInfos(filesElement).ToArray(); + + IEnumerable missingFiles = + _unpackHelper.FindMissingFiles(packageFilePath, + extractFileInPackageInfos.Select(i => i.FileNameInPackage)).ToArray(); + + if (missingFiles.Any()) + { + throw new Exception("The following file(s) are missing in the package: " + + string.Join(", ", missingFiles.Select( + mf => + { + FileInPackageInfo fileInPackageInfo = + extractFileInPackageInfos.Single(fi => fi.FileNameInPackage == mf); + return string.Format("Guid: \"{0}\" Original File: \"{1}\"", + fileInPackageInfo.FileNameInPackage, fileInPackageInfo.RelativePath); + }))); + } + } } + private static string GetUninstallActions(XElement actionsElement) { //saving the uninstall actions untill the package is uninstalled. return actionsElement.Elements(Constants.Packaging.ActionNodeName) - .Where(e => e.HasAttributes && e.Attribute(Constants.Packaging.UndoNodeAttribute) != null && e.Attribute(Constants.Packaging.UndoNodeAttribute) - .Value.Equals("false()", StringComparison.InvariantCultureIgnoreCase) == false) // SelectNodes("Actions/Action [@undo != false()]"); - .Select(m => m.Value).Aggregate((workingSentence, next) => next + workingSentence); + .Where( + e => + e.HasAttributes && e.Attribute(Constants.Packaging.UndoNodeAttribute) != null && + e.Attribute(Constants.Packaging.UndoNodeAttribute) + .Value.Equals("false()", StringComparison.InvariantCultureIgnoreCase) == false) + // SelectNodes("Actions/Action [@undo != false()]"); + .Select(m => m.Value).Aggregate((workingSentence, next) => next + workingSentence); } private static IEnumerable> GetInstallActions(XElement actionsElement) { - if (actionsElement == null) { return Enumerable.Empty>(); } + if (actionsElement == null) + { + return Enumerable.Empty>(); + } - if (string.Equals(Constants.Packaging.ActionsNodeName, actionsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.ActionsNodeName + "\" as root", "actionsElement"); } + if (string.Equals(Constants.Packaging.ActionsNodeName, actionsElement.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.ActionsNodeName + "\" as root", + "actionsElement"); + } return actionsElement.Elements(Constants.Packaging.ActionNodeName) .Where( e => e.HasAttributes && (e.Attribute(Constants.Packaging.RunatNodeAttribute) == null || - e.Attribute(Constants.Packaging.RunatNodeAttribute).Value.Equals("uninstall", StringComparison.InvariantCultureIgnoreCase) == + e.Attribute(Constants.Packaging.RunatNodeAttribute) + .Value.Equals("uninstall", StringComparison.InvariantCultureIgnoreCase) == false)) // .SelectNodes("Actions/Action [@runat != 'uninstall']") .Select(elemet => { - var aliasAttr = elemet.Attribute(Constants.Packaging.AliasNodeName); + XAttribute aliasAttr = elemet.Attribute(Constants.Packaging.AliasNodeName); if (aliasAttr == null) - throw new ArgumentException("missing \"" + Constants.Packaging.AliasNodeName + "\" atribute in alias element", "actionsElement"); + throw new ArgumentException( + "missing \"" + Constants.Packaging.AliasNodeName + "\" atribute in alias element", + "actionsElement"); return new {elemet, alias = aliasAttr.Value}; }).ToDictionary(x => x.alias, x => x.elemet); } - private IEnumerable InstallDocuments(XElement documentsElement, int userId = 0) + private IContent[] InstallDocuments(XElement documentsElement, int userId = 0) { - if (string.Equals(Constants.Packaging.DocumentSetNodeName, documentsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.DocumentSetNodeName + "\" as root", "documentsElement"); } - return _packagingService.ImportContent(documentsElement, -1, userId).Select(c => c.Id); - } - - private IEnumerable InstallStylesheets(XElement styleSheetsElement, int userId = 0) - { - if (string.Equals(Constants.Packaging.StylesheetsNodeName, styleSheetsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.StylesheetsNodeName + "\" as root", "styleSheetsElement"); } - return _packagingService.ImportStylesheets(styleSheetsElement, userId).Select(f => f.Id); - } - - private IEnumerable InstallDocumentTypes(XElement documentTypes, int userId = 0) - { - if (string.Equals(Constants.Packaging.DocumentTypesNodeName, documentTypes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) + if (string.Equals(Constants.Packaging.DocumentSetNodeName, documentsElement.Name.LocalName) == false) { - if (string.Equals(Constants.Packaging.DocumentTypeNodeName, documentTypes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) - throw new ArgumentException("Must be \"" + Constants.Packaging.DocumentTypesNodeName + "\" as root", "documentTypes"); + throw new ArgumentException("Must be \"" + Constants.Packaging.DocumentSetNodeName + "\" as root", + "documentsElement"); + } + return _packagingService.ImportContent(documentsElement, -1, userId).ToArray(); + } + + private IStylesheet[] InstallStylesheets(XElement styleSheetsElement, int userId = 0) + { + if (string.Equals(Constants.Packaging.StylesheetsNodeName, styleSheetsElement.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.StylesheetsNodeName + "\" as root", + "styleSheetsElement"); + } + return _packagingService.ImportStylesheets(styleSheetsElement, userId).ToArray(); + } + + private IContentType[] InstallDocumentTypes(XElement documentTypes, int userId = 0) + { + if (string.Equals(Constants.Packaging.DocumentTypesNodeName, documentTypes.Name.LocalName) == false) + { + if (string.Equals(Constants.Packaging.DocumentTypeNodeName, documentTypes.Name.LocalName) == false) + throw new ArgumentException( + "Must be \"" + Constants.Packaging.DocumentTypesNodeName + "\" as root", "documentTypes"); documentTypes = new XElement(Constants.Packaging.DocumentTypesNodeName, documentTypes); } - return _packagingService.ImportContentTypes(documentTypes, userId).Select(ct => ct.Id); + return _packagingService.ImportContentTypes(documentTypes, userId).ToArray(); } - private IEnumerable InstallTemplats(XElement templateElement, int userId = 0) + private ITemplate[] InstallTemplats(XElement templateElement, int userId = 0) { - if (string.Equals(Constants.Packaging.TemplatesNodeName, templateElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.TemplatesNodeName + "\" as root", "templateElement"); } - return _packagingService.ImportTemplates(templateElement, userId).Select(t => t.Id); - } - - - private static IEnumerable> InstallFiles(string packageDir, XElement filesElement) - { - if (string.Equals(Constants.Packaging.FilesNodeName, filesElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("root element must be \"" + Constants.Packaging.FilesNodeName + "\"", "filesElement"); } - - string basePath = HostingEnvironment.ApplicationPhysicalPath; - - var xmlNodeList = filesElement.Elements(Constants.Packaging.FileNodeName); - - return xmlNodeList.Select(e => + if (string.Equals(Constants.Packaging.TemplatesNodeName, templateElement.Name.LocalName) == false) { - var orgPathElement = e.Element(Constants.Packaging.OrgPathNodeName); - if (orgPathElement == null) { throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgPathNodeName + "\"", "filesElement"); } - - var guidElement = e.Element(Constants.Packaging.GuidNodeName); - if (guidElement == null) { throw new ArgumentException("Missing element \"" + Constants.Packaging.GuidNodeName + "\"", "filesElement"); } - - var orgNameElement = e.Element(Constants.Packaging.OrgnameNodeName); - if (orgNameElement == null) { throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgnameNodeName + "\"", "filesElement"); } - - - var destPath = GetFileName(basePath, orgPathElement.Value); - var sourceFile = GetFileName(packageDir, guidElement.Value); - var destFile = GetFileName(destPath, orgNameElement.Value); - - if (Directory.Exists(destPath) == false) Directory.CreateDirectory(destPath); - - var existingOverrided = File.Exists(destFile); - - File.Copy(sourceFile, destFile, true); - - return new KeyValuePair(orgPathElement.Value + "/" + orgNameElement.Value, existingOverrided); - }); + throw new ArgumentException("Must be \"" + Constants.Packaging.TemplatesNodeName + "\" as root", + "templateElement"); + } + return _packagingService.ImportTemplates(templateElement, userId).ToArray(); } - private IEnumerable InstallMacros(XElement macroElements, int userId = 0) - { - if (string.Equals(Constants.Packaging.MacrosNodeName, macroElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.MacrosNodeName + "\" as root", "macroElements"); } - return _packagingService.ImportMacros(macroElements, userId).Select(m => m.Id); - } - private IEnumerable InstallDictionaryItems(XElement dictionaryItemsElement, int userId = 0) + private IEnumerable> InstallFiles(string packageFilePath, XElement filesElement) { - if (string.Equals(Constants.Packaging.DictionaryitemsNodeName, dictionaryItemsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.DictionaryitemsNodeName + "\" as root", "dictionaryItemsElement"); } - return _packagingService.ImportDictionaryItems(dictionaryItemsElement, userId).Select(di => di.Id); - } - - private IEnumerable InstallLanguages(XElement languageElement, int userId = 0) - { - if (string.Equals(Constants.Packaging.LanguagesNodeName, languageElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Must be \"Templates\" as root", "languageElement"); } - return _packagingService.ImportLanguage(languageElement, userId).Select(l => l.Id); - } - - private IEnumerable InstallDataTypes(XElement dataTypeElements, int userId = 0) - { - if (string.Equals(Constants.Packaging.DataTypesNodeName, dataTypeElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) + return ExtractFileInPackageInfos(filesElement).Select(fpi => { + bool existingOverrided = _unpackHelper.CopyFileFromArchive(packageFilePath, fpi.FileNameInPackage, + fpi.FullPath); - if (string.Equals(Constants.Packaging.DataTypeNodeName, dataTypeElements.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) + return new KeyValuePair(fpi.FullPath, existingOverrided); + }).ToArray(); + } + + private IMacro[] InstallMacros(XElement macroElements, int userId = 0) + { + if (string.Equals(Constants.Packaging.MacrosNodeName, macroElements.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.MacrosNodeName + "\" as root", + "macroElements"); + } + return _packagingService.ImportMacros(macroElements, userId).ToArray(); + } + + private IDictionaryItem[] InstallDictionaryItems(XElement dictionaryItemsElement) + { + if (string.Equals(Constants.Packaging.DictionaryItemsNodeName, dictionaryItemsElement.Name.LocalName) == + false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.DictionaryItemsNodeName + "\" as root", + "dictionaryItemsElement"); + } + return _packagingService.ImportDictionaryItems(dictionaryItemsElement).ToArray(); + } + + private ILanguage[] InstallLanguages(XElement languageElement, int userId = 0) + { + if (string.Equals(Constants.Packaging.LanguagesNodeName, languageElement.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"Templates\" as root", "languageElement"); + } + return _packagingService.ImportLanguages(languageElement, userId).ToArray(); + } + + private IDataTypeDefinition[] InstallDataTypes(XElement dataTypeElements, int userId = 0) + { + if (string.Equals(Constants.Packaging.DataTypesNodeName, dataTypeElements.Name.LocalName) == false) + { + if (string.Equals(Constants.Packaging.DataTypeNodeName, dataTypeElements.Name.LocalName) == false) { throw new ArgumentException("Must be \"Templates\" as root", "dataTypeElements"); } } - return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId).Select(e => e.Id); + return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId).ToArray(); } - private static XDocument GetConfigXmlDocFromPackageDirectory(string packageDir) - { - string packageXmlPath = Path.Combine(packageDir, Constants.Packaging.PackageXmlFileName); - if (File.Exists(packageXmlPath) == false) { throw new FileNotFoundException("Could not find " + Constants.Packaging.PackageXmlFileName + " in package"); } - return XDocument.Load(packageXmlPath); - } - - private PackageImportIssues FindImportIssues(XElement rootElement) { - var files = rootElement.Element(Constants.Packaging.FilesNodeName); - var styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); - var templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); - var alias = rootElement.Element(Constants.Packaging.MacrosNodeName); + XElement files = rootElement.Element(Constants.Packaging.FilesNodeName); + XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); + XElement templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); + XElement alias = rootElement.Element(Constants.Packaging.MacrosNodeName); var packageImportIssues = new PackageImportIssues { - UnsecureFiles = files == null ? Enumerable.Empty() : FindUnsecureFiles(files), - ConflictingMacroAliases = alias == null ? Enumerable.Empty>() : FindConflictingMacroAliases(alias), - ConflictingTemplateAliases = templates == null ? Enumerable.Empty>() : FindConflictingTemplateAliases(templates), - ConflictingStylesheetNames = styleSheets == null ? Enumerable.Empty>() : FindConflictingStylesheetNames(styleSheets) + UnsecureFiles = files == null ? new IFileInPackageInfo[0] : FindUnsecureFiles(files), + ConflictingMacroAliases = alias == null ? new IMacro[0] : FindConflictingMacroAliases(alias), + ConflictingTemplateAliases = + templates == null ? new ITemplate[0] : FindConflictingTemplateAliases(templates), + ConflictingStylesheetNames = + styleSheets == null ? new IStylesheet[0] : FindConflictingStylesheetNames(styleSheets) }; return packageImportIssues; } - private IEnumerable FindUnsecureFiles(XElement fileElement) + private IFileInPackageInfo[] FindUnsecureFiles(XElement fileElement) { - if (string.Equals(Constants.Packaging.FilesNodeName, fileElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("the root element must be \"Files\"", "fileElement"); } - - return fileElement.Elements(Constants.Packaging.FileNodeName) - .Where(FileNodeIsUnsecure) - .Select(n => - { - var xElement = n.Element(Constants.Packaging.OrgnameNodeName); - if (xElement == null) { throw new ArgumentException("missing a element: " + Constants.Packaging.OrgnameNodeName, "n"); } - return xElement.Value; - }); + return ExtractFileInPackageInfos(fileElement) + .Where(IsFileNodeUnsecure).Cast().ToArray(); } - private IEnumerable> FindConflictingStylesheetNames(XElement stylesheetNotes) + private IStylesheet[] FindConflictingStylesheetNames(XElement stylesheetNotes) { - if (string.Equals(Constants.Packaging.StylesheetsNodeName, stylesheetNotes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("the root element must be \"Stylesheets\"", "stylesheetNotes"); } + if (string.Equals(Constants.Packaging.StylesheetsNodeName, stylesheetNotes.Name.LocalName) == false) + { + throw new ArgumentException("the root element must be \"Stylesheets\"", "stylesheetNotes"); + } return stylesheetNotes.Elements(Constants.Packaging.StylesheetNodeName) - .Select(n => + .Select(n => + { + XElement xElement = n.Element(Constants.Packaging.NameNodeName); + if (xElement == null) { - var xElement = n.Element(Constants.Packaging.NameNodeName); - if (xElement == null) { throw new ArgumentException("Missing \"" + Constants.Packaging.NameNodeName + "\" element", "stylesheetNotes"); } + throw new ArgumentException("Missing \"" + Constants.Packaging.NameNodeName + "\" element", + "stylesheetNotes"); + } - string name = xElement.Value; + string name = xElement.Value; - Stylesheet existingStyleSheet; - if (_packageValidationHelper.StylesheetExists(name, out existingStyleSheet)) - { - // Don't know what to put in here... existing path was the best i could come up with - return new KeyValuePair(name, existingStyleSheet.Path); - } - return new KeyValuePair(name, null); - }) - .Where(kv => kv.Value != null); + IStylesheet existingStyleSheet; + if (PackageValidationHelper.StylesheetExists(name, out existingStyleSheet)) + { + // Don't know what to put in here... existing path was the best i could come up with + return existingStyleSheet; + } + return null; + }) + .Where(v => v != null).ToArray(); } - private IEnumerable> FindConflictingTemplateAliases(XElement templateNotes) + private ITemplate[] FindConflictingTemplateAliases(XElement templateNotes) { - if (string.Equals(Constants.Packaging.TemplatesNodeName, templateNotes.Name.LocalName, StringComparison.InvariantCultureIgnoreCase) == false) { throw new ArgumentException("Node must be a \"" + Constants.Packaging.TemplatesNodeName + "\" node", "templateNotes"); } + if (string.Equals(Constants.Packaging.TemplatesNodeName, templateNotes.Name.LocalName) == false) + { + throw new ArgumentException("Node must be a \"" + Constants.Packaging.TemplatesNodeName + "\" node", + "templateNotes"); + } return templateNotes.Elements(Constants.Packaging.TemplateNodeName) - .Select(n => + .Select(n => + { + XElement alias = n.Element(Constants.Packaging.AliasNodeName); + if (alias == null) { - var alias = n.Element(Constants.Packaging.AliasNodeName); - if (alias == null) { throw new ArgumentException("missing a \"" + Constants.Packaging.AliasNodeName + "\" element", "templateNotes"); } - string aliasStr = alias.Value; + throw new ArgumentException("missing a \"" + Constants.Packaging.AliasNodeName + "\" element", + "templateNotes"); + } + string aliasStr = alias.Value; - ITemplate existingTemplate; + ITemplate existingTemplate; - if (_packageValidationHelper.TemplateExists(aliasStr, out existingTemplate)) - { - return new KeyValuePair(aliasStr, existingTemplate.Name); - } + if (PackageValidationHelper.TemplateExists(aliasStr, out existingTemplate)) + { + return existingTemplate; + } - return new KeyValuePair(aliasStr, null); - }) - .Where(kv => kv.Value != null); + return null; + }) + .Where(v => v != null).ToArray(); } - private IEnumerable> FindConflictingMacroAliases(XElement macroNodes) + private IMacro[] FindConflictingMacroAliases(XElement macroNodes) { return macroNodes.Elements(Constants.Packaging.MacroNodeName) - .Select(n => + .Select(n => + { + XElement xElement = n.Element(Constants.Packaging.AliasNodeName); + if (xElement == null) { - var xElement = n.Element(Constants.Packaging.AliasNodeName); - if (xElement == null) { throw new ArgumentException("missing a \"" + Constants.Packaging.AliasNodeName + "\" element", "macroNodes"); } - string alias = xElement.Value; + throw new ArgumentException("missing a \"" + Constants.Packaging.AliasNodeName + "\" element", + "macroNodes"); + } + string alias = xElement.Value; - IMacro existingMacro; - if (_packageValidationHelper.MacroExists(alias, out existingMacro)) - { - return new KeyValuePair(alias, existingMacro.Name); - } - - return new KeyValuePair(alias, null); - }) - .Where(kv => kv.Key != null && kv.Value != null); + IMacro existingMacro; + if (PackageValidationHelper.MacroExists(alias, out existingMacro)) + { + return existingMacro; + } + + return null; + }) + .Where(v => v != null).ToArray(); } - private bool FileNodeIsUnsecure(XElement fileNode) + private bool IsFileNodeUnsecure(FileInPackageInfo fileInPackageInfo) { - string basePath = HostingEnvironment.ApplicationPhysicalPath; - var orgName = fileNode.Element(Constants.Packaging.OrgnameNodeName); - if (orgName == null) { throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgnameNodeName + "\"", "fileNode"); } - - string destPath = GetFileName(basePath, orgName.Value); - // Should be done with regex :) - if (destPath.ToLower().Contains(IOHelper.DirSepChar + "app_code")) return true; - if (destPath.ToLower().Contains(IOHelper.DirSepChar + "bin")) return true; + if (fileInPackageInfo.Directory.ToLower().Contains(IOHelper.DirSepChar + "app_code")) return true; + if (fileInPackageInfo.Directory.ToLower().Contains(IOHelper.DirSepChar + "bin")) return true; - return destPath.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase); + string extension = Path.GetExtension(fileInPackageInfo.Directory); + + return extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase); + } + + + private IEnumerable ExtractFileInPackageInfos(XElement filesElement) + { + if (string.Equals(Constants.Packaging.FilesNodeName, filesElement.Name.LocalName) == false) + { + throw new ArgumentException("the root element must be \"Files\"", "filesElement"); + } + + return filesElement.Elements(Constants.Packaging.FileNodeName) + .Select(e => + { + XElement guidElement = e.Element(Constants.Packaging.GuidNodeName); + if (guidElement == null) + { + throw new ArgumentException("Missing element \"" + Constants.Packaging.GuidNodeName + "\"", + "filesElement"); + } + + XElement orgPathElement = e.Element(Constants.Packaging.OrgPathNodeName); + if (orgPathElement == null) + { + throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgPathNodeName + "\"", + "filesElement"); + } + + XElement orgNameElement = e.Element(Constants.Packaging.OrgNameNodeName); + if (orgNameElement == null) + { + throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgNameNodeName + "\"", + "filesElement"); + } + + + return new FileInPackageInfo + { + FileNameInPackage = guidElement.Value, + FileName = PrepareAsFilePathElement(orgNameElement.Value), + RelativeDir = UpdatePathPlaceholders( + PrepareAsFilePathElement(orgPathElement.Value)), + DestinationRootDir = FullpathToRoot + }; + }).ToArray(); + } + + private static string PrepareAsFilePathElement(string pathElement) + { + return pathElement.TrimStart(new[] {'\\', '/', '~'}).Replace("/", "\\"); } @@ -390,20 +527,24 @@ namespace Umbraco.Core.Services { XElement infoElement = xRootElement.Element(Constants.Packaging.InfoNodeName); - if (infoElement == null) { throw new ArgumentException("Did not hold a \"" + Constants.Packaging.InfoNodeName + "\" element", "xRootElement"); } + if (infoElement == null) + { + throw new ArgumentException("Did not hold a \"" + Constants.Packaging.InfoNodeName + "\" element", + "xRootElement"); + } - var majorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMajorXpath); - var minorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMinorXpath); - var patchElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsPatchXpath); - var nameElement = infoElement.XPathSelectElement(Constants.Packaging.PackageNameXpath); - var versionElement = infoElement.XPathSelectElement(Constants.Packaging.PackageVersionXpath); - var urlElement = infoElement.XPathSelectElement(Constants.Packaging.PackageUrlXpath); - var licenseElement = infoElement.XPathSelectElement(Constants.Packaging.PackageLicenseXpath); - var authorNameElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorNameXpath); - var authorUrlElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorWebsiteXpath); - var readmeElement = infoElement.XPathSelectElement(Constants.Packaging.ReadmeXpath); + XElement majorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMajorXpath); + XElement minorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMinorXpath); + XElement patchElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsPatchXpath); + XElement nameElement = infoElement.XPathSelectElement(Constants.Packaging.PackageNameXpath); + XElement versionElement = infoElement.XPathSelectElement(Constants.Packaging.PackageVersionXpath); + XElement urlElement = infoElement.XPathSelectElement(Constants.Packaging.PackageUrlXpath); + XElement licenseElement = infoElement.XPathSelectElement(Constants.Packaging.PackageLicenseXpath); + XElement authorNameElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorNameXpath); + XElement authorUrlElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorWebsiteXpath); + XElement readmeElement = infoElement.XPathSelectElement(Constants.Packaging.ReadmeXpath); - var controlElement = xRootElement.Element(Constants.Packaging.ControlNodeName); + XElement controlElement = xRootElement.Element(Constants.Packaging.ControlNodeName); int val; @@ -413,7 +554,10 @@ namespace Umbraco.Core.Services Version = versionElement == null ? string.Empty : versionElement.Value, Url = urlElement == null ? string.Empty : urlElement.Value, License = licenseElement == null ? string.Empty : licenseElement.Value, - LicenseUrl = licenseElement == null ? string.Empty : licenseElement.HasAttributes ? licenseElement.AttributeValue("url") : string.Empty, + LicenseUrl = + licenseElement == null + ? string.Empty + : licenseElement.HasAttributes ? licenseElement.AttributeValue("url") : string.Empty, AuthorName = authorNameElement == null ? string.Empty : authorNameElement.Value, AuthorUrl = authorUrlElement == null ? string.Empty : authorUrlElement.Value, Readme = readmeElement == null ? string.Empty : readmeElement.Value, @@ -424,20 +568,8 @@ namespace Umbraco.Core.Services }; } - - /// - /// Gets the name of the file in the specified path. - /// Corrects possible problems with slashes that would result from a simple concatenation. - /// Can also be used to concatenate paths. - /// - /// The path. - /// Name of the file. - /// The name of the file in the specified path. - private static String GetFileName(string path, string fileName) + private static string UpdatePathPlaceholders(string path) { - // virtual dir support - fileName = IOHelper.FindFile(fileName); - if (path.Contains("[$")) { //this is experimental and undocumented... @@ -446,32 +578,38 @@ namespace Umbraco.Core.Services path = path.Replace("[$CONFIG]", SystemDirectories.Config); path = path.Replace("[$DATA]", SystemDirectories.Data); } - - //to support virtual dirs we try to lookup the file... - path = IOHelper.FindFile(path); - - Debug.Assert(path != null && path.Length >= 1); - Debug.Assert(fileName != null && fileName.Length >= 1); - - path = path.Replace('/', '\\'); - fileName = fileName.Replace('/', '\\'); - - // Does filename start with a slash? Does path end with one? - bool fileNameStartsWithSlash = (fileName[0] == Path.DirectorySeparatorChar); - bool pathEndsWithSlash = (path[path.Length - 1] == Path.DirectorySeparatorChar); - - // Path ends with a slash - if (pathEndsWithSlash) - { - if (fileNameStartsWithSlash == false) - // No double slash, just concatenate - return path + fileName; - return path + fileName.Substring(1); - } - if (fileNameStartsWithSlash) - // Required slash specified, just concatenate - return path + fileName; - return path + Path.DirectorySeparatorChar + fileName; + return path; } } + + public class FileInPackageInfo : IFileInPackageInfo + { + public string RelativePath + { + get { return Path.Combine(RelativeDir, FileName); } + } + + public string FileNameInPackage { get; set; } + public string RelativeDir { get; set; } + public string DestinationRootDir { private get; set; } + + public string Directory + { + get { return Path.Combine(DestinationRootDir, RelativeDir); } + } + + public string FullPath + { + get { return Path.Combine(DestinationRootDir, RelativePath); } + } + + public string FileName { get; set; } + } + + public interface IFileInPackageInfo + { + string RelativeDir { get; } + string RelativePath { get; } + string FileName { get; set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackageValidationHelper.cs b/src/Umbraco.Core/Services/PackageValidationHelper.cs index e818781734..87495a4652 100644 --- a/src/Umbraco.Core/Services/PackageValidationHelper.cs +++ b/src/Umbraco.Core/Services/PackageValidationHelper.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Services else throw new ArgumentNullException("macroService"); } - public bool StylesheetExists(string styleSheetName, out Stylesheet existingStyleSheet) + public bool StylesheetExists(string styleSheetName, out IStylesheet existingStyleSheet) { existingStyleSheet = _fileService.GetStylesheetByName(styleSheetName); return existingStyleSheet != null; diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index f358e41bf3..5ffb45703f 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -22,6 +22,7 @@ namespace Umbraco.Core.Services private Lazy _fileService; private Lazy _localizationService; private Lazy _packagingService; + private Lazy _packageInstallerService; private Lazy _serverRegistrationService; private Lazy _entityService; private Lazy _relationService; @@ -31,7 +32,7 @@ namespace Umbraco.Core.Services private Lazy _memberTypeService; private Lazy _memberGroupService; private Lazy _notificationService; - + /// /// public ctor - will generally just be used for unit testing /// @@ -42,6 +43,7 @@ namespace Umbraco.Core.Services /// /// /// + /// /// /// /// @@ -59,7 +61,8 @@ namespace Umbraco.Core.Services IDataTypeService dataTypeService, IFileService fileService, ILocalizationService localizationService, - IPackagingService packagingService, + IPackagingService packagingService, + IPackageInstallerService packageInstallerService, IEntityService entityService, IRelationService relationService, IMemberGroupService memberGroupService, @@ -79,6 +82,7 @@ namespace Umbraco.Core.Services _fileService = new Lazy(() => fileService); _localizationService = new Lazy(() => localizationService); _packagingService = new Lazy(() => packagingService); + _packageInstallerService = new Lazy(() => packageInstallerService); _entityService = new Lazy(() => entityService); _relationService = new Lazy(() => relationService); _sectionService = new Lazy(() => sectionService); @@ -152,6 +156,9 @@ namespace Umbraco.Core.Services if (_packagingService == null) _packagingService = new Lazy(() => new PackagingService(_contentService.Value, _contentTypeService.Value, _mediaService.Value, _macroService.Value, _dataTypeService.Value, _fileService.Value, _localizationService.Value, repositoryFactory.Value, provider)); + if (_packageInstallerService == null) + _packageInstallerService = new Lazy(() => new PackageInstallerService(_packagingService.Value, _macroService.Value, _fileService.Value)); + if (_entityService == null) _entityService = new Lazy(() => new EntityService(provider, repositoryFactory.Value, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value)); @@ -328,6 +335,10 @@ namespace Umbraco.Core.Services { get { return _memberGroupService.Value; } } - + + public IPackageInstallerService PackageInstallerService + { + get { return _packageInstallerService.Value; } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ee90d31374..ab427d59e5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -350,6 +350,7 @@ + @@ -364,9 +365,12 @@ - - + + + + + @@ -374,8 +378,8 @@ - + @@ -1020,7 +1024,6 @@ - @@ -1036,7 +1039,6 @@ - @@ -1046,7 +1048,6 @@ - @@ -1055,7 +1056,6 @@ - @@ -1065,9 +1065,7 @@ - - diff --git a/src/Umbraco.Tests/MockTests.cs b/src/Umbraco.Tests/MockTests.cs index f8810fe67f..eaa7b81850 100644 --- a/src/Umbraco.Tests/MockTests.cs +++ b/src/Umbraco.Tests/MockTests.cs @@ -44,6 +44,10 @@ namespace Umbraco.Tests new Mock().Object, new RepositoryFactory(true), new Mock().Object), + new PackageInstallerService( + new Mock().Object, + new Mock().Object, + new Mock().Object), new Mock().Object, new RelationService( new Mock().Object, @@ -89,6 +93,10 @@ namespace Umbraco.Tests new Mock().Object, new RepositoryFactory(true), new Mock().Object), + new PackageInstallerService( + new Mock().Object, + new Mock().Object, + new Mock().Object), new Mock().Object, new RelationService( new Mock().Object, diff --git a/src/Umbraco.Tests/Packages/Document_Type_Picker_1.1.umb b/src/Umbraco.Tests/Packages/Document_Type_Picker_1.1.umb new file mode 100644 index 0000000000000000000000000000000000000000..18449bd3735870c98e711525a7ea9883941e321f GIT binary patch literal 6147 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 E0K6^9ApigX literal 0 HcmV?d00001 diff --git a/src/Umbraco.Tests/Services/PackageInstallerServiceTest.cs b/src/Umbraco.Tests/Services/PackageInstallerServiceTest.cs new file mode 100644 index 0000000000..f46e243d95 --- /dev/null +++ b/src/Umbraco.Tests/Services/PackageInstallerServiceTest.cs @@ -0,0 +1,82 @@ +using System.IO; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers; + +namespace Umbraco.Tests.Services +{ + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture] + public class PackageInstallerServiceTest : BaseServiceTest + { + private const string DOCUMENT_TYPE_PICKER_UMB = "Document_Type_Picker_1.1.umb"; + private const string TEST_PACKAGES_DIR_NAME = "Packages"; + + [SetUp] + public override void Initialize() + { + base.Initialize(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + [Test] + public void PackageInstallerService_TestSomething() + { + // Arrange + var path = GetTestPackagePath(DOCUMENT_TYPE_PICKER_UMB); + + // Act + var packageMetaData = ServiceContext.PackageInstallerService.GetMetaData(path); + + // Assert + Assert.IsNotNull(packageMetaData); + } + + [Test] + public void PackageInstallerService_TestSomethingelse() + { + // Arrange + var path = GetTestPackagePath(DOCUMENT_TYPE_PICKER_UMB); + + // Act + var importIssues = ServiceContext.PackageInstallerService.FindPackageImportIssues(path); + + // Assert + Assert.IsNotNull(importIssues); + } + + + [Test] + public void PackageInstallerService_TestSomethingthered() + { + // Arrange + var path = GetTestPackagePath(DOCUMENT_TYPE_PICKER_UMB); + + // Act + var packageMetaData = ServiceContext.PackageInstallerService.InstallPackageFile(path, -1); + // Assert + IDataTypeDefinition dataTypeDefinitionById = ApplicationContext.Services.DataTypeService.GetDataTypeDefinitionById( + packageMetaData.DataTypesInstalled.Single().Id); + + Assert.IsNotNull(dataTypeDefinitionById); + + foreach (var result in packageMetaData.FilesInstalled.Select(fi => fi.Key)) + { + Assert.IsTrue(System.IO.File.Exists(result)); + System.IO.File.Delete(result); + } + } + + private static string GetTestPackagePath(string packageName) + { + string path = Path.Combine(Core.Configuration.GlobalSettings.FullpathToRoot, TEST_PACKAGES_DIR_NAME, packageName); + return path; + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 499ebaa336..acc47f85f6 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -322,6 +322,7 @@ + @@ -700,6 +701,7 @@ + From 46eaba668c51aaf5a92ff613d4564744940c12e5 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Thu, 8 May 2014 08:44:56 +0200 Subject: [PATCH 013/189] Missed this in last commit --- src/Umbraco.Core/Services/PackagingService.cs | 94 +++++++++++-------- 1 file changed, 56 insertions(+), 38 deletions(-) diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 70ff9c09e8..269a6a1c06 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -837,6 +837,36 @@ namespace Umbraco.Core.Services return list; } + + //public IEnumerable ImportDictionaryItems(XElement element, int userId = 0, bool raiseEvents = true) + //{ + + // if (raiseEvents) + // { + // if (ImportingDictionaryItem.IsRaisedEventCancelled(new ImportEventArgs(element), this)) + // return Enumerable.Empty(); + // } + + // var name = element.Name.LocalName; + // if (name.Equals(Constants.Packaging.DictionaryItemsNodeName) == false) + // { + // throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called '" + Constants.Packaging.DictionaryItemsNodeName + "' for import"); + // } + + // IEnumerable list = Enumerable.Empty(); + + // //throw new NotImplementedException(); + + + // if (raiseEvents) + // { + + // ImportedDataType.RaiseEvent(new ImportEventArgs(list, element, false), this); + // } + + // return list; + //} + private void SavePrevaluesFromXml(List dataTypes, IEnumerable dataTypeElements) { foreach (var dataTypeElement in dataTypeElements) @@ -1474,47 +1504,29 @@ namespace Umbraco.Core.Services return templates; } - public IEnumerable ImportLanguage(XElement element, int userId = 0) + + public IEnumerable ImportStylesheets(XElement element, int userId = 0, bool raiseEvents = true) { - throw new NotImplementedException(); + + if (raiseEvents) + { + if (ImportingStylesheets.IsRaisedEventCancelled(new ImportEventArgs(element), this)) + return Enumerable.Empty(); + } + + IEnumerable styleSheets = Enumerable.Empty(); + + if(element.Elements().Any()) + throw new NotImplementedException("This needs to be implimentet"); + + + if (raiseEvents) + ImportingStylesheets.RaiseEvent(new ImportEventArgs(styleSheets, element, false), this); + + return styleSheets; + } - public IEnumerable ImportStylesheets(XElement element, int userId = 0) - { - throw new NotImplementedException(); - - - //foreach (XmlNode n in xmlNodeList.OfType()) - //{ - // StyleSheet s = StyleSheet.MakeNew( - // currentUser, - // XmlHelper.GetNodeValue(n.SelectSingleNode("Name")), - // XmlHelper.GetNodeValue(n.SelectSingleNode("FileName")), - // XmlHelper.GetNodeValue(n.SelectSingleNode("Content"))); - - // foreach (XmlNode prop in n.SelectNodes("Properties/Property")) - // { - // StylesheetProperty sp = StylesheetProperty.MakeNew( - // xmlHelper.GetNodeValue(prop.SelectSingleNode("Name")), - // s, - // currentUser); - // sp.Alias = XmlHelper.GetNodeValue(prop.SelectSingleNode("Alias")); - // sp.value = XmlHelper.GetNodeValue(prop.SelectSingleNode("Value")); - // } - // s.saveCssToFile(); - // s.Save(); - //} - } - - public IEnumerable ImportMacros(XElement xElement, int userId = 0) - { - throw new NotImplementedException(); - } - - public IEnumerable ImportDictionaryItems(XElement xElement, int userId = 0) - { - throw new NotImplementedException(); - } private bool IsMasterPageSyntax(string code) { @@ -1739,6 +1751,12 @@ namespace Umbraco.Core.Services /// public static event TypedEventHandler> ImportingTemplate; + /// + /// Occurs before Importing Stylesheets + /// + public static event TypedEventHandler> ImportingStylesheets; + + /// /// Occurs after Template is Imported and Saved /// From 4c6ec3ac4b6089c319920a5e64c2212f058a59be Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Thu, 8 May 2014 09:26:47 +0200 Subject: [PATCH 014/189] Removed som out commentet code --- src/Umbraco.Core/Services/PackagingService.cs | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 269a6a1c06..03e039be87 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -837,36 +837,6 @@ namespace Umbraco.Core.Services return list; } - - //public IEnumerable ImportDictionaryItems(XElement element, int userId = 0, bool raiseEvents = true) - //{ - - // if (raiseEvents) - // { - // if (ImportingDictionaryItem.IsRaisedEventCancelled(new ImportEventArgs(element), this)) - // return Enumerable.Empty(); - // } - - // var name = element.Name.LocalName; - // if (name.Equals(Constants.Packaging.DictionaryItemsNodeName) == false) - // { - // throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called '" + Constants.Packaging.DictionaryItemsNodeName + "' for import"); - // } - - // IEnumerable list = Enumerable.Empty(); - - // //throw new NotImplementedException(); - - - // if (raiseEvents) - // { - - // ImportedDataType.RaiseEvent(new ImportEventArgs(list, element, false), this); - // } - - // return list; - //} - private void SavePrevaluesFromXml(List dataTypes, IEnumerable dataTypeElements) { foreach (var dataTypeElement in dataTypeElements) From 531eba6e289ec622a102b6617287f4dd6b134111 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Thu, 8 May 2014 11:21:10 +0200 Subject: [PATCH 015/189] corrected comment --- src/Umbraco.Core/Services/IPackagingService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/IPackagingService.cs b/src/Umbraco.Core/Services/IPackagingService.cs index e49f6e24ba..44481dd1de 100644 --- a/src/Umbraco.Core/Services/IPackagingService.cs +++ b/src/Umbraco.Core/Services/IPackagingService.cs @@ -71,7 +71,7 @@ namespace Umbraco.Core.Services IEnumerable ImportMacros(XElement element, int userId = 0, bool raiseEvents = true); /// - /// Imports and saves package xml as + /// Imports and saves package xml as /// /// Xml to import /// Optional id of the User performing the operation. Default is zero (admin) From cb5fa8ac135ec8725c656295211804db9fb1b4c0 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Thu, 8 May 2014 12:30:53 +0200 Subject: [PATCH 016/189] Removed "double" file --- src/Umbraco.Core/Services/IMacroProperty.cs | 56 --------------------- src/Umbraco.Core/Umbraco.Core.csproj | 3 +- 2 files changed, 1 insertion(+), 58 deletions(-) delete mode 100644 src/Umbraco.Core/Services/IMacroProperty.cs diff --git a/src/Umbraco.Core/Services/IMacroProperty.cs b/src/Umbraco.Core/Services/IMacroProperty.cs deleted file mode 100644 index 3ff879509e..0000000000 --- a/src/Umbraco.Core/Services/IMacroProperty.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Xml; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Services -{ - internal interface IMacroProperty - { - /// - /// The sortorder - /// - int SortOrder { get; set; } - - /// - /// The alias if of the macroproperty, this is used in the special macro element - /// - /// - /// - string Alias { get; set; } - - /// - /// The userfriendly name - /// - string Name { get; set; } - - /// - /// Gets the id. - /// - /// The id. - int Id { get; } - - /// - /// Gets or sets the macro. - /// - /// The macro. - IMacro Macro { get; set; } - - /// - /// The basetype which defines which component is used in the UI for editing content - /// - IMacroPropertyType Type { get; set; } - - /// - /// Deletes the current macroproperty - /// - void Delete(); - - void Save(); - - /// - /// Retrieve a Xmlrepresentation of the MacroProperty used for exporting the Macro to the package - /// - /// XmlDocument context - /// A xmlrepresentation of the object - XmlElement ToXml(XmlDocument xd); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ab427d59e5..a2475919fc 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -350,7 +350,7 @@ - + @@ -1039,7 +1039,6 @@ - From fa56413d029e2fa1778fd9a5b4ecc01d25d02cbe Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Thu, 8 May 2014 12:35:34 +0200 Subject: [PATCH 017/189] undo rename of argument --- src/Umbraco.Core/Services/IPackagingService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Services/IPackagingService.cs b/src/Umbraco.Core/Services/IPackagingService.cs index 44481dd1de..97f11d8cd3 100644 --- a/src/Umbraco.Core/Services/IPackagingService.cs +++ b/src/Umbraco.Core/Services/IPackagingService.cs @@ -47,10 +47,10 @@ namespace Umbraco.Core.Services /// /// Imports and saves the 'DictionaryItems' part of the package xml as a list of /// - /// Xml to import + /// Xml to import /// Optional parameter indicating whether or not to raise events /// An enumerable list of dictionary items - IEnumerable ImportDictionaryItems(XElement element, bool raiseEvents = true); + IEnumerable ImportDictionaryItems(XElement dictionaryItemElementList, bool raiseEvents = true); /// /// Imports and saves the 'Languages' part of a package xml as a list of From 5691bb0de879bba3623e02e1c462674c366da1c9 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 9 May 2014 08:13:38 +0200 Subject: [PATCH 018/189] Xml elements case sensitive problems --- src/Umbraco.Core/Constants-Packaging.cs | 3 +- .../Services/PackageInstallerService.cs | 137 ++++++++++++------ 2 files changed, 91 insertions(+), 49 deletions(-) diff --git a/src/Umbraco.Core/Constants-Packaging.cs b/src/Umbraco.Core/Constants-Packaging.cs index 43c0e6f69a..ef68b2a656 100644 --- a/src/Umbraco.Core/Constants-Packaging.cs +++ b/src/Umbraco.Core/Constants-Packaging.cs @@ -20,7 +20,8 @@ namespace Umbraco.Core public const string TemplatesNodeName = "Templates"; public const string NameNodeName = "Name"; public const string TemplateNodeName = "Template"; - public const string AliasNodeName = "Alias"; + public const string AliasNodeNameSmall = "alias"; + public const string AliasNodeNameCapital = "Alias"; public const string DictionaryItemsNodeName = "DictionaryItems"; public const string DictionaryItemNodeName = "DictionaryItem"; public const string MacrosNodeName = "Macros"; diff --git a/src/Umbraco.Core/Services/PackageInstallerService.cs b/src/Umbraco.Core/Services/PackageInstallerService.cs index b172b617ca..b5e0a85b61 100644 --- a/src/Umbraco.Core/Services/PackageInstallerService.cs +++ b/src/Umbraco.Core/Services/PackageInstallerService.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Packaging; +using Umbraco.Core.Packaging.Models; namespace Umbraco.Core.Services { @@ -82,57 +83,97 @@ namespace Umbraco.Core.Services public PackageMetaData GetMetaData(string packageFilePath) { - XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); - return GetMetaData(rootElement); + try + { + XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); + return GetMetaData(rootElement); + } + catch (Exception e) + { + throw new Exception("Error reading " + packageFilePath, e); + } } public PackageImportIssues FindPackageImportIssues(string packageFilePath) { - XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); - return FindImportIssues(rootElement); + try + { + XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); + return FindImportIssues(rootElement); + } + catch (Exception e) + { + throw new Exception("Error reading " + packageFilePath, e); + } } public PackageInstallationSummary InstallPackageFile(string packageFile, int userId) { - ValidateFilesExistsInPackage(packageFile); - - - XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFile); - - XElement dataTypes = rootElement.Element(Constants.Packaging.DataTypesNodeName); - XElement languages = rootElement.Element(Constants.Packaging.LanguagesNodeName); - XElement dictionaryItems = rootElement.Element(Constants.Packaging.DictionaryItemsNodeName); - XElement macroes = rootElement.Element(Constants.Packaging.MacrosNodeName); - XElement files = rootElement.Element(Constants.Packaging.FilesNodeName); - XElement templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); - XElement documentTypes = rootElement.Element(Constants.Packaging.DocumentTypesNodeName); - XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); - XElement documentSet = rootElement.Element(Constants.Packaging.DocumentSetNodeName); - XElement actions = rootElement.Element(Constants.Packaging.ActionsNodeName); - - return new PackageInstallationSummary + XElement dataTypes; + XElement languages; + XElement dictionaryItems; + XElement macroes; + XElement files; + XElement templates; + XElement documentTypes; + XElement styleSheets; + XElement documentSet; + XElement actions; + PackageMetaData metaData; + + try { - MetaData = GetMetaData(rootElement), - DataTypesInstalled = - dataTypes == null ? new IDataTypeDefinition[0] : InstallDataTypes(dataTypes, userId), - LanguagesInstalled = languages == null ? new ILanguage[0] : InstallLanguages(languages, userId), - DictionaryItemsInstalled = - dictionaryItems == null ? new IDictionaryItem[0] : InstallDictionaryItems(dictionaryItems), - MacrosInstalled = macroes == null ? new IMacro[0] : InstallMacros(macroes, userId), - FilesInstalled = - packageFile == null - ? Enumerable.Empty>() - : InstallFiles(packageFile, files), - TemplatesInstalled = templates == null ? new ITemplate[0] : InstallTemplats(templates, userId), - DocumentTypesInstalled = - documentTypes == null ? new IContentType[0] : InstallDocumentTypes(documentTypes, userId), - StylesheetsInstalled = - styleSheets == null ? new IStylesheet[0] : InstallStylesheets(styleSheets, userId), - DocumentsInstalled = documentSet == null ? new IContent[0] : InstallDocuments(documentSet, userId), - PackageInstallActions = - actions == null ? Enumerable.Empty>() : GetInstallActions(actions), - PackageUninstallActions = actions == null ? string.Empty : GetUninstallActions(actions) - }; + XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFile); + ValidateFilesExistsInPackage(packageFile); + dataTypes = rootElement.Element(Constants.Packaging.DataTypesNodeName); + languages = rootElement.Element(Constants.Packaging.LanguagesNodeName); + dictionaryItems = rootElement.Element(Constants.Packaging.DictionaryItemsNodeName); + macroes = rootElement.Element(Constants.Packaging.MacrosNodeName); + files = rootElement.Element(Constants.Packaging.FilesNodeName); + templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); + documentTypes = rootElement.Element(Constants.Packaging.DocumentTypesNodeName); + styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); + documentSet = rootElement.Element(Constants.Packaging.DocumentSetNodeName); + actions = rootElement.Element(Constants.Packaging.ActionsNodeName); + + metaData = GetMetaData(rootElement); + } + catch (Exception e) + { + throw new Exception("Error reading " + packageFile, e); + } + + try + { + + return new PackageInstallationSummary + { + MetaData = metaData, + DataTypesInstalled = + dataTypes == null ? new IDataTypeDefinition[0] : InstallDataTypes(dataTypes, userId), + LanguagesInstalled = languages == null ? new ILanguage[0] : InstallLanguages(languages, userId), + DictionaryItemsInstalled = + dictionaryItems == null ? new IDictionaryItem[0] : InstallDictionaryItems(dictionaryItems), + MacrosInstalled = macroes == null ? new IMacro[0] : InstallMacros(macroes, userId), + FilesInstalled = + packageFile == null + ? Enumerable.Empty>() + : InstallFiles(packageFile, files), + TemplatesInstalled = templates == null ? new ITemplate[0] : InstallTemplats(templates, userId), + DocumentTypesInstalled = + documentTypes == null ? new IContentType[0] : InstallDocumentTypes(documentTypes, userId), + StylesheetsInstalled = + styleSheets == null ? new IStylesheet[0] : InstallStylesheets(styleSheets, userId), + DocumentsInstalled = documentSet == null ? new IContent[0] : InstallDocuments(documentSet, userId), + PackageInstallActions = + actions == null ? Enumerable.Empty>() : GetInstallActions(actions), + PackageUninstallActions = actions == null ? string.Empty : GetUninstallActions(actions) + }; + } + catch (Exception e) + { + throw new Exception("Error installing package " + packageFile, e); + } } @@ -244,10 +285,10 @@ namespace Umbraco.Core.Services false)) // .SelectNodes("Actions/Action [@runat != 'uninstall']") .Select(elemet => { - XAttribute aliasAttr = elemet.Attribute(Constants.Packaging.AliasNodeName); + XAttribute aliasAttr = elemet.Attribute(Constants.Packaging.AliasNodeNameSmall) ?? elemet.Attribute(Constants.Packaging.AliasNodeNameCapital); if (aliasAttr == null) throw new ArgumentException( - "missing \"" + Constants.Packaging.AliasNodeName + "\" atribute in alias element", + "missing \"" + Constants.Packaging.AliasNodeNameSmall + "\" atribute in alias element", "actionsElement"); return new {elemet, alias = aliasAttr.Value}; }).ToDictionary(x => x.alias, x => x.elemet); @@ -417,10 +458,10 @@ namespace Umbraco.Core.Services return templateNotes.Elements(Constants.Packaging.TemplateNodeName) .Select(n => { - XElement alias = n.Element(Constants.Packaging.AliasNodeName); + XElement alias = n.Element(Constants.Packaging.AliasNodeNameCapital) ?? n.Element(Constants.Packaging.AliasNodeNameSmall); if (alias == null) { - throw new ArgumentException("missing a \"" + Constants.Packaging.AliasNodeName + "\" element", + throw new ArgumentException("missing a \"" + Constants.Packaging.AliasNodeNameCapital + "\" element", "templateNotes"); } string aliasStr = alias.Value; @@ -442,10 +483,10 @@ namespace Umbraco.Core.Services return macroNodes.Elements(Constants.Packaging.MacroNodeName) .Select(n => { - XElement xElement = n.Element(Constants.Packaging.AliasNodeName); + XElement xElement = n.Element(Constants.Packaging.AliasNodeNameSmall) ?? n.Element(Constants.Packaging.AliasNodeNameCapital); if (xElement == null) { - throw new ArgumentException("missing a \"" + Constants.Packaging.AliasNodeName + "\" element", + throw new ArgumentException(string.Format("missing a \"{0}\" element in {0} element", Constants.Packaging.AliasNodeNameSmall), "macroNodes"); } string alias = xElement.Value; From f8b4caea852020f18244ea6cfca97d9c52694fa8 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 9 May 2014 10:24:16 +0200 Subject: [PATCH 019/189] Fixes now the package does not die hard if data type not found directories in umb packages are now ignored - not sure if this is th right behaver small logical errors --- src/Umbraco.Core/Packaging/UnpackHelper.cs | 21 ++++++++++++------- src/Umbraco.Core/Services/DataTypeService.cs | 15 +++++++++++-- src/Umbraco.Core/Services/IDataTypeService.cs | 9 ++++++++ src/Umbraco.Core/Services/PackagingService.cs | 2 +- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/Packaging/UnpackHelper.cs b/src/Umbraco.Core/Packaging/UnpackHelper.cs index b8a7ad3fb5..832afacf3e 100644 --- a/src/Umbraco.Core/Packaging/UnpackHelper.cs +++ b/src/Umbraco.Core/Packaging/UnpackHelper.cs @@ -19,7 +19,9 @@ namespace Umbraco.Core.Packaging ZipEntry zipEntry; while ((zipEntry = zipStream.GetNextEntry()) != null) { - if (zipEntry.Name.EndsWith(fileToRead, StringComparison.CurrentCultureIgnoreCase)) + string fileName = Path.GetFileName(zipEntry.Name); + + if (string.IsNullOrEmpty(fileName) == false && fileName.Equals(fileToRead, StringComparison.CurrentCultureIgnoreCase)) { using (var reader = new StreamReader(zipStream)) { @@ -58,7 +60,9 @@ namespace Umbraco.Core.Packaging ZipEntry zipEntry; while ((zipEntry = zipInputStream.GetNextEntry()) != null) { - if(zipEntry.Name.Equals(fileInPackageName)) + string fileName = Path.GetFileName(zipEntry.Name); + + if (string.IsNullOrEmpty(fileName) == false && fileName.Equals(fileInPackageName, StringComparison.InvariantCultureIgnoreCase)) { fileFoundInArchive = true; @@ -94,7 +98,7 @@ namespace Umbraco.Core.Packaging { CheckPackageExists(packageFilePath); - var exp = expectedFiles.ToDictionary(k => k, v => true); + var retVal = expectedFiles.ToList(); using (var fs = File.OpenRead(packageFilePath)) { @@ -103,10 +107,11 @@ namespace Umbraco.Core.Packaging ZipEntry zipEntry; while ((zipEntry = zipInputStream.GetNextEntry()) != null) { - if (exp.ContainsKey(zipEntry.Name)) - { - exp[zipEntry.Name] = false; - } + string fileName = Path.GetFileName(zipEntry.Name); + + int index = retVal.FindIndex(f => f.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)); + + if (index != -1) { retVal.RemoveAt(index); } } zipInputStream.Close(); @@ -115,7 +120,7 @@ namespace Umbraco.Core.Packaging } - return exp.Where(kv => kv.Value).Select(kv => kv.Key); + return retVal; } } diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index f1bf4d1321..81d4cba48e 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -76,12 +76,23 @@ namespace Umbraco.Core.Services /// Gets a by its control Id /// /// Id of the DataType control + /// /// Collection of objects with a matching contorl id + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] + public IEnumerable GetDataTypeDefinitionByControlId(Guid id, bool throwIfNotFound) + { + var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, throwIfNotFound); + if (alias == null) + { + return Enumerable.Empty(); + } + return GetDataTypeDefinitionByPropertyEditorAlias(alias); + } + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] public IEnumerable GetDataTypeDefinitionByControlId(Guid id) { - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, true); - return GetDataTypeDefinitionByPropertyEditorAlias(alias); + return GetDataTypeDefinitionByControlId(id, true); } /// diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 7bf938aa25..d0da5a64a8 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -79,6 +79,15 @@ namespace Umbraco.Core.Services [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] IEnumerable GetDataTypeDefinitionByControlId(Guid id); + /// + /// Gets a by its control Id + /// + /// Id of the DataType control + /// Throw exception if the type is not found if true. If false return empty Enumerable if not found + /// + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] + IEnumerable GetDataTypeDefinitionByControlId(Guid id, bool throwIfNotFound); + /// /// Gets a by its control Id /// diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 03e039be87..5dfd32a231 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -568,7 +568,7 @@ namespace Umbraco.Core.Services if (dataTypeDefinition == null) { var dataTypeDefinitions = legacyPropertyEditorId != Guid.Empty - ? _dataTypeService.GetDataTypeDefinitionByControlId(legacyPropertyEditorId) + ? _dataTypeService.GetDataTypeDefinitionByControlId(legacyPropertyEditorId, false) : _dataTypeService.GetDataTypeDefinitionByPropertyEditorAlias(propertyEditorAlias); if (dataTypeDefinitions != null && dataTypeDefinitions.Any()) { From de266351d1d543a2295fd24a6e091b550bf5623a Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 9 May 2014 12:58:45 +0200 Subject: [PATCH 020/189] Refactoring to move redundant code and added sanity check for doublet filenames in package --- src/Umbraco.Core/Packaging/IUnpackHelper.cs | 4 +- src/Umbraco.Core/Packaging/UnpackHelper.cs | 168 +++++++++++------- .../Services/PackageInstallerService.cs | 45 ++--- 3 files changed, 125 insertions(+), 92 deletions(-) diff --git a/src/Umbraco.Core/Packaging/IUnpackHelper.cs b/src/Umbraco.Core/Packaging/IUnpackHelper.cs index c8d1b9234a..1c51c10cba 100644 --- a/src/Umbraco.Core/Packaging/IUnpackHelper.cs +++ b/src/Umbraco.Core/Packaging/IUnpackHelper.cs @@ -4,8 +4,10 @@ namespace Umbraco.Core.Packaging { public interface IUnpackHelper { - string ReadTextFileFromArchive(string packageFilePath, string fileToRead); + string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage); bool CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath); IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles); + IEnumerable FindDubletFileNames(string packageFilePath); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/UnpackHelper.cs b/src/Umbraco.Core/Packaging/UnpackHelper.cs index 832afacf3e..0ad5397d32 100644 --- a/src/Umbraco.Core/Packaging/UnpackHelper.cs +++ b/src/Umbraco.Core/Packaging/UnpackHelper.cs @@ -8,86 +8,95 @@ namespace Umbraco.Core.Packaging { public class UnpackHelper : IUnpackHelper { - public string ReadTextFileFromArchive(string packageFilePath, string fileToRead) + public string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage) { - CheckPackageExists(packageFilePath); - using (var fs = File.OpenRead(packageFilePath)) + string retVal = null; + bool fileFound = false; + string foundDir = null; + + ReadZipfileEntries(packageFilePath, (entry, stream) => { - using (var zipStream = new ZipInputStream(fs)) + string fileName = Path.GetFileName(entry.Name); + + if (string.IsNullOrEmpty(fileName) == false && + fileName.Equals(fileToRead, StringComparison.CurrentCultureIgnoreCase)) { - ZipEntry zipEntry; - while ((zipEntry = zipStream.GetNextEntry()) != null) + + foundDir = entry.Name.Substring(0, entry.Name.Length - fileName.Length); + fileFound = true; + using (var reader = new StreamReader(stream)) { - string fileName = Path.GetFileName(zipEntry.Name); - - if (string.IsNullOrEmpty(fileName) == false && fileName.Equals(fileToRead, StringComparison.CurrentCultureIgnoreCase)) - { - using (var reader = new StreamReader(zipStream)) - { - return reader.ReadToEnd(); - } - } + retVal = reader.ReadToEnd(); + return false; } - - zipStream.Close(); } - fs.Close(); - } + return true; + }); - throw new FileNotFoundException(string.Format("Could not find file in package file {0}", packageFilePath), fileToRead); + if (fileFound == false) + { + directoryInPackage = null; + throw new FileNotFoundException(string.Format("Could not find file in package {0}", packageFilePath), fileToRead); + } + directoryInPackage = foundDir; + return retVal; } private static void CheckPackageExists(string packageFilePath) { + if (string.IsNullOrEmpty(packageFilePath)) + { + throw new ArgumentNullException("packageFilePath"); + } + if (File.Exists(packageFilePath) == false) - throw new ArgumentException(string.Format("Package file: {0} could not be found", packageFilePath), - "packageFilePath"); + { + if (File.Exists(packageFilePath) == false) + throw new ArgumentException(string.Format("Package file: {0} could not be found", packageFilePath)); + } + + // Check if the file is a valid package + if (Path.GetExtension(packageFilePath).Equals(".umb", StringComparison.InvariantCultureIgnoreCase) == false) + { + throw new ArgumentException( + "Error - file isn't a package (doesn't have a .umb extension). Check if the file automatically got named '.zip' upon download."); + } } public bool CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath) { - CheckPackageExists(packageFilePath); - bool fileFoundInArchive = false; bool fileOverwritten = false; - using (var fs = File.OpenRead(packageFilePath)) + ReadZipfileEntries(packageFilePath, (entry, stream) => { - using (var zipInputStream = new ZipInputStream(fs)) + string fileName = Path.GetFileName(entry.Name); + + if (string.IsNullOrEmpty(fileName) == false && + fileName.Equals(fileInPackageName, StringComparison.InvariantCultureIgnoreCase)) { - ZipEntry zipEntry; - while ((zipEntry = zipInputStream.GetNextEntry()) != null) + fileFoundInArchive = true; + + fileOverwritten = File.Exists(destinationfilePath); + + using (var streamWriter = File.Open(destinationfilePath, FileMode.Create)) { - string fileName = Path.GetFileName(zipEntry.Name); - - if (string.IsNullOrEmpty(fileName) == false && fileName.Equals(fileInPackageName, StringComparison.InvariantCultureIgnoreCase)) + var data = new byte[2048]; + int size; + while ((size = stream.Read(data, 0, data.Length)) > 0) { - fileFoundInArchive = true; - - fileOverwritten = File.Exists(destinationfilePath); - - using (var streamWriter = File.Open(destinationfilePath, FileMode.Create)) - { - var data = new byte[2048]; - int size; - while ((size = zipInputStream.Read(data, 0, data.Length)) > 0) - { - streamWriter.Write(data, 0, size); - } - - streamWriter.Close(); - } - - break; + streamWriter.Write(data, 0, size); } - } - zipInputStream.Close(); + streamWriter.Close(); + } + return false; } - fs.Close(); - } + return true; + }); + if (fileFoundInArchive == false) throw new ArgumentException(string.Format("Could not find file: {0} in package file: {1}", fileInPackageName, packageFilePath), "fileInPackageName"); @@ -96,10 +105,50 @@ namespace Umbraco.Core.Packaging public IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles) { - CheckPackageExists(packageFilePath); - var retVal = expectedFiles.ToList(); + ReadZipfileEntries(packageFilePath, (zipEntry, stream) => + { + string fileName = Path.GetFileName(zipEntry.Name); + + int index = retVal.FindIndex(f => f.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)); + + if (index != -1) { retVal.RemoveAt(index); } + + return retVal.Any(); + }); + return retVal; + + } + + public IEnumerable FindDubletFileNames(string packageFilePath) + { + var dictionary = new Dictionary>(); + + + ReadZipfileEntries(packageFilePath, (entry, stream) => + { + string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower(); + + List list; + if (dictionary.TryGetValue(fileName, out list) == false) + { + list = new List(); + dictionary.Add(fileName, list); + } + + list.Add(entry.Name); + + return true; + }); + + return dictionary.Values.Where(v => v.Count > 1).SelectMany(v => v); + } + + private void ReadZipfileEntries(string packageFilePath, Func entryFunc, bool skipsDirectories = true) + { + CheckPackageExists(packageFilePath); + using (var fs = File.OpenRead(packageFilePath)) { using (var zipInputStream = new ZipInputStream(fs)) @@ -107,21 +156,14 @@ namespace Umbraco.Core.Packaging ZipEntry zipEntry; while ((zipEntry = zipInputStream.GetNextEntry()) != null) { - string fileName = Path.GetFileName(zipEntry.Name); - - int index = retVal.FindIndex(f => f.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)); - - if (index != -1) { retVal.RemoveAt(index); } + if (zipEntry.IsDirectory && skipsDirectories) continue; + if( entryFunc(zipEntry, zipInputStream) == false ) break; } zipInputStream.Close(); } fs.Close(); } - - - return retVal; - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackageInstallerService.cs b/src/Umbraco.Core/Services/PackageInstallerService.cs index b5e0a85b61..2462e5ef5b 100644 --- a/src/Umbraco.Core/Services/PackageInstallerService.cs +++ b/src/Umbraco.Core/Services/PackageInstallerService.cs @@ -9,7 +9,6 @@ using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Packaging; -using Umbraco.Core.Packaging.Models; namespace Umbraco.Core.Services { @@ -124,7 +123,7 @@ namespace Umbraco.Core.Services try { XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFile); - ValidateFilesExistsInPackage(packageFile); + PackageStructureSanetyCheck(packageFile); dataTypes = rootElement.Element(Constants.Packaging.DataTypesNodeName); languages = rootElement.Element(Constants.Packaging.LanguagesNodeName); dictionaryItems = rootElement.Element(Constants.Packaging.DictionaryItemsNodeName); @@ -177,33 +176,13 @@ namespace Umbraco.Core.Services } - private void ValidatePackageFileExists(string packageFilePath) - { - if (string.IsNullOrEmpty(packageFilePath)) - { - throw new ArgumentNullException("packageFilePath"); - } - - if (System.IO.File.Exists(packageFilePath) == false) - { - throw new Exception("Error - file not found. Could find file named '" + packageFilePath + "'"); - } - - // Check if the file is a valid package - if (Path.GetExtension(packageFilePath).Equals(".umb", StringComparison.InvariantCultureIgnoreCase) == false) - { - throw new Exception( - "Error - file isn't a package (doesn't have a .umb extension). Check if the file automatically got named '.zip' upon download."); - } - } private XDocument GetConfigXmlDocFromPackageFile(string packageFilePath) { - ValidatePackageFileExists(packageFilePath); - + string filePathInPackage; string configXmlContent = UnpackHelper.ReadTextFileFromArchive(packageFilePath, - Constants.Packaging.PackageXmlFileName); + Constants.Packaging.PackageXmlFileName, out filePathInPackage); return XDocument.Parse(configXmlContent); } @@ -220,7 +199,7 @@ namespace Umbraco.Core.Services return document.Root; } - private void ValidateFilesExistsInPackage(string packageFilePath) + private void PackageStructureSanetyCheck(string packageFilePath) { XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); XElement filesElement = rootElement.Element(Constants.Packaging.FilesNodeName); @@ -245,6 +224,16 @@ namespace Umbraco.Core.Services fileInPackageInfo.FileNameInPackage, fileInPackageInfo.RelativePath); }))); } + + IEnumerable dubletFileNames = _unpackHelper.FindDubletFileNames(packageFilePath).ToArray(); + if (dubletFileNames.Any()) + { + throw new Exception("The following filename(s) are found more than one time in the package, since the filename is used ad primary key, this is not allowed: " + + string.Join(", ", dubletFileNames)); + } + + + } } @@ -375,7 +364,7 @@ namespace Umbraco.Core.Services { if (string.Equals(Constants.Packaging.LanguagesNodeName, languageElement.Name.LocalName) == false) { - throw new ArgumentException("Must be \"Templates\" as root", "languageElement"); + throw new ArgumentException("Must be \"" + Constants.Packaging.LanguagesNodeName + "\" as root", "languageElement"); } return _packagingService.ImportLanguages(languageElement, userId).ToArray(); } @@ -386,7 +375,7 @@ namespace Umbraco.Core.Services { if (string.Equals(Constants.Packaging.DataTypeNodeName, dataTypeElements.Name.LocalName) == false) { - throw new ArgumentException("Must be \"Templates\" as root", "dataTypeElements"); + throw new ArgumentException("Must be \"" + Constants.Packaging.DataTypeNodeName + "\" as root", "dataTypeElements"); } } return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId).ToArray(); @@ -421,7 +410,7 @@ namespace Umbraco.Core.Services { if (string.Equals(Constants.Packaging.StylesheetsNodeName, stylesheetNotes.Name.LocalName) == false) { - throw new ArgumentException("the root element must be \"Stylesheets\"", "stylesheetNotes"); + throw new ArgumentException("the root element must be \"" + Constants.Packaging.StylesheetsNodeName + "\"", "stylesheetNotes"); } return stylesheetNotes.Elements(Constants.Packaging.StylesheetNodeName) From 5d3d2e933173becd5712f64ff253ae4a177d14f0 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 9 May 2014 13:32:26 +0200 Subject: [PATCH 021/189] added comments to IUnpackHelper --- src/Umbraco.Core/Packaging/IUnpackHelper.cs | 35 +++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/Umbraco.Core/Packaging/IUnpackHelper.cs b/src/Umbraco.Core/Packaging/IUnpackHelper.cs index 1c51c10cba..2f65bc8335 100644 --- a/src/Umbraco.Core/Packaging/IUnpackHelper.cs +++ b/src/Umbraco.Core/Packaging/IUnpackHelper.cs @@ -2,11 +2,46 @@ namespace Umbraco.Core.Packaging { + /// + /// Used to access an umbraco package file + /// Remeber that filenames must be unique + /// use "FindDubletFileNames" for sanitycheck + /// public interface IUnpackHelper { + /// + /// Returns the content of the file with the given filename + /// + /// Full path to the ubraco package file + /// filename of the file for wich to get the text content + /// this is the relative directory for the location of the file in the package + /// I dont know why umbraco packages contains directories in the first place?? + /// text content of the file string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage); + + /// + /// Copies a file from package to given destination + /// + /// Full path to the ubraco package file + /// filename of the file to copy + /// destination path (including destination filename) + /// True a file was overwritten bool CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath); + + /// + /// Check if given list of files can be found in the package + /// + /// Full path to the ubraco package file + /// a list of files you would like to find in the package + /// a subset if any of the files in "expectedFiles" that could not be found in the package IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles); + + + /// + /// Sanitycheck - should return en empty collection if package is valid + /// + /// Full path to the ubraco package file + /// list of files that can are found more than ones (accross directories) in the package IEnumerable FindDubletFileNames(string packageFilePath); } From c0b22c43498e5a0511ab860fa7edbdb38330cf9d Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 9 May 2014 14:00:07 +0200 Subject: [PATCH 022/189] renamed a class --- ....cs => ConflictingPackageContentFinder.cs} | 4 +-- ...cs => IConflictingPackageContentFinder.cs} | 2 +- .../Services/PackageInstallerService.cs | 25 ++++++++++--------- src/Umbraco.Core/Umbraco.Core.csproj | 4 +-- 4 files changed, 18 insertions(+), 17 deletions(-) rename src/Umbraco.Core/Services/{PackageValidationHelper.cs => ConflictingPackageContentFinder.cs} (85%) rename src/Umbraco.Core/Services/{IPackageValidationHelper.cs => IConflictingPackageContentFinder.cs} (83%) diff --git a/src/Umbraco.Core/Services/PackageValidationHelper.cs b/src/Umbraco.Core/Services/ConflictingPackageContentFinder.cs similarity index 85% rename from src/Umbraco.Core/Services/PackageValidationHelper.cs rename to src/Umbraco.Core/Services/ConflictingPackageContentFinder.cs index 87495a4652..3eef651fe8 100644 --- a/src/Umbraco.Core/Services/PackageValidationHelper.cs +++ b/src/Umbraco.Core/Services/ConflictingPackageContentFinder.cs @@ -3,12 +3,12 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Services { - public class PackageValidationHelper : IPackageValidationHelper + public class ConflictingPackageContentFinder : IConflictingPackageContentFinder { private readonly IMacroService _macroService; private readonly IFileService _fileService; - public PackageValidationHelper(IMacroService macroService, + public ConflictingPackageContentFinder(IMacroService macroService, IFileService fileService) { if (fileService != null) _fileService = fileService; diff --git a/src/Umbraco.Core/Services/IPackageValidationHelper.cs b/src/Umbraco.Core/Services/IConflictingPackageContentFinder.cs similarity index 83% rename from src/Umbraco.Core/Services/IPackageValidationHelper.cs rename to src/Umbraco.Core/Services/IConflictingPackageContentFinder.cs index dbbbeafd61..d4715aecbe 100644 --- a/src/Umbraco.Core/Services/IPackageValidationHelper.cs +++ b/src/Umbraco.Core/Services/IConflictingPackageContentFinder.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Services { - public interface IPackageValidationHelper + public interface IConflictingPackageContentFinder { bool StylesheetExists(string styleSheetName, out IStylesheet existingStylesheet); bool TemplateExists(string templateAlias, out ITemplate existingTemplate); diff --git a/src/Umbraco.Core/Services/PackageInstallerService.cs b/src/Umbraco.Core/Services/PackageInstallerService.cs index 2462e5ef5b..53fc3bf6ca 100644 --- a/src/Umbraco.Core/Services/PackageInstallerService.cs +++ b/src/Umbraco.Core/Services/PackageInstallerService.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Services private readonly IFileService _fileService; private readonly IMacroService _macroService; private readonly IPackagingService _packagingService; - private IPackageValidationHelper _packageValidationHelper; + private IConflictingPackageContentFinder _conflictingPackageContentFinder; private IUnpackHelper _unpackHelper; public PackageInstallerService(IPackagingService packagingService, IMacroService macroService, @@ -32,20 +32,20 @@ namespace Umbraco.Core.Services } - public IPackageValidationHelper PackageValidationHelper + public IConflictingPackageContentFinder ConflictingPackageContentFinder { private get { - return _packageValidationHelper ?? - (_packageValidationHelper = new PackageValidationHelper(_macroService, _fileService)); + return _conflictingPackageContentFinder ?? + (_conflictingPackageContentFinder = new ConflictingPackageContentFinder(_macroService, _fileService)); } set { - if (_packageValidationHelper != null) + if (_conflictingPackageContentFinder != null) { - throw new PropertyConstraintException("This property allraedy have a value"); + throw new PropertyConstraintException("This property already have a value"); } - _packageValidationHelper = value; + _conflictingPackageContentFinder = value; } } @@ -57,7 +57,7 @@ namespace Umbraco.Core.Services { if (_unpackHelper != null) { - throw new PropertyConstraintException("This property allraedy have a value"); + throw new PropertyConstraintException("This property already have a value"); } _unpackHelper = value; } @@ -72,7 +72,7 @@ namespace Umbraco.Core.Services if (_fullpathToRoot != null) { - throw new PropertyConstraintException("This property allraedy have a value"); + throw new PropertyConstraintException("This property already have a value"); } _fullpathToRoot = value; @@ -426,7 +426,7 @@ namespace Umbraco.Core.Services string name = xElement.Value; IStylesheet existingStyleSheet; - if (PackageValidationHelper.StylesheetExists(name, out existingStyleSheet)) + if (ConflictingPackageContentFinder.StylesheetExists(name, out existingStyleSheet)) { // Don't know what to put in here... existing path was the best i could come up with return existingStyleSheet; @@ -457,7 +457,7 @@ namespace Umbraco.Core.Services ITemplate existingTemplate; - if (PackageValidationHelper.TemplateExists(aliasStr, out existingTemplate)) + if (ConflictingPackageContentFinder.TemplateExists(aliasStr, out existingTemplate)) { return existingTemplate; } @@ -481,7 +481,7 @@ namespace Umbraco.Core.Services string alias = xElement.Value; IMacro existingMacro; - if (PackageValidationHelper.MacroExists(alias, out existingMacro)) + if (ConflictingPackageContentFinder.MacroExists(alias, out existingMacro)) { return existingMacro; } @@ -494,6 +494,7 @@ namespace Umbraco.Core.Services private bool IsFileNodeUnsecure(FileInPackageInfo fileInPackageInfo) { + // Should be done with regex :) if (fileInPackageInfo.Directory.ToLower().Contains(IOHelper.DirSepChar + "app_code")) return true; if (fileInPackageInfo.Directory.ToLower().Contains(IOHelper.DirSepChar + "bin")) return true; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index a2475919fc..65cfa61f54 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1048,7 +1048,7 @@ - + @@ -1065,7 +1065,7 @@ - + From 95ff1a03fbd5e4fd595b475d33ac3ab0040d4702 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 9 May 2014 15:15:51 +0200 Subject: [PATCH 023/189] some renaming --- ...eImportIssues.cs => PreInstallWarnings.cs} | 4 +- .../ConflictingPackageContentFinder.cs | 72 +++++++++++-- .../IConflictingPackageContentFinder.cs | 10 +- .../Services/IPackageInstallerService.cs | 2 +- .../Services/PackageInstallerService.cs | 102 ++---------------- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- 6 files changed, 80 insertions(+), 112 deletions(-) rename src/Umbraco.Core/Packaging/{PackageImportIssues.cs => PreInstallWarnings.cs} (67%) diff --git a/src/Umbraco.Core/Packaging/PackageImportIssues.cs b/src/Umbraco.Core/Packaging/PreInstallWarnings.cs similarity index 67% rename from src/Umbraco.Core/Packaging/PackageImportIssues.cs rename to src/Umbraco.Core/Packaging/PreInstallWarnings.cs index 284ca9c3f7..9fd069a5df 100644 --- a/src/Umbraco.Core/Packaging/PackageImportIssues.cs +++ b/src/Umbraco.Core/Packaging/PreInstallWarnings.cs @@ -1,12 +1,10 @@ -using System.Linq; using Umbraco.Core.Models; using Umbraco.Core.Services; namespace Umbraco.Core.Packaging { - public class PackageImportIssues + public class PreInstallWarnings { - public bool ContainsUnsecureFiles { get { return UnsecureFiles != null && UnsecureFiles.Any(); } } public IFileInPackageInfo[] UnsecureFiles { get; set; } public IMacro[] ConflictingMacroAliases { get; set; } public ITemplate[] ConflictingTemplateAliases { get; set; } diff --git a/src/Umbraco.Core/Services/ConflictingPackageContentFinder.cs b/src/Umbraco.Core/Services/ConflictingPackageContentFinder.cs index 3eef651fe8..be2e9a03a0 100644 --- a/src/Umbraco.Core/Services/ConflictingPackageContentFinder.cs +++ b/src/Umbraco.Core/Services/ConflictingPackageContentFinder.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Xml.Linq; using Umbraco.Core.Models; namespace Umbraco.Core.Services @@ -17,22 +19,74 @@ namespace Umbraco.Core.Services else throw new ArgumentNullException("macroService"); } - public bool StylesheetExists(string styleSheetName, out IStylesheet existingStyleSheet) + + public IStylesheet[] FindConflictingStylesheets(XElement stylesheetNotes) { - existingStyleSheet = _fileService.GetStylesheetByName(styleSheetName); - return existingStyleSheet != null; + if (string.Equals(Constants.Packaging.StylesheetsNodeName, stylesheetNotes.Name.LocalName) == false) + { + throw new ArgumentException("the root element must be \"" + Constants.Packaging.StylesheetsNodeName + "\"", "stylesheetNotes"); + } + + return stylesheetNotes.Elements(Constants.Packaging.StylesheetNodeName) + .Select(n => + { + XElement xElement = n.Element(Constants.Packaging.NameNodeName); + if (xElement == null) + { + throw new ArgumentException("Missing \"" + Constants.Packaging.NameNodeName + "\" element", + "stylesheetNotes"); + } + + return _fileService.GetStylesheetByName(xElement.Value) as IStylesheet; + }) + .Where(v => v != null).ToArray(); } - public bool TemplateExists(string templateAlias, out ITemplate existingTemplate) + public ITemplate[] FindConflictingTemplates(XElement templateNotes) { - existingTemplate = _fileService.GetTemplate(templateAlias); - return existingTemplate != null; + if (string.Equals(Constants.Packaging.TemplatesNodeName, templateNotes.Name.LocalName) == false) + { + throw new ArgumentException("Node must be a \"" + Constants.Packaging.TemplatesNodeName + "\" node", + "templateNotes"); + } + + return templateNotes.Elements(Constants.Packaging.TemplateNodeName) + .Select(n => + { + XElement xElement = n.Element(Constants.Packaging.AliasNodeNameCapital) ?? n.Element(Constants.Packaging.AliasNodeNameSmall); + if (xElement == null) + { + throw new ArgumentException("missing a \"" + Constants.Packaging.AliasNodeNameCapital + "\" element", + "templateNotes"); + } + + return _fileService.GetTemplate(xElement.Value); + }) + .Where(v => v != null).ToArray(); } - public bool MacroExists(string macroAlias, out IMacro existingMacro) + public IMacro[] FindConflictingMacros(XElement macroNodes) { - existingMacro = _macroService.GetByAlias(macroAlias); - return macroAlias != null; + if (string.Equals(Constants.Packaging.MacrosNodeName, macroNodes.Name.LocalName) == false) + { + throw new ArgumentException("Node must be a \"" + Constants.Packaging.MacrosNodeName + "\" node", + "macroNodes"); + } + + return macroNodes.Elements(Constants.Packaging.MacroNodeName) + .Select(n => + { + XElement xElement = n.Element(Constants.Packaging.AliasNodeNameSmall) ?? n.Element(Constants.Packaging.AliasNodeNameCapital); + if (xElement == null) + { + throw new ArgumentException(string.Format("missing a \"{0}\" element in {0} element", Constants.Packaging.AliasNodeNameSmall), + "macroNodes"); + } + + return _macroService.GetByAlias(xElement.Value); + }) + .Where(v => v != null).ToArray(); } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IConflictingPackageContentFinder.cs b/src/Umbraco.Core/Services/IConflictingPackageContentFinder.cs index d4715aecbe..88b6e0dd34 100644 --- a/src/Umbraco.Core/Services/IConflictingPackageContentFinder.cs +++ b/src/Umbraco.Core/Services/IConflictingPackageContentFinder.cs @@ -1,11 +1,13 @@ -using Umbraco.Core.Models; +using System.Xml.Linq; +using Umbraco.Core.Models; namespace Umbraco.Core.Services { public interface IConflictingPackageContentFinder { - bool StylesheetExists(string styleSheetName, out IStylesheet existingStylesheet); - bool TemplateExists(string templateAlias, out ITemplate existingTemplate); - bool MacroExists(string macroAlias, out IMacro existingMacro); + + IStylesheet[] FindConflictingStylesheets(XElement stylesheetNotes); + ITemplate[] FindConflictingTemplates(XElement templateNotes); + IMacro[] FindConflictingMacros(XElement macroNodes); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IPackageInstallerService.cs b/src/Umbraco.Core/Services/IPackageInstallerService.cs index e22384756c..d340c572a5 100644 --- a/src/Umbraco.Core/Services/IPackageInstallerService.cs +++ b/src/Umbraco.Core/Services/IPackageInstallerService.cs @@ -6,6 +6,6 @@ namespace Umbraco.Core.Services { PackageInstallationSummary InstallPackageFile(string packageFilePath, int userId); PackageMetaData GetMetaData(string packageFilePath); - PackageImportIssues FindPackageImportIssues(string packageFilePath); + PreInstallWarnings GetPreInstallWarnings(string packageFilePath); } } diff --git a/src/Umbraco.Core/Services/PackageInstallerService.cs b/src/Umbraco.Core/Services/PackageInstallerService.cs index 53fc3bf6ca..707ddfbe4b 100644 --- a/src/Umbraco.Core/Services/PackageInstallerService.cs +++ b/src/Umbraco.Core/Services/PackageInstallerService.cs @@ -93,12 +93,12 @@ namespace Umbraco.Core.Services } } - public PackageImportIssues FindPackageImportIssues(string packageFilePath) + public PreInstallWarnings GetPreInstallWarnings(string packageFilePath) { try { XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); - return FindImportIssues(rootElement); + return GetPreInstallWarnings(rootElement); } catch (Exception e) { @@ -381,23 +381,23 @@ namespace Umbraco.Core.Services return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId).ToArray(); } - private PackageImportIssues FindImportIssues(XElement rootElement) + private PreInstallWarnings GetPreInstallWarnings(XElement rootElement) { XElement files = rootElement.Element(Constants.Packaging.FilesNodeName); XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); XElement templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); XElement alias = rootElement.Element(Constants.Packaging.MacrosNodeName); - var packageImportIssues = new PackageImportIssues + var conflictingPackageContent = new PreInstallWarnings { UnsecureFiles = files == null ? new IFileInPackageInfo[0] : FindUnsecureFiles(files), - ConflictingMacroAliases = alias == null ? new IMacro[0] : FindConflictingMacroAliases(alias), + ConflictingMacroAliases = alias == null ? new IMacro[0] : ConflictingPackageContentFinder.FindConflictingMacros(alias), ConflictingTemplateAliases = - templates == null ? new ITemplate[0] : FindConflictingTemplateAliases(templates), + templates == null ? new ITemplate[0] : ConflictingPackageContentFinder.FindConflictingTemplates(templates), ConflictingStylesheetNames = - styleSheets == null ? new IStylesheet[0] : FindConflictingStylesheetNames(styleSheets) + styleSheets == null ? new IStylesheet[0] : ConflictingPackageContentFinder.FindConflictingStylesheets(styleSheets) }; - return packageImportIssues; + return conflictingPackageContent; } private IFileInPackageInfo[] FindUnsecureFiles(XElement fileElement) @@ -406,92 +406,6 @@ namespace Umbraco.Core.Services .Where(IsFileNodeUnsecure).Cast().ToArray(); } - private IStylesheet[] FindConflictingStylesheetNames(XElement stylesheetNotes) - { - if (string.Equals(Constants.Packaging.StylesheetsNodeName, stylesheetNotes.Name.LocalName) == false) - { - throw new ArgumentException("the root element must be \"" + Constants.Packaging.StylesheetsNodeName + "\"", "stylesheetNotes"); - } - - return stylesheetNotes.Elements(Constants.Packaging.StylesheetNodeName) - .Select(n => - { - XElement xElement = n.Element(Constants.Packaging.NameNodeName); - if (xElement == null) - { - throw new ArgumentException("Missing \"" + Constants.Packaging.NameNodeName + "\" element", - "stylesheetNotes"); - } - - string name = xElement.Value; - - IStylesheet existingStyleSheet; - if (ConflictingPackageContentFinder.StylesheetExists(name, out existingStyleSheet)) - { - // Don't know what to put in here... existing path was the best i could come up with - return existingStyleSheet; - } - return null; - }) - .Where(v => v != null).ToArray(); - } - - private ITemplate[] FindConflictingTemplateAliases(XElement templateNotes) - { - if (string.Equals(Constants.Packaging.TemplatesNodeName, templateNotes.Name.LocalName) == false) - { - throw new ArgumentException("Node must be a \"" + Constants.Packaging.TemplatesNodeName + "\" node", - "templateNotes"); - } - - return templateNotes.Elements(Constants.Packaging.TemplateNodeName) - .Select(n => - { - XElement alias = n.Element(Constants.Packaging.AliasNodeNameCapital) ?? n.Element(Constants.Packaging.AliasNodeNameSmall); - if (alias == null) - { - throw new ArgumentException("missing a \"" + Constants.Packaging.AliasNodeNameCapital + "\" element", - "templateNotes"); - } - string aliasStr = alias.Value; - - ITemplate existingTemplate; - - if (ConflictingPackageContentFinder.TemplateExists(aliasStr, out existingTemplate)) - { - return existingTemplate; - } - - return null; - }) - .Where(v => v != null).ToArray(); - } - - private IMacro[] FindConflictingMacroAliases(XElement macroNodes) - { - return macroNodes.Elements(Constants.Packaging.MacroNodeName) - .Select(n => - { - XElement xElement = n.Element(Constants.Packaging.AliasNodeNameSmall) ?? n.Element(Constants.Packaging.AliasNodeNameCapital); - if (xElement == null) - { - throw new ArgumentException(string.Format("missing a \"{0}\" element in {0} element", Constants.Packaging.AliasNodeNameSmall), - "macroNodes"); - } - string alias = xElement.Value; - - IMacro existingMacro; - if (ConflictingPackageContentFinder.MacroExists(alias, out existingMacro)) - { - return existingMacro; - } - - return null; - }) - .Where(v => v != null).ToArray(); - } - - private bool IsFileNodeUnsecure(FileInPackageInfo fileInPackageInfo) { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 65cfa61f54..271aebf475 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -370,7 +370,7 @@ - + From c67f0baf74205bd855f6e8bda3b83715cb8f99f6 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Thu, 5 Jun 2014 11:07:17 +0200 Subject: [PATCH 024/189] Method renaming and zip allowed --- build/NuSpecs/build/UmbracoCms.targets | 92 +++++++-------- src/Umbraco.Core/Constants-Packaging.cs | 107 +++++++++--------- src/Umbraco.Core/Packaging/UnpackHelper.cs | 9 +- .../Netmester.BestPractice.Base_0.0.0.1.umb | Bin 0 -> 6634 bytes .../Services/PackageInstallerServiceTest.cs | 18 ++- 5 files changed, 121 insertions(+), 105 deletions(-) create mode 100644 src/Umbraco.Tests/Packages/Netmester.BestPractice.Base_0.0.0.1.umb diff --git a/build/NuSpecs/build/UmbracoCms.targets b/build/NuSpecs/build/UmbracoCms.targets index 024d8af7ad..b057ce5202 100644 --- a/build/NuSpecs/build/UmbracoCms.targets +++ b/build/NuSpecs/build/UmbracoCms.targets @@ -1,50 +1,50 @@ - - - - - $(MSBuildThisFileDirectory)..\UmbracoFiles\ - - - - - - - - - - - App_Browsers - - - App_Code - - - App_Plugins - - - bin\amd64 - - - bin\x86 - - - Config\Splashes - + + + + + $(MSBuildThisFileDirectory)..\UmbracoFiles\ + + + + + + + + + + + App_Browsers + + + App_Code + + + App_Plugins + + + bin\amd64 + + + bin\x86 + + + Config\Splashes + data - - umbraco - - - umbraco_client - - - . - - - %(CustomFilesToInclude.Dir)\%(RecursiveDir)%(Filename)%(Extension) - - - + + umbraco + + + umbraco_client + + + . + + + %(CustomFilesToInclude.Dir)\%(RecursiveDir)%(Filename)%(Extension) + + + \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-Packaging.cs b/src/Umbraco.Core/Constants-Packaging.cs index ef68b2a656..0beb034508 100644 --- a/src/Umbraco.Core/Constants-Packaging.cs +++ b/src/Umbraco.Core/Constants-Packaging.cs @@ -1,56 +1,53 @@ -using System.Xml.Linq; - -namespace Umbraco.Core -{ - public static partial class Constants - { - /// - /// Defines the constants used for Umbraco packages in the package.config xml - /// - public static class Packaging - { - public const string UmbPackageNodeName = "umbPackage"; - public const string DataTypesNodeName = "DataTypes"; - public const string PackageXmlFileName = "package.xml"; - public const string UmbracoPackageExtention = ".umb"; - public const string DataTypeNodeName = "DataType"; - public const string LanguagesNodeName = "Languages"; - public const string FilesNodeName = "files"; - public const string StylesheetsNodeName = "Stylesheets"; - public const string TemplatesNodeName = "Templates"; - public const string NameNodeName = "Name"; - public const string TemplateNodeName = "Template"; - public const string AliasNodeNameSmall = "alias"; - public const string AliasNodeNameCapital = "Alias"; - public const string DictionaryItemsNodeName = "DictionaryItems"; - public const string DictionaryItemNodeName = "DictionaryItem"; - public const string MacrosNodeName = "Macros"; - public const string DocumentSetNodeName = "DocumentSet"; - public const string DocumentTypesNodeName = "DocumentTypes"; - public const string DocumentTypeNodeName = "DocumentType"; - public const string FileNodeName = "file"; - public const string OrgNameNodeName = "orgName"; - public const string OrgPathNodeName = "orgPath"; - public const string GuidNodeName = "guid"; - public const string StylesheetNodeName = "styleSheet"; - public const string MacroNodeName = "macro"; - public const string InfoNodeName = "info"; - public const string PackageRequirementsMajorXpath = "./package/requirements/major"; - public const string PackageRequirementsMinorXpath = "./package/requirements/minor"; - public const string PackageRequirementsPatchXpath = "./package/requirements/patch"; - public const string PackageNameXpath = "./package/name"; - public const string PackageVersionXpath = "./package/version"; - public const string PackageUrlXpath = "./package/url"; - public const string PackageLicenseXpath = "./package/license"; - public const string AuthorNameXpath = "./author/name"; - public const string AuthorWebsiteXpath = "./author/website"; - public const string ReadmeXpath = "./readme"; - public const string ControlNodeName = "control"; - public const string ActionNodeName = "Action"; - public const string ActionsNodeName = "Actions"; - public const string UndoNodeAttribute = "undo"; - public const string RunatNodeAttribute = "runat"; - - } - } +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines the constants used for Umbraco packages in the package.config xml + /// + public static class Packaging + { + public static readonly string UmbPackageNodeName = "umbPackage"; + public static readonly string DataTypesNodeName = "DataTypes"; + public static readonly string PackageXmlFileName = "package.xml"; + public static readonly string UmbracoPackageExtention = ".umb"; + public static readonly string DataTypeNodeName = "DataType"; + public static readonly string LanguagesNodeName = "Languages"; + public static readonly string FilesNodeName = "files"; + public static readonly string StylesheetsNodeName = "Stylesheets"; + public static readonly string TemplatesNodeName = "Templates"; + public static readonly string NameNodeName = "Name"; + public static readonly string TemplateNodeName = "Template"; + public static readonly string AliasNodeNameSmall = "alias"; + public static readonly string AliasNodeNameCapital = "Alias"; + public static readonly string DictionaryItemsNodeName = "DictionaryItems"; + public static readonly string DictionaryItemNodeName = "DictionaryItem"; + public static readonly string MacrosNodeName = "Macros"; + public static readonly string DocumentSetNodeName = "DocumentSet"; + public static readonly string DocumentTypesNodeName = "DocumentTypes"; + public static readonly string DocumentTypeNodeName = "DocumentType"; + public static readonly string FileNodeName = "file"; + public static readonly string OrgNameNodeName = "orgName"; + public static readonly string OrgPathNodeName = "orgPath"; + public static readonly string GuidNodeName = "guid"; + public static readonly string StylesheetNodeName = "styleSheet"; + public static readonly string MacroNodeName = "macro"; + public static readonly string InfoNodeName = "info"; + public static readonly string PackageRequirementsMajorXpath = "./package/requirements/major"; + public static readonly string PackageRequirementsMinorXpath = "./package/requirements/minor"; + public static readonly string PackageRequirementsPatchXpath = "./package/requirements/patch"; + public static readonly string PackageNameXpath = "./package/name"; + public static readonly string PackageVersionXpath = "./package/version"; + public static readonly string PackageUrlXpath = "./package/url"; + public static readonly string PackageLicenseXpath = "./package/license"; + public static readonly string AuthorNameXpath = "./author/name"; + public static readonly string AuthorWebsiteXpath = "./author/website"; + public static readonly string ReadmeXpath = "./readme"; + public static readonly string ControlNodeName = "control"; + public static readonly string ActionNodeName = "Action"; + public static readonly string ActionsNodeName = "Actions"; + public static readonly string UndoNodeAttribute = "undo"; + public static readonly string RunatNodeAttribute = "runat"; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/UnpackHelper.cs b/src/Umbraco.Core/Packaging/UnpackHelper.cs index 0ad5397d32..970e0c85a0 100644 --- a/src/Umbraco.Core/Packaging/UnpackHelper.cs +++ b/src/Umbraco.Core/Packaging/UnpackHelper.cs @@ -10,7 +10,6 @@ namespace Umbraco.Core.Packaging { public string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage) { - string retVal = null; bool fileFound = false; string foundDir = null; @@ -56,11 +55,15 @@ namespace Umbraco.Core.Packaging throw new ArgumentException(string.Format("Package file: {0} could not be found", packageFilePath)); } + string extension = Path.GetExtension(packageFilePath).ToLower(); + + var alowedExtension = new[] {".umb", ".zip"}; + // Check if the file is a valid package - if (Path.GetExtension(packageFilePath).Equals(".umb", StringComparison.InvariantCultureIgnoreCase) == false) + if (alowedExtension.All(ae => ae.Equals(extension) == false)) { throw new ArgumentException( - "Error - file isn't a package (doesn't have a .umb extension). Check if the file automatically got named '.zip' upon download."); + string.Format("Error - file isn't a package. only extentions: \"{0}\" is allowed", string.Join(", ", alowedExtension))); } } diff --git a/src/Umbraco.Tests/Packages/Netmester.BestPractice.Base_0.0.0.1.umb b/src/Umbraco.Tests/Packages/Netmester.BestPractice.Base_0.0.0.1.umb new file mode 100644 index 0000000000000000000000000000000000000000..11610beb1e6957ee18c4121974ecd482d42d3ee7 GIT binary patch literal 6634 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 Date: Fri, 6 Jun 2014 13:38:04 +0200 Subject: [PATCH 025/189] Moved and renamed classes to fit standard --- ...IUnpackHelper.cs => IPackageExtraction.cs} | 12 +- .../Packaging/IPackageInstallation.cs | 12 + .../Packaging/Models/InstallAction.cs | 21 - .../Packaging/Models/InstallationSummary.cs | 46 +- .../Packaging/Models/PackageAction.cs | 41 ++ .../{ => Models}/PreInstallWarnings.cs | 3 +- .../Packaging/Models/UninstallAction.cs | 21 - .../Packaging/PackageBinaryInspector.cs | 3 - src/Umbraco.Core/Packaging/PackageBuilding.cs | 4 +- .../Packaging/PackageExtraction.cs | 198 +++++- .../Packaging/PackageInstallation.cs | 577 +++++++++++++++++- .../Packaging/PackageInstallationSummary.cs | 22 - src/Umbraco.Core/Packaging/PackageMetaData.cs | 18 - src/Umbraco.Core/Packaging/UnpackHelper.cs | 172 ------ .../Services/IPackageInstallerService.cs | 11 - .../Services/PackageInstallerService.cs | 560 ----------------- src/Umbraco.Core/Services/PackagingService.cs | 44 +- src/Umbraco.Core/Services/ServiceContext.cs | 13 - src/Umbraco.Core/Umbraco.Core.csproj | 13 +- src/Umbraco.Tests/MockTests.cs | 9 +- .../Services/PackageInstallationTest.cs | 76 +++ .../Services/PackageInstallerServiceTest.cs | 98 --- .../Services/PackagingServiceTests.cs | 52 ++ src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- 24 files changed, 973 insertions(+), 1055 deletions(-) rename src/Umbraco.Core/Packaging/{IUnpackHelper.cs => IPackageExtraction.cs} (81%) create mode 100644 src/Umbraco.Core/Packaging/IPackageInstallation.cs delete mode 100644 src/Umbraco.Core/Packaging/Models/InstallAction.cs create mode 100644 src/Umbraco.Core/Packaging/Models/PackageAction.cs rename src/Umbraco.Core/Packaging/{ => Models}/PreInstallWarnings.cs (82%) delete mode 100644 src/Umbraco.Core/Packaging/Models/UninstallAction.cs delete mode 100644 src/Umbraco.Core/Packaging/PackageInstallationSummary.cs delete mode 100644 src/Umbraco.Core/Packaging/PackageMetaData.cs delete mode 100644 src/Umbraco.Core/Packaging/UnpackHelper.cs delete mode 100644 src/Umbraco.Core/Services/IPackageInstallerService.cs delete mode 100644 src/Umbraco.Core/Services/PackageInstallerService.cs create mode 100644 src/Umbraco.Tests/Services/PackageInstallationTest.cs delete mode 100644 src/Umbraco.Tests/Services/PackageInstallerServiceTest.cs diff --git a/src/Umbraco.Core/Packaging/IUnpackHelper.cs b/src/Umbraco.Core/Packaging/IPackageExtraction.cs similarity index 81% rename from src/Umbraco.Core/Packaging/IUnpackHelper.cs rename to src/Umbraco.Core/Packaging/IPackageExtraction.cs index 2f65bc8335..efe2949356 100644 --- a/src/Umbraco.Core/Packaging/IUnpackHelper.cs +++ b/src/Umbraco.Core/Packaging/IPackageExtraction.cs @@ -7,12 +7,13 @@ namespace Umbraco.Core.Packaging /// Remeber that filenames must be unique /// use "FindDubletFileNames" for sanitycheck /// - public interface IUnpackHelper + + internal interface IPackageExtraction { /// /// Returns the content of the file with the given filename /// - /// Full path to the ubraco package file + /// Full path to the umbraco package file /// filename of the file for wich to get the text content /// this is the relative directory for the location of the file in the package /// I dont know why umbraco packages contains directories in the first place?? @@ -31,7 +32,7 @@ namespace Umbraco.Core.Packaging /// /// Check if given list of files can be found in the package /// - /// Full path to the ubraco package file + /// Full path to the umbraco package file /// a list of files you would like to find in the package /// a subset if any of the files in "expectedFiles" that could not be found in the package IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles); @@ -40,9 +41,8 @@ namespace Umbraco.Core.Packaging /// /// Sanitycheck - should return en empty collection if package is valid /// - /// Full path to the ubraco package file - /// list of files that can are found more than ones (accross directories) in the package + /// Full path to the umbraco package file + /// list of files that are found more than ones (accross directories) in the package IEnumerable FindDubletFileNames(string packageFilePath); - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/IPackageInstallation.cs b/src/Umbraco.Core/Packaging/IPackageInstallation.cs new file mode 100644 index 0000000000..6c4bfb4349 --- /dev/null +++ b/src/Umbraco.Core/Packaging/IPackageInstallation.cs @@ -0,0 +1,12 @@ +using Umbraco.Core.Packaging.Models; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Packaging +{ + internal interface IPackageInstallation : IService + { + InstallationSummary InstallPackage(string packageFilePath, int userId); + MetaData GetMetaData(string packageFilePath); + PreInstallWarnings GetPreInstallWarnings(string packageFilePath); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/Models/InstallAction.cs b/src/Umbraco.Core/Packaging/Models/InstallAction.cs deleted file mode 100644 index 257f0c5a03..0000000000 --- a/src/Umbraco.Core/Packaging/Models/InstallAction.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Runtime.Serialization; -using System.Xml; - -namespace Umbraco.Core.Packaging.Models -{ - [Serializable] - [DataContract(IsReference = true)] - internal class InstallAction - { - public string Alias { get; set; } - - public string PackageName { get; set; } - - public string RunAt { get; set; }//NOTE Should this default to "install" - - public bool Undo { get; set; } - - public XmlNode XmlData { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs b/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs index 7ee077855d..c627f9b813 100644 --- a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs +++ b/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs @@ -1,24 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Packaging.Models -{ - [Serializable] - [DataContract(IsReference = true)] - internal class InstallationSummary - { - public MetaData MetaData { get; set; } - public IEnumerable DataTypesInstalled { get; set; } - public IEnumerable LanguagesInstalled { get; set; } - public IEnumerable DictionaryItemsInstalled { get; set; } - public IEnumerable MacrosInstalled { get; set; } - public IEnumerable> FilesInstalled { get; set;} - public IEnumerable TemplatesInstalled { get; set; } - public IEnumerable DocumentTypesInstalled { get; set; } - public IEnumerable StylesheetsInstalled { get; set; } - public IEnumerable DocumentsInstalled { get; set; } - public IEnumerable InstallActions { get; set; } - public IEnumerable UninstallActions { get; set; } - } +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Packaging.Models +{ + [Serializable] + [DataContract(IsReference = true)] + internal class InstallationSummary + { + public MetaData MetaData { get; set; } + public IDataTypeDefinition[] DataTypesInstalled { get; set; } + public ILanguage[] LanguagesInstalled { get; set; } + public IDictionaryItem[] DictionaryItemsInstalled { get; set; } + public IMacro[] MacrosInstalled { get; set; } + public IEnumerable> FilesInstalled { get; set; } + public ITemplate[] TemplatesInstalled { get; set; } + public IContentType[] DocumentTypesInstalled { get; set; } + public IStylesheet[] StylesheetsInstalled { get; set; } + public IContent[] DocumentsInstalled { get; set; } + public PackageAction[] Actions { get; set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/Models/PackageAction.cs b/src/Umbraco.Core/Packaging/Models/PackageAction.cs new file mode 100644 index 0000000000..08c586c7f4 --- /dev/null +++ b/src/Umbraco.Core/Packaging/Models/PackageAction.cs @@ -0,0 +1,41 @@ +using System; +using System.Runtime.Serialization; +using System.Xml; +using System.Xml.Linq; + +namespace Umbraco.Core.Packaging.Models +{ + internal enum ActionRunAt + { + Undefined = 0, + Install, + Uninstall + } + + + [Serializable] + [DataContract(IsReference = true)] + internal class PackageAction + { + private ActionRunAt _runAt; + private bool? _undo; + public string Alias { get; set; } + + public string PackageName { get; set; } + + public ActionRunAt RunAt + { + get { return _runAt == ActionRunAt.Undefined ? ActionRunAt.Install : _runAt; } + set { _runAt = value; } + } + + public bool Undo //NOTE: Should thid default to "False"? but the documentation says default "True" (http://our.umbraco.org/wiki/reference/packaging/package-actions) + { + get { return _undo ?? true; } + set { _undo = value; } + } + + + public XElement XmlData { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PreInstallWarnings.cs b/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs similarity index 82% rename from src/Umbraco.Core/Packaging/PreInstallWarnings.cs rename to src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs index 9fd069a5df..e64f564a2c 100644 --- a/src/Umbraco.Core/Packaging/PreInstallWarnings.cs +++ b/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs @@ -1,7 +1,6 @@ using Umbraco.Core.Models; -using Umbraco.Core.Services; -namespace Umbraco.Core.Packaging +namespace Umbraco.Core.Packaging.Models { public class PreInstallWarnings { diff --git a/src/Umbraco.Core/Packaging/Models/UninstallAction.cs b/src/Umbraco.Core/Packaging/Models/UninstallAction.cs deleted file mode 100644 index 886c10afba..0000000000 --- a/src/Umbraco.Core/Packaging/Models/UninstallAction.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Runtime.Serialization; -using System.Xml; - -namespace Umbraco.Core.Packaging.Models -{ - [Serializable] - [DataContract(IsReference = true)] - internal class UninstallAction - { - public string Alias { get; set; } - - public string PackageName { get; set; } - - public string RunAt { get; set; }//NOTE Should this default to "install" - - public bool Undo { get; set; }//NOTE: Should thid default to "False"? - - public XmlNode XmlData { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs index 2b9ada9278..e1fbdc7f75 100644 --- a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs +++ b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs @@ -5,9 +5,6 @@ using System.Linq; using System.Reflection; using System.Security; using System.Security.Permissions; -using System.Text; -using System.Threading.Tasks; -using System.Web; using Umbraco.Core.Logging; namespace Umbraco.Core.Packaging diff --git a/src/Umbraco.Core/Packaging/PackageBuilding.cs b/src/Umbraco.Core/Packaging/PackageBuilding.cs index 6cbb17b158..e8c09d12b8 100644 --- a/src/Umbraco.Core/Packaging/PackageBuilding.cs +++ b/src/Umbraco.Core/Packaging/PackageBuilding.cs @@ -8,9 +8,9 @@ namespace Umbraco.Core.Packaging internal class PackageBuilding : IPackageBuilding { - private readonly PackagingService _packagingService; + private readonly IPackagingService _packagingService; - public PackageBuilding(PackagingService packagingService) + public PackageBuilding(IPackagingService packagingService) { _packagingService = packagingService; } diff --git a/src/Umbraco.Core/Packaging/PackageExtraction.cs b/src/Umbraco.Core/Packaging/PackageExtraction.cs index d3de2a3da4..ea59ac671f 100644 --- a/src/Umbraco.Core/Packaging/PackageExtraction.cs +++ b/src/Umbraco.Core/Packaging/PackageExtraction.cs @@ -1,34 +1,172 @@ -using System; -using System.IO; -using Umbraco.Core.IO; - +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ICSharpCode.SharpZipLib.Zip; + namespace Umbraco.Core.Packaging -{ - internal interface IPackageExtraction - { - bool Extract(string packageFilePath, string destinationFolder); - string ExtractToTemporaryFolder(string packageFilePath); - string GetPackageConfigFromArchive(string packageFilePath, string fileToRead = "package.xml"); - } - +{ internal class PackageExtraction : IPackageExtraction - { - public bool Extract(string packageFilePath, string destinationFolder) - { - return true; - } - - public string ExtractToTemporaryFolder(string packageFilePath) - { - string tempDir = Path.Combine(IOHelper.MapPath(SystemDirectories.Data), Guid.NewGuid().ToString("D")); - Directory.CreateDirectory(tempDir); - Extract(packageFilePath, tempDir); - return tempDir; - } - - public string GetPackageConfigFromArchive(string packageFilePath, string fileToRead = "package.xml") - { - return string.Empty; - } + { + public string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage) + { + string retVal = null; + bool fileFound = false; + string foundDir = null; + + ReadZipfileEntries(packageFilePath, (entry, stream) => + { + string fileName = Path.GetFileName(entry.Name); + + if (string.IsNullOrEmpty(fileName) == false && + fileName.Equals(fileToRead, StringComparison.CurrentCultureIgnoreCase)) + { + + foundDir = entry.Name.Substring(0, entry.Name.Length - fileName.Length); + fileFound = true; + using (var reader = new StreamReader(stream)) + { + retVal = reader.ReadToEnd(); + return false; + } + } + return true; + }); + + if (fileFound == false) + { + directoryInPackage = null; + throw new FileNotFoundException(string.Format("Could not find file in package {0}", packageFilePath), fileToRead); + } + directoryInPackage = foundDir; + return retVal; + } + + private static void CheckPackageExists(string packageFilePath) + { + if (string.IsNullOrEmpty(packageFilePath)) + { + throw new ArgumentNullException("packageFilePath"); + } + + if (File.Exists(packageFilePath) == false) + { + if (File.Exists(packageFilePath) == false) + throw new ArgumentException(string.Format("Package file: {0} could not be found", packageFilePath)); + } + + string extension = Path.GetExtension(packageFilePath).ToLower(); + + var alowedExtension = new[] { ".umb", ".zip" }; + + // Check if the file is a valid package + if (alowedExtension.All(ae => ae.Equals(extension) == false)) + { + throw new ArgumentException( + string.Format("Error - file isn't a package. only extentions: \"{0}\" is allowed", string.Join(", ", alowedExtension))); + } + } + + + public bool CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath) + { + bool fileFoundInArchive = false; + bool fileOverwritten = false; + + ReadZipfileEntries(packageFilePath, (entry, stream) => + { + string fileName = Path.GetFileName(entry.Name); + + if (string.IsNullOrEmpty(fileName) == false && + fileName.Equals(fileInPackageName, StringComparison.InvariantCultureIgnoreCase)) + { + fileFoundInArchive = true; + + fileOverwritten = File.Exists(destinationfilePath); + + using (var streamWriter = File.Open(destinationfilePath, FileMode.Create)) + { + var data = new byte[2048]; + int size; + while ((size = stream.Read(data, 0, data.Length)) > 0) + { + streamWriter.Write(data, 0, size); + } + + streamWriter.Close(); + } + return false; + } + return true; + }); + + + if (fileFoundInArchive == false) throw new ArgumentException(string.Format("Could not find file: {0} in package file: {1}", fileInPackageName, packageFilePath), "fileInPackageName"); + + return fileOverwritten; + } + + public IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles) + { + var retVal = expectedFiles.ToList(); + + ReadZipfileEntries(packageFilePath, (zipEntry, stream) => + { + string fileName = Path.GetFileName(zipEntry.Name); + + int index = retVal.FindIndex(f => f.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)); + + if (index != -1) { retVal.RemoveAt(index); } + + return retVal.Any(); + }); + return retVal; + + } + + public IEnumerable FindDubletFileNames(string packageFilePath) + { + var dictionary = new Dictionary>(); + + + ReadZipfileEntries(packageFilePath, (entry, stream) => + { + string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower(); + + List list; + if (dictionary.TryGetValue(fileName, out list) == false) + { + list = new List(); + dictionary.Add(fileName, list); + } + + list.Add(entry.Name); + + return true; + }); + + return dictionary.Values.Where(v => v.Count > 1).SelectMany(v => v); + } + + private void ReadZipfileEntries(string packageFilePath, Func entryFunc, bool skipsDirectories = true) + { + CheckPackageExists(packageFilePath); + + using (var fs = File.OpenRead(packageFilePath)) + { + using (var zipInputStream = new ZipInputStream(fs)) + { + ZipEntry zipEntry; + while ((zipEntry = zipInputStream.GetNextEntry()) != null) + { + if (zipEntry.IsDirectory && skipsDirectories) continue; + if (entryFunc(zipEntry, zipInputStream) == false) break; + } + + zipInputStream.Close(); + } + fs.Close(); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index 5d8c292098..031d477ae7 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -1,35 +1,554 @@ -using Umbraco.Core.Packaging.Models; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using System.Xml.XPath; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Packaging.Models; using Umbraco.Core.Services; namespace Umbraco.Core.Packaging -{ - internal interface IPackageInstallation - { - InstallationSummary InstallPackage(string packageFilePath, int userId = 0); - MetaData GetPackageMetaData(string packageFilePath); - } - +{ internal class PackageInstallation : IPackageInstallation { - private readonly PackagingService _packagingService; - private readonly PackageExtraction _packageExtraction; - - public PackageInstallation(PackagingService packagingService, PackageExtraction packageExtraction) - { - _packagingService = packagingService; - _packageExtraction = packageExtraction; - } - - public InstallationSummary InstallPackage(string packageFilePath, int userId = 0) - { - var summary = new InstallationSummary(); - return summary; - } - - public MetaData GetPackageMetaData(string packageFilePath) - { - var metaData = new MetaData(); - return metaData; - } - } + private readonly IFileService _fileService; + private readonly IMacroService _macroService; + private readonly IPackagingService _packagingService; + private IConflictingPackageContentFinder _conflictingPackageContentFinder; + private readonly IPackageExtraction _packageExtraction; + + public PackageInstallation(IPackagingService packagingService, IMacroService macroService, + IFileService fileService, IPackageExtraction packageExtraction) + { + if (packageExtraction != null) _packageExtraction = packageExtraction; + else throw new ArgumentNullException("packageExtraction"); + if (macroService != null) _macroService = macroService; + else throw new ArgumentNullException("macroService"); + if (fileService != null) _fileService = fileService; + else throw new ArgumentNullException("fileService"); + if (packagingService != null) _packagingService = packagingService; + else throw new ArgumentNullException("packagingService"); + } + + + public IConflictingPackageContentFinder ConflictingPackageContentFinder + { + private get + { + return _conflictingPackageContentFinder ?? + (_conflictingPackageContentFinder = new ConflictingPackageContentFinder(_macroService, _fileService)); + } + set + { + if (_conflictingPackageContentFinder != null) + { + throw new PropertyConstraintException("This property already have a value"); + } + _conflictingPackageContentFinder = value; + } + } + + + + private string _fullpathToRoot; + public string FullpathToRoot + { + private get { return _fullpathToRoot ?? (_fullpathToRoot = GlobalSettings.FullpathToRoot); } + set + { + + if (_fullpathToRoot != null) + { + throw new PropertyConstraintException("This property already have a value"); + } + + _fullpathToRoot = value; + } + } + + + public MetaData GetMetaData(string packageFilePath) + { + try + { + XElement rootElement = GetConfigXmlElement(packageFilePath); + return GetMetaData(rootElement); + } + catch (Exception e) + { + throw new Exception("Error reading " + packageFilePath, e); + } + } + + public PreInstallWarnings GetPreInstallWarnings(string packageFilePath) + { + try + { + XElement rootElement = GetConfigXmlElement(packageFilePath); + return GetPreInstallWarnings(rootElement); + } + catch (Exception e) + { + throw new Exception("Error reading " + packageFilePath, e); + } + } + + public InstallationSummary InstallPackage(string packageFile, int userId) + { + XElement dataTypes; + XElement languages; + XElement dictionaryItems; + XElement macroes; + XElement files; + XElement templates; + XElement documentTypes; + XElement styleSheets; + XElement documentSet; + XElement actions; + MetaData metaData; + + try + { + XElement rootElement = GetConfigXmlElement(packageFile); + PackageStructureSanetyCheck(packageFile, rootElement); + dataTypes = rootElement.Element(Constants.Packaging.DataTypesNodeName); + languages = rootElement.Element(Constants.Packaging.LanguagesNodeName); + dictionaryItems = rootElement.Element(Constants.Packaging.DictionaryItemsNodeName); + macroes = rootElement.Element(Constants.Packaging.MacrosNodeName); + files = rootElement.Element(Constants.Packaging.FilesNodeName); + templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); + documentTypes = rootElement.Element(Constants.Packaging.DocumentTypesNodeName); + styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); + documentSet = rootElement.Element(Constants.Packaging.DocumentSetNodeName); + actions = rootElement.Element(Constants.Packaging.ActionsNodeName); + + metaData = GetMetaData(rootElement); + } + catch (Exception e) + { + throw new Exception("Error reading " + packageFile, e); + } + + try + { + + return new InstallationSummary + { + MetaData = metaData, + DataTypesInstalled = + dataTypes == null ? new IDataTypeDefinition[0] : InstallDataTypes(dataTypes, userId), + LanguagesInstalled = languages == null ? new ILanguage[0] : InstallLanguages(languages, userId), + DictionaryItemsInstalled = + dictionaryItems == null ? new IDictionaryItem[0] : InstallDictionaryItems(dictionaryItems), + MacrosInstalled = macroes == null ? new IMacro[0] : InstallMacros(macroes, userId), + FilesInstalled = + packageFile == null + ? Enumerable.Empty>() + : InstallFiles(packageFile, files), + TemplatesInstalled = templates == null ? new ITemplate[0] : InstallTemplats(templates, userId), + DocumentTypesInstalled = + documentTypes == null ? new IContentType[0] : InstallDocumentTypes(documentTypes, userId), + StylesheetsInstalled = + styleSheets == null ? new IStylesheet[0] : InstallStylesheets(styleSheets, userId), + DocumentsInstalled = documentSet == null ? new IContent[0] : InstallDocuments(documentSet, userId), + Actions = actions == null ? new PackageAction[0] : GetPackageActions(actions, metaData.Name), + + }; + } + catch (Exception e) + { + throw new Exception("Error installing package " + packageFile, e); + } + } + + + + + private XDocument GetConfigXmlDoc(string packageFilePath) + { + string filePathInPackage; + string configXmlContent = _packageExtraction.ReadTextFileFromArchive(packageFilePath, + Constants.Packaging.PackageXmlFileName, out filePathInPackage); + + return XDocument.Parse(configXmlContent); + } + + + private XElement GetConfigXmlElement(string packageFilePath) + { + XDocument document = GetConfigXmlDoc(packageFilePath); + if (document.Root == null || + document.Root.Name.LocalName.Equals(Constants.Packaging.UmbPackageNodeName) == false) + { + throw new ArgumentException("xml does not have a root node called \"umbPackage\"", packageFilePath); + } + return document.Root; + } + + + internal void PackageStructureSanetyCheck(string packageFilePath) + { + XElement rootElement = GetConfigXmlElement(packageFilePath); + PackageStructureSanetyCheck(packageFilePath, rootElement); + } + + private void PackageStructureSanetyCheck(string packageFilePath, XElement rootElement) + { + + XElement filesElement = rootElement.Element(Constants.Packaging.FilesNodeName); + if (filesElement != null) + { + IEnumerable extractFileInPackageInfos = + ExtractFileInPackageInfos(filesElement).ToArray(); + + IEnumerable missingFiles = + _packageExtraction.FindMissingFiles(packageFilePath, + extractFileInPackageInfos.Select(i => i.FileNameInPackage)).ToArray(); + + if (missingFiles.Any()) + { + throw new Exception("The following file(s) are missing in the package: " + + string.Join(", ", missingFiles.Select( + mf => + { + FileInPackageInfo fileInPackageInfo = + extractFileInPackageInfos.Single(fi => fi.FileNameInPackage == mf); + return string.Format("Guid: \"{0}\" Original File: \"{1}\"", + fileInPackageInfo.FileNameInPackage, fileInPackageInfo.RelativePath); + }))); + } + + IEnumerable dubletFileNames = _packageExtraction.FindDubletFileNames(packageFilePath).ToArray(); + if (dubletFileNames.Any()) + { + throw new Exception("The following filename(s) are found more than one time in the package, since the filename is used ad primary key, this is not allowed: " + + string.Join(", ", dubletFileNames)); + } + } + } + + + + + private static PackageAction[] GetPackageActions(XElement actionsElement, string packageName) + { + if (actionsElement == null) { return new PackageAction[0]; } + + if (string.Equals(Constants.Packaging.ActionsNodeName, actionsElement.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.ActionsNodeName + "\" as root", + "actionsElement"); + } + + return actionsElement.Elements(Constants.Packaging.ActionNodeName) + .Select(elemet => + { + XAttribute aliasAttr = elemet.Attribute(Constants.Packaging.AliasNodeNameCapital); + if (aliasAttr == null) + throw new ArgumentException( + "missing \"" + Constants.Packaging.AliasNodeNameCapital + "\" atribute in alias element", + "actionsElement"); + + var packageAction = new PackageAction + { + XmlData = elemet, + Alias = aliasAttr.Value, + PackageName = packageName, + }; + + + XAttribute attr = elemet.Attribute(Constants.Packaging.RunatNodeAttribute); + + ActionRunAt runAt; + if (attr != null && Enum.TryParse(attr.Value, true, out runAt)) { packageAction.RunAt = runAt; } + + attr = elemet.Attribute(Constants.Packaging.UndoNodeAttribute); + + bool undo; + if (attr != null && bool.TryParse(attr.Value, out undo)) { packageAction.Undo = undo; } + + + return packageAction; + }).ToArray(); + } + + private IContent[] InstallDocuments(XElement documentsElement, int userId = 0) + { + if (string.Equals(Constants.Packaging.DocumentSetNodeName, documentsElement.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.DocumentSetNodeName + "\" as root", + "documentsElement"); + } + return _packagingService.ImportContent(documentsElement, -1, userId).ToArray(); + } + + private IStylesheet[] InstallStylesheets(XElement styleSheetsElement, int userId = 0) + { + if (string.Equals(Constants.Packaging.StylesheetsNodeName, styleSheetsElement.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.StylesheetsNodeName + "\" as root", + "styleSheetsElement"); + } + return _packagingService.ImportStylesheets(styleSheetsElement, userId).ToArray(); + } + + private IContentType[] InstallDocumentTypes(XElement documentTypes, int userId = 0) + { + if (string.Equals(Constants.Packaging.DocumentTypesNodeName, documentTypes.Name.LocalName) == false) + { + if (string.Equals(Constants.Packaging.DocumentTypeNodeName, documentTypes.Name.LocalName) == false) + throw new ArgumentException( + "Must be \"" + Constants.Packaging.DocumentTypesNodeName + "\" as root", "documentTypes"); + + documentTypes = new XElement(Constants.Packaging.DocumentTypesNodeName, documentTypes); + } + + return _packagingService.ImportContentTypes(documentTypes, userId).ToArray(); + } + + private ITemplate[] InstallTemplats(XElement templateElement, int userId = 0) + { + if (string.Equals(Constants.Packaging.TemplatesNodeName, templateElement.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.TemplatesNodeName + "\" as root", + "templateElement"); + } + return _packagingService.ImportTemplates(templateElement, userId).ToArray(); + } + + + private IEnumerable> InstallFiles(string packageFilePath, XElement filesElement) + { + return ExtractFileInPackageInfos(filesElement).Select(fpi => + { + bool existingOverrided = _packageExtraction.CopyFileFromArchive(packageFilePath, fpi.FileNameInPackage, + fpi.FullPath); + + return new KeyValuePair(fpi.FullPath, existingOverrided); + }).ToArray(); + } + + private IMacro[] InstallMacros(XElement macroElements, int userId = 0) + { + if (string.Equals(Constants.Packaging.MacrosNodeName, macroElements.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.MacrosNodeName + "\" as root", + "macroElements"); + } + return _packagingService.ImportMacros(macroElements, userId).ToArray(); + } + + private IDictionaryItem[] InstallDictionaryItems(XElement dictionaryItemsElement) + { + if (string.Equals(Constants.Packaging.DictionaryItemsNodeName, dictionaryItemsElement.Name.LocalName) == + false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.DictionaryItemsNodeName + "\" as root", + "dictionaryItemsElement"); + } + return _packagingService.ImportDictionaryItems(dictionaryItemsElement).ToArray(); + } + + private ILanguage[] InstallLanguages(XElement languageElement, int userId = 0) + { + if (string.Equals(Constants.Packaging.LanguagesNodeName, languageElement.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.LanguagesNodeName + "\" as root", "languageElement"); + } + return _packagingService.ImportLanguages(languageElement, userId).ToArray(); + } + + private IDataTypeDefinition[] InstallDataTypes(XElement dataTypeElements, int userId = 0) + { + if (string.Equals(Constants.Packaging.DataTypesNodeName, dataTypeElements.Name.LocalName) == false) + { + if (string.Equals(Constants.Packaging.DataTypeNodeName, dataTypeElements.Name.LocalName) == false) + { + throw new ArgumentException("Must be \"" + Constants.Packaging.DataTypeNodeName + "\" as root", "dataTypeElements"); + } + } + return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId).ToArray(); + } + + private PreInstallWarnings GetPreInstallWarnings(XElement rootElement) + { + XElement files = rootElement.Element(Constants.Packaging.FilesNodeName); + XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); + XElement templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); + XElement alias = rootElement.Element(Constants.Packaging.MacrosNodeName); + var conflictingPackageContent = new PreInstallWarnings + { + UnsecureFiles = files == null ? new IFileInPackageInfo[0] : FindUnsecureFiles(files), + ConflictingMacroAliases = alias == null ? new IMacro[0] : ConflictingPackageContentFinder.FindConflictingMacros(alias), + ConflictingTemplateAliases = + templates == null ? new ITemplate[0] : ConflictingPackageContentFinder.FindConflictingTemplates(templates), + ConflictingStylesheetNames = + styleSheets == null ? new IStylesheet[0] : ConflictingPackageContentFinder.FindConflictingStylesheets(styleSheets) + }; + + return conflictingPackageContent; + } + + private IFileInPackageInfo[] FindUnsecureFiles(XElement fileElement) + { + return ExtractFileInPackageInfos(fileElement) + .Where(IsFileNodeUnsecure).Cast().ToArray(); + } + + private bool IsFileNodeUnsecure(FileInPackageInfo fileInPackageInfo) + { + + // Should be done with regex :) + if (fileInPackageInfo.Directory.ToLower().Contains(IOHelper.DirSepChar + "app_code")) return true; + if (fileInPackageInfo.Directory.ToLower().Contains(IOHelper.DirSepChar + "bin")) return true; + + string extension = Path.GetExtension(fileInPackageInfo.Directory); + + return extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase); + } + + + private IEnumerable ExtractFileInPackageInfos(XElement filesElement) + { + if (string.Equals(Constants.Packaging.FilesNodeName, filesElement.Name.LocalName) == false) + { + throw new ArgumentException("the root element must be \"Files\"", "filesElement"); + } + + return filesElement.Elements(Constants.Packaging.FileNodeName) + .Select(e => + { + XElement guidElement = e.Element(Constants.Packaging.GuidNodeName); + if (guidElement == null) + { + throw new ArgumentException("Missing element \"" + Constants.Packaging.GuidNodeName + "\"", + "filesElement"); + } + + XElement orgPathElement = e.Element(Constants.Packaging.OrgPathNodeName); + if (orgPathElement == null) + { + throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgPathNodeName + "\"", + "filesElement"); + } + + XElement orgNameElement = e.Element(Constants.Packaging.OrgNameNodeName); + if (orgNameElement == null) + { + throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgNameNodeName + "\"", + "filesElement"); + } + + + return new FileInPackageInfo + { + FileNameInPackage = guidElement.Value, + FileName = PrepareAsFilePathElement(orgNameElement.Value), + RelativeDir = UpdatePathPlaceholders( + PrepareAsFilePathElement(orgPathElement.Value)), + DestinationRootDir = FullpathToRoot + }; + }).ToArray(); + } + + private static string PrepareAsFilePathElement(string pathElement) + { + return pathElement.TrimStart(new[] {'\\', '/', '~'}).Replace("/", "\\"); + } + + + private MetaData GetMetaData(XElement xRootElement) + { + XElement infoElement = xRootElement.Element(Constants.Packaging.InfoNodeName); + + if (infoElement == null) + { + throw new ArgumentException("Did not hold a \"" + Constants.Packaging.InfoNodeName + "\" element", + "xRootElement"); + } + + XElement majorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMajorXpath); + XElement minorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMinorXpath); + XElement patchElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsPatchXpath); + XElement nameElement = infoElement.XPathSelectElement(Constants.Packaging.PackageNameXpath); + XElement versionElement = infoElement.XPathSelectElement(Constants.Packaging.PackageVersionXpath); + XElement urlElement = infoElement.XPathSelectElement(Constants.Packaging.PackageUrlXpath); + XElement licenseElement = infoElement.XPathSelectElement(Constants.Packaging.PackageLicenseXpath); + XElement authorNameElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorNameXpath); + XElement authorUrlElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorWebsiteXpath); + XElement readmeElement = infoElement.XPathSelectElement(Constants.Packaging.ReadmeXpath); + + XElement controlElement = xRootElement.Element(Constants.Packaging.ControlNodeName); + + int val; + + return new MetaData + { + Name = nameElement == null ? string.Empty : nameElement.Value, + Version = versionElement == null ? string.Empty : versionElement.Value, + Url = urlElement == null ? string.Empty : urlElement.Value, + License = licenseElement == null ? string.Empty : licenseElement.Value, + LicenseUrl = + licenseElement == null + ? string.Empty + : licenseElement.HasAttributes ? licenseElement.AttributeValue("url") : string.Empty, + AuthorName = authorNameElement == null ? string.Empty : authorNameElement.Value, + AuthorUrl = authorUrlElement == null ? string.Empty : authorUrlElement.Value, + Readme = readmeElement == null ? string.Empty : readmeElement.Value, + ReqMajor = majorElement == null ? 0 : int.TryParse(majorElement.Value, out val) ? val : 0, + ReqMinor = minorElement == null ? 0 : int.TryParse(minorElement.Value, out val) ? val : 0, + ReqPatch = patchElement == null ? 0 : int.TryParse(patchElement.Value, out val) ? val : 0, + Control = controlElement == null ? string.Empty : controlElement.Value + }; + } + + private static string UpdatePathPlaceholders(string path) + { + if (path.Contains("[$")) + { + //this is experimental and undocumented... + path = path.Replace("[$UMBRACO]", SystemDirectories.Umbraco); + path = path.Replace("[$UMBRACOCLIENT]", SystemDirectories.UmbracoClient); + path = path.Replace("[$CONFIG]", SystemDirectories.Config); + path = path.Replace("[$DATA]", SystemDirectories.Data); + } + return path; + } + } + + public class FileInPackageInfo : IFileInPackageInfo + { + public string RelativePath + { + get { return Path.Combine(RelativeDir, FileName); } + } + + public string FileNameInPackage { get; set; } + public string RelativeDir { get; set; } + public string DestinationRootDir { private get; set; } + + public string Directory + { + get { return Path.Combine(DestinationRootDir, RelativeDir); } + } + + public string FullPath + { + get { return Path.Combine(DestinationRootDir, RelativePath); } + } + + public string FileName { get; set; } + } + + public interface IFileInPackageInfo + { + string RelativeDir { get; } + string RelativePath { get; } + string FileName { get; set; } + } + } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs b/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs deleted file mode 100644 index 8ad6d36243..0000000000 --- a/src/Umbraco.Core/Packaging/PackageInstallationSummary.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Xml.Linq; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Packaging -{ - public class PackageInstallationSummary - { - public PackageMetaData MetaData { get; set; } - public IDataTypeDefinition[] DataTypesInstalled { get; set; } - public ILanguage[] LanguagesInstalled { get; set; } - public IDictionaryItem[] DictionaryItemsInstalled { get; set; } - public IMacro[] MacrosInstalled { get; set; } - public IEnumerable> FilesInstalled { get;set;} - public ITemplate[] TemplatesInstalled { get; set; } - public IContentType[] DocumentTypesInstalled { get; set; } - public IStylesheet[] StylesheetsInstalled { get; set; } - public IContent[] DocumentsInstalled { get; set; } - public IEnumerable> PackageInstallActions { get; set; } - public string PackageUninstallActions { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageMetaData.cs b/src/Umbraco.Core/Packaging/PackageMetaData.cs deleted file mode 100644 index e0f5354912..0000000000 --- a/src/Umbraco.Core/Packaging/PackageMetaData.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Umbraco.Core.Packaging -{ - public class PackageMetaData - { - public string Name { get; set; } - public string Version { get; set; } - public string Url { get; set; } - public string License { get; set; } - public string LicenseUrl { get; set; } - public int ReqMajor { get; set; } - public int ReqMinor { get; set; } - public int ReqPatch { get; set; } - public string AuthorName { get; set; } - public string AuthorUrl { get; set; } - public string Readme { get; set; } - public string Control { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/UnpackHelper.cs b/src/Umbraco.Core/Packaging/UnpackHelper.cs deleted file mode 100644 index 970e0c85a0..0000000000 --- a/src/Umbraco.Core/Packaging/UnpackHelper.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using ICSharpCode.SharpZipLib.Zip; - -namespace Umbraco.Core.Packaging -{ - public class UnpackHelper : IUnpackHelper - { - public string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage) - { - string retVal = null; - bool fileFound = false; - string foundDir = null; - - ReadZipfileEntries(packageFilePath, (entry, stream) => - { - string fileName = Path.GetFileName(entry.Name); - - if (string.IsNullOrEmpty(fileName) == false && - fileName.Equals(fileToRead, StringComparison.CurrentCultureIgnoreCase)) - { - - foundDir = entry.Name.Substring(0, entry.Name.Length - fileName.Length); - fileFound = true; - using (var reader = new StreamReader(stream)) - { - retVal = reader.ReadToEnd(); - return false; - } - } - return true; - }); - - if (fileFound == false) - { - directoryInPackage = null; - throw new FileNotFoundException(string.Format("Could not find file in package {0}", packageFilePath), fileToRead); - } - directoryInPackage = foundDir; - return retVal; - } - - private static void CheckPackageExists(string packageFilePath) - { - if (string.IsNullOrEmpty(packageFilePath)) - { - throw new ArgumentNullException("packageFilePath"); - } - - if (File.Exists(packageFilePath) == false) - { - if (File.Exists(packageFilePath) == false) - throw new ArgumentException(string.Format("Package file: {0} could not be found", packageFilePath)); - } - - string extension = Path.GetExtension(packageFilePath).ToLower(); - - var alowedExtension = new[] {".umb", ".zip"}; - - // Check if the file is a valid package - if (alowedExtension.All(ae => ae.Equals(extension) == false)) - { - throw new ArgumentException( - string.Format("Error - file isn't a package. only extentions: \"{0}\" is allowed", string.Join(", ", alowedExtension))); - } - } - - - public bool CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath) - { - bool fileFoundInArchive = false; - bool fileOverwritten = false; - - ReadZipfileEntries(packageFilePath, (entry, stream) => - { - string fileName = Path.GetFileName(entry.Name); - - if (string.IsNullOrEmpty(fileName) == false && - fileName.Equals(fileInPackageName, StringComparison.InvariantCultureIgnoreCase)) - { - fileFoundInArchive = true; - - fileOverwritten = File.Exists(destinationfilePath); - - using (var streamWriter = File.Open(destinationfilePath, FileMode.Create)) - { - var data = new byte[2048]; - int size; - while ((size = stream.Read(data, 0, data.Length)) > 0) - { - streamWriter.Write(data, 0, size); - } - - streamWriter.Close(); - } - return false; - } - return true; - }); - - - if (fileFoundInArchive == false) throw new ArgumentException(string.Format("Could not find file: {0} in package file: {1}", fileInPackageName, packageFilePath), "fileInPackageName"); - - return fileOverwritten; - } - - public IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles) - { - var retVal = expectedFiles.ToList(); - - ReadZipfileEntries(packageFilePath, (zipEntry, stream) => - { - string fileName = Path.GetFileName(zipEntry.Name); - - int index = retVal.FindIndex(f => f.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)); - - if (index != -1) { retVal.RemoveAt(index); } - - return retVal.Any(); - }); - return retVal; - - } - - public IEnumerable FindDubletFileNames(string packageFilePath) - { - var dictionary = new Dictionary>(); - - - ReadZipfileEntries(packageFilePath, (entry, stream) => - { - string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower(); - - List list; - if (dictionary.TryGetValue(fileName, out list) == false) - { - list = new List(); - dictionary.Add(fileName, list); - } - - list.Add(entry.Name); - - return true; - }); - - return dictionary.Values.Where(v => v.Count > 1).SelectMany(v => v); - } - - private void ReadZipfileEntries(string packageFilePath, Func entryFunc, bool skipsDirectories = true) - { - CheckPackageExists(packageFilePath); - - using (var fs = File.OpenRead(packageFilePath)) - { - using (var zipInputStream = new ZipInputStream(fs)) - { - ZipEntry zipEntry; - while ((zipEntry = zipInputStream.GetNextEntry()) != null) - { - if (zipEntry.IsDirectory && skipsDirectories) continue; - if( entryFunc(zipEntry, zipInputStream) == false ) break; - } - - zipInputStream.Close(); - } - fs.Close(); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IPackageInstallerService.cs b/src/Umbraco.Core/Services/IPackageInstallerService.cs deleted file mode 100644 index d340c572a5..0000000000 --- a/src/Umbraco.Core/Services/IPackageInstallerService.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Umbraco.Core.Packaging; - -namespace Umbraco.Core.Services -{ - public interface IPackageInstallerService : IService - { - PackageInstallationSummary InstallPackageFile(string packageFilePath, int userId); - PackageMetaData GetMetaData(string packageFilePath); - PreInstallWarnings GetPreInstallWarnings(string packageFilePath); - } -} diff --git a/src/Umbraco.Core/Services/PackageInstallerService.cs b/src/Umbraco.Core/Services/PackageInstallerService.cs deleted file mode 100644 index 707ddfbe4b..0000000000 --- a/src/Umbraco.Core/Services/PackageInstallerService.cs +++ /dev/null @@ -1,560 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.IO; -using System.Linq; -using System.Xml.Linq; -using System.Xml.XPath; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Packaging; - -namespace Umbraco.Core.Services -{ - public class PackageInstallerService : IPackageInstallerService - { - private readonly IFileService _fileService; - private readonly IMacroService _macroService; - private readonly IPackagingService _packagingService; - private IConflictingPackageContentFinder _conflictingPackageContentFinder; - private IUnpackHelper _unpackHelper; - - public PackageInstallerService(IPackagingService packagingService, IMacroService macroService, - IFileService fileService) - { - if (macroService != null) _macroService = macroService; - else throw new ArgumentNullException("macroService"); - if (fileService != null) _fileService = fileService; - else throw new ArgumentNullException("fileService"); - if (packagingService != null) _packagingService = packagingService; - else throw new ArgumentNullException("packagingService"); - } - - - public IConflictingPackageContentFinder ConflictingPackageContentFinder - { - private get - { - return _conflictingPackageContentFinder ?? - (_conflictingPackageContentFinder = new ConflictingPackageContentFinder(_macroService, _fileService)); - } - set - { - if (_conflictingPackageContentFinder != null) - { - throw new PropertyConstraintException("This property already have a value"); - } - _conflictingPackageContentFinder = value; - } - } - - - public IUnpackHelper UnpackHelper - { - private get { return _unpackHelper ?? (_unpackHelper = new UnpackHelper()); } - set - { - if (_unpackHelper != null) - { - throw new PropertyConstraintException("This property already have a value"); - } - _unpackHelper = value; - } - } - - private string _fullpathToRoot; - public string FullpathToRoot - { - private get { return _fullpathToRoot ?? (_fullpathToRoot = GlobalSettings.FullpathToRoot); } - set - { - - if (_fullpathToRoot != null) - { - throw new PropertyConstraintException("This property already have a value"); - } - - _fullpathToRoot = value; - } - } - - - public PackageMetaData GetMetaData(string packageFilePath) - { - try - { - XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); - return GetMetaData(rootElement); - } - catch (Exception e) - { - throw new Exception("Error reading " + packageFilePath, e); - } - } - - public PreInstallWarnings GetPreInstallWarnings(string packageFilePath) - { - try - { - XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); - return GetPreInstallWarnings(rootElement); - } - catch (Exception e) - { - throw new Exception("Error reading " + packageFilePath, e); - } - } - - public PackageInstallationSummary InstallPackageFile(string packageFile, int userId) - { - XElement dataTypes; - XElement languages; - XElement dictionaryItems; - XElement macroes; - XElement files; - XElement templates; - XElement documentTypes; - XElement styleSheets; - XElement documentSet; - XElement actions; - PackageMetaData metaData; - - try - { - XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFile); - PackageStructureSanetyCheck(packageFile); - dataTypes = rootElement.Element(Constants.Packaging.DataTypesNodeName); - languages = rootElement.Element(Constants.Packaging.LanguagesNodeName); - dictionaryItems = rootElement.Element(Constants.Packaging.DictionaryItemsNodeName); - macroes = rootElement.Element(Constants.Packaging.MacrosNodeName); - files = rootElement.Element(Constants.Packaging.FilesNodeName); - templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); - documentTypes = rootElement.Element(Constants.Packaging.DocumentTypesNodeName); - styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); - documentSet = rootElement.Element(Constants.Packaging.DocumentSetNodeName); - actions = rootElement.Element(Constants.Packaging.ActionsNodeName); - - metaData = GetMetaData(rootElement); - } - catch (Exception e) - { - throw new Exception("Error reading " + packageFile, e); - } - - try - { - - return new PackageInstallationSummary - { - MetaData = metaData, - DataTypesInstalled = - dataTypes == null ? new IDataTypeDefinition[0] : InstallDataTypes(dataTypes, userId), - LanguagesInstalled = languages == null ? new ILanguage[0] : InstallLanguages(languages, userId), - DictionaryItemsInstalled = - dictionaryItems == null ? new IDictionaryItem[0] : InstallDictionaryItems(dictionaryItems), - MacrosInstalled = macroes == null ? new IMacro[0] : InstallMacros(macroes, userId), - FilesInstalled = - packageFile == null - ? Enumerable.Empty>() - : InstallFiles(packageFile, files), - TemplatesInstalled = templates == null ? new ITemplate[0] : InstallTemplats(templates, userId), - DocumentTypesInstalled = - documentTypes == null ? new IContentType[0] : InstallDocumentTypes(documentTypes, userId), - StylesheetsInstalled = - styleSheets == null ? new IStylesheet[0] : InstallStylesheets(styleSheets, userId), - DocumentsInstalled = documentSet == null ? new IContent[0] : InstallDocuments(documentSet, userId), - PackageInstallActions = - actions == null ? Enumerable.Empty>() : GetInstallActions(actions), - PackageUninstallActions = actions == null ? string.Empty : GetUninstallActions(actions) - }; - } - catch (Exception e) - { - throw new Exception("Error installing package " + packageFile, e); - } - } - - - - - private XDocument GetConfigXmlDocFromPackageFile(string packageFilePath) - { - string filePathInPackage; - string configXmlContent = UnpackHelper.ReadTextFileFromArchive(packageFilePath, - Constants.Packaging.PackageXmlFileName, out filePathInPackage); - - return XDocument.Parse(configXmlContent); - } - - - private XElement GetConfigXmlRootElementFromPackageFile(string packageFilePath) - { - XDocument document = GetConfigXmlDocFromPackageFile(packageFilePath); - if (document.Root == null || - document.Root.Name.LocalName.Equals(Constants.Packaging.UmbPackageNodeName) == false) - { - throw new ArgumentException("xml does not have a root node called \"umbPackage\"", packageFilePath); - } - return document.Root; - } - - private void PackageStructureSanetyCheck(string packageFilePath) - { - XElement rootElement = GetConfigXmlRootElementFromPackageFile(packageFilePath); - XElement filesElement = rootElement.Element(Constants.Packaging.FilesNodeName); - if (filesElement != null) - { - IEnumerable extractFileInPackageInfos = - ExtractFileInPackageInfos(filesElement).ToArray(); - - IEnumerable missingFiles = - _unpackHelper.FindMissingFiles(packageFilePath, - extractFileInPackageInfos.Select(i => i.FileNameInPackage)).ToArray(); - - if (missingFiles.Any()) - { - throw new Exception("The following file(s) are missing in the package: " + - string.Join(", ", missingFiles.Select( - mf => - { - FileInPackageInfo fileInPackageInfo = - extractFileInPackageInfos.Single(fi => fi.FileNameInPackage == mf); - return string.Format("Guid: \"{0}\" Original File: \"{1}\"", - fileInPackageInfo.FileNameInPackage, fileInPackageInfo.RelativePath); - }))); - } - - IEnumerable dubletFileNames = _unpackHelper.FindDubletFileNames(packageFilePath).ToArray(); - if (dubletFileNames.Any()) - { - throw new Exception("The following filename(s) are found more than one time in the package, since the filename is used ad primary key, this is not allowed: " + - string.Join(", ", dubletFileNames)); - } - - - - } - } - - - private static string GetUninstallActions(XElement actionsElement) - { - //saving the uninstall actions untill the package is uninstalled. - return actionsElement.Elements(Constants.Packaging.ActionNodeName) - .Where( - e => - e.HasAttributes && e.Attribute(Constants.Packaging.UndoNodeAttribute) != null && - e.Attribute(Constants.Packaging.UndoNodeAttribute) - .Value.Equals("false()", StringComparison.InvariantCultureIgnoreCase) == false) - // SelectNodes("Actions/Action [@undo != false()]"); - .Select(m => m.Value).Aggregate((workingSentence, next) => next + workingSentence); - } - - private static IEnumerable> GetInstallActions(XElement actionsElement) - { - if (actionsElement == null) - { - return Enumerable.Empty>(); - } - - if (string.Equals(Constants.Packaging.ActionsNodeName, actionsElement.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.ActionsNodeName + "\" as root", - "actionsElement"); - } - - return actionsElement.Elements(Constants.Packaging.ActionNodeName) - .Where( - e => - e.HasAttributes && - (e.Attribute(Constants.Packaging.RunatNodeAttribute) == null || - e.Attribute(Constants.Packaging.RunatNodeAttribute) - .Value.Equals("uninstall", StringComparison.InvariantCultureIgnoreCase) == - false)) // .SelectNodes("Actions/Action [@runat != 'uninstall']") - .Select(elemet => - { - XAttribute aliasAttr = elemet.Attribute(Constants.Packaging.AliasNodeNameSmall) ?? elemet.Attribute(Constants.Packaging.AliasNodeNameCapital); - if (aliasAttr == null) - throw new ArgumentException( - "missing \"" + Constants.Packaging.AliasNodeNameSmall + "\" atribute in alias element", - "actionsElement"); - return new {elemet, alias = aliasAttr.Value}; - }).ToDictionary(x => x.alias, x => x.elemet); - } - - private IContent[] InstallDocuments(XElement documentsElement, int userId = 0) - { - if (string.Equals(Constants.Packaging.DocumentSetNodeName, documentsElement.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.DocumentSetNodeName + "\" as root", - "documentsElement"); - } - return _packagingService.ImportContent(documentsElement, -1, userId).ToArray(); - } - - private IStylesheet[] InstallStylesheets(XElement styleSheetsElement, int userId = 0) - { - if (string.Equals(Constants.Packaging.StylesheetsNodeName, styleSheetsElement.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.StylesheetsNodeName + "\" as root", - "styleSheetsElement"); - } - return _packagingService.ImportStylesheets(styleSheetsElement, userId).ToArray(); - } - - private IContentType[] InstallDocumentTypes(XElement documentTypes, int userId = 0) - { - if (string.Equals(Constants.Packaging.DocumentTypesNodeName, documentTypes.Name.LocalName) == false) - { - if (string.Equals(Constants.Packaging.DocumentTypeNodeName, documentTypes.Name.LocalName) == false) - throw new ArgumentException( - "Must be \"" + Constants.Packaging.DocumentTypesNodeName + "\" as root", "documentTypes"); - - documentTypes = new XElement(Constants.Packaging.DocumentTypesNodeName, documentTypes); - } - - return _packagingService.ImportContentTypes(documentTypes, userId).ToArray(); - } - - private ITemplate[] InstallTemplats(XElement templateElement, int userId = 0) - { - if (string.Equals(Constants.Packaging.TemplatesNodeName, templateElement.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.TemplatesNodeName + "\" as root", - "templateElement"); - } - return _packagingService.ImportTemplates(templateElement, userId).ToArray(); - } - - - private IEnumerable> InstallFiles(string packageFilePath, XElement filesElement) - { - return ExtractFileInPackageInfos(filesElement).Select(fpi => - { - bool existingOverrided = _unpackHelper.CopyFileFromArchive(packageFilePath, fpi.FileNameInPackage, - fpi.FullPath); - - return new KeyValuePair(fpi.FullPath, existingOverrided); - }).ToArray(); - } - - private IMacro[] InstallMacros(XElement macroElements, int userId = 0) - { - if (string.Equals(Constants.Packaging.MacrosNodeName, macroElements.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.MacrosNodeName + "\" as root", - "macroElements"); - } - return _packagingService.ImportMacros(macroElements, userId).ToArray(); - } - - private IDictionaryItem[] InstallDictionaryItems(XElement dictionaryItemsElement) - { - if (string.Equals(Constants.Packaging.DictionaryItemsNodeName, dictionaryItemsElement.Name.LocalName) == - false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.DictionaryItemsNodeName + "\" as root", - "dictionaryItemsElement"); - } - return _packagingService.ImportDictionaryItems(dictionaryItemsElement).ToArray(); - } - - private ILanguage[] InstallLanguages(XElement languageElement, int userId = 0) - { - if (string.Equals(Constants.Packaging.LanguagesNodeName, languageElement.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.LanguagesNodeName + "\" as root", "languageElement"); - } - return _packagingService.ImportLanguages(languageElement, userId).ToArray(); - } - - private IDataTypeDefinition[] InstallDataTypes(XElement dataTypeElements, int userId = 0) - { - if (string.Equals(Constants.Packaging.DataTypesNodeName, dataTypeElements.Name.LocalName) == false) - { - if (string.Equals(Constants.Packaging.DataTypeNodeName, dataTypeElements.Name.LocalName) == false) - { - throw new ArgumentException("Must be \"" + Constants.Packaging.DataTypeNodeName + "\" as root", "dataTypeElements"); - } - } - return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId).ToArray(); - } - - private PreInstallWarnings GetPreInstallWarnings(XElement rootElement) - { - XElement files = rootElement.Element(Constants.Packaging.FilesNodeName); - XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); - XElement templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); - XElement alias = rootElement.Element(Constants.Packaging.MacrosNodeName); - var conflictingPackageContent = new PreInstallWarnings - { - UnsecureFiles = files == null ? new IFileInPackageInfo[0] : FindUnsecureFiles(files), - ConflictingMacroAliases = alias == null ? new IMacro[0] : ConflictingPackageContentFinder.FindConflictingMacros(alias), - ConflictingTemplateAliases = - templates == null ? new ITemplate[0] : ConflictingPackageContentFinder.FindConflictingTemplates(templates), - ConflictingStylesheetNames = - styleSheets == null ? new IStylesheet[0] : ConflictingPackageContentFinder.FindConflictingStylesheets(styleSheets) - }; - - return conflictingPackageContent; - } - - private IFileInPackageInfo[] FindUnsecureFiles(XElement fileElement) - { - return ExtractFileInPackageInfos(fileElement) - .Where(IsFileNodeUnsecure).Cast().ToArray(); - } - - private bool IsFileNodeUnsecure(FileInPackageInfo fileInPackageInfo) - { - - // Should be done with regex :) - if (fileInPackageInfo.Directory.ToLower().Contains(IOHelper.DirSepChar + "app_code")) return true; - if (fileInPackageInfo.Directory.ToLower().Contains(IOHelper.DirSepChar + "bin")) return true; - - string extension = Path.GetExtension(fileInPackageInfo.Directory); - - return extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase); - } - - - private IEnumerable ExtractFileInPackageInfos(XElement filesElement) - { - if (string.Equals(Constants.Packaging.FilesNodeName, filesElement.Name.LocalName) == false) - { - throw new ArgumentException("the root element must be \"Files\"", "filesElement"); - } - - return filesElement.Elements(Constants.Packaging.FileNodeName) - .Select(e => - { - XElement guidElement = e.Element(Constants.Packaging.GuidNodeName); - if (guidElement == null) - { - throw new ArgumentException("Missing element \"" + Constants.Packaging.GuidNodeName + "\"", - "filesElement"); - } - - XElement orgPathElement = e.Element(Constants.Packaging.OrgPathNodeName); - if (orgPathElement == null) - { - throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgPathNodeName + "\"", - "filesElement"); - } - - XElement orgNameElement = e.Element(Constants.Packaging.OrgNameNodeName); - if (orgNameElement == null) - { - throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgNameNodeName + "\"", - "filesElement"); - } - - - return new FileInPackageInfo - { - FileNameInPackage = guidElement.Value, - FileName = PrepareAsFilePathElement(orgNameElement.Value), - RelativeDir = UpdatePathPlaceholders( - PrepareAsFilePathElement(orgPathElement.Value)), - DestinationRootDir = FullpathToRoot - }; - }).ToArray(); - } - - private static string PrepareAsFilePathElement(string pathElement) - { - return pathElement.TrimStart(new[] {'\\', '/', '~'}).Replace("/", "\\"); - } - - - private PackageMetaData GetMetaData(XElement xRootElement) - { - XElement infoElement = xRootElement.Element(Constants.Packaging.InfoNodeName); - - if (infoElement == null) - { - throw new ArgumentException("Did not hold a \"" + Constants.Packaging.InfoNodeName + "\" element", - "xRootElement"); - } - - XElement majorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMajorXpath); - XElement minorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMinorXpath); - XElement patchElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsPatchXpath); - XElement nameElement = infoElement.XPathSelectElement(Constants.Packaging.PackageNameXpath); - XElement versionElement = infoElement.XPathSelectElement(Constants.Packaging.PackageVersionXpath); - XElement urlElement = infoElement.XPathSelectElement(Constants.Packaging.PackageUrlXpath); - XElement licenseElement = infoElement.XPathSelectElement(Constants.Packaging.PackageLicenseXpath); - XElement authorNameElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorNameXpath); - XElement authorUrlElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorWebsiteXpath); - XElement readmeElement = infoElement.XPathSelectElement(Constants.Packaging.ReadmeXpath); - - XElement controlElement = xRootElement.Element(Constants.Packaging.ControlNodeName); - - int val; - - return new PackageMetaData - { - Name = nameElement == null ? string.Empty : nameElement.Value, - Version = versionElement == null ? string.Empty : versionElement.Value, - Url = urlElement == null ? string.Empty : urlElement.Value, - License = licenseElement == null ? string.Empty : licenseElement.Value, - LicenseUrl = - licenseElement == null - ? string.Empty - : licenseElement.HasAttributes ? licenseElement.AttributeValue("url") : string.Empty, - AuthorName = authorNameElement == null ? string.Empty : authorNameElement.Value, - AuthorUrl = authorUrlElement == null ? string.Empty : authorUrlElement.Value, - Readme = readmeElement == null ? string.Empty : readmeElement.Value, - ReqMajor = majorElement == null ? 0 : int.TryParse(majorElement.Value, out val) ? val : 0, - ReqMinor = minorElement == null ? 0 : int.TryParse(minorElement.Value, out val) ? val : 0, - ReqPatch = patchElement == null ? 0 : int.TryParse(patchElement.Value, out val) ? val : 0, - Control = controlElement == null ? string.Empty : controlElement.Value - }; - } - - private static string UpdatePathPlaceholders(string path) - { - if (path.Contains("[$")) - { - //this is experimental and undocumented... - path = path.Replace("[$UMBRACO]", SystemDirectories.Umbraco); - path = path.Replace("[$UMBRACOCLIENT]", SystemDirectories.UmbracoClient); - path = path.Replace("[$CONFIG]", SystemDirectories.Config); - path = path.Replace("[$DATA]", SystemDirectories.Data); - } - return path; - } - } - - public class FileInPackageInfo : IFileInPackageInfo - { - public string RelativePath - { - get { return Path.Combine(RelativeDir, FileName); } - } - - public string FileNameInPackage { get; set; } - public string RelativeDir { get; set; } - public string DestinationRootDir { private get; set; } - - public string Directory - { - get { return Path.Combine(DestinationRootDir, RelativeDir); } - } - - public string FullPath - { - get { return Path.Combine(DestinationRootDir, RelativePath); } - } - - public string FileName { get; set; } - } - - public interface IFileInPackageInfo - { - string RelativeDir { get; } - string RelativePath { get; } - string FileName { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 5dfd32a231..cf95a63dcd 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -35,6 +35,7 @@ namespace Umbraco.Core.Services private readonly RepositoryFactory _repositoryFactory; private readonly IDatabaseUnitOfWorkProvider _uowProvider; private Dictionary _importedContentTypes; + private IPackageInstallation _packageInstallation; public PackagingService(IContentService contentService, @@ -59,7 +60,24 @@ namespace Umbraco.Core.Services _importedContentTypes = new Dictionary(); } - + + + //// Temperary constructor while packaging service is not final + //internal PackagingService(IContentService contentService, + // IContentTypeService contentTypeService, + // IMediaService mediaService, + // IMacroService macroService, + // IDataTypeService dataTypeService, + // IFileService fileService, + // ILocalizationService localizationService, + // RepositoryFactory repositoryFactory, + // IDatabaseUnitOfWorkProvider uowProvider, + // IPackageInstallation packageInstallation + // ) : this(contentService, contentTypeService, mediaService, macroService, dataTypeService, fileService, localizationService, repositoryFactory, uowProvider) + //{ + // _packageInstallation = packageInstallation; + //} + #region Content /// @@ -1573,13 +1591,27 @@ namespace Umbraco.Core.Services #region Installation + internal IPackageInstallation PackageInstallation + { + private get { return _packageInstallation ?? new PackageInstallation(this, _macroService, _fileService, new PackageExtraction()); } + set { _packageInstallation = value; } + + + } + internal InstallationSummary InstallPackage(string packageFilePath, int userId = 0) { - //TODO Add events ? - //NOTE The PackageInstallation class should be passed as IPackageInstallation through the - //constructor (probably as an overload to avoid breaking stuff), so that its extendable. - var installer = new PackageInstallation(this, new PackageExtraction()); - return installer.InstallPackage(packageFilePath, userId); + return PackageInstallation.InstallPackage(packageFilePath, userId); + } + + internal PreInstallWarnings GetPackageWarnings(string packageFilePath) + { + return PackageInstallation.GetPreInstallWarnings(packageFilePath); + } + + internal MetaData GetPackageMetaData(string packageFilePath) + { + return PackageInstallation.GetMetaData(packageFilePath); } #endregion diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 5ffb45703f..a2bee22c85 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -22,7 +22,6 @@ namespace Umbraco.Core.Services private Lazy _fileService; private Lazy _localizationService; private Lazy _packagingService; - private Lazy _packageInstallerService; private Lazy _serverRegistrationService; private Lazy _entityService; private Lazy _relationService; @@ -43,7 +42,6 @@ namespace Umbraco.Core.Services /// /// /// - /// /// /// /// @@ -62,7 +60,6 @@ namespace Umbraco.Core.Services IFileService fileService, ILocalizationService localizationService, IPackagingService packagingService, - IPackageInstallerService packageInstallerService, IEntityService entityService, IRelationService relationService, IMemberGroupService memberGroupService, @@ -82,7 +79,6 @@ namespace Umbraco.Core.Services _fileService = new Lazy(() => fileService); _localizationService = new Lazy(() => localizationService); _packagingService = new Lazy(() => packagingService); - _packageInstallerService = new Lazy(() => packageInstallerService); _entityService = new Lazy(() => entityService); _relationService = new Lazy(() => relationService); _sectionService = new Lazy(() => sectionService); @@ -92,7 +88,6 @@ namespace Umbraco.Core.Services _memberService = new Lazy(() => memberService); _userService = new Lazy(() => userService); _notificationService = new Lazy(() => notificationService); - } /// @@ -156,9 +151,6 @@ namespace Umbraco.Core.Services if (_packagingService == null) _packagingService = new Lazy(() => new PackagingService(_contentService.Value, _contentTypeService.Value, _mediaService.Value, _macroService.Value, _dataTypeService.Value, _fileService.Value, _localizationService.Value, repositoryFactory.Value, provider)); - if (_packageInstallerService == null) - _packageInstallerService = new Lazy(() => new PackageInstallerService(_packagingService.Value, _macroService.Value, _fileService.Value)); - if (_entityService == null) _entityService = new Lazy(() => new EntityService(provider, repositoryFactory.Value, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value)); @@ -335,10 +327,5 @@ namespace Umbraco.Core.Services { get { return _memberGroupService.Value; } } - - public IPackageInstallerService PackageInstallerService - { - get { return _packageInstallerService.Value; } - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 271aebf475..5c76d2e0bb 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -366,15 +366,12 @@ - - - - - - + + + - + @@ -1050,7 +1047,6 @@ - @@ -1064,7 +1060,6 @@ - diff --git a/src/Umbraco.Tests/MockTests.cs b/src/Umbraco.Tests/MockTests.cs index eaa7b81850..7cea990653 100644 --- a/src/Umbraco.Tests/MockTests.cs +++ b/src/Umbraco.Tests/MockTests.cs @@ -5,6 +5,7 @@ using System.Text; using System.Web; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Packaging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Services; @@ -44,10 +45,6 @@ namespace Umbraco.Tests new Mock().Object, new RepositoryFactory(true), new Mock().Object), - new PackageInstallerService( - new Mock().Object, - new Mock().Object, - new Mock().Object), new Mock().Object, new RelationService( new Mock().Object, @@ -93,10 +90,6 @@ namespace Umbraco.Tests new Mock().Object, new RepositoryFactory(true), new Mock().Object), - new PackageInstallerService( - new Mock().Object, - new Mock().Object, - new Mock().Object), new Mock().Object, new RelationService( new Mock().Object, diff --git a/src/Umbraco.Tests/Services/PackageInstallationTest.cs b/src/Umbraco.Tests/Services/PackageInstallationTest.cs new file mode 100644 index 0000000000..bebeab66df --- /dev/null +++ b/src/Umbraco.Tests/Services/PackageInstallationTest.cs @@ -0,0 +1,76 @@ +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Packaging; +using Umbraco.Core.Packaging.Models; +using Umbraco.Core.Services; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + public class PackageInstallationTest + { + private const string Xml = @" + + + 095e064b-ba4d-442d-9006-3050983c13d8.dll/binAuros.DocumentTypePicker.dll + + + Document Type Picker + 1.1 + MIT + http://www.auros.co.uk + + 3 + 0 + 0 + + + + @tentonipete + auros.co.uk + + + + + + + + + + + + + + +"; + + [Test] + public void Test() + { + // Arrange + const string pagePath = "Test.umb"; + + var packageExtraction = new Mock(); + + string test; + packageExtraction.Setup(a => a.ReadTextFileFromArchive(pagePath, Constants.Packaging.PackageXmlFileName, out test)).Returns(Xml); + + + + var fileService = new Mock(); + var macroService = new Mock(); + var packagingService = new Mock(); + + var sut = new PackageInstallation(packagingService.Object, macroService.Object, fileService.Object, packageExtraction.Object); + + // Act + InstallationSummary installationSummary = sut.InstallPackage(pagePath, -1); + + // Assert + Assert.IsNotNull(installationSummary); + Assert.Inconclusive("Lots of more tests can be written"); + } + + } +} diff --git a/src/Umbraco.Tests/Services/PackageInstallerServiceTest.cs b/src/Umbraco.Tests/Services/PackageInstallerServiceTest.cs deleted file mode 100644 index 6067ff0909..0000000000 --- a/src/Umbraco.Tests/Services/PackageInstallerServiceTest.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.IO; -using System.Linq; -using NUnit.Framework; -using Umbraco.Core.Models; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Services -{ - [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] - [TestFixture] - public class PackageInstallerServiceTest : BaseServiceTest - { - private const string DOCUMENT_TYPE_PICKER_UMB = "Document_Type_Picker_1.1.umb"; - private const string NETMESTER_BEST_PRACTICE_BASE_UMB = "Netmester.BestPractice.Base_0.0.0.1.umb"; - private const string TEST_PACKAGES_DIR_NAME = "Packages"; - - [SetUp] - public override void Initialize() - { - base.Initialize(); - } - - [TearDown] - public override void TearDown() - { - base.TearDown(); - } - - [Test] - public void PackageInstallerService_TestSomething() - { - // Arrange - var path = GetTestPackagePath(DOCUMENT_TYPE_PICKER_UMB); - - // Act - var packageMetaData = ServiceContext.PackageInstallerService.GetMetaData(path); - - // Assert - Assert.IsNotNull(packageMetaData); - } - - [Test] - public void PackageInstallerService_TestSomethingelse() - { - // Arrange - var path = GetTestPackagePath(DOCUMENT_TYPE_PICKER_UMB); - - // Act - var importIssues = ServiceContext.PackageInstallerService.GetPreInstallWarnings(path); - - // Assert - Assert.IsNotNull(importIssues); - } - - - [Test] - public void PackageInstallerService_TestSomethingnew() - { - // Arrange - var path = GetTestPackagePath(NETMESTER_BEST_PRACTICE_BASE_UMB); - - // Act - var importIssues = ServiceContext.PackageInstallerService.GetPreInstallWarnings(path); - - // Assert - Assert.IsNotNull(importIssues); - } - - - - [Test] - public void PackageInstallerService_TestSomethingthered() - { - // Arrange - var path = GetTestPackagePath(DOCUMENT_TYPE_PICKER_UMB); - - // Act - var packageMetaData = ServiceContext.PackageInstallerService.InstallPackageFile(path, -1); - // Assert - IDataTypeDefinition dataTypeDefinitionById = ApplicationContext.Services.DataTypeService.GetDataTypeDefinitionById( - packageMetaData.DataTypesInstalled.Single().Id); - - Assert.IsNotNull(dataTypeDefinitionById); - - foreach (var result in packageMetaData.FilesInstalled.Select(fi => fi.Key)) - { - Assert.IsTrue(System.IO.File.Exists(result)); - System.IO.File.Delete(result); - } - } - - private static string GetTestPackagePath(string packageName) - { - string path = Path.Combine(Core.Configuration.GlobalSettings.FullpathToRoot, TEST_PACKAGES_DIR_NAME, packageName); - return path; - } - } -} diff --git a/src/Umbraco.Tests/Services/PackagingServiceTests.cs b/src/Umbraco.Tests/Services/PackagingServiceTests.cs index 6be4d6acbc..2c4b543c26 100644 --- a/src/Umbraco.Tests/Services/PackagingServiceTests.cs +++ b/src/Umbraco.Tests/Services/PackagingServiceTests.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Xml.Linq; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Packaging.Models; +using Umbraco.Core.Services; using Umbraco.Tests.Services.Importing; using Umbraco.Tests.TestHelpers; @@ -79,6 +82,55 @@ namespace Umbraco.Tests.Services Assert.That(xml.ToString(), Is.EqualTo(languageItemsElement.ToString())); } + private static string GetTestPackagePath(string packageName) + { + const string testPackagesDirName = "Packages"; + string path = Path.Combine(Core.Configuration.GlobalSettings.FullpathToRoot, testPackagesDirName, packageName); + return path; + } + + + [Test] + public void PackagingService_Can_ImportPackage() + { + var packagingService = (PackagingService)ServiceContext.PackagingService; + + const string documentTypePickerUmb = "Document_Type_Picker_1.1.umb"; + + string testPackagePath = GetTestPackagePath(documentTypePickerUmb); + + InstallationSummary installationSummary = packagingService.InstallPackage(testPackagePath); + + Assert.IsNotNull(installationSummary); + } + + + [Test] + public void PackagingService_Can_GetPackageMetaData() + { + var packagingService = (PackagingService)ServiceContext.PackagingService; + + const string documentTypePickerUmb = "Document_Type_Picker_1.1.umb"; + + string testPackagePath = GetTestPackagePath(documentTypePickerUmb); + + MetaData packageMetaData = packagingService.GetPackageMetaData(testPackagePath); + Assert.IsNotNull(packageMetaData); + } + + [Test] + public void PackagingService_Can_GetPackageWarnings() + { + var packagingService = (PackagingService)ServiceContext.PackagingService; + + const string documentTypePickerUmb = "Document_Type_Picker_1.1.umb"; + + string testPackagePath = GetTestPackagePath(documentTypePickerUmb); + + PreInstallWarnings preInstallWarnings = packagingService.GetPackageWarnings(testPackagePath); + Assert.IsNotNull(preInstallWarnings); + } + private void CreateDictionaryData() { var languageNbNo = new Language("nb-NO") { CultureName = "nb-NO" }; diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index acc47f85f6..f0afb997e1 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -322,7 +322,7 @@ - + From 2bea8c56915a4c6e738fc8dc7e386086255b4ae0 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 6 Jun 2014 13:50:39 +0200 Subject: [PATCH 026/189] back to const --- src/Umbraco.Core/Constants-Packaging.cs | 82 ++++++++++++------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Core/Constants-Packaging.cs b/src/Umbraco.Core/Constants-Packaging.cs index 0beb034508..5db3d02532 100644 --- a/src/Umbraco.Core/Constants-Packaging.cs +++ b/src/Umbraco.Core/Constants-Packaging.cs @@ -7,47 +7,47 @@ /// public static class Packaging { - public static readonly string UmbPackageNodeName = "umbPackage"; - public static readonly string DataTypesNodeName = "DataTypes"; - public static readonly string PackageXmlFileName = "package.xml"; - public static readonly string UmbracoPackageExtention = ".umb"; - public static readonly string DataTypeNodeName = "DataType"; - public static readonly string LanguagesNodeName = "Languages"; - public static readonly string FilesNodeName = "files"; - public static readonly string StylesheetsNodeName = "Stylesheets"; - public static readonly string TemplatesNodeName = "Templates"; - public static readonly string NameNodeName = "Name"; - public static readonly string TemplateNodeName = "Template"; - public static readonly string AliasNodeNameSmall = "alias"; - public static readonly string AliasNodeNameCapital = "Alias"; - public static readonly string DictionaryItemsNodeName = "DictionaryItems"; - public static readonly string DictionaryItemNodeName = "DictionaryItem"; - public static readonly string MacrosNodeName = "Macros"; - public static readonly string DocumentSetNodeName = "DocumentSet"; - public static readonly string DocumentTypesNodeName = "DocumentTypes"; - public static readonly string DocumentTypeNodeName = "DocumentType"; - public static readonly string FileNodeName = "file"; - public static readonly string OrgNameNodeName = "orgName"; - public static readonly string OrgPathNodeName = "orgPath"; - public static readonly string GuidNodeName = "guid"; - public static readonly string StylesheetNodeName = "styleSheet"; - public static readonly string MacroNodeName = "macro"; - public static readonly string InfoNodeName = "info"; - public static readonly string PackageRequirementsMajorXpath = "./package/requirements/major"; - public static readonly string PackageRequirementsMinorXpath = "./package/requirements/minor"; - public static readonly string PackageRequirementsPatchXpath = "./package/requirements/patch"; - public static readonly string PackageNameXpath = "./package/name"; - public static readonly string PackageVersionXpath = "./package/version"; - public static readonly string PackageUrlXpath = "./package/url"; - public static readonly string PackageLicenseXpath = "./package/license"; - public static readonly string AuthorNameXpath = "./author/name"; - public static readonly string AuthorWebsiteXpath = "./author/website"; - public static readonly string ReadmeXpath = "./readme"; - public static readonly string ControlNodeName = "control"; - public static readonly string ActionNodeName = "Action"; - public static readonly string ActionsNodeName = "Actions"; - public static readonly string UndoNodeAttribute = "undo"; - public static readonly string RunatNodeAttribute = "runat"; + public const string UmbPackageNodeName = "umbPackage"; + public const string DataTypesNodeName = "DataTypes"; + public const string PackageXmlFileName = "package.xml"; + public const string UmbracoPackageExtention = ".umb"; + public const string DataTypeNodeName = "DataType"; + public const string LanguagesNodeName = "Languages"; + public const string FilesNodeName = "files"; + public const string StylesheetsNodeName = "Stylesheets"; + public const string TemplatesNodeName = "Templates"; + public const string NameNodeName = "Name"; + public const string TemplateNodeName = "Template"; + public const string AliasNodeNameSmall = "alias"; + public const string AliasNodeNameCapital = "Alias"; + public const string DictionaryItemsNodeName = "DictionaryItems"; + public const string DictionaryItemNodeName = "DictionaryItem"; + public const string MacrosNodeName = "Macros"; + public const string DocumentSetNodeName = "DocumentSet"; + public const string DocumentTypesNodeName = "DocumentTypes"; + public const string DocumentTypeNodeName = "DocumentType"; + public const string FileNodeName = "file"; + public const string OrgNameNodeName = "orgName"; + public const string OrgPathNodeName = "orgPath"; + public const string GuidNodeName = "guid"; + public const string StylesheetNodeName = "styleSheet"; + public const string MacroNodeName = "macro"; + public const string InfoNodeName = "info"; + public const string PackageRequirementsMajorXpath = "./package/requirements/major"; + public const string PackageRequirementsMinorXpath = "./package/requirements/minor"; + public const string PackageRequirementsPatchXpath = "./package/requirements/patch"; + public const string PackageNameXpath = "./package/name"; + public const string PackageVersionXpath = "./package/version"; + public const string PackageUrlXpath = "./package/url"; + public const string PackageLicenseXpath = "./package/license"; + public const string AuthorNameXpath = "./author/name"; + public const string AuthorWebsiteXpath = "./author/website"; + public const string ReadmeXpath = "./readme"; + public const string ControlNodeName = "control"; + public const string ActionNodeName = "Action"; + public const string ActionsNodeName = "Actions"; + public const string UndoNodeAttribute = "undo"; + public const string RunatNodeAttribute = "runat"; } } } \ No newline at end of file From 48b60961b57d4845a99dfc92584883e5d2d26e92 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 6 Jun 2014 13:59:36 +0200 Subject: [PATCH 027/189] removed unused code --- src/Umbraco.Core/Services/PackagingService.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index cf95a63dcd..f685426d4a 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; -using System.Xml; using System.Xml.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Events; @@ -61,23 +60,6 @@ namespace Umbraco.Core.Services _importedContentTypes = new Dictionary(); } - - //// Temperary constructor while packaging service is not final - //internal PackagingService(IContentService contentService, - // IContentTypeService contentTypeService, - // IMediaService mediaService, - // IMacroService macroService, - // IDataTypeService dataTypeService, - // IFileService fileService, - // ILocalizationService localizationService, - // RepositoryFactory repositoryFactory, - // IDatabaseUnitOfWorkProvider uowProvider, - // IPackageInstallation packageInstallation - // ) : this(contentService, contentTypeService, mediaService, macroService, dataTypeService, fileService, localizationService, repositoryFactory, uowProvider) - //{ - // _packageInstallation = packageInstallation; - //} - #region Content /// From bb71efd4fa5b0b384c27fa30d719e1c481b5102f Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 6 Jun 2014 14:17:32 +0200 Subject: [PATCH 028/189] Move moving classes around --- .../ConflictingPackageContentFinder.cs | 3 ++- .../IConflictingPackageContentFinder.cs | 2 +- src/Umbraco.Core/Services/PackagingService.cs | 6 ++++-- src/Umbraco.Core/Services/ServiceContext.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 4 ++-- 5 files changed, 10 insertions(+), 7 deletions(-) rename src/Umbraco.Core/{Services => Packaging}/ConflictingPackageContentFinder.cs (96%) rename src/Umbraco.Core/{Services => Packaging}/IConflictingPackageContentFinder.cs (88%) diff --git a/src/Umbraco.Core/Services/ConflictingPackageContentFinder.cs b/src/Umbraco.Core/Packaging/ConflictingPackageContentFinder.cs similarity index 96% rename from src/Umbraco.Core/Services/ConflictingPackageContentFinder.cs rename to src/Umbraco.Core/Packaging/ConflictingPackageContentFinder.cs index be2e9a03a0..0188c446d4 100644 --- a/src/Umbraco.Core/Services/ConflictingPackageContentFinder.cs +++ b/src/Umbraco.Core/Packaging/ConflictingPackageContentFinder.cs @@ -2,8 +2,9 @@ using System.Linq; using System.Xml.Linq; using Umbraco.Core.Models; +using Umbraco.Core.Services; -namespace Umbraco.Core.Services +namespace Umbraco.Core.Packaging { public class ConflictingPackageContentFinder : IConflictingPackageContentFinder { diff --git a/src/Umbraco.Core/Services/IConflictingPackageContentFinder.cs b/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs similarity index 88% rename from src/Umbraco.Core/Services/IConflictingPackageContentFinder.cs rename to src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs index 88b6e0dd34..5842e978b4 100644 --- a/src/Umbraco.Core/Services/IConflictingPackageContentFinder.cs +++ b/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs @@ -1,7 +1,7 @@ using System.Xml.Linq; using Umbraco.Core.Models; -namespace Umbraco.Core.Services +namespace Umbraco.Core.Packaging { public interface IConflictingPackageContentFinder { diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index f685426d4a..db7eb357a5 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -1575,14 +1575,16 @@ namespace Umbraco.Core.Services internal IPackageInstallation PackageInstallation { + //NOTE The PackageInstallation class should be passed as IPackageInstallation through the + //constructor (probably as an overload to avoid breaking stuff), so that its extendable. + // NOTE COMMENT: But is is not a service? and all other parced in constructor is services... private get { return _packageInstallation ?? new PackageInstallation(this, _macroService, _fileService, new PackageExtraction()); } set { _packageInstallation = value; } - - } internal InstallationSummary InstallPackage(string packageFilePath, int userId = 0) { + //TODO Add events ? return PackageInstallation.InstallPackage(packageFilePath, userId); } diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index a2bee22c85..c32160f5f0 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Services private Lazy _memberTypeService; private Lazy _memberGroupService; private Lazy _notificationService; - + /// /// public ctor - will generally just be used for unit testing /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5c76d2e0bb..8f665a55ab 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1045,7 +1045,7 @@ - + @@ -1060,7 +1060,7 @@ - + From 07d431f52e5732332ed7d4be834fd081c3e42b36 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 6 Jun 2014 14:25:54 +0200 Subject: [PATCH 029/189] more moving --- .../Netmester.BestPractice.Base_0.0.0.1.umb | Bin 6634 -> 0 bytes .../PackageInstallationTest.cs | 0 .../Packages/Document_Type_Picker_1.1.umb | Bin .../Services/PackagingServiceTests.cs | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 4 ++-- 5 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 src/Umbraco.Tests/Packages/Netmester.BestPractice.Base_0.0.0.1.umb rename src/Umbraco.Tests/{Services => Packaging}/PackageInstallationTest.cs (100%) rename src/Umbraco.Tests/{ => Packaging}/Packages/Document_Type_Picker_1.1.umb (100%) diff --git a/src/Umbraco.Tests/Packages/Netmester.BestPractice.Base_0.0.0.1.umb b/src/Umbraco.Tests/Packages/Netmester.BestPractice.Base_0.0.0.1.umb deleted file mode 100644 index 11610beb1e6957ee18c4121974ecd482d42d3ee7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6634 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 - + @@ -558,6 +558,7 @@ + @@ -701,7 +702,6 @@ - From a7928f87eeca24d04345c551f37b5fa1e70da01f Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 6 Jun 2014 15:58:32 +0200 Subject: [PATCH 030/189] small ajustments --- src/Umbraco.Core/Constants-Packaging.cs | 83 +++++------ .../Packaging/Models/InstallationSummary.cs | 23 ++- .../Packaging/PackageInstallation.cs | 139 +++++++++++------- .../Packaging/PackageInstallationTest.cs | 6 +- 4 files changed, 152 insertions(+), 99 deletions(-) diff --git a/src/Umbraco.Core/Constants-Packaging.cs b/src/Umbraco.Core/Constants-Packaging.cs index 5db3d02532..4e9f294406 100644 --- a/src/Umbraco.Core/Constants-Packaging.cs +++ b/src/Umbraco.Core/Constants-Packaging.cs @@ -7,47 +7,48 @@ /// public static class Packaging { - public const string UmbPackageNodeName = "umbPackage"; - public const string DataTypesNodeName = "DataTypes"; - public const string PackageXmlFileName = "package.xml"; - public const string UmbracoPackageExtention = ".umb"; - public const string DataTypeNodeName = "DataType"; - public const string LanguagesNodeName = "Languages"; - public const string FilesNodeName = "files"; - public const string StylesheetsNodeName = "Stylesheets"; - public const string TemplatesNodeName = "Templates"; - public const string NameNodeName = "Name"; - public const string TemplateNodeName = "Template"; - public const string AliasNodeNameSmall = "alias"; - public const string AliasNodeNameCapital = "Alias"; - public const string DictionaryItemsNodeName = "DictionaryItems"; - public const string DictionaryItemNodeName = "DictionaryItem"; - public const string MacrosNodeName = "Macros"; - public const string DocumentSetNodeName = "DocumentSet"; - public const string DocumentTypesNodeName = "DocumentTypes"; - public const string DocumentTypeNodeName = "DocumentType"; - public const string FileNodeName = "file"; - public const string OrgNameNodeName = "orgName"; - public const string OrgPathNodeName = "orgPath"; - public const string GuidNodeName = "guid"; - public const string StylesheetNodeName = "styleSheet"; - public const string MacroNodeName = "macro"; - public const string InfoNodeName = "info"; - public const string PackageRequirementsMajorXpath = "./package/requirements/major"; - public const string PackageRequirementsMinorXpath = "./package/requirements/minor"; - public const string PackageRequirementsPatchXpath = "./package/requirements/patch"; - public const string PackageNameXpath = "./package/name"; - public const string PackageVersionXpath = "./package/version"; - public const string PackageUrlXpath = "./package/url"; - public const string PackageLicenseXpath = "./package/license"; - public const string AuthorNameXpath = "./author/name"; - public const string AuthorWebsiteXpath = "./author/website"; - public const string ReadmeXpath = "./readme"; - public const string ControlNodeName = "control"; - public const string ActionNodeName = "Action"; - public const string ActionsNodeName = "Actions"; - public const string UndoNodeAttribute = "undo"; - public const string RunatNodeAttribute = "runat"; + public const string UmbPackageNodeName = "umbPackage"; + public const string DataTypesNodeName = "DataTypes"; + public const string PackageXmlFileName = "package.xml"; + public const string UmbracoPackageExtention = ".umb"; + public const string DataTypeNodeName = "DataType"; + public const string LanguagesNodeName = "Languages"; + public const string FilesNodeName = "files"; + public const string StylesheetsNodeName = "Stylesheets"; + public const string TemplatesNodeName = "Templates"; + public const string NameNodeName = "Name"; + public const string TemplateNodeName = "Template"; + public const string AliasNodeNameSmall = "alias"; + public const string AliasNodeNameCapital = "Alias"; + public const string DictionaryItemsNodeName = "DictionaryItems"; + public const string DictionaryItemNodeName = "DictionaryItem"; + public const string MacrosNodeName = "Macros"; + public const string DocumentSetNodeName = "DocumentSet"; + public const string DocumentTypesNodeName = "DocumentTypes"; + public const string DocumentTypeNodeName = "DocumentType"; + public const string FileNodeName = "file"; + public const string OrgNameNodeName = "orgName"; + public const string OrgPathNodeName = "orgPath"; + public const string GuidNodeName = "guid"; + public const string StylesheetNodeName = "styleSheet"; + public const string MacroNodeName = "macro"; + public const string InfoNodeName = "info"; + public const string PackageRequirementsMajorXpath = "./package/requirements/major"; + public const string PackageRequirementsMinorXpath = "./package/requirements/minor"; + public const string PackageRequirementsPatchXpath = "./package/requirements/patch"; + public const string PackageNameXpath = "./package/name"; + public const string PackageVersionXpath = "./package/version"; + public const string PackageUrlXpath = "./package/url"; + public const string PackageLicenseXpath = "./package/license"; + public const string PackageLicenseXpathUrlAttribute = "url"; + public const string AuthorNameXpath = "./author/name"; + public const string AuthorWebsiteXpath = "./author/website"; + public const string ReadmeXpath = "./readme"; + public const string ControlNodeName = "control"; + public const string ActionNodeName = "Action"; + public const string ActionsNodeName = "Actions"; + public const string UndoNodeAttribute = "undo"; + public const string RunatNodeAttribute = "runat"; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs b/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs index c627f9b813..1d136f8716 100644 --- a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs +++ b/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs @@ -1,10 +1,11 @@ using System; -using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Core.Models; namespace Umbraco.Core.Packaging.Models { + + [Serializable] [DataContract(IsReference = true)] internal class InstallationSummary @@ -14,11 +15,29 @@ namespace Umbraco.Core.Packaging.Models public ILanguage[] LanguagesInstalled { get; set; } public IDictionaryItem[] DictionaryItemsInstalled { get; set; } public IMacro[] MacrosInstalled { get; set; } - public IEnumerable> FilesInstalled { get; set; } + public Details[] FilesInstalled { get; set; } public ITemplate[] TemplatesInstalled { get; set; } public IContentType[] DocumentTypesInstalled { get; set; } public IStylesheet[] StylesheetsInstalled { get; set; } public IContent[] DocumentsInstalled { get; set; } public PackageAction[] Actions { get; set; } } + + [Serializable] + [DataContract(IsReference = true)] + public enum InstallStatus + { + Inserted, + Overwridden + } + + + [Serializable] + [DataContract(IsReference = true)] + public class Details + { + public InstallStatus Status { get; set; } + public TItem Destination { get; set; } + public TItem Source { get; set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index 031d477ae7..830a71600e 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -135,29 +135,39 @@ namespace Umbraco.Core.Packaging try { - - return new InstallationSummary - { - MetaData = metaData, - DataTypesInstalled = - dataTypes == null ? new IDataTypeDefinition[0] : InstallDataTypes(dataTypes, userId), - LanguagesInstalled = languages == null ? new ILanguage[0] : InstallLanguages(languages, userId), - DictionaryItemsInstalled = - dictionaryItems == null ? new IDictionaryItem[0] : InstallDictionaryItems(dictionaryItems), - MacrosInstalled = macroes == null ? new IMacro[0] : InstallMacros(macroes, userId), - FilesInstalled = - packageFile == null - ? Enumerable.Empty>() - : InstallFiles(packageFile, files), - TemplatesInstalled = templates == null ? new ITemplate[0] : InstallTemplats(templates, userId), - DocumentTypesInstalled = - documentTypes == null ? new IContentType[0] : InstallDocumentTypes(documentTypes, userId), - StylesheetsInstalled = - styleSheets == null ? new IStylesheet[0] : InstallStylesheets(styleSheets, userId), - DocumentsInstalled = documentSet == null ? new IContent[0] : InstallDocuments(documentSet, userId), - Actions = actions == null ? new PackageAction[0] : GetPackageActions(actions, metaData.Name), + var installationSummary = new InstallationSummary() { MetaData = metaData }; - }; + var dataTypeDefinitions = EmptyArrayIfNull(dataTypes) ?? InstallDataTypes(dataTypes, userId); + installationSummary.DataTypesInstalled = dataTypeDefinitions; + + var languagesInstalled = EmptyArrayIfNull(languages) ?? InstallLanguages(languages, userId); + installationSummary.LanguagesInstalled = languagesInstalled; + + var dictionaryInstalled = EmptyArrayIfNull(dictionaryItems) ?? InstallDictionaryItems(dictionaryItems); + installationSummary.DictionaryItemsInstalled = dictionaryInstalled; + + var macros = EmptyArrayIfNull(macroes)?? InstallMacros(macroes, userId); + installationSummary.MacrosInstalled = macros; + + var keyValuePairs = EmptyArrayIfNull>(packageFile) ?? InstallFiles(packageFile, files); + installationSummary.FilesInstalled = keyValuePairs; + + var templatesInstalled = EmptyArrayIfNull(templates) ?? InstallTemplats(templates, userId); + installationSummary.TemplatesInstalled = templatesInstalled; + + var documentTypesInstalled = EmptyArrayIfNull(documentTypes) ?? InstallDocumentTypes(documentTypes, userId); + installationSummary.DocumentTypesInstalled =documentTypesInstalled; + + var stylesheetsInstalled = EmptyArrayIfNull(styleSheets) ?? InstallStylesheets(styleSheets, userId); + installationSummary.StylesheetsInstalled = stylesheetsInstalled; + + var documentsInstalled = EmptyArrayIfNull(documentSet) ?? InstallDocuments(documentSet, userId); + installationSummary.DocumentsInstalled = documentsInstalled; + + var packageActions = EmptyArrayIfNull(actions) ?? GetPackageActions(actions, metaData.Name); + installationSummary.Actions = packageActions; + + return installationSummary; } catch (Exception e) { @@ -165,7 +175,10 @@ namespace Umbraco.Core.Packaging } } - + private static T[] EmptyArrayIfNull(object obj) + { + return obj == null ? new T[0] : null; + } private XDocument GetConfigXmlDoc(string packageFilePath) @@ -321,14 +334,20 @@ namespace Umbraco.Core.Packaging } - private IEnumerable> InstallFiles(string packageFilePath, XElement filesElement) + private Details[] InstallFiles(string packageFilePath, XElement filesElement) { return ExtractFileInPackageInfos(filesElement).Select(fpi => { bool existingOverrided = _packageExtraction.CopyFileFromArchive(packageFilePath, fpi.FileNameInPackage, fpi.FullPath); - return new KeyValuePair(fpi.FullPath, existingOverrided); + return new Details() + { + Source = fpi.FileNameInPackage, + Destination = fpi.FullPath, + Status = existingOverrided ? InstallStatus.Overwridden : InstallStatus.Inserted + }; + }).ToArray(); } @@ -471,41 +490,57 @@ namespace Umbraco.Core.Packaging "xRootElement"); } - XElement majorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMajorXpath); - XElement minorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMinorXpath); - XElement patchElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsPatchXpath); - XElement nameElement = infoElement.XPathSelectElement(Constants.Packaging.PackageNameXpath); - XElement versionElement = infoElement.XPathSelectElement(Constants.Packaging.PackageVersionXpath); - XElement urlElement = infoElement.XPathSelectElement(Constants.Packaging.PackageUrlXpath); - XElement licenseElement = infoElement.XPathSelectElement(Constants.Packaging.PackageLicenseXpath); - XElement authorNameElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorNameXpath); - XElement authorUrlElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorWebsiteXpath); - XElement readmeElement = infoElement.XPathSelectElement(Constants.Packaging.ReadmeXpath); + var majorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMajorXpath); + var minorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMinorXpath); + var patchElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsPatchXpath); + var nameElement = infoElement.XPathSelectElement(Constants.Packaging.PackageNameXpath); + var versionElement = infoElement.XPathSelectElement(Constants.Packaging.PackageVersionXpath); + var urlElement = infoElement.XPathSelectElement(Constants.Packaging.PackageUrlXpath); + var licenseElement = infoElement.XPathSelectElement(Constants.Packaging.PackageLicenseXpath); + var authorNameElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorNameXpath); + var authorUrlElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorWebsiteXpath); + var readmeElement = infoElement.XPathSelectElement(Constants.Packaging.ReadmeXpath); XElement controlElement = xRootElement.Element(Constants.Packaging.ControlNodeName); - int val; - return new MetaData { - Name = nameElement == null ? string.Empty : nameElement.Value, - Version = versionElement == null ? string.Empty : versionElement.Value, - Url = urlElement == null ? string.Empty : urlElement.Value, - License = licenseElement == null ? string.Empty : licenseElement.Value, - LicenseUrl = - licenseElement == null - ? string.Empty - : licenseElement.HasAttributes ? licenseElement.AttributeValue("url") : string.Empty, - AuthorName = authorNameElement == null ? string.Empty : authorNameElement.Value, - AuthorUrl = authorUrlElement == null ? string.Empty : authorUrlElement.Value, - Readme = readmeElement == null ? string.Empty : readmeElement.Value, - ReqMajor = majorElement == null ? 0 : int.TryParse(majorElement.Value, out val) ? val : 0, - ReqMinor = minorElement == null ? 0 : int.TryParse(minorElement.Value, out val) ? val : 0, - ReqPatch = patchElement == null ? 0 : int.TryParse(patchElement.Value, out val) ? val : 0, - Control = controlElement == null ? string.Empty : controlElement.Value + Name = StringValue(nameElement), + Version = StringValue(versionElement), + Url = StringValue(urlElement), + License = StringValue(licenseElement), + LicenseUrl = StringAttribute(licenseElement, Constants.Packaging.PackageLicenseXpathUrlAttribute), + AuthorName = StringValue(authorNameElement), + AuthorUrl = StringValue(authorUrlElement), + Readme = StringValue(readmeElement), + Control = StringValue(controlElement), + ReqMajor = IntValue(majorElement), + ReqMinor = IntValue(minorElement), + ReqPatch = IntValue(patchElement), + }; } + private static string StringValue(XElement xElement, string defaultValue = "") + { + return xElement == null ? defaultValue : xElement.Value; + } + + private static string StringAttribute(XElement xElement, string attribute, string defaultValue = "") + { + return xElement == null + ? defaultValue + : xElement.HasAttributes ? xElement.AttributeValue(attribute) : defaultValue; + } + + + private static int IntValue(XElement xElement, int defaultValue = 0) + { + int val; + return xElement == null ? defaultValue : int.TryParse(xElement.Value, out val) ? val : defaultValue; + } + + private static string UpdatePathPlaceholders(string path) { if (path.Contains("[$")) diff --git a/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs b/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs index bebeab66df..a0a5f9f5da 100644 --- a/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs +++ b/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Packaging; using Umbraco.Core.Packaging.Models; using Umbraco.Core.Services; -namespace Umbraco.Tests.Services +namespace Umbraco.Tests.Packaging { [TestFixture] public class PackageInstallationTest @@ -56,8 +56,6 @@ namespace Umbraco.Tests.Services string test; packageExtraction.Setup(a => a.ReadTextFileFromArchive(pagePath, Constants.Packaging.PackageXmlFileName, out test)).Returns(Xml); - - var fileService = new Mock(); var macroService = new Mock(); var packagingService = new Mock(); @@ -69,7 +67,7 @@ namespace Umbraco.Tests.Services // Assert Assert.IsNotNull(installationSummary); - Assert.Inconclusive("Lots of more tests can be written"); + //Assert.Inconclusive("Lots of more tests can be written"); } } From 6d77121c056c10adfb693fa28767759d7632953e Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 6 Jun 2014 16:07:46 +0200 Subject: [PATCH 031/189] Test for support stylesheets --- src/Umbraco.Core/Packaging/PackageInstallation.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index 830a71600e..b5e288f0cd 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -114,6 +114,7 @@ namespace Umbraco.Core.Packaging try { XElement rootElement = GetConfigXmlElement(packageFile); + PackageSupportedCheck(rootElement); PackageStructureSanetyCheck(packageFile, rootElement); dataTypes = rootElement.Element(Constants.Packaging.DataTypesNodeName); languages = rootElement.Element(Constants.Packaging.LanguagesNodeName); @@ -175,6 +176,18 @@ namespace Umbraco.Core.Packaging } } + /// + /// Temperary check to test that we support stylesheets + /// + /// + private void PackageSupportedCheck(XElement rootElement) + { + XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); + if (styleSheets != null && styleSheets.Elements().Any()) + throw new NotSupportedException("Stylesheets is not suported in this version of umbraco"); + + } + private static T[] EmptyArrayIfNull(object obj) { return obj == null ? new T[0] : null; From 697d4e3b232c59c72a595d355302f58705142cf8 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 6 Jun 2014 16:14:44 +0200 Subject: [PATCH 032/189] more logical code --- src/Umbraco.Core/Packaging/PackageInstallation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index b5e288f0cd..f15ddfd790 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -110,6 +110,7 @@ namespace Umbraco.Core.Packaging XElement documentSet; XElement actions; MetaData metaData; + InstallationSummary installationSummary; try { @@ -128,6 +129,7 @@ namespace Umbraco.Core.Packaging actions = rootElement.Element(Constants.Packaging.ActionsNodeName); metaData = GetMetaData(rootElement); + installationSummary = new InstallationSummary() { MetaData = metaData }; } catch (Exception e) { @@ -136,8 +138,6 @@ namespace Umbraco.Core.Packaging try { - var installationSummary = new InstallationSummary() { MetaData = metaData }; - var dataTypeDefinitions = EmptyArrayIfNull(dataTypes) ?? InstallDataTypes(dataTypes, userId); installationSummary.DataTypesInstalled = dataTypeDefinitions; From eef7ce45d3102595d5a2918e3c2de17d8d377c82 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Fri, 6 Jun 2014 16:21:37 +0200 Subject: [PATCH 033/189] cleanup --- src/Umbraco.Core/Packaging/IPackageInstallation.cs | 3 +-- src/Umbraco.Tests/MockTests.cs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Packaging/IPackageInstallation.cs b/src/Umbraco.Core/Packaging/IPackageInstallation.cs index 6c4bfb4349..720ee3393b 100644 --- a/src/Umbraco.Core/Packaging/IPackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/IPackageInstallation.cs @@ -1,9 +1,8 @@ using Umbraco.Core.Packaging.Models; -using Umbraco.Core.Services; namespace Umbraco.Core.Packaging { - internal interface IPackageInstallation : IService + internal interface IPackageInstallation { InstallationSummary InstallPackage(string packageFilePath, int userId); MetaData GetMetaData(string packageFilePath); diff --git a/src/Umbraco.Tests/MockTests.cs b/src/Umbraco.Tests/MockTests.cs index 7cea990653..f8810fe67f 100644 --- a/src/Umbraco.Tests/MockTests.cs +++ b/src/Umbraco.Tests/MockTests.cs @@ -5,7 +5,6 @@ using System.Text; using System.Web; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Packaging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Services; From dfb08b9ec773784a0260beb0b66a2c3eae880c00 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Tue, 10 Jun 2014 08:47:41 +0200 Subject: [PATCH 034/189] Removed IStyleSheet --- src/Umbraco.Core/Models/IStyleSheet.cs | 12 ------------ src/Umbraco.Core/Models/Stylesheet.cs | 2 +- .../Packaging/ConflictingPackageContentFinder.cs | 4 ++-- .../Packaging/IConflictingPackageContentFinder.cs | 2 +- .../Packaging/Models/InstallationSummary.cs | 2 +- .../Packaging/Models/PreInstallWarnings.cs | 2 +- src/Umbraco.Core/Packaging/PackageInstallation.cs | 6 +++--- src/Umbraco.Core/Services/IPackagingService.cs | 2 +- src/Umbraco.Core/Services/PackagingService.cs | 12 ++++++------ src/Umbraco.Core/Umbraco.Core.csproj | 1 - 10 files changed, 16 insertions(+), 29 deletions(-) delete mode 100644 src/Umbraco.Core/Models/IStyleSheet.cs diff --git a/src/Umbraco.Core/Models/IStyleSheet.cs b/src/Umbraco.Core/Models/IStyleSheet.cs deleted file mode 100644 index 08e10aedea..0000000000 --- a/src/Umbraco.Core/Models/IStyleSheet.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Core.Models -{ - public interface IStylesheet : IFile - { - } -} diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs index 7c92b1b9a9..2bed225361 100644 --- a/src/Umbraco.Core/Models/Stylesheet.cs +++ b/src/Umbraco.Core/Models/Stylesheet.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class Stylesheet : File, IStylesheet + public class Stylesheet : File { public Stylesheet(string path) : base(path) { diff --git a/src/Umbraco.Core/Packaging/ConflictingPackageContentFinder.cs b/src/Umbraco.Core/Packaging/ConflictingPackageContentFinder.cs index 0188c446d4..f0b510ba21 100644 --- a/src/Umbraco.Core/Packaging/ConflictingPackageContentFinder.cs +++ b/src/Umbraco.Core/Packaging/ConflictingPackageContentFinder.cs @@ -21,7 +21,7 @@ namespace Umbraco.Core.Packaging } - public IStylesheet[] FindConflictingStylesheets(XElement stylesheetNotes) + public IFile[] FindConflictingStylesheets(XElement stylesheetNotes) { if (string.Equals(Constants.Packaging.StylesheetsNodeName, stylesheetNotes.Name.LocalName) == false) { @@ -38,7 +38,7 @@ namespace Umbraco.Core.Packaging "stylesheetNotes"); } - return _fileService.GetStylesheetByName(xElement.Value) as IStylesheet; + return _fileService.GetStylesheetByName(xElement.Value) as IFile; }) .Where(v => v != null).ToArray(); } diff --git a/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs b/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs index 5842e978b4..ce528b56e3 100644 --- a/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs +++ b/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Packaging public interface IConflictingPackageContentFinder { - IStylesheet[] FindConflictingStylesheets(XElement stylesheetNotes); + IFile[] FindConflictingStylesheets(XElement stylesheetNotes); ITemplate[] FindConflictingTemplates(XElement templateNotes); IMacro[] FindConflictingMacros(XElement macroNodes); } diff --git a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs b/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs index 1d136f8716..198218f70a 100644 --- a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs +++ b/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Packaging.Models public Details[] FilesInstalled { get; set; } public ITemplate[] TemplatesInstalled { get; set; } public IContentType[] DocumentTypesInstalled { get; set; } - public IStylesheet[] StylesheetsInstalled { get; set; } + public IFile[] StylesheetsInstalled { get; set; } public IContent[] DocumentsInstalled { get; set; } public PackageAction[] Actions { get; set; } } diff --git a/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs b/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs index e64f564a2c..5953616e20 100644 --- a/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs +++ b/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs @@ -7,6 +7,6 @@ namespace Umbraco.Core.Packaging.Models public IFileInPackageInfo[] UnsecureFiles { get; set; } public IMacro[] ConflictingMacroAliases { get; set; } public ITemplate[] ConflictingTemplateAliases { get; set; } - public IStylesheet[] ConflictingStylesheetNames { get; set; } + public IFile[] ConflictingStylesheetNames { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index f15ddfd790..deaf43416b 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -159,7 +159,7 @@ namespace Umbraco.Core.Packaging var documentTypesInstalled = EmptyArrayIfNull(documentTypes) ?? InstallDocumentTypes(documentTypes, userId); installationSummary.DocumentTypesInstalled =documentTypesInstalled; - var stylesheetsInstalled = EmptyArrayIfNull(styleSheets) ?? InstallStylesheets(styleSheets, userId); + var stylesheetsInstalled = EmptyArrayIfNull(styleSheets) ?? InstallStylesheets(styleSheets, userId); installationSummary.StylesheetsInstalled = stylesheetsInstalled; var documentsInstalled = EmptyArrayIfNull(documentSet) ?? InstallDocuments(documentSet, userId); @@ -312,7 +312,7 @@ namespace Umbraco.Core.Packaging return _packagingService.ImportContent(documentsElement, -1, userId).ToArray(); } - private IStylesheet[] InstallStylesheets(XElement styleSheetsElement, int userId = 0) + private IFile[] InstallStylesheets(XElement styleSheetsElement, int userId = 0) { if (string.Equals(Constants.Packaging.StylesheetsNodeName, styleSheetsElement.Name.LocalName) == false) { @@ -419,7 +419,7 @@ namespace Umbraco.Core.Packaging ConflictingTemplateAliases = templates == null ? new ITemplate[0] : ConflictingPackageContentFinder.FindConflictingTemplates(templates), ConflictingStylesheetNames = - styleSheets == null ? new IStylesheet[0] : ConflictingPackageContentFinder.FindConflictingStylesheets(styleSheets) + styleSheets == null ? new IFile[0] : ConflictingPackageContentFinder.FindConflictingStylesheets(styleSheets) }; return conflictingPackageContent; diff --git a/src/Umbraco.Core/Services/IPackagingService.cs b/src/Umbraco.Core/Services/IPackagingService.cs index 97f11d8cd3..2c274724fa 100644 --- a/src/Umbraco.Core/Services/IPackagingService.cs +++ b/src/Umbraco.Core/Services/IPackagingService.cs @@ -86,7 +86,7 @@ namespace Umbraco.Core.Services /// Optional id of the User performing the operation. Default is zero (admin) /// Optional parameter indicating whether or not to raise events /// An enumerable list of generated stylesheets - IEnumerable ImportStylesheets(XElement element, int userId = 0, bool raiseEvents = true); + IEnumerable ImportStylesheets(XElement element, int userId = 0, bool raiseEvents = true); /// /// Exports an to xml as an diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index db7eb357a5..a05de1d1f4 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -1475,23 +1475,23 @@ namespace Umbraco.Core.Services } - public IEnumerable ImportStylesheets(XElement element, int userId = 0, bool raiseEvents = true) + public IEnumerable ImportStylesheets(XElement element, int userId = 0, bool raiseEvents = true) { if (raiseEvents) { - if (ImportingStylesheets.IsRaisedEventCancelled(new ImportEventArgs(element), this)) - return Enumerable.Empty(); + if (ImportingStylesheets.IsRaisedEventCancelled(new ImportEventArgs(element), this)) + return Enumerable.Empty(); } - IEnumerable styleSheets = Enumerable.Empty(); + IEnumerable styleSheets = Enumerable.Empty(); if(element.Elements().Any()) throw new NotImplementedException("This needs to be implimentet"); if (raiseEvents) - ImportingStylesheets.RaiseEvent(new ImportEventArgs(styleSheets, element, false), this); + ImportingStylesheets.RaiseEvent(new ImportEventArgs(styleSheets, element, false), this); return styleSheets; @@ -1740,7 +1740,7 @@ namespace Umbraco.Core.Services /// /// Occurs before Importing Stylesheets /// - public static event TypedEventHandler> ImportingStylesheets; + public static event TypedEventHandler> ImportingStylesheets; /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8f665a55ab..8397f8a63a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -350,7 +350,6 @@ - From 3c6bf1ce92919ae7d84ae29e290c6aff8792fc89 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Mon, 16 Jun 2014 08:52:38 +0200 Subject: [PATCH 035/189] Removed some complex objects that didn't need to be complex --- .../IConflictingPackageContentFinder.cs | 1 - .../Packaging/IPackageExtraction.cs | 17 +- .../Packaging/Models/InstallationSummary.cs | 26 +-- .../Packaging/Models/PreInstallWarnings.cs | 9 +- .../Packaging/PackageBinaryByteInspector.cs | 219 ++++++++++++++++++ .../Packaging/PackageExtraction.cs | 79 +++++-- .../Packaging/PackageInstallation.cs | 134 ++++++----- src/Umbraco.Core/Umbraco.Core.csproj | 1 + 8 files changed, 380 insertions(+), 106 deletions(-) create mode 100644 src/Umbraco.Core/Packaging/PackageBinaryByteInspector.cs diff --git a/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs b/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs index ce528b56e3..e9690ab672 100644 --- a/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs +++ b/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs @@ -5,7 +5,6 @@ namespace Umbraco.Core.Packaging { public interface IConflictingPackageContentFinder { - IFile[] FindConflictingStylesheets(XElement stylesheetNotes); ITemplate[] FindConflictingTemplates(XElement templateNotes); IMacro[] FindConflictingMacros(XElement macroNodes); diff --git a/src/Umbraco.Core/Packaging/IPackageExtraction.cs b/src/Umbraco.Core/Packaging/IPackageExtraction.cs index efe2949356..da0e718779 100644 --- a/src/Umbraco.Core/Packaging/IPackageExtraction.cs +++ b/src/Umbraco.Core/Packaging/IPackageExtraction.cs @@ -27,7 +27,14 @@ namespace Umbraco.Core.Packaging /// filename of the file to copy /// destination path (including destination filename) /// True a file was overwritten - bool CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath); + void CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath); + + /// + /// Copies a file from package to given destination + /// + /// Full path to the ubraco package file + /// Key: Source file in package. Value: Destination path inclusive file name + void CopyFilesFromArchive(string packageFilePath, IEnumerable> sourceDestination); /// /// Check if given list of files can be found in the package @@ -44,5 +51,13 @@ namespace Umbraco.Core.Packaging /// Full path to the umbraco package file /// list of files that are found more than ones (accross directories) in the package IEnumerable FindDubletFileNames(string packageFilePath); + + /// + /// Reads the given files from archive and returns them as a collection of byte arrays + /// + /// + /// + /// + IEnumerable ReadFilesFromArchive(string packageFilePath, IEnumerable filesToGet); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs b/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs index 198218f70a..ab501eaaf0 100644 --- a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs +++ b/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs @@ -4,8 +4,6 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Packaging.Models { - - [Serializable] [DataContract(IsReference = true)] internal class InstallationSummary @@ -15,29 +13,11 @@ namespace Umbraco.Core.Packaging.Models public ILanguage[] LanguagesInstalled { get; set; } public IDictionaryItem[] DictionaryItemsInstalled { get; set; } public IMacro[] MacrosInstalled { get; set; } - public Details[] FilesInstalled { get; set; } + public string[] FilesInstalled { get; set; } public ITemplate[] TemplatesInstalled { get; set; } - public IContentType[] DocumentTypesInstalled { get; set; } + public IContentType[] ContentTypesInstalled { get; set; } public IFile[] StylesheetsInstalled { get; set; } - public IContent[] DocumentsInstalled { get; set; } + public IContent[] ContentInstalled { get; set; } public PackageAction[] Actions { get; set; } } - - [Serializable] - [DataContract(IsReference = true)] - public enum InstallStatus - { - Inserted, - Overwridden - } - - - [Serializable] - [DataContract(IsReference = true)] - public class Details - { - public InstallStatus Status { get; set; } - public TItem Destination { get; set; } - public TItem Source { get; set; } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs b/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs index 5953616e20..3dbe1f3e0a 100644 --- a/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs +++ b/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs @@ -1,12 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; using Umbraco.Core.Models; namespace Umbraco.Core.Packaging.Models { + [Serializable] + [DataContract(IsReference = true)] public class PreInstallWarnings { - public IFileInPackageInfo[] UnsecureFiles { get; set; } + public KeyValuePair[] UnsecureFiles { get; set; } + public KeyValuePair[] FilesReplaced { get; set; } public IMacro[] ConflictingMacroAliases { get; set; } public ITemplate[] ConflictingTemplateAliases { get; set; } public IFile[] ConflictingStylesheetNames { get; set; } + public string[] AssembliesWithLegacyPropertyEditors { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageBinaryByteInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryByteInspector.cs new file mode 100644 index 0000000000..010e3a8521 --- /dev/null +++ b/src/Umbraco.Core/Packaging/PackageBinaryByteInspector.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Packaging +{ + internal class PackageBinaryByteInspector : MarshalByRefObject + { + /// + /// Entry point to call from your code + /// + /// + /// + /// + /// + /// + /// Will perform the assembly scan in a separate app domain + /// + public static IEnumerable ScanAssembliesForTypeReference(IEnumerable assemblys, out string[] errorReport) + { + var appDomain = GetTempAppDomain(); + var type = typeof(PackageBinaryByteInspector); + try + { + var value = (PackageBinaryByteInspector)appDomain.CreateInstanceAndUnwrap( + type.Assembly.FullName, + type.FullName); + var result = value.PerformScan(assemblys.ToArray(), out errorReport); + return result; + } + finally + { + AppDomain.Unload(appDomain); + } + } + + /// + /// Performs the assembly scanning + /// + /// + /// + /// + /// + /// + /// This method is executed in a separate app domain + /// + internal IEnumerable PerformScan(IEnumerable assemblies, out string[] errorReport) + { + var dllsWithReference = new List(); + var errors = new List(); + var assembliesWithErrors = new List(); + + //we need this handler to resolve assembly dependencies below + AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (s, e) => + { + var a = Assembly.ReflectionOnlyLoad(e.Name); + if (a == null) throw new TypeLoadException("Could not load assembly " + e.Name); + return a; + }; + + //First load each dll file into the context + var loaded = assemblies.Select(Assembly.ReflectionOnlyLoad).ToList(); + + //load each of the LoadFrom assemblies into the Load context too + foreach (var a in loaded) + { + Assembly.ReflectionOnlyLoad(a.FullName); + } + + //get the list of assembly names to compare below + var loadedNames = loaded.Select(x => x.GetName().Name).ToArray(); + + //Then load each referenced assembly into the context + foreach (var a in loaded) + { + //don't load any referenced assemblies that are already found in the loaded array - this is based on name + // regardless of version. We'll assume that if the assembly found in the folder matches the assembly name + // being looked for, that is the version the user has shipped their package with and therefore it 'must' be correct + foreach (var assemblyName in a.GetReferencedAssemblies().Where(ass => loadedNames.Contains(ass.Name) == false)) + { + try + { + Assembly.ReflectionOnlyLoad(assemblyName.FullName); + } + catch (FileNotFoundException) + { + //if an exception occurs it means that a referenced assembly could not be found + errors.Add( + string.Concat("This package references the assembly '", + assemblyName.Name, + "' which was not found")); + assembliesWithErrors.Add(a); + } + catch (Exception ex) + { + //if an exception occurs it means that a referenced assembly could not be found + errors.Add( + string.Concat("This package could not be verified for compatibility. An error occurred while loading a referenced assembly '", + assemblyName.Name, + "' see error log for full details.")); + assembliesWithErrors.Add(a); + LogHelper.Error("An error occurred scanning package assemblies", ex); + } + } + } + + var contractType = GetLoadFromContractType(); + + //now that we have all referenced types into the context we can look up stuff + foreach (var a in loaded.Except(assembliesWithErrors)) + { + //now we need to see if they contain any type 'T' + var reflectedAssembly = a; + + try + { + var found = reflectedAssembly.GetExportedTypes() + .Where(contractType.IsAssignableFrom); + + if (found.Any()) + { + dllsWithReference.Add(reflectedAssembly.FullName); + } + } + catch (Exception ex) + { + //This is a hack that nobody can seem to get around, I've read everything and it seems that + // this is quite a common thing when loading types into reflection only load context, so + // we're just going to ignore this specific one for now + var typeLoadEx = ex as TypeLoadException; + if (typeLoadEx != null) + { + if (typeLoadEx.Message.InvariantContains("does not have an implementation")) + { + //ignore + continue; + } + } + else + { + errors.Add( + string.Concat("This package could not be verified for compatibility. An error occurred while scanning a packaged assembly '", + a.GetName().Name, + "' see error log for full details.")); + assembliesWithErrors.Add(a); + LogHelper.Error("An error occurred scanning package assemblies", ex); + } + } + + } + + errorReport = errors.ToArray(); + return dllsWithReference; + } + + + /// + /// In order to compare types, the types must be in the same context, this method will return the type that + /// we are checking against but from the Load context. + /// + /// + /// + private static Type GetLoadFromContractType() + { + var contractAssemblyLoadFrom =Assembly.ReflectionOnlyLoad(typeof (T).Assembly.FullName); + + var contractType = contractAssemblyLoadFrom.GetExportedTypes() + .FirstOrDefault(x => x.FullName == typeof(T).FullName && x.Assembly.FullName == typeof(T).Assembly.FullName); + + if (contractType == null) + { + throw new InvalidOperationException("Could not find type " + typeof(T) + " in the LoadFrom assemblies"); + } + return contractType; + } + + /// + /// Create an app domain + /// + /// + private static AppDomain GetTempAppDomain() + { + //copy the current app domain setup but don't shadow copy files + var appName = "TempDomain" + Guid.NewGuid(); + var domainSetup = new AppDomainSetup + { + ApplicationName = appName, + ShadowCopyFiles = "false", + ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase, + ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, + DynamicBase = AppDomain.CurrentDomain.SetupInformation.DynamicBase, + LicenseFile = AppDomain.CurrentDomain.SetupInformation.LicenseFile, + LoaderOptimization = AppDomain.CurrentDomain.SetupInformation.LoaderOptimization, + PrivateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath, + PrivateBinPathProbe = AppDomain.CurrentDomain.SetupInformation.PrivateBinPathProbe + }; + + //create new domain with full trust + return AppDomain.CreateDomain( + appName, + AppDomain.CurrentDomain.Evidence, + domainSetup, + new PermissionSet(PermissionState.Unrestricted)); + } + + private static string GetAssemblyPath(Assembly a) + { + var codeBase = a.CodeBase; + var uri = new Uri(codeBase); + return uri.LocalPath; + } + + } +} diff --git a/src/Umbraco.Core/Packaging/PackageExtraction.cs b/src/Umbraco.Core/Packaging/PackageExtraction.cs index ea59ac671f..6550fd435d 100644 --- a/src/Umbraco.Core/Packaging/PackageExtraction.cs +++ b/src/Umbraco.Core/Packaging/PackageExtraction.cs @@ -67,43 +67,41 @@ namespace Umbraco.Core.Packaging } } + - public bool CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath) + public void CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath) { - bool fileFoundInArchive = false; - bool fileOverwritten = false; + CopyFilesFromArchive(packageFilePath, new[]{new KeyValuePair(fileInPackageName, destinationfilePath) } ); + } + + public void CopyFilesFromArchive(string packageFilePath, IEnumerable> sourceDestination) + { + var d = sourceDestination.ToDictionary(k => k.Key.ToLower(), v => v.Value); + ReadZipfileEntries(packageFilePath, (entry, stream) => { - string fileName = Path.GetFileName(entry.Name); + string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower(); + if (fileName == string.Empty) { return true; } - if (string.IsNullOrEmpty(fileName) == false && - fileName.Equals(fileInPackageName, StringComparison.InvariantCultureIgnoreCase)) + string destination; + if (string.IsNullOrEmpty(fileName) == false && d.TryGetValue(fileName, out destination)) { - fileFoundInArchive = true; - - fileOverwritten = File.Exists(destinationfilePath); - - using (var streamWriter = File.Open(destinationfilePath, FileMode.Create)) + using (var streamWriter = File.Open(destination, FileMode.Create)) { - var data = new byte[2048]; - int size; - while ((size = stream.Read(data, 0, data.Length)) > 0) - { - streamWriter.Write(data, 0, size); - } - - streamWriter.Close(); + stream.CopyTo(streamWriter); } - return false; + + d.Remove(fileName); + return d.Any(); } return true; }); - - if (fileFoundInArchive == false) throw new ArgumentException(string.Format("Could not find file: {0} in package file: {1}", fileInPackageName, packageFilePath), "fileInPackageName"); - - return fileOverwritten; + if (d.Any()) + { + throw new ArgumentException(string.Format("The following source file(s): \"{0}\" could not be found in archive: \"{1}\"", string.Join("\", \"",d.Keys), packageFilePath)); + } } public IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles) @@ -148,6 +146,39 @@ namespace Umbraco.Core.Packaging return dictionary.Values.Where(v => v.Count > 1).SelectMany(v => v); } + public IEnumerable ReadFilesFromArchive(string packageFilePath, IEnumerable filesToGet) + { + CheckPackageExists(packageFilePath); + + var files = new HashSet(filesToGet.Select(f => f.ToLower())); + + using (var fs = File.OpenRead(packageFilePath)) + { + using (var zipInputStream = new ZipInputStream(fs)) + { + ZipEntry zipEntry; + while ((zipEntry = zipInputStream.GetNextEntry()) != null) + { + + if (zipEntry.IsDirectory) continue; + + if (files.Contains(zipEntry.Name)) + { + using (var memStream = new MemoryStream()) + { + zipInputStream.CopyTo(memStream); + yield return memStream.ToArray(); + memStream.Close(); + } + } + } + + zipInputStream.Close(); + } + fs.Close(); + } + } + private void ReadZipfileEntries(string packageFilePath, Func entryFunc, bool skipsDirectories = true) { CheckPackageExists(packageFilePath); diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index deaf43416b..5b91e76a22 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -9,8 +9,10 @@ using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Packaging.Models; -using Umbraco.Core.Services; - +using Umbraco.Core.Services; +using umbraco.interfaces; +using File = System.IO.File; + namespace Umbraco.Core.Packaging { internal class PackageInstallation : IPackageInstallation @@ -89,7 +91,7 @@ namespace Umbraco.Core.Packaging try { XElement rootElement = GetConfigXmlElement(packageFilePath); - return GetPreInstallWarnings(rootElement); + return GetPreInstallWarnings(packageFilePath, rootElement); } catch (Exception e) { @@ -150,20 +152,20 @@ namespace Umbraco.Core.Packaging var macros = EmptyArrayIfNull(macroes)?? InstallMacros(macroes, userId); installationSummary.MacrosInstalled = macros; - var keyValuePairs = EmptyArrayIfNull>(packageFile) ?? InstallFiles(packageFile, files); + var keyValuePairs = EmptyArrayIfNull(packageFile) ?? InstallFiles(packageFile, files); installationSummary.FilesInstalled = keyValuePairs; var templatesInstalled = EmptyArrayIfNull(templates) ?? InstallTemplats(templates, userId); installationSummary.TemplatesInstalled = templatesInstalled; var documentTypesInstalled = EmptyArrayIfNull(documentTypes) ?? InstallDocumentTypes(documentTypes, userId); - installationSummary.DocumentTypesInstalled =documentTypesInstalled; + installationSummary.ContentTypesInstalled =documentTypesInstalled; var stylesheetsInstalled = EmptyArrayIfNull(styleSheets) ?? InstallStylesheets(styleSheets, userId); installationSummary.StylesheetsInstalled = stylesheetsInstalled; var documentsInstalled = EmptyArrayIfNull(documentSet) ?? InstallDocuments(documentSet, userId); - installationSummary.DocumentsInstalled = documentsInstalled; + installationSummary.ContentInstalled = documentsInstalled; var packageActions = EmptyArrayIfNull(actions) ?? GetPackageActions(actions, metaData.Name); installationSummary.Actions = packageActions; @@ -228,12 +230,9 @@ namespace Umbraco.Core.Packaging XElement filesElement = rootElement.Element(Constants.Packaging.FilesNodeName); if (filesElement != null) { - IEnumerable extractFileInPackageInfos = - ExtractFileInPackageInfos(filesElement).ToArray(); + var sourceDestination = ExtractSourceDestinationFileInformation(filesElement).ToArray(); - IEnumerable missingFiles = - _packageExtraction.FindMissingFiles(packageFilePath, - extractFileInPackageInfos.Select(i => i.FileNameInPackage)).ToArray(); + var missingFiles = _packageExtraction.FindMissingFiles(packageFilePath, sourceDestination.Select(i => i.Key)).ToArray(); if (missingFiles.Any()) { @@ -241,14 +240,14 @@ namespace Umbraco.Core.Packaging string.Join(", ", missingFiles.Select( mf => { - FileInPackageInfo fileInPackageInfo = - extractFileInPackageInfos.Single(fi => fi.FileNameInPackage == mf); - return string.Format("Guid: \"{0}\" Original File: \"{1}\"", - fileInPackageInfo.FileNameInPackage, fileInPackageInfo.RelativePath); + var sd = sourceDestination.Single(fi => fi.Key == mf); + return string.Format("source: \"{0}\" destination: \"{1}\"", + sd.Key, sd.Value); }))); } IEnumerable dubletFileNames = _packageExtraction.FindDubletFileNames(packageFilePath).ToArray(); + if (dubletFileNames.Any()) { throw new Exception("The following filename(s) are found more than one time in the package, since the filename is used ad primary key, this is not allowed: " + @@ -347,21 +346,21 @@ namespace Umbraco.Core.Packaging } - private Details[] InstallFiles(string packageFilePath, XElement filesElement) + private string[] InstallFiles(string packageFilePath, XElement filesElement) { - return ExtractFileInPackageInfos(filesElement).Select(fpi => - { - bool existingOverrided = _packageExtraction.CopyFileFromArchive(packageFilePath, fpi.FileNameInPackage, - fpi.FullPath); + var sourceDestination = ExtractSourceDestinationFileInformation(filesElement); + sourceDestination = AppendRootToDestination(FullpathToRoot, sourceDestination); - return new Details() - { - Source = fpi.FileNameInPackage, - Destination = fpi.FullPath, - Status = existingOverrided ? InstallStatus.Overwridden : InstallStatus.Inserted - }; + _packageExtraction.CopyFilesFromArchive(packageFilePath, sourceDestination); + + return sourceDestination.Select(sd => sd.Value).ToArray(); + } - }).ToArray(); + private KeyValuePair[] AppendRootToDestination(string fullpathToRoot, IEnumerable> sourceDestination) + { + return + sourceDestination.Select( + sd => new KeyValuePair(sd.Key, Path.Combine(fullpathToRoot, sd.Value))).ToArray(); } private IMacro[] InstallMacros(XElement macroElements, int userId = 0) @@ -406,45 +405,70 @@ namespace Umbraco.Core.Packaging return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId).ToArray(); } - private PreInstallWarnings GetPreInstallWarnings(XElement rootElement) + private PreInstallWarnings GetPreInstallWarnings(string packagePath, XElement rootElement) { XElement files = rootElement.Element(Constants.Packaging.FilesNodeName); XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); XElement templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); XElement alias = rootElement.Element(Constants.Packaging.MacrosNodeName); - var conflictingPackageContent = new PreInstallWarnings - { - UnsecureFiles = files == null ? new IFileInPackageInfo[0] : FindUnsecureFiles(files), - ConflictingMacroAliases = alias == null ? new IMacro[0] : ConflictingPackageContentFinder.FindConflictingMacros(alias), - ConflictingTemplateAliases = - templates == null ? new ITemplate[0] : ConflictingPackageContentFinder.FindConflictingTemplates(templates), - ConflictingStylesheetNames = - styleSheets == null ? new IFile[0] : ConflictingPackageContentFinder.FindConflictingStylesheets(styleSheets) - }; + + var sourceDestination = EmptyArrayIfNull>(files) ?? ExtractSourceDestinationFileInformation(files); - return conflictingPackageContent; + var installWarnings = new PreInstallWarnings(); + + var macroAliases = EmptyArrayIfNull(alias) ?? ConflictingPackageContentFinder.FindConflictingMacros(alias); + installWarnings.ConflictingMacroAliases = macroAliases; + + var templateAliases = EmptyArrayIfNull(templates) ?? ConflictingPackageContentFinder.FindConflictingTemplates(templates); + installWarnings.ConflictingTemplateAliases = templateAliases; + + var stylesheetNames = EmptyArrayIfNull(styleSheets) ?? ConflictingPackageContentFinder.FindConflictingStylesheets(styleSheets); + installWarnings.ConflictingStylesheetNames = stylesheetNames; + + installWarnings.UnsecureFiles = FindUnsecureFiles(sourceDestination); + installWarnings.FilesReplaced = FindFilesToBeReplaced(sourceDestination); + installWarnings.AssembliesWithLegacyPropertyEditors = FindLegacyPropertyEditors(packagePath, sourceDestination); + + return installWarnings; } - private IFileInPackageInfo[] FindUnsecureFiles(XElement fileElement) + private KeyValuePair[] FindFilesToBeReplaced(IEnumerable> sourceDestination) { - return ExtractFileInPackageInfos(fileElement) - .Where(IsFileNodeUnsecure).Cast().ToArray(); + return sourceDestination.Where(sd => File.Exists(Path.Combine(FullpathToRoot, sd.Value))).ToArray(); } - private bool IsFileNodeUnsecure(FileInPackageInfo fileInPackageInfo) + private string[] FindLegacyPropertyEditors(string packagePath, IEnumerable> sourceDestinationPair) { + var dlls = sourceDestinationPair.Where( + sd => (Path.GetExtension(sd.Value) ?? string.Empty).Equals(".dll", StringComparison.InvariantCultureIgnoreCase)).Select(sd => sd.Key).ToArray(); - // Should be done with regex :) - if (fileInPackageInfo.Directory.ToLower().Contains(IOHelper.DirSepChar + "app_code")) return true; - if (fileInPackageInfo.Directory.ToLower().Contains(IOHelper.DirSepChar + "bin")) return true; + if (dlls.Any() == false) { return new string[0]; } + + + // Now we want to see if the DLLs contain any legacy data types since we want to warn people about that + string[] assemblyErrors; + IEnumerable assemblyesToScan =_packageExtraction.ReadFilesFromArchive(packagePath, dlls); + return PackageBinaryByteInspector.ScanAssembliesForTypeReference(assemblyesToScan, out assemblyErrors).ToArray(); + + } - string extension = Path.GetExtension(fileInPackageInfo.Directory); + private KeyValuePair[] FindUnsecureFiles(IEnumerable> sourceDestinationPair) + { + return sourceDestinationPair.Where(sd => IsFileDestinationUnsecure(sd.Value)).ToArray(); + } - return extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase); + private bool IsFileDestinationUnsecure(string destination) + { + var unsecureDirNames = new[] {"bin", "app_code"}; + if(unsecureDirNames.Any(ud => destination.StartsWith(ud, StringComparison.InvariantCultureIgnoreCase))) + return true; + + string extension = Path.GetExtension(destination); + return extension != null && extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase); } - private IEnumerable ExtractFileInPackageInfos(XElement filesElement) + private KeyValuePair[] ExtractSourceDestinationFileInformation(XElement filesElement) { if (string.Equals(Constants.Packaging.FilesNodeName, filesElement.Name.LocalName) == false) { @@ -475,15 +499,13 @@ namespace Umbraco.Core.Packaging "filesElement"); } + var fileName = PrepareAsFilePathElement(orgNameElement.Value); + var relativeDir = UpdatePathPlaceholders(PrepareAsFilePathElement(orgPathElement.Value)); - return new FileInPackageInfo - { - FileNameInPackage = guidElement.Value, - FileName = PrepareAsFilePathElement(orgNameElement.Value), - RelativeDir = UpdatePathPlaceholders( - PrepareAsFilePathElement(orgPathElement.Value)), - DestinationRootDir = FullpathToRoot - }; + var relativePath = Path.Combine(relativeDir, fileName); + + + return new KeyValuePair(guidElement.Value, relativePath); }).ToArray(); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8397f8a63a..64627e6a09 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -365,6 +365,7 @@ + From 3b3e41245071755d9f06b4eda2576f1532863da5 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Mon, 16 Jun 2014 12:29:32 +0200 Subject: [PATCH 036/189] Undo some changes --- build/NuSpecs/build/UmbracoCms.targets | 92 +++++++++++++------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/build/NuSpecs/build/UmbracoCms.targets b/build/NuSpecs/build/UmbracoCms.targets index b057ce5202..024d8af7ad 100644 --- a/build/NuSpecs/build/UmbracoCms.targets +++ b/build/NuSpecs/build/UmbracoCms.targets @@ -1,50 +1,50 @@ - - - - - $(MSBuildThisFileDirectory)..\UmbracoFiles\ - - - - - - - - - - - App_Browsers - - - App_Code - - - App_Plugins - - - bin\amd64 - - - bin\x86 - - - Config\Splashes - + + + + + $(MSBuildThisFileDirectory)..\UmbracoFiles\ + + + + + + + + + + + App_Browsers + + + App_Code + + + App_Plugins + + + bin\amd64 + + + bin\x86 + + + Config\Splashes + data - - umbraco - - - umbraco_client - - - . - - - %(CustomFilesToInclude.Dir)\%(RecursiveDir)%(Filename)%(Extension) - - - + + umbraco + + + umbraco_client + + + . + + + %(CustomFilesToInclude.Dir)\%(RecursiveDir)%(Filename)%(Extension) + + + \ No newline at end of file From 09b34625b97ad779014e21ef429b8fbb4174e22c Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Mon, 16 Jun 2014 13:58:50 +0200 Subject: [PATCH 037/189] removed change to src/Umbraco.Core/Services/DataTypeService.cs --- src/Umbraco.Core/Services/DataTypeService.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 81d4cba48e..f1bf4d1321 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -76,23 +76,12 @@ namespace Umbraco.Core.Services /// Gets a by its control Id /// /// Id of the DataType control - /// /// Collection of objects with a matching contorl id - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] - public IEnumerable GetDataTypeDefinitionByControlId(Guid id, bool throwIfNotFound) - { - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, throwIfNotFound); - if (alias == null) - { - return Enumerable.Empty(); - } - return GetDataTypeDefinitionByPropertyEditorAlias(alias); - } - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] public IEnumerable GetDataTypeDefinitionByControlId(Guid id) { - return GetDataTypeDefinitionByControlId(id, true); + var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, true); + return GetDataTypeDefinitionByPropertyEditorAlias(alias); } /// From f7e046deafe14fe68d7fbcb0c5a934812b04f8b8 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Mon, 16 Jun 2014 14:01:59 +0200 Subject: [PATCH 038/189] Undo some non changes --- src/Umbraco.Core/Models/IMacro.cs | 2 +- src/Umbraco.Core/Models/IMacroPropertyType.cs | 2 +- src/Umbraco.Core/Services/IMacroService.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Models/IMacro.cs b/src/Umbraco.Core/Models/IMacro.cs index 27435ccb5a..f28160c345 100644 --- a/src/Umbraco.Core/Models/IMacro.cs +++ b/src/Umbraco.Core/Models/IMacro.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; diff --git a/src/Umbraco.Core/Models/IMacroPropertyType.cs b/src/Umbraco.Core/Models/IMacroPropertyType.cs index a7b6d4bfa9..7c4bc0057f 100644 --- a/src/Umbraco.Core/Models/IMacroPropertyType.cs +++ b/src/Umbraco.Core/Models/IMacroPropertyType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Models +namespace Umbraco.Core.Models { /// /// Defines a PropertyType (plugin) for a Macro diff --git a/src/Umbraco.Core/Services/IMacroService.cs b/src/Umbraco.Core/Services/IMacroService.cs index d88996a6fa..a123fb063e 100644 --- a/src/Umbraco.Core/Services/IMacroService.cs +++ b/src/Umbraco.Core/Services/IMacroService.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Services From 6f27f839e05b0eb74da24c1ecaeeb5fc74f300f3 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Mon, 16 Jun 2014 16:29:15 +0200 Subject: [PATCH 039/189] Related to U4-4199 lets get rid of all these Console.WriteLine's --- .../Strings/DefaultShortStringHelper.cs | 36 ++----------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index 7c29a7aeef..8ba97db220 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -1,20 +1,9 @@ - -// debugging -// define WRTCONS to write cleaning details & steps to console -// leave it wrapped within #if DEBUG to make sure it does leak -// into RELEASE, see http://issues.umbraco.org/issue/U4-4199 -#if DEBUG -#undef WRTCONS -#endif - -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Globalization; -using System.Text; -using System.Text.RegularExpressions; using Umbraco.Core.Configuration; namespace Umbraco.Core.Strings @@ -519,10 +508,6 @@ function validateSafeAlias(input, value, immediate, callback) {{ if (culture == null) throw new ArgumentNullException("culture"); -#if WRTCONS - Console.WriteLine("STRING TYPE {0}", stringType); -#endif - // get config var config = GetConfig(stringType, culture); stringType = config.StringTypeExtend(stringType); @@ -594,9 +579,6 @@ function validateSafeAlias(input, value, immediate, callback) {{ var state = StateBreak; caseType &= CleanStringType.CaseMask; -#if WRTCONS - Console.WriteLine("CASE {0}", caseType); -#endif // if we apply global ToUpper or ToLower to text here // then we cannot break words on uppercase chars @@ -622,13 +604,7 @@ function validateSafeAlias(input, value, immediate, callback) {{ var isPair = char.IsSurrogate(c); if (isPair) throw new NotSupportedException("Surrogate pairs are not supported."); -#if WRTCONS - Console.WriteLine("CHAR '{0}' {1} {2} - {3} - {4}/{5} {6}", - c, - isTerm ? "term" : "!term", isUpper ? "upper" : "!upper", - state, - i, ipos, leading ? "leading" : "!leading"); -#endif + switch (state) { // within a break @@ -662,7 +638,6 @@ function validateSafeAlias(input, value, immediate, callback) {{ case StateAcronym: // end an acronym if char is not a term char, // or if it's not uppercase / config - //Console.WriteLine("acro {0} {1}", c, (config.CutAcronymOnNonUpper && isUpper == false)); if (isTerm == false || (config.CutAcronymOnNonUpper && isUpper == false)) { // whether it's part of the acronym depends on whether we're greedy @@ -735,12 +710,7 @@ function validateSafeAlias(input, value, immediate, callback) {{ CleanStringType caseType, CultureInfo culture, bool isAcronym) { var term = input.Substring(ipos, len); -#if WRTCONS - Console.WriteLine("TERM \"{0}\" {1} {2}", - term, - isAcronym ? "acronym" : "word", - caseType); -#endif + if (isAcronym) { if ((caseType == CleanStringType.CamelCase && len <= 2 && opos > 0) || From 7b48a59ca228053f2d404b0dd7d38d11691c1ca0 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Tue, 17 Jun 2014 07:06:07 +0200 Subject: [PATCH 040/189] added events --- src/Umbraco.Core/Events/ExportEventArgs.cs | 2 +- .../Events/ImportPackageEventArgs.cs | 26 +++++++++++++ .../Packaging/IPackageInstallation.cs | 4 +- .../Packaging/Models/InstallationSummary.cs | 21 ++++++++++ .../Packaging/PackageInstallation.cs | 7 +++- src/Umbraco.Core/Services/DataTypeService.cs | 15 +++++++- src/Umbraco.Core/Services/IDataTypeService.cs | 9 ----- src/Umbraco.Core/Services/PackagingService.cs | 38 +++++++++++++++++-- src/Umbraco.Core/Umbraco.Core.csproj | 1 + 9 files changed, 104 insertions(+), 19 deletions(-) create mode 100644 src/Umbraco.Core/Events/ImportPackageEventArgs.cs diff --git a/src/Umbraco.Core/Events/ExportEventArgs.cs b/src/Umbraco.Core/Events/ExportEventArgs.cs index 161a073615..ba464acee7 100644 --- a/src/Umbraco.Core/Events/ExportEventArgs.cs +++ b/src/Umbraco.Core/Events/ExportEventArgs.cs @@ -2,7 +2,7 @@ using System.Xml.Linq; namespace Umbraco.Core.Events -{ +{ public class ExportEventArgs : CancellableObjectEventArgs> { /// diff --git a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs new file mode 100644 index 0000000000..8596629731 --- /dev/null +++ b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Umbraco.Core.Packaging.Models; + +namespace Umbraco.Core.Events +{ + internal class ImportPackageEventArgs : CancellableObjectEventArgs> + { + private readonly MetaData _packageMetaData; + + public ImportPackageEventArgs(TEntity eventObject, bool canCancel) + : base(new[] { eventObject }, canCancel) + { + } + + public ImportPackageEventArgs(TEntity eventObject, MetaData packageMetaData) + : base(new[] { eventObject }) + { + _packageMetaData = packageMetaData; + } + + public MetaData PackageMetaData + { + get { return _packageMetaData; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/IPackageInstallation.cs b/src/Umbraco.Core/Packaging/IPackageInstallation.cs index 720ee3393b..2a0b764a37 100644 --- a/src/Umbraco.Core/Packaging/IPackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/IPackageInstallation.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Packaging.Models; +using System.Xml.Linq; +using Umbraco.Core.Packaging.Models; namespace Umbraco.Core.Packaging { @@ -7,5 +8,6 @@ namespace Umbraco.Core.Packaging InstallationSummary InstallPackage(string packageFilePath, int userId); MetaData GetMetaData(string packageFilePath); PreInstallWarnings GetPreInstallWarnings(string packageFilePath); + XElement GetConfigXmlElement(string packageFilePath); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs b/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs index ab501eaaf0..2ef4dfec9d 100644 --- a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs +++ b/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs @@ -19,5 +19,26 @@ namespace Umbraco.Core.Packaging.Models public IFile[] StylesheetsInstalled { get; set; } public IContent[] ContentInstalled { get; set; } public PackageAction[] Actions { get; set; } + public bool PackageInstalled { get; set; } + } + + + internal static class InstallationSummaryExtentions + { + public static InstallationSummary InitEmpty(this InstallationSummary @this) + { + @this.Actions = new PackageAction[0]; + @this.ContentInstalled = new IContent[0]; + @this.ContentTypesInstalled = new IContentType[0]; + @this.DataTypesInstalled = new IDataTypeDefinition[0]; + @this.DictionaryItemsInstalled = new IDictionaryItem[0]; + @this.FilesInstalled = new string[0]; + @this.LanguagesInstalled = new ILanguage[0]; + @this.MacrosInstalled = new IMacro[0]; + @this.MetaData = new MetaData(); + @this.TemplatesInstalled = new ITemplate[0]; + @this.PackageInstalled = false; + return @this; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index 5b91e76a22..09861354df 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -99,6 +99,7 @@ namespace Umbraco.Core.Packaging } } + public InstallationSummary InstallPackage(string packageFile, int userId) { XElement dataTypes; @@ -131,7 +132,7 @@ namespace Umbraco.Core.Packaging actions = rootElement.Element(Constants.Packaging.ActionsNodeName); metaData = GetMetaData(rootElement); - installationSummary = new InstallationSummary() { MetaData = metaData }; + installationSummary = new InstallationSummary {MetaData = metaData}; } catch (Exception e) { @@ -170,6 +171,8 @@ namespace Umbraco.Core.Packaging var packageActions = EmptyArrayIfNull(actions) ?? GetPackageActions(actions, metaData.Name); installationSummary.Actions = packageActions; + installationSummary.PackageInstalled = true; + return installationSummary; } catch (Exception e) @@ -206,7 +209,7 @@ namespace Umbraco.Core.Packaging } - private XElement GetConfigXmlElement(string packageFilePath) + public XElement GetConfigXmlElement(string packageFilePath) { XDocument document = GetConfigXmlDoc(packageFilePath); if (document.Root == null || diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index f1bf4d1321..81d4cba48e 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -76,12 +76,23 @@ namespace Umbraco.Core.Services /// Gets a by its control Id /// /// Id of the DataType control + /// /// Collection of objects with a matching contorl id + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] + public IEnumerable GetDataTypeDefinitionByControlId(Guid id, bool throwIfNotFound) + { + var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, throwIfNotFound); + if (alias == null) + { + return Enumerable.Empty(); + } + return GetDataTypeDefinitionByPropertyEditorAlias(alias); + } + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] public IEnumerable GetDataTypeDefinitionByControlId(Guid id) { - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, true); - return GetDataTypeDefinitionByPropertyEditorAlias(alias); + return GetDataTypeDefinitionByControlId(id, true); } /// diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index d0da5a64a8..7bf938aa25 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -79,15 +79,6 @@ namespace Umbraco.Core.Services [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] IEnumerable GetDataTypeDefinitionByControlId(Guid id); - /// - /// Gets a by its control Id - /// - /// Id of the DataType control - /// Throw exception if the type is not found if true. If false return empty Enumerable if not found - /// - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] - IEnumerable GetDataTypeDefinitionByControlId(Guid id, bool throwIfNotFound); - /// /// Gets a by its control Id /// diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index a05de1d1f4..1f02612e0a 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; @@ -568,7 +569,7 @@ namespace Umbraco.Core.Services if (dataTypeDefinition == null) { var dataTypeDefinitions = legacyPropertyEditorId != Guid.Empty - ? _dataTypeService.GetDataTypeDefinitionByControlId(legacyPropertyEditorId, false) + ? _dataTypeService.GetDataTypeDefinitionByControlId(legacyPropertyEditorId) : _dataTypeService.GetDataTypeDefinitionByPropertyEditorAlias(propertyEditorAlias); if (dataTypeDefinitions != null && dataTypeDefinitions.Any()) { @@ -1582,10 +1583,27 @@ namespace Umbraco.Core.Services set { _packageInstallation = value; } } - internal InstallationSummary InstallPackage(string packageFilePath, int userId = 0) + internal InstallationSummary InstallPackage(string packageFilePath, int userId = 0, bool raiseEvents = false) { - //TODO Add events ? - return PackageInstallation.InstallPackage(packageFilePath, userId); + if (raiseEvents) + { + var metaData = GetPackageMetaData(packageFilePath); + if (ImportingPackage.IsRaisedEventCancelled(new ImportPackageEventArgs(packageFilePath, metaData), this)) + { + var initEmpty = new InstallationSummary().InitEmpty(); + initEmpty.MetaData = metaData; + return initEmpty; + } + } + var installationSummary = PackageInstallation.InstallPackage(packageFilePath, userId); + + if (raiseEvents) + { + ImportedPackage.RaiseEvent(new ImportPackageEventArgs(installationSummary, false), this); + } + + + return installationSummary; } internal PreInstallWarnings GetPackageWarnings(string packageFilePath) @@ -1757,6 +1775,18 @@ namespace Umbraco.Core.Services /// Occurs after Template is Exported to Xml /// public static event TypedEventHandler> ExportedTemplate; + + + /// + /// Occurs before Importing umbraco package + /// + internal static event TypedEventHandler> ImportingPackage; + + /// + /// Occurs after a apckage is imported + /// + internal static event TypedEventHandler> ImportedPackage; + #endregion } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 64627e6a09..9f0b4ca6a6 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -298,6 +298,7 @@ + From 448bfc166f17543ec0744f9268c935f65c42becb Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Tue, 17 Jun 2014 07:12:59 +0200 Subject: [PATCH 041/189] revert to org files --- src/Umbraco.Core/Events/ExportEventArgs.cs | 2 +- src/Umbraco.Core/Services/DataTypeService.cs | 15 ++------------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Core/Events/ExportEventArgs.cs b/src/Umbraco.Core/Events/ExportEventArgs.cs index ba464acee7..161a073615 100644 --- a/src/Umbraco.Core/Events/ExportEventArgs.cs +++ b/src/Umbraco.Core/Events/ExportEventArgs.cs @@ -2,7 +2,7 @@ using System.Xml.Linq; namespace Umbraco.Core.Events -{ +{ public class ExportEventArgs : CancellableObjectEventArgs> { /// diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 81d4cba48e..f1bf4d1321 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -76,23 +76,12 @@ namespace Umbraco.Core.Services /// Gets a by its control Id /// /// Id of the DataType control - /// /// Collection of objects with a matching contorl id - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] - public IEnumerable GetDataTypeDefinitionByControlId(Guid id, bool throwIfNotFound) - { - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, throwIfNotFound); - if (alias == null) - { - return Enumerable.Empty(); - } - return GetDataTypeDefinitionByPropertyEditorAlias(alias); - } - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] public IEnumerable GetDataTypeDefinitionByControlId(Guid id) { - return GetDataTypeDefinitionByControlId(id, true); + var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, true); + return GetDataTypeDefinitionByPropertyEditorAlias(alias); } /// From 48e0e5da750b9416e157c474089813158251098a Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Tue, 17 Jun 2014 07:31:22 +0200 Subject: [PATCH 042/189] revert to org files --- .../Packaging/PackageInstallation.cs | 2 +- .../Services/IPackagingService.cs | 21 ++++++------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index 09861354df..f8415bee74 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -321,7 +321,7 @@ namespace Umbraco.Core.Packaging throw new ArgumentException("Must be \"" + Constants.Packaging.StylesheetsNodeName + "\" as root", "styleSheetsElement"); } - return _packagingService.ImportStylesheets(styleSheetsElement, userId).ToArray(); + throw new NotImplementedException("The packaging service do not yes have a method for this"); } private IContentType[] InstallDocumentTypes(XElement documentTypes, int userId = 0) diff --git a/src/Umbraco.Core/Services/IPackagingService.cs b/src/Umbraco.Core/Services/IPackagingService.cs index 2c274724fa..c7e68e6eca 100644 --- a/src/Umbraco.Core/Services/IPackagingService.cs +++ b/src/Umbraco.Core/Services/IPackagingService.cs @@ -1,7 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Xml.Linq; -using Umbraco.Core.Models; - +using Umbraco.Core.Models; + namespace Umbraco.Core.Services { public interface IPackagingService : IService @@ -49,7 +49,7 @@ namespace Umbraco.Core.Services /// /// Xml to import /// Optional parameter indicating whether or not to raise events - /// An enumerable list of dictionary items + /// An enumerable list of dictionary items IEnumerable ImportDictionaryItems(XElement dictionaryItemElementList, bool raiseEvents = true); /// @@ -77,16 +77,7 @@ namespace Umbraco.Core.Services /// Optional id of the User performing the operation. Default is zero (admin) /// Optional parameter indicating whether or not to raise events /// An enumrable list of generated Templates - IEnumerable ImportTemplates(XElement element, int userId = 0, bool raiseEvents = true); - - /// - /// Imports and saves package xml as - /// - /// Xml to import - /// Optional id of the User performing the operation. Default is zero (admin) - /// Optional parameter indicating whether or not to raise events - /// An enumerable list of generated stylesheets - IEnumerable ImportStylesheets(XElement element, int userId = 0, bool raiseEvents = true); + IEnumerable ImportTemplates(XElement element, int userId = 0, bool raiseEvents = true); /// /// Exports an to xml as an @@ -194,6 +185,6 @@ namespace Umbraco.Core.Services /// Macro to export /// Optional parameter indicating whether or not to raise events /// containing the xml representation of the IMacro object - XElement Export(IMacro macro, bool raiseEvents = true); + XElement Export(IMacro macro, bool raiseEvents = true); } } \ No newline at end of file From 7ec13146b26077552db877c9cfd824a9e391b8e4 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Tue, 17 Jun 2014 07:34:49 +0200 Subject: [PATCH 043/189] more revert --- src/Umbraco.Core/Packaging/PackageBinaryInspector.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs index e1fbdc7f75..2b9ada9278 100644 --- a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs +++ b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs @@ -5,6 +5,9 @@ using System.Linq; using System.Reflection; using System.Security; using System.Security.Permissions; +using System.Text; +using System.Threading.Tasks; +using System.Web; using Umbraco.Core.Logging; namespace Umbraco.Core.Packaging From d3bb750f98a36bcdacc6a5965a572dfe2171af78 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 17 Jun 2014 16:07:08 +1000 Subject: [PATCH 044/189] Updated to latest Examine with lock fixes --- src/Umbraco.Tests/Umbraco.Tests.csproj | 4 ++-- src/Umbraco.Tests/packages.config | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- src/Umbraco.Web.UI/packages.config | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 4 ++-- src/Umbraco.Web/packages.config | 2 +- src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj | 2 +- src/UmbracoExamine.Azure/packages.config | 2 +- src/UmbracoExamine.PDF.Azure/UmbracoExamine.PDF.Azure.csproj | 5 ++--- src/UmbracoExamine.PDF.Azure/packages.config | 2 +- src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj | 2 +- src/UmbracoExamine.PDF/packages.config | 2 +- src/UmbracoExamine/UmbracoExamine.csproj | 3 ++- src/UmbracoExamine/packages.config | 2 +- src/umbraco.MacroEngines/packages.config | 2 +- src/umbraco.MacroEngines/umbraco.MacroEngines.csproj | 2 +- 16 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 6e56759ca5..97e3d53b61 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -1,4 +1,4 @@ - + Debug @@ -50,7 +50,7 @@ False - ..\packages\Examine.0.1.55.2941\lib\Examine.dll + ..\packages\Examine.0.1.56.2941\lib\Examine.dll False diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index dd2c7f6d65..a9e31b4e57 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 08dda3d73e..fbc922fc2c 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -110,8 +110,8 @@ ..\packages\ClientDependency-Mvc.1.7.0.4\lib\ClientDependency.Core.Mvc.dll - False - ..\packages\Examine.0.1.55.2941\lib\Examine.dll + ..\packages\Examine.0.1.56.2941\lib\Examine.dll + True False diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index 8d99805c71..ff2f6ab5cd 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b742f0ad63..49da691cd2 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -102,8 +102,8 @@ ..\packages\xmlrpcnet.2.5.0\lib\net20\CookComputing.XmlRpcV2.dll - False - ..\packages\Examine.0.1.55.2941\lib\Examine.dll + ..\packages\Examine.0.1.56.2941\lib\Examine.dll + True False diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index 7932361039..474c7c8973 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj b/src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj index b2fe3e2758..5571697c65 100644 --- a/src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj +++ b/src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj @@ -39,7 +39,7 @@ False - ..\packages\Examine.0.1.55.2941\lib\Examine.dll + ..\packages\Examine.0.1.56.2941\lib\Examine.dll False diff --git a/src/UmbracoExamine.Azure/packages.config b/src/UmbracoExamine.Azure/packages.config index 4dce0f43bc..05e36b051e 100644 --- a/src/UmbracoExamine.Azure/packages.config +++ b/src/UmbracoExamine.Azure/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/UmbracoExamine.PDF.Azure/UmbracoExamine.PDF.Azure.csproj b/src/UmbracoExamine.PDF.Azure/UmbracoExamine.PDF.Azure.csproj index 94495d21d1..0aafee84d7 100644 --- a/src/UmbracoExamine.PDF.Azure/UmbracoExamine.PDF.Azure.csproj +++ b/src/UmbracoExamine.PDF.Azure/UmbracoExamine.PDF.Azure.csproj @@ -1,4 +1,4 @@ - + Debug @@ -38,8 +38,7 @@ ..\packages\AzureDirectory.1.0.5\lib\AzureDirectory.dll - False - ..\packages\Examine.0.1.55.2941\lib\Examine.dll + ..\packages\Examine.0.1.56.2941\lib\Examine.dll False diff --git a/src/UmbracoExamine.PDF.Azure/packages.config b/src/UmbracoExamine.PDF.Azure/packages.config index 4dce0f43bc..05e36b051e 100644 --- a/src/UmbracoExamine.PDF.Azure/packages.config +++ b/src/UmbracoExamine.PDF.Azure/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj b/src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj index a7e57efc14..5b01d4d38c 100644 --- a/src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj +++ b/src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj @@ -46,7 +46,7 @@ False - ..\packages\Examine.0.1.55.2941\lib\Examine.dll + ..\packages\Examine.0.1.56.2941\lib\Examine.dll ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll diff --git a/src/UmbracoExamine.PDF/packages.config b/src/UmbracoExamine.PDF/packages.config index 1169a8ab9c..4e1b27d4f3 100644 --- a/src/UmbracoExamine.PDF/packages.config +++ b/src/UmbracoExamine.PDF/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/UmbracoExamine/UmbracoExamine.csproj b/src/UmbracoExamine/UmbracoExamine.csproj index c9d8502076..8047ce4d96 100644 --- a/src/UmbracoExamine/UmbracoExamine.csproj +++ b/src/UmbracoExamine/UmbracoExamine.csproj @@ -82,7 +82,8 @@ False - ..\packages\Examine.0.1.55.2941\lib\Examine.dll + ..\packages\Examine.0.1.56.2941\lib\Examine.dll + True False diff --git a/src/UmbracoExamine/packages.config b/src/UmbracoExamine/packages.config index e923fba924..6045ab5822 100644 --- a/src/UmbracoExamine/packages.config +++ b/src/UmbracoExamine/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/umbraco.MacroEngines/packages.config b/src/umbraco.MacroEngines/packages.config index 7665569f18..bdd36b16f7 100644 --- a/src/umbraco.MacroEngines/packages.config +++ b/src/umbraco.MacroEngines/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj index 22e7c846d8..b2b0cb5b4b 100644 --- a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj +++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj @@ -44,7 +44,7 @@ False - ..\packages\Examine.0.1.55.2941\lib\Examine.dll + ..\packages\Examine.0.1.56.2941\lib\Examine.dll False From 3e0cd0a605a546545067153b48093c5e946ec29d Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Tue, 17 Jun 2014 09:13:54 +0200 Subject: [PATCH 045/189] Now it works with a notimplimentet exception --- src/Umbraco.Core/Packaging/PackageInstallation.cs | 6 +++++- src/Umbraco.Core/Services/ServiceContext.cs | 4 +++- src/Umbraco.Core/packages.config | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index f8415bee74..c530f5536f 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -321,7 +321,11 @@ namespace Umbraco.Core.Packaging throw new ArgumentException("Must be \"" + Constants.Packaging.StylesheetsNodeName + "\" as root", "styleSheetsElement"); } - throw new NotImplementedException("The packaging service do not yes have a method for this"); + + // TODO: Call _packagingService when import stylesheets import has been implimentet + if (styleSheetsElement.HasElements == false) { return new IFile[0]; } + + throw new NotImplementedException("The packaging service do not yes have a method for importing stylesheets"); } private IContentType[] InstallDocumentTypes(XElement documentTypes, int userId = 0) diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index c32160f5f0..f358e41bf3 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -59,7 +59,7 @@ namespace Umbraco.Core.Services IDataTypeService dataTypeService, IFileService fileService, ILocalizationService localizationService, - IPackagingService packagingService, + IPackagingService packagingService, IEntityService entityService, IRelationService relationService, IMemberGroupService memberGroupService, @@ -88,6 +88,7 @@ namespace Umbraco.Core.Services _memberService = new Lazy(() => memberService); _userService = new Lazy(() => userService); _notificationService = new Lazy(() => notificationService); + } /// @@ -327,5 +328,6 @@ namespace Umbraco.Core.Services { get { return _memberGroupService.Value; } } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config index 510e3ce0ba..c9fb3695fe 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -10,8 +10,8 @@ - + \ No newline at end of file From b8575ef158dc0578499f7a73afab5d5d0456af00 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 17 Jun 2014 18:53:23 +1000 Subject: [PATCH 046/189] Started writing test implementation to mimic what developers might test in their controllers to expose the pitfalls of umbraco testing so we can make this simpler - identifying what needs to be public and how to re-strcuture some objects constructors, etc... to simplify and make this possible. --- .../Mvc/SurfaceControllerTests.cs | 151 ++++++++++++++++++ .../TestHelpers/DisposableUmbracoTest.cs | 52 ++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 4 +- src/Umbraco.Web/Properties/AssemblyInfo.cs | 2 + 4 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs create mode 100644 src/Umbraco.Tests/TestHelpers/DisposableUmbracoTest.cs diff --git a/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs new file mode 100644 index 0000000000..572ff5aa92 --- /dev/null +++ b/src/Umbraco.Tests/Mvc/SurfaceControllerTests.cs @@ -0,0 +1,151 @@ +using System.CodeDom; +using System.Web; +using System.Web.Mvc; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.ObjectResolution; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web; +using Umbraco.Web.Mvc; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Tests.Mvc +{ + [TestFixture] + public class SurfaceControllerTests + { + [Test] + public void Can_Construct_And_Get_Result() + { + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + ApplicationContext.EnsureContext(appCtx, true); + + var umbCtx = UmbracoContext.EnsureContext( + new Mock().Object, + appCtx, + true); + + var ctrl = new TestSurfaceController(umbCtx); + + var result = ctrl.Index(); + + Assert.IsNotNull(result); + } + + [Test] + public void Umbraco_Context_Not_Null() + { + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + ApplicationContext.EnsureContext(appCtx, true); + + var umbCtx = UmbracoContext.EnsureContext( + new Mock().Object, + appCtx, + true); + + var ctrl = new TestSurfaceController(umbCtx); + + Assert.IsNotNull(ctrl.UmbracoContext); + } + + [Test] + public void Umbraco_Helper_Not_Null() + { + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + ApplicationContext.EnsureContext(appCtx, true); + + var umbCtx = UmbracoContext.EnsureContext( + new Mock().Object, + appCtx, + true); + + var ctrl = new TestSurfaceController(umbCtx); + + Assert.IsNotNull(ctrl.Umbraco); + } + + [Test] + public void Can_Lookup_Content() + { + //init app context + + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + + //TODO: Need to either make this public or make all methods on the UmbracoHelper or + // in v7 the PublishedContentQuery object virtual so we can just mock the methods + + var contentCaches = new Mock(); + + //init content resolver + //TODO: This is not public so people cannot actually do this! + + PublishedCachesResolver.Current = new PublishedCachesResolver(contentCaches.Object); + + //init umb context + + var umbCtx = UmbracoContext.EnsureContext( + new Mock().Object, + appCtx, + true); + + //setup the mock + + contentCaches.Setup(caches => caches.CreateContextualContentCache(It.IsAny())) + .Returns(new ContextualPublishedContentCache( + Mock.Of(cache => + cache.GetById(It.IsAny(), false, It.IsAny()) == + //return mock of IPublishedContent for any call to GetById + Mock.Of(content => content.Id == 2)), + umbCtx)); + + + + + + using (var uTest = new DisposableUmbracoTest(appCtx)) + { + var ctrl = new TestSurfaceController(uTest.UmbracoContext); + var result = ctrl.GetContent(2) as PublishedContentResult; + + Assert.IsNotNull(result); + Assert.AreEqual(2, result.Content.Id); + } + } + + public class TestSurfaceController : SurfaceController + { + public TestSurfaceController(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + + public ActionResult Index() + { + return View(); + } + + public ActionResult GetContent(int id) + { + var content = Umbraco.TypedContent(id); + + return new PublishedContentResult(content); + } + } + + public class PublishedContentResult : ActionResult + { + public IPublishedContent Content { get; set; } + + public PublishedContentResult(IPublishedContent content) + { + Content = content; + } + + public override void ExecuteResult(ControllerContext context) + { + } + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/DisposableUmbracoTest.cs b/src/Umbraco.Tests/TestHelpers/DisposableUmbracoTest.cs new file mode 100644 index 0000000000..e6de8fb8f3 --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/DisposableUmbracoTest.cs @@ -0,0 +1,52 @@ +using System.Web; +using Moq; +using Umbraco.Core; +using Umbraco.Core.ObjectResolution; +using Umbraco.Web; + +namespace Umbraco.Tests.TestHelpers +{ + //NOTE: This is just a POC! Looking at the simplest way to expose some code so people can very easily test + // their Umbraco controllers, etc.... + public class DisposableUmbracoTest : DisposableObject + { + public ApplicationContext ApplicationContext { get; set; } + public UmbracoContext UmbracoContext { get; set; } + + public DisposableUmbracoTest(ApplicationContext applicationContext) + { + //init umb context + var umbctx = UmbracoContext.EnsureContext( + new Mock().Object, + applicationContext, + true); + + Init(applicationContext, umbctx); + } + + public DisposableUmbracoTest(ApplicationContext applicationContext, UmbracoContext umbracoContext) + { + Init(applicationContext, umbracoContext); + } + + private void Init(ApplicationContext applicationContext, UmbracoContext umbracoContext) + { + TestHelper.SetupLog4NetForTests(); + + ApplicationContext = applicationContext; + UmbracoContext = umbracoContext; + + ApplicationContext.Current = applicationContext; + UmbracoContext.Current = umbracoContext; + + Resolution.Freeze(); + } + + protected override void DisposeResources() + { + ApplicationContext.Current = null; + UmbracoContext.Current = null; + Resolution.Reset(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 97e3d53b61..b7c5d9e9a1 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -1,4 +1,4 @@ - + Debug @@ -184,6 +184,7 @@ + @@ -417,6 +418,7 @@ + diff --git a/src/Umbraco.Web/Properties/AssemblyInfo.cs b/src/Umbraco.Web/Properties/AssemblyInfo.cs index 7dc3b796df..ad54647119 100644 --- a/src/Umbraco.Web/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Web/Properties/AssemblyInfo.cs @@ -34,3 +34,5 @@ using System.Security; [assembly: InternalsVisibleTo("umbraco.webservices")] [assembly: InternalsVisibleTo("Concorde.Sync")] [assembly: InternalsVisibleTo("Umbraco.Belle")] + +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file From 56111be82dd1ed2fa24c3023c61b1d209229e35b Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Jun 2014 17:11:47 +1000 Subject: [PATCH 047/189] Properly fixes: U4-4893 Inconsistent display of file extensions in the UI --- src/Umbraco.Core/StringExtensions.cs | 19 +++++++++++++ .../CoreStrings/StringExtensionsTests.cs | 10 +++++++ src/Umbraco.Web.UI.Client/src/less/tree.less | 3 --- .../src/views/directives/umb-contextmenu.html | 2 +- src/Umbraco.Web/Trees/PartialViewsTree.cs | 2 ++ .../umbraco/Trees/FileSystemTree.cs | 3 +-- .../umbraco/Trees/loadDLRScripts.cs | 27 +++---------------- .../umbraco/Trees/loadScripts.cs | 4 ++- .../umbraco/Trees/loadStylesheets.cs | 3 ++- .../umbraco/Trees/loadXslt.cs | 2 ++ 10 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 723ff2efc9..3eedb69eb4 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -40,6 +40,25 @@ namespace Umbraco.Core ToCSharpEscapeChars[escape[0]] = escape[1]; } + internal static string StripFileExtension(this string fileName) + { + //filenames cannot contain line breaks + if (fileName.Contains(Environment.NewLine) || fileName.Contains("\r") || fileName.Contains("\n")) return fileName; + + var lastIndex = fileName.LastIndexOf('.'); + if (lastIndex > 0) + { + var ext = fileName.Substring(lastIndex); + //file extensions cannot contain whitespace + if (ext.Contains(" ")) return fileName; + + return string.Format("{0}", fileName.Substring(0, fileName.IndexOf(ext, StringComparison.Ordinal))); + } + return fileName; + + + } + /// /// This tries to detect a json string, this is not a fail safe way but it is quicker than doing /// a try/catch when deserializing when it is not json. diff --git a/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs b/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs index 89d03baa2d..e7007fe384 100644 --- a/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs @@ -27,6 +27,16 @@ namespace Umbraco.Tests.CoreStrings ShortStringHelperResolver.Reset(); } + [TestCase("hello.txt", "hello")] + [TestCase("this.is.a.Txt", "this.is.a")] + [TestCase("this.is.not.a. Txt", "this.is.not.a. Txt")] + [TestCase("not a file","not a file")] + public void Strip_File_Extension(string input, string result) + { + var stripped = input.StripFileExtension(); + Assert.AreEqual(stripped, result); + } + [TestCase("This is a string to encrypt")] [TestCase("This is a string to encrypt\nThis is a second line")] [TestCase(" White space is preserved ")] diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less index 9376f54e8a..e843499d2e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/tree.less @@ -48,9 +48,6 @@ padding-left: 20px; } -.umb-tree li .file-ext { - color: @grayLight; -} .umb-tree li.root > div h5 { margin-top: 10px; diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html b/src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html index faeb90033f..0aa58c8fae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html @@ -1,6 +1,6 @@
-

+

{{menuDialogTitle}}

diff --git a/src/Umbraco.Web/Trees/PartialViewsTree.cs b/src/Umbraco.Web/Trees/PartialViewsTree.cs index 341a906d25..c971f9573d 100644 --- a/src/Umbraco.Web/Trees/PartialViewsTree.cs +++ b/src/Umbraco.Web/Trees/PartialViewsTree.cs @@ -70,6 +70,8 @@ namespace Umbraco.Web.Trees ChangeNodeAction(xNode); xNode.Icon = "settingView.gif"; xNode.OpenIcon = "settingView.gif"; + + xNode.Text = xNode.Text.StripFileExtension(); } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs index db81d1b68e..2349d8943f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs @@ -103,8 +103,7 @@ namespace umbraco.cms.presentation.Trees XmlTreeNode xFileNode = XmlTreeNode.Create(this); xFileNode.NodeID = orgPath + file.Name; - xFileNode.Text = - string.Format("{0}", file.Name.Substring(0, file.Name.IndexOf(file.Extension, StringComparison.Ordinal))); + xFileNode.Text = file.Name; if (!((orgPath == ""))) xFileNode.Action = "javascript:openFile('" + orgPath + file.Name + "');"; else diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadDLRScripts.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadDLRScripts.cs index c64e044400..a941772b23 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadDLRScripts.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadDLRScripts.cs @@ -1,28 +1,7 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Data; -using System.IO; -using System.Text; -using System.Web; -using System.Xml; -using System.Configuration; -using umbraco.BasePages; -using umbraco.BusinessLogic; -using umbraco.cms.businesslogic; -using umbraco.cms.businesslogic.cache; -using umbraco.cms.businesslogic.contentitem; -using umbraco.cms.businesslogic.datatype; -using umbraco.cms.businesslogic.language; -using umbraco.cms.businesslogic.media; -using umbraco.cms.businesslogic.member; -using umbraco.cms.businesslogic.property; -using umbraco.cms.businesslogic.web; -using umbraco.interfaces; -using umbraco.DataLayer; -using umbraco.BusinessLogic.Utils; +using System.Text; using umbraco.cms.presentation.Trees; using Umbraco.Core.IO; +using Umbraco.Core; namespace umbraco @@ -92,6 +71,8 @@ namespace umbraco xNode.Icon = icon; xNode.OpenIcon = icon; + + xNode.Text = xNode.Text.StripFileExtension(); } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadScripts.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadScripts.cs index 91f5d57caa..f74cc78be4 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadScripts.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadScripts.cs @@ -79,7 +79,7 @@ namespace umbraco xNode.Action = xNode.Action.Replace("openFile", "openScriptEditor"); // add special icons for javascript files - if (xNode.Action.Contains(".js")) + if (xNode.Text.Contains(".js")) { xNode.Icon = "icon-script"; xNode.OpenIcon = "icon-script"; @@ -89,6 +89,8 @@ namespace umbraco xNode.Icon = "icon-code"; xNode.OpenIcon = "icon-code"; } + + xNode.Text = xNode.Text.StripFileExtension(); } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadStylesheets.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadStylesheets.cs index 4fe41fe6ea..3a8b58005b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadStylesheets.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadStylesheets.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Data; +using System.Globalization; using System.IO; using System.Text; using System.Web; @@ -63,7 +64,7 @@ namespace umbraco foreach (StyleSheet n in StyleSheet.GetAll()) { XmlTreeNode xNode = XmlTreeNode.Create(this); - xNode.NodeID = n.Id.ToString(); + xNode.NodeID = n.Id.ToString(CultureInfo.InvariantCulture); xNode.Text = n.Text; xNode.Action = "javascript:openStylesheet(" + n.Id + ");"; loadStylesheetProperty styleSheetPropertyTree = new loadStylesheetProperty(this.app); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadXslt.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadXslt.cs index 49c54554c2..4a03b31551 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadXslt.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadXslt.cs @@ -73,6 +73,8 @@ function openXslt(id) { xNode.Action = xNode.Action.Replace("openFile", "openXslt"); xNode.Icon = "icon-code"; xNode.OpenIcon = "icon-code"; + + xNode.Text = xNode.Text.StripFileExtension(); } protected override void OnRenderFolderNode(ref XmlTreeNode xNode) From 8ded6fff935bcdc2e9f199ebaa491e42cdc96005 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Jun 2014 17:15:09 +1000 Subject: [PATCH 048/189] adds vs task to grunt --- src/Umbraco.Web.UI.Client/gruntFile.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js index 999eab2d31..9c522d0f7e 100644 --- a/src/Umbraco.Web.UI.Client/gruntFile.js +++ b/src/Umbraco.Web.UI.Client/gruntFile.js @@ -4,6 +4,7 @@ module.exports = function (grunt) { // Default task. grunt.registerTask('default', ['jshint:dev','build','karma:unit']); grunt.registerTask('dev', ['jshint:dev', 'build', 'webserver', 'open:dev', 'watch']); + grunt.registerTask('vs', ['jshint:dev', 'build', 'watch']); //run by the watch task grunt.registerTask('watch-js', ['jshint:dev','concat','copy:app','copy:mocks','copy:packages','copy:vs','karma:unit']); From dfb40802ffb099976fa2d5d8c3bf3c67d9828932 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Jun 2014 17:31:00 +1000 Subject: [PATCH 049/189] makes grunt vs and grunt dev a little faster --- src/Umbraco.Web.UI.Client/gruntFile.js | 765 ++++++++++++------------- 1 file changed, 382 insertions(+), 383 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js index 9c522d0f7e..181e05ec92 100644 --- a/src/Umbraco.Web.UI.Client/gruntFile.js +++ b/src/Umbraco.Web.UI.Client/gruntFile.js @@ -1,409 +1,408 @@ module.exports = function (grunt) { - // Default task. - grunt.registerTask('default', ['jshint:dev','build','karma:unit']); - grunt.registerTask('dev', ['jshint:dev', 'build', 'webserver', 'open:dev', 'watch']); - grunt.registerTask('vs', ['jshint:dev', 'build', 'watch']); + // Default task. + grunt.registerTask('default', ['jshint:dev', 'build', 'karma:unit']); + grunt.registerTask('dev', ['jshint:dev', 'build-dev', 'webserver', 'open:dev', 'watch']); + grunt.registerTask('vs', ['jshint:dev', 'build-dev', 'watch']); - //run by the watch task - grunt.registerTask('watch-js', ['jshint:dev','concat','copy:app','copy:mocks','copy:packages','copy:vs','karma:unit']); - grunt.registerTask('watch-less', ['recess:build','recess:installer','copy:assets','copy:vs']); - grunt.registerTask('watch-html', ['copy:views', 'copy:vs']); - grunt.registerTask('watch-packages', ['copy:packages']); - grunt.registerTask('watch-installer', ['concat:install','concat:installJs','copy:installer', 'copy:vs']); - grunt.registerTask('watch-test', ['jshint:dev', 'karma:unit']); + //TODO: Too much watching, this brings windows to it's knees when in dev mode + //run by the watch task + grunt.registerTask('watch-js', ['jshint:dev', 'concat', 'copy:app', 'copy:mocks', 'copy:packages', 'copy:vs', 'karma:unit']); + grunt.registerTask('watch-less', ['recess:build', 'recess:installer', 'copy:assets', 'copy:vs']); + grunt.registerTask('watch-html', ['copy:views', 'copy:vs']); + grunt.registerTask('watch-packages', ['copy:packages']); + grunt.registerTask('watch-installer', ['concat:install', 'concat:installJs', 'copy:installer', 'copy:vs']); + grunt.registerTask('watch-test', ['jshint:dev', 'karma:unit']); - //triggered from grunt dev or grunt - grunt.registerTask('build', ['clean','concat','recess:min','recess:installer','copy']); + //triggered from grunt dev or grunt + grunt.registerTask('build', ['clean', 'concat', 'recess:min', 'recess:installer', 'copy']); - //utillity tasks - grunt.registerTask('docs', ['ngdocs']); - grunt.registerTask('webserver', ['connect:devserver']); + //build-dev doesn't min - we are trying to speed this up and we don't want minified stuff when we are in dev mode + grunt.registerTask('build-dev', ['clean', 'concat', 'recess:build', 'recess:installer', 'copy']); + + //utillity tasks + grunt.registerTask('docs', ['ngdocs']); + grunt.registerTask('webserver', ['connect:devserver']); - // Print a timestamp (useful for when watching) - grunt.registerTask('timestamp', function() { - grunt.log.subhead(Date()); - }); + // Print a timestamp (useful for when watching) + grunt.registerTask('timestamp', function () { + grunt.log.subhead(Date()); + }); // Custom task to run the bower dependency installer // tried, a few other things but this seems to work the best. // https://coderwall.com/p/xnkdqw - grunt.registerTask('bower', 'Get js packages listed in bower.json', - function () { - var bower = require('bower'); - var done = this.async(); + grunt.registerTask('bower', 'Get js packages listed in bower.json', + function () { + var bower = require('bower'); + var done = this.async(); - bower.commands.install(undefined, { }, { interactive: false }) - .on('log', function (data) { - grunt.log.write(data.message + "\n"); - }) - .on('error', function (data) { - grunt.log.write(data.message + "\n"); - done(false); - }) - .on('end', function (data) { - done(); - }); - } - ); - - - // Project configuration. - grunt.initConfig({ - buildVersion: grunt.option('buildversion') || '7', - connect: { - devserver: { - options: { - port: 9990, - hostname: '0.0.0.0', - base: './build', - middleware: function(connect, options){ - return [ - //uncomment to enable CSP - // util.csp(), - //util.rewrite(), - connect.favicon('images/favicon.ico'), - connect.static(options.base), - connect.directory(options.base) - ]; - } - } - }, - testserver: {} - }, - - open : { - dev : { - path: 'http://localhost:9990/belle/' - } - }, - - distdir: 'build/belle', - bowerfiles: 'bower_components', - vsdir: '../Umbraco.Web.UI/umbraco', - pkg: grunt.file.readJSON('package.json'), - banner: - '/*! <%= pkg.title || pkg.name %> - v<%= buildVersion %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + - '<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' + - ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;\n' + - ' * Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n */\n', - src: { - js: ['src/**/*.js', 'src/*.js'], - common: ['src/common/**/*.js'], - specs: ['test/**/*.spec.js'], - scenarios: ['test/**/*.scenario.js'], - samples: ['sample files/*.js'], - html: ['src/index.html','src/install.html'], - - everything:['src/**/*.*', 'test/**/*.*', 'docs/**/*.*'], - - tpl: { - app: ['src/views/**/*.html'], - common: ['src/common/**/*.tpl.html'] - }, - less: ['src/less/belle.less'], // recess:build doesn't accept ** in its file patterns - prod: ['<%= distdir %>/js/*.js'] - }, - - clean: ['<%= distdir %>/*'], - - copy: { - - /* Copies over the files downloaded by bower - bower: { - files: [ - { dest: '<%= distdir %>/lib/typeahead/typeahead.bundle.min.js', src: '<%= bowerfiles %>/typeahead.js/dist/typeahead.bundle.min.js' } - ] - }, - */ - - assets: { - files: [{ dest: '<%= distdir %>/assets', src : '**', expand: true, cwd: 'src/assets/' }] - }, - - installer: { - files: [{ dest: '<%= distdir %>/views/install', src : '**/*.html', expand: true, cwd: 'src/installer/steps' }] - }, - - vendor: { - files: [{ dest: '<%= distdir %>/lib', src : '**', expand: true, cwd: 'lib/' }] - }, - views: { - files: [{ dest: '<%= distdir %>/views', src : ['**/*.*', '!**/*.controller.js'], expand: true, cwd: 'src/views/' }] - }, - app: { - files: [ - { dest: '<%= distdir %>/js', src : '*.js', expand: true, cwd: 'src/' } - ] - }, - mocks: { - files: [{ dest: '<%= distdir %>/js', src : '*.js', expand: true, cwd: 'src/common/mocks/' }] - }, - vs: { - files: [ - //everything except the index.html root file! - //then we need to figure out how to not copy all the test stuff either!? - { dest: '<%= vsdir %>/assets', src: '**', expand: true, cwd: '<%= distdir %>/assets' }, - { dest: '<%= vsdir %>/js', src: '**', expand: true, cwd: '<%= distdir %>/js' }, - { dest: '<%= vsdir %>/lib', src: '**', expand: true, cwd: '<%= distdir %>/lib' }, - { dest: '<%= vsdir %>/views', src: '**', expand: true, cwd: '<%= distdir %>/views' } - ] - }, - - packages: { - files: [{ dest: '<%= vsdir %>/../App_Plugins', src : '**', expand: true, cwd: 'src/packages/' }] - } - }, - - karma: { - unit: { configFile: 'test/config/karma.conf.js', keepalive: true }, - e2e: { configFile: 'test/config/e2e.js', keepalive: true }, - watch: { configFile: 'test/config/unit.js', singleRun:false, autoWatch: true, keepalive: true } - }, - - concat:{ - index: { - src: ['src/index.html'], - dest: '<%= distdir %>/index.html', - options: { - process: true - } - }, - install: { - src: ['src/installer/installer.html'], - dest: '<%= distdir %>/installer.html', - options: { - process: true - } - }, - - installJs: { - src: ['src/installer/**/*.js'], - dest: '<%= distdir %>/js/umbraco.installer.js', - options: { - banner: "<%= banner %>\n(function() { \n\n angular.module('umbraco.install', []); \n", - footer: "\n\n})();" - } - }, - controllers: { - src:['src/controllers/**/*.controller.js','src/views/**/*.controller.js'], - dest: '<%= distdir %>/js/umbraco.controllers.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - }, - services: { - src:['src/common/services/*.js'], - dest: '<%= distdir %>/js/umbraco.services.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - }, - security: { - src:['src/common/security/*.js'], - dest: '<%= distdir %>/js/umbraco.security.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - }, - resources: { - src:['src/common/resources/*.js'], - dest: '<%= distdir %>/js/umbraco.resources.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - }, - testing: { - src:['src/common/mocks/*/*.js'], - dest: '<%= distdir %>/js/umbraco.testing.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - }, - directives: { - src:['src/common/directives/**/*.js'], - dest: '<%= distdir %>/js/umbraco.directives.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } - }, - filters: { - src:['src/common/filters/*.js'], - dest: '<%= distdir %>/js/umbraco.filters.js', - options: { - banner: "<%= banner %>\n(function() { \n\n", - footer: "\n\n})();" - } + bower.commands.install(undefined, {}, { interactive: false }) + .on('log', function (data) { + grunt.log.write(data.message + "\n"); + }) + .on('error', function (data) { + grunt.log.write(data.message + "\n"); + done(false); + }) + .on('end', function (data) { + done(); + }); } - }, + ); - uglify: { - options: { - mangle: true - }, - combine: { - files: { - '<%= distdir %>/js/umbraco.min.js': ['<%= distdir %>/js/umbraco.*.js'] - } - } - }, - recess: { - build: { - files: { - '<%= distdir %>/assets/css/<%= pkg.name %>.css': - ['<%= src.less %>'] }, - options: { - compile: true - } - }, - installer: { - files: { - '<%= distdir %>/assets/css/installer.css': - ['src/less/installer.less'] }, - options: { - compile: true - } - }, - min: { - files: { - '<%= distdir %>/assets/css/<%= pkg.name %>.css': ['<%= src.less %>'] + // Project configuration. + grunt.initConfig({ + buildVersion: grunt.option('buildversion') || '7', + connect: { + devserver: { + options: { + port: 9990, + hostname: '0.0.0.0', + base: './build', + middleware: function (connect, options) { + return [ + //uncomment to enable CSP + // util.csp(), + //util.rewrite(), + connect.favicon('images/favicon.ico'), + connect.static(options.base), + connect.directory(options.base) + ]; + } + } + }, + testserver: {} }, - options: { - compile: true, - compress: true + + open: { + dev: { + path: 'http://localhost:9990/belle/' + } + }, + + distdir: 'build/belle', + bowerfiles: 'bower_components', + vsdir: '../Umbraco.Web.UI/umbraco', + pkg: grunt.file.readJSON('package.json'), + banner: + '/*! <%= pkg.title || pkg.name %> - v<%= buildVersion %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + + '<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' + + ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;\n' + + ' * Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n */\n', + src: { + js: ['src/**/*.js', 'src/*.js'], + common: ['src/common/**/*.js'], + specs: ['test/**/*.spec.js'], + scenarios: ['test/**/*.scenario.js'], + samples: ['sample files/*.js'], + html: ['src/index.html', 'src/install.html'], + + everything: ['src/**/*.*', 'test/**/*.*', 'docs/**/*.*'], + + tpl: { + app: ['src/views/**/*.html'], + common: ['src/common/**/*.tpl.html'] + }, + less: ['src/less/belle.less'], // recess:build doesn't accept ** in its file patterns + prod: ['<%= distdir %>/js/*.js'] + }, + + clean: ['<%= distdir %>/*'], + + copy: { + + assets: { + files: [{ dest: '<%= distdir %>/assets', src: '**', expand: true, cwd: 'src/assets/' }] + }, + + installer: { + files: [{ dest: '<%= distdir %>/views/install', src: '**/*.html', expand: true, cwd: 'src/installer/steps' }] + }, + + vendor: { + files: [{ dest: '<%= distdir %>/lib', src: '**', expand: true, cwd: 'lib/' }] + }, + views: { + files: [{ dest: '<%= distdir %>/views', src: ['**/*.*', '!**/*.controller.js'], expand: true, cwd: 'src/views/' }] + }, + app: { + files: [ + { dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/' } + ] + }, + mocks: { + files: [{ dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/common/mocks/' }] + }, + vs: { + files: [ + //everything except the index.html root file! + //then we need to figure out how to not copy all the test stuff either!? + { dest: '<%= vsdir %>/assets', src: '**', expand: true, cwd: '<%= distdir %>/assets' }, + { dest: '<%= vsdir %>/js', src: '**', expand: true, cwd: '<%= distdir %>/js' }, + { dest: '<%= vsdir %>/lib', src: '**', expand: true, cwd: '<%= distdir %>/lib' }, + { dest: '<%= vsdir %>/views', src: '**', expand: true, cwd: '<%= distdir %>/views' } + ] + }, + + packages: { + files: [{ dest: '<%= vsdir %>/../App_Plugins', src: '**', expand: true, cwd: 'src/packages/' }] + } + }, + + karma: { + unit: { configFile: 'test/config/karma.conf.js', keepalive: true }, + e2e: { configFile: 'test/config/e2e.js', keepalive: true }, + watch: { configFile: 'test/config/unit.js', singleRun: false, autoWatch: true, keepalive: true } + }, + + concat: { + index: { + src: ['src/index.html'], + dest: '<%= distdir %>/index.html', + options: { + process: true + } + }, + install: { + src: ['src/installer/installer.html'], + dest: '<%= distdir %>/installer.html', + options: { + process: true + } + }, + + installJs: { + src: ['src/installer/**/*.js'], + dest: '<%= distdir %>/js/umbraco.installer.js', + options: { + banner: "<%= banner %>\n(function() { \n\n angular.module('umbraco.install', []); \n", + footer: "\n\n})();" + } + }, + controllers: { + src: ['src/controllers/**/*.controller.js', 'src/views/**/*.controller.js'], + dest: '<%= distdir %>/js/umbraco.controllers.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + }, + services: { + src: ['src/common/services/*.js'], + dest: '<%= distdir %>/js/umbraco.services.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + }, + security: { + src: ['src/common/security/*.js'], + dest: '<%= distdir %>/js/umbraco.security.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + }, + resources: { + src: ['src/common/resources/*.js'], + dest: '<%= distdir %>/js/umbraco.resources.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + }, + testing: { + src: ['src/common/mocks/*/*.js'], + dest: '<%= distdir %>/js/umbraco.testing.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + }, + directives: { + src: ['src/common/directives/**/*.js'], + dest: '<%= distdir %>/js/umbraco.directives.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + }, + filters: { + src: ['src/common/filters/*.js'], + dest: '<%= distdir %>/js/umbraco.filters.js', + options: { + banner: "<%= banner %>\n(function() { \n\n", + footer: "\n\n})();" + } + } + }, + + uglify: { + options: { + mangle: true + }, + combine: { + files: { + '<%= distdir %>/js/umbraco.min.js': ['<%= distdir %>/js/umbraco.*.js'] + } + } + }, + + recess: { + build: { + files: { + '<%= distdir %>/assets/css/<%= pkg.name %>.css': + ['<%= src.less %>'] + }, + options: { + compile: true + } + }, + installer: { + files: { + '<%= distdir %>/assets/css/installer.css': + ['src/less/installer.less'] + }, + options: { + compile: true + } + }, + min: { + files: { + '<%= distdir %>/assets/css/<%= pkg.name %>.css': + ['<%= src.less %>'] + }, + options: { + compile: true, + compress: true + } + } + }, + + + watch: { + css: { + files: '**/*.less', + tasks: ['watch-less', 'timestamp'], + options: { + livereload: true, + }, + }, + js: { + files: ['src/**/*.js', 'src/*.js'], + tasks: ['watch-js', 'timestamp'], + }, + test: { + files: ['test/**/*.js'], + tasks: ['watch-test', 'timestamp'], + }, + installer: { + files: ['src/installer/**/*.*'], + tasks: ['watch-installer', 'timestamp'], + }, + html: { + files: ['src/views/**/*.html', 'src/*.html'], + tasks: ['watch-html', 'timestamp'] + }, + + packages: { + files: 'src/packages/**/*.*', + tasks: ['watch-packages', 'timestamp'], + } + }, + + + ngdocs: { + options: { + dest: 'docs/api', + startPage: '/api', + title: "Umbraco 7", + html5Mode: false, + }, + api: { + src: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'], + title: 'API Documentation' + }, + tutorials: { + src: ['docs/src/tutorials/**/*.ngdoc'], + title: 'Tutorials' + } + }, + + jshint: { + dev: { + files: { + src: ['<%= src.common %>', '<%= src.specs %>', '<%= src.scenarios %>', '<%= src.samples %>'] + }, + options: { + curly: true, + eqeqeq: true, + immed: true, + latedef: true, + newcap: true, + noarg: true, + sub: true, + boss: true, + //NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete + es5: true, + eqnull: true, + //NOTE: we need to use eval sometimes so ignore it + evil: true, + //NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those + scripturl: true, + //NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used + smarttabs: true, + globals: {} + } + }, + build: { + files: { + src: ['<%= src.prod %>'] + }, + options: { + curly: true, + eqeqeq: true, + immed: true, + latedef: true, + newcap: true, + noarg: true, + sub: true, + boss: true, + //NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete + es5: true, + eqnull: true, + //NOTE: we need to use eval sometimes so ignore it + evil: true, + //NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those + scripturl: true, + //NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used + smarttabs: true, + globalstrict: true, + globals: { $: false, jQuery: false, define: false, require: false, window: false } + } + } } - } - }, - - - watch:{ - css: { - files: '**/*.less', - tasks: ['watch-less', 'timestamp'], - options: { - livereload: true, - }, - }, - js: { - files: ['src/**/*.js', 'src/*.js'], - tasks: ['watch-js', 'timestamp'], - }, - test: { - files: ['test/**/*.js'], - tasks: ['watch-test', 'timestamp'], - }, - installer: { - files: ['src/installer/**/*.*'], - tasks: ['watch-installer', 'timestamp'], - }, - html: { - files: ['src/views/**/*.html', 'src/*.html'], - tasks:['watch-html','timestamp'] - }, - - packages: { - files: 'src/packages/**/*.*', - tasks: ['watch-packages', 'timestamp'], - } - }, - - - ngdocs: { - options: { - dest: 'docs/api', - startPage: '/api', - title: "Umbraco 7", - html5Mode: false, - }, - api: { - src: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'], - title: 'API Documentation' - }, - tutorials: { - src: ['docs/src/tutorials/**/*.ngdoc'], - title: 'Tutorials' - } - }, - - jshint:{ - dev:{ - files: { - src: ['<%= src.common %>', '<%= src.specs %>', '<%= src.scenarios %>', '<%= src.samples %>'] - }, - options:{ - curly:true, - eqeqeq:true, - immed:true, - latedef:true, - newcap:true, - noarg:true, - sub:true, - boss: true, - //NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete - es5: true, - eqnull: true, - //NOTE: we need to use eval sometimes so ignore it - evil: true, - //NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those - scripturl: true, - //NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used - smarttabs: true, - globals:{} - } - }, - build:{ - files: { - src: ['<%= src.prod %>'] - }, - options:{ - curly:true, - eqeqeq:true, - immed:true, - latedef:true, - newcap:true, - noarg:true, - sub:true, - boss: true, - //NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete - es5: true, - eqnull: true, - //NOTE: we need to use eval sometimes so ignore it - evil: true, - //NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those - scripturl: true, - //NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used - smarttabs: true, - globalstrict:true, - globals:{$:false, jQuery:false,define:false,require:false,window:false} - } - } - } - }); + }); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-copy'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-recess'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-recess'); - grunt.loadNpmTasks('grunt-karma'); + grunt.loadNpmTasks('grunt-karma'); - grunt.loadNpmTasks('grunt-open'); - grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-open'); + grunt.loadNpmTasks('grunt-contrib-connect'); - grunt.loadNpmTasks('grunt-ngdocs'); + grunt.loadNpmTasks('grunt-ngdocs'); }; From ad3502d438c29f177483882f0f09dfb7e7c4a749 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 18 Jun 2014 19:28:16 +1000 Subject: [PATCH 050/189] Fixes: U4-5004 Pressing escape in the login screen causes a blank screen --- .../src/common/services/dialog.service.js | 26 +++++++++++++++---- .../src/common/services/user.service.js | 4 +++ .../src/controllers/navigation.controller.js | 1 - 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js b/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js index 85d9878e4d..5418c41871 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js @@ -37,7 +37,13 @@ angular.module('umbraco.services') function removeAllDialogs(args) { for (var i = 0; i < dialogs.length; i++) { var dialog = dialogs[i]; - dialog.close(args); + + //very special flag which means that global events cannot close this dialog - currently only used on the login + // dialog since it's special and cannot be closed without logging in. + if (!dialog.manualClose) { + dialog.close(args); + } + } } @@ -192,6 +198,7 @@ angular.module('umbraco.services') }; scope.swipeHide = function (e) { + if (appState.getGlobalState("touchDevice")) { var selection = window.getSelection(); if (selection.type !== "Range") { @@ -222,7 +229,15 @@ angular.module('umbraco.services') // You CANNOT call show() after you call hide(). hide = close, they are the same thing and once // a dialog is closed it's resources are disposed of. scope.show = function () { - dialog.element.modal('show'); + if (dialog.manualClose === true) { + //show and configure that the keyboard events are not enabled on this modal + dialog.element.modal({ keyboard: false }); + } + else { + //just show normally + dialog.element.modal('show'); + } + }; scope.select = function (item) { @@ -249,12 +264,13 @@ angular.module('umbraco.services') $('input[autofocus]', dialog.element).first().trigger('focus'); }); + dialog.scope = scope; + //Autoshow if (dialog.show) { - dialog.element.modal('show'); + scope.show(); } - - dialog.scope = scope; + }); //Return the modal object outside of the promise! diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 0094aec8ad..1a0762a161 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -11,6 +11,10 @@ angular.module('umbraco.services') function openLoginDialog(isTimedOut) { if (!loginDialog) { loginDialog = dialogService.open({ + + //very special flag which means that global events cannot close this dialog + manualClose: true, + template: 'views/common/dialogs/login.html', modalClass: "login-overlay", animation: "slide", diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index 2237330bc0..c0b7dddce9 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -40,7 +40,6 @@ function NavigationController($scope, $rootScope, $location, $log, $routeParams, }); //trigger dialods with a hotkey: - //TODO: Unfortunately this will also close the login dialog. keyboardService.bind("esc", function () { eventsService.emit("app.closeDialogs"); }); From bb655afd7292bde92182611ef160d75751506f9b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Jun 2014 17:39:31 +1000 Subject: [PATCH 051/189] Fixes: U4-5080 Error deleting property type that references tags --- src/umbraco.cms/businesslogic/propertytype/propertytype.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/umbraco.cms/businesslogic/propertytype/propertytype.cs b/src/umbraco.cms/businesslogic/propertytype/propertytype.cs index 7a5804dc1f..df7cc04c38 100644 --- a/src/umbraco.cms/businesslogic/propertytype/propertytype.cs +++ b/src/umbraco.cms/businesslogic/propertytype/propertytype.cs @@ -382,6 +382,9 @@ namespace umbraco.cms.businesslogic.propertytype // Delete all properties of propertytype CleanPropertiesOnDeletion(_contenttypeid); + //delete tag refs + SqlHelper.ExecuteNonQuery("Delete from cmsTagRelationship where propertyTypeId = " + Id); + // Delete PropertyType .. SqlHelper.ExecuteNonQuery("Delete from cmsPropertyType where id = " + Id); From 6b5cb3a9cf26cf74de5de46080dcf211836c3ade Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Jun 2014 18:04:32 +1000 Subject: [PATCH 052/189] Fixes: U4-4947 Tag Editor twitter typeahead --- .../propertyeditors/tags/tags.controller.js | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js index be97b0b962..46e38c78f2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js @@ -2,6 +2,8 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.TagsController", function ($rootScope, $scope, $log, assetsService, umbRequestHelper, angularHelper, $timeout, $element) { + var $typeahead; + $scope.isLoading = true; $scope.tagToAdd = ""; @@ -18,9 +20,13 @@ angular.module("umbraco") } else { //it is csv - $scope.currentTags = $scope.model.value.split(","); + if (!$scope.model.value) { + $scope.currentTags = []; + } + else { + $scope.currentTags = $scope.model.value.split(","); + } } - } //Helper method to add a tag on enter or on typeahead select @@ -43,6 +49,9 @@ angular.module("umbraco") //we need to use jquery because typeahead duplicates the text box addTag($scope.tagToAdd); $scope.tagToAdd = ""; + //this clears the value stored in typeahead so it doesn't try to add the text again + // http://issues.umbraco.org/issue/U4-4947 + $typeahead.typeahead('val', ''); } } @@ -71,7 +80,12 @@ angular.module("umbraco") } else { //it is csv - $scope.currentTags = $scope.model.value.split(","); + if (!$scope.model.value) { + $scope.currentTags = []; + } + else { + $scope.currentTags = $scope.model.value.split(","); + } } }; @@ -112,8 +126,9 @@ angular.module("umbraco") tagsHound.initialize(); //configure the type ahead - $timeout(function() { - $element.find('.tags-' + $scope.model.alias).typeahead( + $timeout(function () { + + $typeahead = $element.find('.tags-' + $scope.model.alias).typeahead( { //This causes some strangeness as it duplicates the textbox, best leave off for now. hint: false, From e8f7f77bb6182168ca95fbc8b981a01ee04b4717 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Jun 2014 14:34:21 +1000 Subject: [PATCH 053/189] Fixes: U4-581 Automatic publishing not working in load balanced setup - added some more convention and configuration to distributed calls so that servers are aware of the master and how to call into themselves for scheduled tasks, ping and scheduled publishing. Will need to update the docs on LB regarding this too. Cleaned up the code that does the scheduling and separates it into proper segments. Obsoletes the old presentation classes that were doing it. --- .../Publishing/ScheduledPublisher.cs | 61 +++++++++ .../Sync/CurrentServerEnvironmentStatus.cs | 28 +++++ .../Sync/ServerEnvironmentHelper.cs | 116 ++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 3 + src/Umbraco.Tests/TypeHelperTests.cs | 3 +- .../config/umbracoSettings.Release.config | 41 ++++++- .../config/umbracoSettings.config | 47 ++++++- src/Umbraco.Web/LegacyScheduledTasks.cs | 107 ---------------- src/Umbraco.Web/Scheduling/KeepAlive.cs | 35 ++++++ src/Umbraco.Web/Scheduling/LogScrubber.cs | 77 ++++++++++++ .../Scheduling/ScheduledPublishing.cs | 47 +++++++ src/Umbraco.Web/Scheduling/ScheduledTasks.cs | 98 +++++++++++++++ src/Umbraco.Web/Scheduling/Scheduler.cs | 82 +++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 7 +- src/Umbraco.Web/WebBootManager.cs | 3 +- .../WebServices/ScheduledPublishController.cs | 59 +++++++++ .../umbraco.presentation/keepAliveService.cs | 6 +- .../umbraco.presentation/publishingService.cs | 63 +++------- 18 files changed, 711 insertions(+), 172 deletions(-) create mode 100644 src/Umbraco.Core/Publishing/ScheduledPublisher.cs create mode 100644 src/Umbraco.Core/Sync/CurrentServerEnvironmentStatus.cs create mode 100644 src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs delete mode 100644 src/Umbraco.Web/LegacyScheduledTasks.cs create mode 100644 src/Umbraco.Web/Scheduling/KeepAlive.cs create mode 100644 src/Umbraco.Web/Scheduling/LogScrubber.cs create mode 100644 src/Umbraco.Web/Scheduling/ScheduledPublishing.cs create mode 100644 src/Umbraco.Web/Scheduling/ScheduledTasks.cs create mode 100644 src/Umbraco.Web/Scheduling/Scheduler.cs create mode 100644 src/Umbraco.Web/WebServices/ScheduledPublishController.cs diff --git a/src/Umbraco.Core/Publishing/ScheduledPublisher.cs b/src/Umbraco.Core/Publishing/ScheduledPublisher.cs new file mode 100644 index 0000000000..45492423ab --- /dev/null +++ b/src/Umbraco.Core/Publishing/ScheduledPublisher.cs @@ -0,0 +1,61 @@ +using System; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Publishing +{ + /// + /// Used to perform scheduled publishing/unpublishing + /// + internal class ScheduledPublisher + { + private readonly IContentService _contentService; + + public ScheduledPublisher(IContentService contentService) + { + _contentService = contentService; + } + + public void CheckPendingAndProcess() + { + foreach (var d in _contentService.GetContentForRelease()) + { + try + { + d.ReleaseDate = null; + var result = _contentService.SaveAndPublishWithStatus(d, (int)d.GetWriterProfile().Id); + if (result.Success == false) + { + if (result.Exception != null) + { + LogHelper.Error("Could not published the document (" + d.Id + ") based on it's scheduled release, status result: " + result.Result.StatusType, result.Exception); + } + else + { + LogHelper.Warn("Could not published the document (" + d.Id + ") based on it's scheduled release. Status result: " + result.Result.StatusType); + } + } + } + catch (Exception ee) + { + LogHelper.Error(string.Format("Error publishing node {0}", d.Id), ee); + throw; + } + } + foreach (var d in _contentService.GetContentForExpiration()) + { + try + { + d.ExpireDate = null; + _contentService.UnPublish(d, (int)d.GetWriterProfile().Id); + } + catch (Exception ee) + { + LogHelper.Error(string.Format("Error unpublishing node {0}", d.Id), ee); + throw; + } + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/CurrentServerEnvironmentStatus.cs b/src/Umbraco.Core/Sync/CurrentServerEnvironmentStatus.cs new file mode 100644 index 0000000000..95305b7cdc --- /dev/null +++ b/src/Umbraco.Core/Sync/CurrentServerEnvironmentStatus.cs @@ -0,0 +1,28 @@ +namespace Umbraco.Core.Sync +{ + /// + /// The current status of the server in the Umbraco environment + /// + internal enum CurrentServerEnvironmentStatus + { + /// + /// If the current server is detected as the 'master' server when configured in a load balanced scenario + /// + Master, + + /// + /// If the current server is detected as a 'slave' server when configured in a load balanced scenario + /// + Slave, + + /// + /// If the current server cannot be detected as a 'slave' or 'master' when configured in a load balanced scenario + /// + Unknown, + + /// + /// If load balancing is not enabled and this is the only server in the umbraco environment + /// + Single + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs new file mode 100644 index 0000000000..c5363b494f --- /dev/null +++ b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs @@ -0,0 +1,116 @@ +using System; +using System.Linq; +using System.Web; +using System.Xml; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Sync +{ + /// + /// A helper used to determine the current server environment status + /// + internal static class ServerEnvironmentHelper + { + /// + /// Returns the current umbraco base url for the current server depending on it's environment + /// status. This will attempt to determine the internal umbraco base url that can be used by the current + /// server to send a request to itself if it is in a load balanced environment. + /// + /// The full base url including schema (i.e. http://myserver:80/umbraco ) + public static string GetCurrentServerUmbracoBaseUrl() + { + var status = GetStatus(); + + if (status == CurrentServerEnvironmentStatus.Single) + { + //if it's a single install, then the base url has to be the first url registered + return ApplicationContext.Current.OriginalRequestUrl; + } + + var servers = UmbracoSettings.DistributionServers; + + var nodes = servers.SelectNodes("./server"); + if (nodes == null) + { + //cannot be determined, then the base url has to be the first url registered + return ApplicationContext.Current.OriginalRequestUrl; + } + + var xmlNodes = nodes.Cast().ToList(); + + foreach (var xmlNode in xmlNodes) + { + var appId = xmlNode.AttributeValue("appId"); + var serverName = xmlNode.AttributeValue("serverName"); + + if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace()) + { + continue; + } + + if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId)) + || (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName))) + { + //match by appId or computer name! return the url configured + return string.Format("{0}://{1}:{2}/{3}", + xmlNode.AttributeValue("forceProtocol").IsNullOrWhiteSpace() ? "http" : xmlNode.AttributeValue("forceProtocol"), + xmlNode.InnerText, + xmlNode.AttributeValue("forcePortnumber").IsNullOrWhiteSpace() ? "80" : xmlNode.AttributeValue("forcePortnumber"), + IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/')); + } + } + + //cannot be determined, then the base url has to be the first url registered + return ApplicationContext.Current.OriginalRequestUrl; + } + + /// + /// Returns the current environment status for the current server + /// + /// + public static CurrentServerEnvironmentStatus GetStatus() + { + if (UmbracoSettings.UseDistributedCalls == false) + { + return CurrentServerEnvironmentStatus.Single; + } + + var servers = UmbracoSettings.DistributionServers; + + var nodes = servers.SelectNodes("./server"); + if (nodes == null) + { + return CurrentServerEnvironmentStatus.Unknown; + } + + var master = nodes.Cast().FirstOrDefault(); + + if (master == null) + { + return CurrentServerEnvironmentStatus.Unknown; + } + + //we determine master/slave based on the first server registered + //TODO: In v7 we have publicized ServerRegisterResolver - we won't be able to determine this based on that + // but we'd need to change the IServerAddress interfaces which is breaking. + + var appId = master.AttributeValue("appId"); + var serverName = master.AttributeValue("serverName"); + + if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace()) + { + return CurrentServerEnvironmentStatus.Unknown; + } + + if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId)) + || (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName))) + { + //match by appdid or server name! + return CurrentServerEnvironmentStatus.Master; + } + + return CurrentServerEnvironmentStatus.Slave; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 89752d47ed..d0d0cd573b 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -770,6 +770,7 @@ + @@ -830,7 +831,9 @@ + + diff --git a/src/Umbraco.Tests/TypeHelperTests.cs b/src/Umbraco.Tests/TypeHelperTests.cs index f637b1ff20..6e9ee10152 100644 --- a/src/Umbraco.Tests/TypeHelperTests.cs +++ b/src/Umbraco.Tests/TypeHelperTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core; using Umbraco.Tests.PartialTrust; using Umbraco.Web; using Umbraco.Web.Cache; +using Umbraco.Web.Scheduling; using UmbracoExamine; using umbraco; using umbraco.presentation; @@ -67,7 +68,7 @@ namespace Umbraco.Tests Assert.AreEqual(typeof(UmbracoEventManager), t5.Result); var t6 = TypeHelper.GetLowestBaseType(typeof (IApplicationEventHandler), - typeof (LegacyScheduledTasks), + typeof (Scheduler), typeof(CacheRefresherEventHandler)); Assert.IsTrue(t6.Success); Assert.AreEqual(typeof(IApplicationEventHandler), t6.Result); diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index 0dc21ae2fc..0851581225 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -223,16 +223,47 @@ - + 0 + + + - - - - + + + diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index a089b97608..0a60537af5 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -223,16 +223,51 @@ - - + + 0 + + + - - - - + + + + localhost + umb1.dev + umb2.dev + diff --git a/src/Umbraco.Web/LegacyScheduledTasks.cs b/src/Umbraco.Web/LegacyScheduledTasks.cs deleted file mode 100644 index 8b4ccce8e9..0000000000 --- a/src/Umbraco.Web/LegacyScheduledTasks.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Web; -using System.Web.Caching; -using Umbraco.Core; -using Umbraco.Core.Logging; -using global::umbraco.BusinessLogic; - -namespace Umbraco.Web -{ - // note: has to be public to be detected by the resolver - // if it's made internal, which would make more sense, then it's not detected - // and it needs to be manually registered - which we want to avoid, in order - // to be as unobtrusive as possible - - internal sealed class LegacyScheduledTasks : ApplicationEventHandler - { - Timer _pingTimer; - Timer _publishingTimer; - CacheItemRemovedCallback _onCacheRemove; - - protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, Core.ApplicationContext applicationContext) - { - if (umbracoApplication.Context == null) - return; - - // time to setup the tasks - - // these are the legacy tasks - // just copied over here for backward compatibility - // of course we should have a proper scheduler, see #U4-809 - - // ping/keepalive - _pingTimer = new Timer(new TimerCallback(global::umbraco.presentation.keepAliveService.PingUmbraco), applicationContext, 60000, 300000); - - // (un)publishing _and_ also run scheduled tasks (!) - _publishingTimer = new Timer(new TimerCallback(global::umbraco.presentation.publishingService.CheckPublishing), applicationContext, 30000, 60000); - - // log scrubbing - AddTask(LOG_SCRUBBER_TASK_NAME, GetLogScrubbingInterval()); - } - - #region Log Scrubbing - - // this is a raw copy of the legacy code in all its uglyness - - const string LOG_SCRUBBER_TASK_NAME = "ScrubLogs"; - - private static int GetLogScrubbingInterval() - { - int interval = 24 * 60 * 60; //24 hours - try - { - if (global::umbraco.UmbracoSettings.CleaningMiliseconds > -1) - interval = global::umbraco.UmbracoSettings.CleaningMiliseconds; - } - catch (Exception e) - { - LogHelper.Error("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e); - } - return interval; - } - - private static int GetLogScrubbingMaximumAge() - { - int maximumAge = 24 * 60 * 60; - try - { - if (global::umbraco.UmbracoSettings.MaxLogAge > -1) - maximumAge = global::umbraco.UmbracoSettings.MaxLogAge; - } - catch (Exception e) - { - LogHelper.Error("Unable to locate a log scrubbing maximum age. Defaulting to 24 horus", e); - } - return maximumAge; - - } - - private void AddTask(string name, int seconds) - { - _onCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved); - HttpRuntime.Cache.Insert(name, seconds, null, - DateTime.Now.AddSeconds(seconds), System.Web.Caching.Cache.NoSlidingExpiration, - CacheItemPriority.NotRemovable, _onCacheRemove); - } - - public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r) - { - if (k.Equals(LOG_SCRUBBER_TASK_NAME)) - { - ScrubLogs(); - } - AddTask(k, Convert.ToInt32(v)); - } - - private static void ScrubLogs() - { - Log.CleanLogs(GetLogScrubbingMaximumAge()); - } - - #endregion - } -} diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs new file mode 100644 index 0000000000..443c3727e1 --- /dev/null +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -0,0 +1,35 @@ +using System; +using System.Net; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Sync; + +namespace Umbraco.Web.Scheduling +{ + internal class KeepAlive + { + public static void Start(object sender) + { + //NOTE: sender will be the umbraco ApplicationContext + + var appContext = sender as ApplicationContext; + if (appContext == null) return; + + var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(); + + var url = string.Format("{0}/ping.aspx", umbracoBaseUrl); + + try + { + using (var wc = new WebClient()) + { + wc.DownloadString(url); + } + } + catch (Exception ee) + { + LogHelper.Error("Error in ping", ee); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs new file mode 100644 index 0000000000..cea4d1b01c --- /dev/null +++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs @@ -0,0 +1,77 @@ +using System; +using System.Web; +using System.Web.Caching; +using umbraco.BusinessLogic; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.Scheduling +{ + //TODO: Refactor this to use a normal scheduling processor! + + internal class LogScrubber + { + // this is a raw copy of the legacy code in all its uglyness + + CacheItemRemovedCallback _onCacheRemove; + const string LogScrubberTaskName = "ScrubLogs"; + + public void Start() + { + // log scrubbing + AddTask(LogScrubberTaskName, GetLogScrubbingInterval()); + } + + private static int GetLogScrubbingInterval() + { + int interval = 24 * 60 * 60; //24 hours + try + { + if (global::umbraco.UmbracoSettings.CleaningMiliseconds > -1) + interval = global::umbraco.UmbracoSettings.CleaningMiliseconds; + } + catch (Exception e) + { + LogHelper.Error("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e); + } + return interval; + } + + private static int GetLogScrubbingMaximumAge() + { + int maximumAge = 24 * 60 * 60; + try + { + if (global::umbraco.UmbracoSettings.MaxLogAge > -1) + maximumAge = global::umbraco.UmbracoSettings.MaxLogAge; + } + catch (Exception e) + { + LogHelper.Error("Unable to locate a log scrubbing maximum age. Defaulting to 24 horus", e); + } + return maximumAge; + + } + + private void AddTask(string name, int seconds) + { + _onCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved); + HttpRuntime.Cache.Insert(name, seconds, null, + DateTime.Now.AddSeconds(seconds), System.Web.Caching.Cache.NoSlidingExpiration, + CacheItemPriority.NotRemovable, _onCacheRemove); + } + + public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r) + { + if (k.Equals(LogScrubberTaskName)) + { + ScrubLogs(); + } + AddTask(k, Convert.ToInt32(v)); + } + + private static void ScrubLogs() + { + Log.CleanLogs(GetLogScrubbingMaximumAge()); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs new file mode 100644 index 0000000000..7a249ce4ac --- /dev/null +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -0,0 +1,47 @@ +using System; +using System.Diagnostics; +using System.Net; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Publishing; +using Umbraco.Core.Sync; + +namespace Umbraco.Web.Scheduling +{ + internal class ScheduledPublishing + { + private static bool _isPublishingRunning = false; + + public void Start(object sender) + { + //NOTE: sender will be the umbraco ApplicationContext + + var appContext = sender as ApplicationContext; + if (appContext == null) return; + + if (_isPublishingRunning) return; + + _isPublishingRunning = true; + + try + { + var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(); + var url = string.Format("{0}/RestServices/ScheduledPublish/", umbracoBaseUrl); + using (var wc = new WebClient()) + { + var result = wc.UploadString(url, ""); + } + } + catch (Exception ee) + { + LogHelper.Error("An error occurred with the scheduled publishing", ee); + } + finally + { + _isPublishingRunning = false; + } + } + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs new file mode 100644 index 0000000000..9abf7d9f69 --- /dev/null +++ b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections; +using System.Net; +using System.Xml; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Publishing; +using Umbraco.Core.Sync; + +namespace Umbraco.Web.Scheduling +{ + //TODO: No scheduled task (i.e. URL) would be secured, so if people are actually using these each task + // would need to be a publicly available task (URL) which isn't really very good :( + + internal class ScheduledTasks + { + private static readonly Hashtable ScheduledTaskTimes = new Hashtable(); + private static bool _isPublishingRunning = false; + + public void Start(object sender) + { + //NOTE: sender will be the umbraco ApplicationContext + + if (_isPublishingRunning) return; + + _isPublishingRunning = true; + + try + { + ProcessTasks(); + } + catch (Exception ee) + { + LogHelper.Error("Error executing scheduled task", ee); + } + finally + { + _isPublishingRunning = false; + } + } + + private static void ProcessTasks() + { + + + var scheduledTasks = UmbracoSettings.ScheduledTasks; + if (scheduledTasks != null) + { + var tasks = scheduledTasks.SelectNodes("./task"); + if (tasks == null) return; + + foreach (XmlNode task in tasks) + { + var runTask = false; + if (ScheduledTaskTimes.ContainsKey(task.Attributes.GetNamedItem("alias").Value) == false) + { + runTask = true; + ScheduledTaskTimes.Add(task.Attributes.GetNamedItem("alias").Value, DateTime.Now); + } + // Add 1 second to timespan to compensate for differencies in timer + else if (new TimeSpan( + DateTime.Now.Ticks - ((DateTime)ScheduledTaskTimes[task.Attributes.GetNamedItem("alias").Value]).Ticks).TotalSeconds + 1 + >= int.Parse(task.Attributes.GetNamedItem("interval").Value)) + { + runTask = true; + ScheduledTaskTimes[task.Attributes.GetNamedItem("alias").Value] = DateTime.Now; + } + + if (runTask) + { + bool taskResult = GetTaskByHttp(task.Attributes.GetNamedItem("url").Value); + if (bool.Parse(task.Attributes.GetNamedItem("log").Value)) + LogHelper.Info(string.Format("{0} has been called with response: {1}", task.Attributes.GetNamedItem("alias").Value, taskResult)); + } + } + } + } + + private static bool GetTaskByHttp(string url) + { + var myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); + + try + { + using (var response = (HttpWebResponse)myHttpWebRequest.GetResponse()) + { + return response.StatusCode == HttpStatusCode.OK; + } + } + catch (Exception ex) + { + LogHelper.Error("An error occurred calling web task for url: " + url, ex); + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/Scheduler.cs b/src/Umbraco.Web/Scheduling/Scheduler.cs new file mode 100644 index 0000000000..75930fed4c --- /dev/null +++ b/src/Umbraco.Web/Scheduling/Scheduler.cs @@ -0,0 +1,82 @@ +using System.Threading; +using Umbraco.Core; +using Umbraco.Core.Sync; + +namespace Umbraco.Web.Scheduling +{ + /// + /// Used to do the scheduling for tasks, publishing, etc... + /// + /// + /// + /// TODO: Much of this code is legacy and needs to be updated, there are a few new/better ways to do scheduling + /// in a web project nowadays. + /// + /// + internal sealed class Scheduler : ApplicationEventHandler + { + private Timer _pingTimer; + private Timer _schedulingTimer; + private LogScrubber _scrubber; + + protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + if (umbracoApplication.Context == null) + return; + + // time to setup the tasks + + // these are the legacy tasks + // just copied over here for backward compatibility + // of course we should have a proper scheduler, see #U4-809 + + // ping/keepalive + _pingTimer = new Timer(KeepAlive.Start, applicationContext, 60000, 300000); + + // scheduled publishing/unpublishing + + _schedulingTimer = new Timer(PerformScheduling, applicationContext, 30000, 60000); + + //log scrubbing + _scrubber = new LogScrubber(); + _scrubber.Start(); + } + + /// + /// This performs all of the scheduling on the one timer + /// + /// + /// + /// No processing will be done if this server is a slave + /// + private static void PerformScheduling(object sender) + { + + //get the current server status to see if this server should execute the scheduled publishing + var serverStatus = ServerEnvironmentHelper.GetStatus(); + + switch (serverStatus) + { + case CurrentServerEnvironmentStatus.Single: + case CurrentServerEnvironmentStatus.Master: + case CurrentServerEnvironmentStatus.Unknown: + //if it's a single server install, a master or it cannot be determined + // then we will process the scheduling + + //do the scheduled publishing + var scheduledPublishing = new ScheduledPublishing(); + scheduledPublishing.Start(sender); + + //do the scheduled tasks + var scheduledTasks = new ScheduledTasks(); + scheduledTasks.Start(sender); + + break; + case CurrentServerEnvironmentStatus.Slave: + //do not process + break; + } + } + + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 49da691cd2..a8bcafd5f7 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -325,6 +325,10 @@ + + + + @@ -499,7 +503,7 @@ ASPXCodeBehind - + @@ -1782,6 +1786,7 @@ + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 05398a708c..5a313b2350 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -24,6 +24,7 @@ using Umbraco.Web.PropertyEditors; using Umbraco.Web.PropertyEditors.ValueConverters; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; +using Umbraco.Web.Scheduling; using Umbraco.Web.WebApi; using umbraco.BusinessLogic; using umbraco.presentation.cache; @@ -116,7 +117,7 @@ namespace Umbraco.Web protected override void InitializeApplicationEventsResolver() { base.InitializeApplicationEventsResolver(); - ApplicationEventsResolver.Current.AddType(); + ApplicationEventsResolver.Current.AddType(); //We need to remove these types because we've obsoleted them and we don't want them executing: ApplicationEventsResolver.Current.RemoveType(); } diff --git a/src/Umbraco.Web/WebServices/ScheduledPublishController.cs b/src/Umbraco.Web/WebServices/ScheduledPublishController.cs new file mode 100644 index 0000000000..bc2b5df18c --- /dev/null +++ b/src/Umbraco.Web/WebServices/ScheduledPublishController.cs @@ -0,0 +1,59 @@ +using System; +using System.Web.Mvc; +using umbraco; +using Umbraco.Core.Logging; +using Umbraco.Core.Publishing; +using Umbraco.Web.Mvc; + +namespace Umbraco.Web.WebServices +{ + //TODO: How to authenticate? + + /// + /// A REST controller used for running the scheduled publishing, this is called from the background worker timer + /// + public class ScheduledPublishController : UmbracoController + { + private static bool _isPublishingRunning = false; + + [HttpPost] + public JsonResult Index() + { + if (_isPublishingRunning) + return null; + _isPublishingRunning = true; + + try + { + // DO not run publishing if content is re-loading + if (content.Instance.isInitializing == false) + { + var publisher = new ScheduledPublisher(Services.ContentService); + publisher.CheckPendingAndProcess(); + } + + return Json(new + { + success = true + }); + + } + catch (Exception ee) + { + LogHelper.Error("Error executing scheduled task", ee); + + Response.StatusCode = 400; + + return Json(new + { + success = false, + message = ee.Message + }); + } + finally + { + _isPublishingRunning = false; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs b/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs index 4ab1646a9e..6d80d8bedd 100644 --- a/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs +++ b/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs @@ -7,9 +7,7 @@ using Umbraco.Core.Logging; namespace umbraco.presentation { - /// - /// Makes a call to /umbraco/ping.aspx which is used to keep the web app alive - /// + [Obsolete("This is no longer used and will be removed in future versions")] public class keepAliveService { //NOTE: sender will be the umbraco ApplicationContext @@ -20,6 +18,8 @@ namespace umbraco.presentation var appContext = (ApplicationContext) sender; + //TODO: This won't always work, in load balanced scenarios ping will not work because + // this original request url will be public and not internal to the server. var url = string.Format("http://{0}/ping.aspx", appContext.OriginalRequestUrl); try { diff --git a/src/Umbraco.Web/umbraco.presentation/publishingService.cs b/src/Umbraco.Web/umbraco.presentation/publishingService.cs index d22165d648..7bd85236e6 100644 --- a/src/Umbraco.Web/umbraco.presentation/publishingService.cs +++ b/src/Umbraco.Web/umbraco.presentation/publishingService.cs @@ -4,15 +4,15 @@ using System.Diagnostics; using System.Net; using System.Web; using System.Xml; +using Umbraco.Core; using Umbraco.Core.Logging; using umbraco.BusinessLogic; using umbraco.cms.businesslogic.web; +using Umbraco.Core.Publishing; namespace umbraco.presentation { - /// - /// Summary description for publishingService. - /// + [Obsolete("This is no longer used and will be removed in future versions")] public class publishingService { private static readonly Hashtable ScheduledTaskTimes = new Hashtable(); @@ -26,37 +26,10 @@ namespace umbraco.presentation _isPublishingRunning = true; try { - // DO not run publishing if content is re-loading - if(!content.Instance.isInitializing) - { - - foreach (var d in Document.GetDocumentsForRelease()) - { - try - { - d.ReleaseDate = DateTime.MinValue; //new DateTime(1, 1, 1); // Causes release date to be null - d.SaveAndPublish(d.User); - } - catch(Exception ee) - { - LogHelper.Error(string.Format("Error publishing node {0}", d.Id), ee); - } - } - foreach(Document d in Document.GetDocumentsForExpiration()) - { - try - { - d.ExpireDate = DateTime.MinValue; + //run the scheduled publishing - we need to determine if this server - d.UnPublish(); - } - catch (Exception ee) - { - LogHelper.Error(string.Format("Error unpublishing node {0}", d.Id), ee); - } - - } - } + var publisher = new ScheduledPublisher(ApplicationContext.Current.Services.ContentService); + publisher.CheckPendingAndProcess(); // run scheduled url tasks try @@ -70,7 +43,7 @@ namespace umbraco.presentation foreach (XmlNode task in tasks) { bool runTask = false; - if (!ScheduledTaskTimes.ContainsKey(task.Attributes.GetNamedItem("alias").Value)) + if (ScheduledTaskTimes.ContainsKey(task.Attributes.GetNamedItem("alias").Value) == false) { runTask = true; ScheduledTaskTimes.Add(task.Attributes.GetNamedItem("alias").Value, DateTime.Now); @@ -88,7 +61,7 @@ namespace umbraco.presentation if (runTask) { - bool taskResult = getTaskByHttp(task.Attributes.GetNamedItem("url").Value); + bool taskResult = GetTaskByHttp(task.Attributes.GetNamedItem("url").Value); if (bool.Parse(task.Attributes.GetNamedItem("log").Value)) LogHelper.Info(string.Format("{0} has been called with response: {1}", task.Attributes.GetNamedItem("alias").Value, taskResult)); } @@ -111,26 +84,20 @@ namespace umbraco.presentation } } - private static bool getTaskByHttp(string url) + private static bool GetTaskByHttp(string url) { var myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); HttpWebResponse myHttpWebResponse = null; try { - myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); - if(myHttpWebResponse.StatusCode == HttpStatusCode.OK) - { - myHttpWebResponse.Close(); - return true; - } - else - { - myHttpWebResponse.Close(); - return false; - } + using (myHttpWebResponse = (HttpWebResponse) myHttpWebRequest.GetResponse()) + { + return myHttpWebResponse.StatusCode == HttpStatusCode.OK; + } } - catch + catch (Exception ex) { + LogHelper.Error("An error occurred calling web task for url: " + url, ex); } finally { From 9158ea1ace8d958a11dd94dccaf13f0d05532059 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Jun 2014 14:45:45 +1000 Subject: [PATCH 054/189] fixes url returned in GetCurrentServerUmbracoBaseUrl --- src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs index c5363b494f..73dca4cc98 100644 --- a/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs +++ b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Sync if (status == CurrentServerEnvironmentStatus.Single) { //if it's a single install, then the base url has to be the first url registered - return ApplicationContext.Current.OriginalRequestUrl; + return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl); } var servers = UmbracoSettings.DistributionServers; @@ -34,7 +34,7 @@ namespace Umbraco.Core.Sync if (nodes == null) { //cannot be determined, then the base url has to be the first url registered - return ApplicationContext.Current.OriginalRequestUrl; + return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl); } var xmlNodes = nodes.Cast().ToList(); @@ -62,7 +62,7 @@ namespace Umbraco.Core.Sync } //cannot be determined, then the base url has to be the first url registered - return ApplicationContext.Current.OriginalRequestUrl; + return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl); } /// From 3af3e054d7da078ea4ad8b936f15a313bea368d3 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Fri, 20 Jun 2014 12:57:33 +0200 Subject: [PATCH 055/189] Minor refactoring of the MigrationRunner to allow a bit more flexibility for using it for non-Core migrations. Allowing the list of migrations to be changed through the two migration events using the MigrationEventArgs. --- src/Umbraco.Core/Events/MigrationEventArgs.cs | 10 +- .../Persistence/Migrations/MigrationRunner.cs | 135 ++++++++++-------- 2 files changed, 77 insertions(+), 68 deletions(-) diff --git a/src/Umbraco.Core/Events/MigrationEventArgs.cs b/src/Umbraco.Core/Events/MigrationEventArgs.cs index c447ebcd1a..c6da480999 100644 --- a/src/Umbraco.Core/Events/MigrationEventArgs.cs +++ b/src/Umbraco.Core/Events/MigrationEventArgs.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Persistence.Migrations; namespace Umbraco.Core.Events { - public class MigrationEventArgs : CancellableObjectEventArgs> + public class MigrationEventArgs : CancellableObjectEventArgs> { /// /// Constructor accepting multiple migrations that are used in the migration runner @@ -13,7 +13,7 @@ namespace Umbraco.Core.Events /// /// /// - public MigrationEventArgs(IEnumerable eventObject, Version configuredVersion, Version targetVersion, bool canCancel) + public MigrationEventArgs(IList eventObject, Version configuredVersion, Version targetVersion, bool canCancel) : base(eventObject, canCancel) { ConfiguredVersion = configuredVersion; @@ -28,7 +28,7 @@ namespace Umbraco.Core.Events /// /// /// - internal MigrationEventArgs(IEnumerable eventObject, MigrationContext migrationContext, Version configuredVersion, Version targetVersion, bool canCancel) + internal MigrationEventArgs(IList eventObject, MigrationContext migrationContext, Version configuredVersion, Version targetVersion, bool canCancel) : base(eventObject, canCancel) { MigrationContext = migrationContext; @@ -42,7 +42,7 @@ namespace Umbraco.Core.Events /// /// /// - public MigrationEventArgs(IEnumerable eventObject, Version configuredVersion, Version targetVersion) + public MigrationEventArgs(IList eventObject, Version configuredVersion, Version targetVersion) : base(eventObject) { ConfiguredVersion = configuredVersion; @@ -52,7 +52,7 @@ namespace Umbraco.Core.Events /// /// Returns all migrations that were used in the migration runner /// - public IEnumerable Migrations + public IList Migrations { get { return EventObject; } } diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs index f9bf78ae2a..6c077dd987 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Migrations /// The PetaPoco Database, which the migrations will be run against /// Boolean indicating whether this is an upgrade or downgrade /// True if migrations were applied, otherwise False - public bool Execute(Database database, bool isUpgrade = true) + public virtual bool Execute(Database database, bool isUpgrade = true) { return Execute(database, database.GetDatabaseProvider(), isUpgrade); } @@ -42,11 +42,11 @@ namespace Umbraco.Core.Persistence.Migrations /// /// Boolean indicating whether this is an upgrade or downgrade /// True if migrations were applied, otherwise False - public bool Execute(Database database, DatabaseProviders databaseProvider, bool isUpgrade = true) + public virtual bool Execute(Database database, DatabaseProviders databaseProvider, bool isUpgrade = true) { LogHelper.Info("Initializing database migrations"); - var foundMigrations = MigrationResolver.Current.Migrations.ToArray(); + var foundMigrations = FindMigrations(); //filter all non-schema migrations var migrations = isUpgrade @@ -54,6 +54,7 @@ namespace Umbraco.Core.Persistence.Migrations : OrderedDowngradeMigrations(foundMigrations).ToList(); //SD: Why do we want this? + //MCH: Because extensibility ... Mostly relevant to package developers who needs to utilize this type of event to add or remove migrations from the list if (Migrating.IsRaisedEventCancelled(new MigrationEventArgs(migrations, _currentVersion, _targetVersion, true), this)) return false; @@ -69,7 +70,6 @@ namespace Umbraco.Core.Persistence.Migrations //if this fails then the transaction will be rolled back, BUT if we are using MySql this is not the case, //since it does not support schema changes in a transaction, see: http://dev.mysql.com/doc/refman/5.0/en/implicit-commit.html //so in that case we have to downgrade - if (databaseProvider == DatabaseProviders.MySql) { throw new DataLossException( @@ -86,28 +86,57 @@ namespace Umbraco.Core.Persistence.Migrations return true; } - private void ExecuteMigrations(IMigrationContext context, Database database) + /// + /// Filters and orders migrations based on the migrations listed and the currently configured version and the target installation version + /// + /// + /// + public IEnumerable OrderedUpgradeMigrations(IEnumerable foundMigrations) { - //Transactional execution of the sql that was generated from the found migrations - using (var transaction = database.GetTransaction()) - { - int i = 1; - foreach (var expression in context.Expressions) - { - var sql = expression.Process(database); - if (string.IsNullOrEmpty(sql)) - { - i++; - continue; - } + var migrations = (from migration in foundMigrations + let migrationAttributes = migration.GetType().GetCustomAttributes(false) + from migrationAttribute in migrationAttributes + where migrationAttribute != null + where migrationAttribute.TargetVersion > _currentVersion && + migrationAttribute.TargetVersion <= _targetVersion && + migrationAttribute.ProductName == _productName && + //filter if the migration specifies a minimum current version for which to execute + (migrationAttribute.MinimumCurrentVersion == null || _currentVersion >= migrationAttribute.MinimumCurrentVersion) + orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder ascending + select migration).Distinct(); + return migrations; + } - LogHelper.Info("Executing sql statement " + i + ": " + sql); - database.Execute(sql); - i++; - } + /// + /// Filters and orders migrations based on the migrations listed and the currently configured version and the target installation version + /// + /// + /// + public IEnumerable OrderedDowngradeMigrations(IEnumerable foundMigrations) + { + var migrations = (from migration in foundMigrations + let migrationAttributes = migration.GetType().GetCustomAttributes(false) + from migrationAttribute in migrationAttributes + where migrationAttribute != null + where + migrationAttribute.TargetVersion > _currentVersion && + migrationAttribute.TargetVersion <= _targetVersion && + migrationAttribute.ProductName == _productName && + //filter if the migration specifies a minimum current version for which to execute + (migrationAttribute.MinimumCurrentVersion == null || _currentVersion >= migrationAttribute.MinimumCurrentVersion) + orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder descending + select migration).Distinct(); + return migrations; + } - transaction.Complete(); - } + /// + /// Find all migrations that are available through the + /// + /// An array of + protected virtual IMigration[] FindMigrations() + { + //MCH NOTE: Consider adding the ProductName filter to the Resolver so we don't get a bunch of irrelevant migrations + return MigrationResolver.Current.Migrations.ToArray(); } internal MigrationContext InitializeMigrations(List migrations, Database database, DatabaseProviders databaseProvider, bool isUpgrade = true) @@ -150,48 +179,28 @@ namespace Umbraco.Core.Persistence.Migrations return context; } - /// - /// Filters and orders migrations based on the migrations listed and the currently configured version and the target installation version - /// - /// - /// - internal IEnumerable OrderedUpgradeMigrations(IEnumerable foundMigrations) + private void ExecuteMigrations(IMigrationContext context, Database database) { - var migrations = (from migration in foundMigrations - let migrationAttributes = migration.GetType().GetCustomAttributes(false) - from migrationAttribute in migrationAttributes - where migrationAttribute != null - where - migrationAttribute.TargetVersion > _currentVersion && - migrationAttribute.TargetVersion <= _targetVersion && - migrationAttribute.ProductName == _productName && - //filter if the migration specifies a minimum current version for which to execute - (migrationAttribute.MinimumCurrentVersion == null || _currentVersion >= migrationAttribute.MinimumCurrentVersion) - orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder ascending - select migration).Distinct(); - return migrations; - } + //Transactional execution of the sql that was generated from the found migrations + using (var transaction = database.GetTransaction()) + { + int i = 1; + foreach (var expression in context.Expressions) + { + var sql = expression.Process(database); + if (string.IsNullOrEmpty(sql)) + { + i++; + continue; + } - /// - /// Filters and orders migrations based on the migrations listed and the currently configured version and the target installation version - /// - /// - /// - public IEnumerable OrderedDowngradeMigrations(IEnumerable foundMigrations) - { - var migrations = (from migration in foundMigrations - let migrationAttributes = migration.GetType().GetCustomAttributes(false) - from migrationAttribute in migrationAttributes - where migrationAttribute != null - where - migrationAttribute.TargetVersion > _currentVersion && - migrationAttribute.TargetVersion <= _targetVersion && - migrationAttribute.ProductName == _productName && - //filter if the migration specifies a minimum current version for which to execute - (migrationAttribute.MinimumCurrentVersion == null || _currentVersion >= migrationAttribute.MinimumCurrentVersion) - orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder descending - select migration).Distinct(); - return migrations; + LogHelper.Info("Executing sql statement " + i + ": " + sql); + database.Execute(sql); + i++; + } + + transaction.Complete(); + } } /// From 65ea6f94d337441377a4ad4959549060da18da05 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 23 Jun 2014 15:02:23 +1000 Subject: [PATCH 056/189] adds authorization to the scheduled publishing , this new attribute can be used to secure scheduled tasks as well. --- .../Mvc/AdminTokenAuthorizeAttribute.cs | 108 ++++++++++++++++++ .../Mvc/UmbracoAuthorizeAttribute.cs | 2 +- .../Scheduling/ScheduledPublishing.cs | 7 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + .../WebServices/ScheduledPublishController.cs | 3 +- 5 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs diff --git a/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs b/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs new file mode 100644 index 0000000000..de8cf65a53 --- /dev/null +++ b/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs @@ -0,0 +1,108 @@ +using System; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.Mvc +{ + /// + /// Used for authorizing scheduled tasks + /// + public sealed class AdminTokenAuthorizeAttribute : AuthorizeAttribute + { + private readonly ApplicationContext _applicationContext; + + /// + /// THIS SHOULD BE ONLY USED FOR UNIT TESTS + /// + /// + public AdminTokenAuthorizeAttribute(ApplicationContext appContext) + { + if (appContext == null) throw new ArgumentNullException("appContext"); + _applicationContext = appContext; + } + + public AdminTokenAuthorizeAttribute() + { + } + + private ApplicationContext GetApplicationContext() + { + return _applicationContext ?? ApplicationContext.Current; + } + + /// + /// Used to return the value that needs to go in the Authorization header + /// + /// + /// + public static string GetAuthHeaderTokenVal(ApplicationContext appContext) + { + var admin = appContext.Services.UserService.GetUserById(0); + + var token = string.Format("{0}u____u{1}u____u{2}", admin.Email, admin.Username, admin.RawPasswordValue); + + var encrypted = token.EncryptWithMachineKey(); + var bytes = Encoding.UTF8.GetBytes(encrypted); + var base64 = Convert.ToBase64String(bytes); + return "AToken val=\"" + base64 + "\""; + } + + /// + /// Ensures that the user must be in the Administrator or the Install role + /// + /// + /// + protected override bool AuthorizeCore(HttpContextBase httpContext) + { + if (httpContext == null) throw new ArgumentNullException("httpContext"); + + var appContext = GetApplicationContext(); + + //we need to that the app is configured and that a user is logged in + if (appContext.IsConfigured == false) return false; + + //need the auth header + if (httpContext.Request.Headers["Authorization"] == null || httpContext.Request.Headers["Authorization"].IsNullOrWhiteSpace()) return false; + + var header = httpContext.Request.Headers["Authorization"]; + if (header.StartsWith("AToken ") == false) return false; + + var keyVal = Regex.Matches(header, "AToken val=(.*?)(?:$|\\s)"); + if (keyVal.Count != 1) return false; + if (keyVal[0].Groups.Count != 2) return false; + + var admin = appContext.Services.UserService.GetUserById(0); + if (admin == null) return false; + + try + { + //get the token value from the header + var val = keyVal[0].Groups[1].Value.Trim("\""); + //un-base 64 the string + var bytes = Convert.FromBase64String(val); + var encrypted = Encoding.UTF8.GetString(bytes); + //decrypt the string + var text = encrypted.DecryptWithMachineKey(); + + //split + var split = text.Split(new[] {"u____u"}, StringSplitOptions.RemoveEmptyEntries); + if (split.Length != 3) return false; + + //compare + return + split[0] == admin.Email + && split[1] == admin.Username + && split[2] == admin.RawPasswordValue; + } + catch (Exception ex) + { + LogHelper.Error("Failed to format passed in token value", ex); + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs b/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs index ff7056838b..5ff8af1f0f 100644 --- a/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs +++ b/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs @@ -7,7 +7,7 @@ using umbraco.BasePages; namespace Umbraco.Web.Mvc { - /// + /// /// Ensures authorization is successful for a back office user /// public sealed class UmbracoAuthorizeAttribute : AuthorizeAttribute diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index 7a249ce4ac..ef8d5ef65a 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -1,10 +1,12 @@ using System; using System.Diagnostics; using System.Net; +using System.Text; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Publishing; using Umbraco.Core.Sync; +using Umbraco.Web.Mvc; namespace Umbraco.Web.Scheduling { @@ -28,7 +30,10 @@ namespace Umbraco.Web.Scheduling var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(); var url = string.Format("{0}/RestServices/ScheduledPublish/", umbracoBaseUrl); using (var wc = new WebClient()) - { + { + //pass custom the authorization header + wc.Headers.Set("Authorization", AdminTokenAuthorizeAttribute.GetAuthHeaderTokenVal(appContext)); + var result = wc.UploadString(url, ""); } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a8bcafd5f7..bca502ff8b 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -302,6 +302,7 @@ + diff --git a/src/Umbraco.Web/WebServices/ScheduledPublishController.cs b/src/Umbraco.Web/WebServices/ScheduledPublishController.cs index bc2b5df18c..0ab76738dc 100644 --- a/src/Umbraco.Web/WebServices/ScheduledPublishController.cs +++ b/src/Umbraco.Web/WebServices/ScheduledPublishController.cs @@ -7,11 +7,10 @@ using Umbraco.Web.Mvc; namespace Umbraco.Web.WebServices { - //TODO: How to authenticate? - /// /// A REST controller used for running the scheduled publishing, this is called from the background worker timer /// + [AdminTokenAuthorize] public class ScheduledPublishController : UmbracoController { private static bool _isPublishingRunning = false; From a23fb7bc741df76376b640a4db5abe2fb92991fe Mon Sep 17 00:00:00 2001 From: Tom Fulton Date: Mon, 23 Jun 2014 12:16:14 -0400 Subject: [PATCH 057/189] Support virtual paths when defining PropertyEditors via Attributes (U4-5128) --- src/Umbraco.Core/PropertyEditors/PropertyEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs index cdc38f7cc2..baa1431f29 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs @@ -114,7 +114,7 @@ namespace Umbraco.Core.PropertyEditors throw new NotImplementedException("This method must be implemented if a view is not explicitly set"); } - editor.View = _attribute.EditorView; + editor.View = _attribute.EditorView.StartsWith("~/") ? IOHelper.ResolveUrl(_attribute.EditorView) : _attribute.EditorView; editor.ValueType = _attribute.ValueType; editor.HideLabel = _attribute.HideLabel; return editor; From 8c36a683e61f209027326628e8b6fac13a4b6a80 Mon Sep 17 00:00:00 2001 From: jakobdyrby Date: Tue, 24 Jun 2014 11:27:04 +0200 Subject: [PATCH 058/189] Change some class from public to internal --- .../ConflictingPackageContentFinder.cs | 2 +- .../IConflictingPackageContentFinder.cs | 2 +- .../Packaging/Models/PreInstallWarnings.cs | 2 +- .../Packaging/PackageInstallation.cs | 32 ------------------- 4 files changed, 3 insertions(+), 35 deletions(-) diff --git a/src/Umbraco.Core/Packaging/ConflictingPackageContentFinder.cs b/src/Umbraco.Core/Packaging/ConflictingPackageContentFinder.cs index f0b510ba21..19bc93eeaa 100644 --- a/src/Umbraco.Core/Packaging/ConflictingPackageContentFinder.cs +++ b/src/Umbraco.Core/Packaging/ConflictingPackageContentFinder.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Packaging { - public class ConflictingPackageContentFinder : IConflictingPackageContentFinder + internal class ConflictingPackageContentFinder : IConflictingPackageContentFinder { private readonly IMacroService _macroService; private readonly IFileService _fileService; diff --git a/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs b/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs index e9690ab672..351f5ad229 100644 --- a/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs +++ b/src/Umbraco.Core/Packaging/IConflictingPackageContentFinder.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Packaging { - public interface IConflictingPackageContentFinder + internal interface IConflictingPackageContentFinder { IFile[] FindConflictingStylesheets(XElement stylesheetNotes); ITemplate[] FindConflictingTemplates(XElement templateNotes); diff --git a/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs b/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs index 3dbe1f3e0a..383c9bd26b 100644 --- a/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs +++ b/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Packaging.Models { [Serializable] [DataContract(IsReference = true)] - public class PreInstallWarnings + internal class PreInstallWarnings { public KeyValuePair[] UnsecureFiles { get; set; } public KeyValuePair[] FilesReplaced { get; set; } diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index c530f5536f..57f04914d3 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -596,36 +596,4 @@ namespace Umbraco.Core.Packaging return path; } } - - public class FileInPackageInfo : IFileInPackageInfo - { - public string RelativePath - { - get { return Path.Combine(RelativeDir, FileName); } - } - - public string FileNameInPackage { get; set; } - public string RelativeDir { get; set; } - public string DestinationRootDir { private get; set; } - - public string Directory - { - get { return Path.Combine(DestinationRootDir, RelativeDir); } - } - - public string FullPath - { - get { return Path.Combine(DestinationRootDir, RelativePath); } - } - - public string FileName { get; set; } - } - - public interface IFileInPackageInfo - { - string RelativeDir { get; } - string RelativePath { get; } - string FileName { get; set; } - } - } \ No newline at end of file From e27163f3a77d1b39dcd696803808b4faab6cc2c0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 11:00:11 +1000 Subject: [PATCH 059/189] adds logging to scheduled bits --- .../config/umbracoSettings.config | 7 +- src/Umbraco.Web/Scheduling/KeepAlive.cs | 36 +++++---- .../Scheduling/ScheduledPublishing.cs | 47 ++++++------ src/Umbraco.Web/Scheduling/ScheduledTasks.cs | 33 ++++---- src/Umbraco.Web/Scheduling/Scheduler.cs | 75 ++++++++++--------- 5 files changed, 107 insertions(+), 91 deletions(-) diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index 0a60537af5..840ac9cb66 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -224,7 +224,7 @@ - + 0 @@ -263,10 +263,7 @@ server1.mysite.com server1.mysite.com --> - - localhost - umb1.dev - umb2.dev + diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs index 443c3727e1..e5092c2a7b 100644 --- a/src/Umbraco.Web/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -10,26 +10,30 @@ namespace Umbraco.Web.Scheduling { public static void Start(object sender) { - //NOTE: sender will be the umbraco ApplicationContext - - var appContext = sender as ApplicationContext; - if (appContext == null) return; - - var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(); - - var url = string.Format("{0}/ping.aspx", umbracoBaseUrl); - - try + using (DisposableTimer.DebugDuration(() => "Keep alive executing", () => "Keep alive complete")) { - using (var wc = new WebClient()) + //NOTE: sender will be the umbraco ApplicationContext + + var appContext = sender as ApplicationContext; + if (appContext == null) return; + + var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(); + + var url = string.Format("{0}/ping.aspx", umbracoBaseUrl); + + try { - wc.DownloadString(url); + using (var wc = new WebClient()) + { + wc.DownloadString(url); + } + } + catch (Exception ee) + { + LogHelper.Error("Error in ping", ee); } } - catch (Exception ee) - { - LogHelper.Error("Error in ping", ee); - } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index ef8d5ef65a..6bc5492973 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -16,35 +16,38 @@ namespace Umbraco.Web.Scheduling public void Start(object sender) { - //NOTE: sender will be the umbraco ApplicationContext + using (DisposableTimer.DebugDuration(() => "Scheduled publishing executing", () => "Scheduled publishing complete")) + { + //NOTE: sender will be the umbraco ApplicationContext - var appContext = sender as ApplicationContext; - if (appContext == null) return; + var appContext = sender as ApplicationContext; + if (appContext == null) return; - if (_isPublishingRunning) return; + if (_isPublishingRunning) return; - _isPublishingRunning = true; + _isPublishingRunning = true; - try - { - var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(); - var url = string.Format("{0}/RestServices/ScheduledPublish/", umbracoBaseUrl); - using (var wc = new WebClient()) + try { - //pass custom the authorization header - wc.Headers.Set("Authorization", AdminTokenAuthorizeAttribute.GetAuthHeaderTokenVal(appContext)); + var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(); + var url = string.Format("{0}/RestServices/ScheduledPublish/", umbracoBaseUrl); + using (var wc = new WebClient()) + { + //pass custom the authorization header + wc.Headers.Set("Authorization", AdminTokenAuthorizeAttribute.GetAuthHeaderTokenVal(appContext)); - var result = wc.UploadString(url, ""); + var result = wc.UploadString(url, ""); + } } - } - catch (Exception ee) - { - LogHelper.Error("An error occurred with the scheduled publishing", ee); - } - finally - { - _isPublishingRunning = false; - } + catch (Exception ee) + { + LogHelper.Error("An error occurred with the scheduled publishing", ee); + } + finally + { + _isPublishingRunning = false; + } + } } diff --git a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs index 9abf7d9f69..526ebb0abb 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Publishing; using Umbraco.Core.Sync; +using Umbraco.Core; namespace Umbraco.Web.Scheduling { @@ -19,24 +20,28 @@ namespace Umbraco.Web.Scheduling public void Start(object sender) { - //NOTE: sender will be the umbraco ApplicationContext + using (DisposableTimer.DebugDuration(() => "Scheduled tasks executing", () => "Scheduled tasks complete")) + { + //NOTE: sender will be the umbraco ApplicationContext - if (_isPublishingRunning) return; + if (_isPublishingRunning) return; - _isPublishingRunning = true; + _isPublishingRunning = true; - try - { - ProcessTasks(); - } - catch (Exception ee) - { - LogHelper.Error("Error executing scheduled task", ee); - } - finally - { - _isPublishingRunning = false; + try + { + ProcessTasks(); + } + catch (Exception ee) + { + LogHelper.Error("Error executing scheduled task", ee); + } + finally + { + _isPublishingRunning = false; + } } + } private static void ProcessTasks() diff --git a/src/Umbraco.Web/Scheduling/Scheduler.cs b/src/Umbraco.Web/Scheduling/Scheduler.cs index 75930fed4c..db3ff1b5ed 100644 --- a/src/Umbraco.Web/Scheduling/Scheduler.cs +++ b/src/Umbraco.Web/Scheduling/Scheduler.cs @@ -1,5 +1,6 @@ using System.Threading; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Sync; namespace Umbraco.Web.Scheduling @@ -13,10 +14,10 @@ namespace Umbraco.Web.Scheduling /// in a web project nowadays. /// /// - internal sealed class Scheduler : ApplicationEventHandler - { - private Timer _pingTimer; - private Timer _schedulingTimer; + internal sealed class Scheduler : ApplicationEventHandler + { + private Timer _pingTimer; + private Timer _schedulingTimer; private LogScrubber _scrubber; protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) @@ -24,23 +25,23 @@ namespace Umbraco.Web.Scheduling if (umbracoApplication.Context == null) return; - // time to setup the tasks + // time to setup the tasks - // these are the legacy tasks - // just copied over here for backward compatibility - // of course we should have a proper scheduler, see #U4-809 + // these are the legacy tasks + // just copied over here for backward compatibility + // of course we should have a proper scheduler, see #U4-809 - // ping/keepalive + // ping/keepalive _pingTimer = new Timer(KeepAlive.Start, applicationContext, 60000, 300000); - // scheduled publishing/unpublishing - + // scheduled publishing/unpublishing + _schedulingTimer = new Timer(PerformScheduling, applicationContext, 30000, 60000); - + //log scrubbing _scrubber = new LogScrubber(); _scrubber.Start(); - } + } /// /// This performs all of the scheduling on the one timer @@ -51,32 +52,38 @@ namespace Umbraco.Web.Scheduling /// private static void PerformScheduling(object sender) { - - //get the current server status to see if this server should execute the scheduled publishing - var serverStatus = ServerEnvironmentHelper.GetStatus(); - - switch (serverStatus) + using (DisposableTimer.DebugDuration(() => "Scheduling interval executing", () => "Scheduling interval complete")) { - case CurrentServerEnvironmentStatus.Single: - case CurrentServerEnvironmentStatus.Master: - case CurrentServerEnvironmentStatus.Unknown: - //if it's a single server install, a master or it cannot be determined - // then we will process the scheduling + //get the current server status to see if this server should execute the scheduled publishing + var serverStatus = ServerEnvironmentHelper.GetStatus(); + + switch (serverStatus) + { + case CurrentServerEnvironmentStatus.Single: + case CurrentServerEnvironmentStatus.Master: + case CurrentServerEnvironmentStatus.Unknown: + //if it's a single server install, a master or it cannot be determined + // then we will process the scheduling - //do the scheduled publishing - var scheduledPublishing = new ScheduledPublishing(); - scheduledPublishing.Start(sender); + //do the scheduled publishing + var scheduledPublishing = new ScheduledPublishing(); + scheduledPublishing.Start(sender); - //do the scheduled tasks - var scheduledTasks = new ScheduledTasks(); - scheduledTasks.Start(sender); + //do the scheduled tasks + var scheduledTasks = new ScheduledTasks(); + scheduledTasks.Start(sender); - break; - case CurrentServerEnvironmentStatus.Slave: - //do not process - break; + break; + case CurrentServerEnvironmentStatus.Slave: + //do not process + + LogHelper.Debug( + () => string.Format("Current server ({0}) detected as a slave, no scheduled processes will execute on this server", NetworkHelper.MachineName)); + + break; + } } } - } + } } From 5011eee037dfcfdca1cdef5f614144ed3310b920 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 11:00:53 +1000 Subject: [PATCH 060/189] fixes merge issue --- .../Configuration/UmbracoSettings/IServer.cs | 3 ++ .../UmbracoSettings/ServerElement.cs | 19 ++++++++ .../Sync/ServerEnvironmentHelper.cs | 32 ++++++------- src/Umbraco.Web/Scheduling/ScheduledTasks.cs | 47 +++++++++---------- 4 files changed, 57 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IServer.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IServer.cs index 5ad0715302..856c570277 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IServer.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IServer.cs @@ -5,5 +5,8 @@ string ForcePortnumber { get; } string ForceProtocol { get; } string ServerAddress { get; } + + string AppId { get; } + string ServerName { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ServerElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ServerElement.cs index 2374005eec..7c00c02277 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ServerElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ServerElement.cs @@ -26,5 +26,24 @@ { get { return Value; } } + + public string AppId + { + get + { + return RawXml.Attribute("appId") == null + ? null + : RawXml.Attribute("appId").Value; + } + } + public string ServerName + { + get + { + return RawXml.Attribute("serverName") == null + ? null + : RawXml.Attribute("serverName").Value; + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs index 73dca4cc98..aadad2abe0 100644 --- a/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs +++ b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs @@ -28,21 +28,18 @@ namespace Umbraco.Core.Sync return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl); } - var servers = UmbracoSettings.DistributionServers; + var servers = UmbracoConfig.For.UmbracoSettings().DistributedCall.Servers.ToArray(); - var nodes = servers.SelectNodes("./server"); - if (nodes == null) + if (servers.Any() == false) { //cannot be determined, then the base url has to be the first url registered return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl); } - var xmlNodes = nodes.Cast().ToList(); - - foreach (var xmlNode in xmlNodes) + foreach (var server in servers) { - var appId = xmlNode.AttributeValue("appId"); - var serverName = xmlNode.AttributeValue("serverName"); + var appId = server.AppId; + var serverName = server.ServerName; if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace()) { @@ -54,9 +51,9 @@ namespace Umbraco.Core.Sync { //match by appId or computer name! return the url configured return string.Format("{0}://{1}:{2}/{3}", - xmlNode.AttributeValue("forceProtocol").IsNullOrWhiteSpace() ? "http" : xmlNode.AttributeValue("forceProtocol"), - xmlNode.InnerText, - xmlNode.AttributeValue("forcePortnumber").IsNullOrWhiteSpace() ? "80" : xmlNode.AttributeValue("forcePortnumber"), + server.ForceProtocol.IsNullOrWhiteSpace() ? "http" : server.ForceProtocol, + server.ServerAddress, + server.ForcePortnumber.IsNullOrWhiteSpace() ? "80" : server.ForcePortnumber, IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/')); } } @@ -71,20 +68,19 @@ namespace Umbraco.Core.Sync /// public static CurrentServerEnvironmentStatus GetStatus() { - if (UmbracoSettings.UseDistributedCalls == false) + if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled == false) { return CurrentServerEnvironmentStatus.Single; } - var servers = UmbracoSettings.DistributionServers; + var servers = UmbracoConfig.For.UmbracoSettings().DistributedCall.Servers.ToArray(); - var nodes = servers.SelectNodes("./server"); - if (nodes == null) + if (servers.Any() == false) { return CurrentServerEnvironmentStatus.Unknown; } - var master = nodes.Cast().FirstOrDefault(); + var master = servers.FirstOrDefault(); if (master == null) { @@ -95,8 +91,8 @@ namespace Umbraco.Core.Sync //TODO: In v7 we have publicized ServerRegisterResolver - we won't be able to determine this based on that // but we'd need to change the IServerAddress interfaces which is breaking. - var appId = master.AttributeValue("appId"); - var serverName = master.AttributeValue("serverName"); + var appId = master.AppId; + var serverName = master.ServerName; if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace()) { diff --git a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs index 9abf7d9f69..b25f50c198 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Linq; using System.Net; using System.Xml; using Umbraco.Core.Configuration; @@ -43,35 +44,29 @@ namespace Umbraco.Web.Scheduling { - var scheduledTasks = UmbracoSettings.ScheduledTasks; - if (scheduledTasks != null) + var scheduledTasks = UmbracoConfig.For.UmbracoSettings().ScheduledTasks.Tasks; + foreach (var t in scheduledTasks) { - var tasks = scheduledTasks.SelectNodes("./task"); - if (tasks == null) return; - - foreach (XmlNode task in tasks) + var runTask = false; + if (!ScheduledTaskTimes.ContainsKey(t.Alias)) { - var runTask = false; - if (ScheduledTaskTimes.ContainsKey(task.Attributes.GetNamedItem("alias").Value) == false) - { - runTask = true; - ScheduledTaskTimes.Add(task.Attributes.GetNamedItem("alias").Value, DateTime.Now); - } - // Add 1 second to timespan to compensate for differencies in timer - else if (new TimeSpan( - DateTime.Now.Ticks - ((DateTime)ScheduledTaskTimes[task.Attributes.GetNamedItem("alias").Value]).Ticks).TotalSeconds + 1 - >= int.Parse(task.Attributes.GetNamedItem("interval").Value)) - { - runTask = true; - ScheduledTaskTimes[task.Attributes.GetNamedItem("alias").Value] = DateTime.Now; - } + runTask = true; + ScheduledTaskTimes.Add(t.Alias, DateTime.Now); + } + /// Add 1 second to timespan to compensate for differencies in timer + else if ( + new TimeSpan( + DateTime.Now.Ticks - ((DateTime)ScheduledTaskTimes[t.Alias]).Ticks).TotalSeconds + 1 >= t.Interval) + { + runTask = true; + ScheduledTaskTimes[t.Alias] = DateTime.Now; + } - if (runTask) - { - bool taskResult = GetTaskByHttp(task.Attributes.GetNamedItem("url").Value); - if (bool.Parse(task.Attributes.GetNamedItem("log").Value)) - LogHelper.Info(string.Format("{0} has been called with response: {1}", task.Attributes.GetNamedItem("alias").Value, taskResult)); - } + if (runTask) + { + bool taskResult = GetTaskByHttp(t.Url); + if (t.Log) + LogHelper.Info(string.Format("{0} has been called with response: {1}", t.Alias, taskResult)); } } } From 625b8f7e655269a8a1c67b7895e986463133aa4e Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 11:26:08 +1000 Subject: [PATCH 061/189] Ensures the 'Index' portion of the path is used for scheduled publishing --- src/Umbraco.Web/Scheduling/ScheduledPublishing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index 6bc5492973..2d16b08511 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -30,7 +30,7 @@ namespace Umbraco.Web.Scheduling try { var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(); - var url = string.Format("{0}/RestServices/ScheduledPublish/", umbracoBaseUrl); + var url = string.Format("{0}/RestServices/ScheduledPublish/Index", umbracoBaseUrl); using (var wc = new WebClient()) { //pass custom the authorization header From 5aa0621c908637b2ae4dddaeb9d31568b737a391 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 11:36:14 +1000 Subject: [PATCH 062/189] adds some notes --- src/Umbraco.Web/Scheduling/Scheduler.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Web/Scheduling/Scheduler.cs b/src/Umbraco.Web/Scheduling/Scheduler.cs index db3ff1b5ed..a00f7b14f8 100644 --- a/src/Umbraco.Web/Scheduling/Scheduler.cs +++ b/src/Umbraco.Web/Scheduling/Scheduler.cs @@ -13,6 +13,10 @@ namespace Umbraco.Web.Scheduling /// TODO: Much of this code is legacy and needs to be updated, there are a few new/better ways to do scheduling /// in a web project nowadays. /// + /// //TODO: We need a much more robust way of handing scheduled tasks and also need to take into account app shutdowns during + /// a scheduled tasks operation + /// http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx/ + /// /// internal sealed class Scheduler : ApplicationEventHandler { From 3aa486a463f658ca98b4a9d3d4242c8601c3a343 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 11:36:58 +1000 Subject: [PATCH 063/189] another log --- src/Umbraco.Web/Scheduling/Scheduler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web/Scheduling/Scheduler.cs b/src/Umbraco.Web/Scheduling/Scheduler.cs index a00f7b14f8..a23f942f76 100644 --- a/src/Umbraco.Web/Scheduling/Scheduler.cs +++ b/src/Umbraco.Web/Scheduling/Scheduler.cs @@ -29,6 +29,8 @@ namespace Umbraco.Web.Scheduling if (umbracoApplication.Context == null) return; + LogHelper.Debug(() => "Initializing the scheduler"); + // time to setup the tasks // these are the legacy tasks From acb934e3f1f167c050a6f3d5715e49c50a639d3f Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 11:52:48 +1000 Subject: [PATCH 064/189] ensures timer's are not GCd --- src/Umbraco.Web/Scheduling/Scheduler.cs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/Scheduling/Scheduler.cs b/src/Umbraco.Web/Scheduling/Scheduler.cs index a23f942f76..aab1e6c666 100644 --- a/src/Umbraco.Web/Scheduling/Scheduler.cs +++ b/src/Umbraco.Web/Scheduling/Scheduler.cs @@ -20,9 +20,9 @@ namespace Umbraco.Web.Scheduling /// internal sealed class Scheduler : ApplicationEventHandler { - private Timer _pingTimer; - private Timer _schedulingTimer; - private LogScrubber _scrubber; + private static Timer _pingTimer; + private static Timer _schedulingTimer; + private static LogScrubber _scrubber; protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { @@ -37,12 +37,18 @@ namespace Umbraco.Web.Scheduling // just copied over here for backward compatibility // of course we should have a proper scheduler, see #U4-809 - // ping/keepalive - _pingTimer = new Timer(KeepAlive.Start, applicationContext, 60000, 300000); + //NOTE: It is important to note that we need to use the ctor for a timer without the 'state' object specified, this is in order + // to ensure that the timer itself is not GC'd since internally .net will pass itself in as the state object and that will keep it alive. + // There's references to this here: http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer + // we also make these timers static to ensure further GC safety. + + // ping/keepalive + _pingTimer = new Timer(KeepAlive.Start); + _pingTimer.Change(60000, 300000); // scheduled publishing/unpublishing - - _schedulingTimer = new Timer(PerformScheduling, applicationContext, 30000, 60000); + _schedulingTimer = new Timer(PerformScheduling); + _schedulingTimer.Change(30000, 60000); //log scrubbing _scrubber = new LogScrubber(); From 7ce0272169a7137e46803b89aa634e6ad599576c Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 11:56:36 +1000 Subject: [PATCH 065/189] updates state param with timers to use singleton --- src/Umbraco.Web/Scheduling/ScheduledPublishing.cs | 13 +++++-------- src/Umbraco.Web/Scheduling/ScheduledTasks.cs | 5 ++--- src/Umbraco.Web/Scheduling/Scheduler.cs | 4 ++-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index 2d16b08511..9d7fde0789 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -13,16 +13,13 @@ namespace Umbraco.Web.Scheduling internal class ScheduledPublishing { private static bool _isPublishingRunning = false; - - public void Start(object sender) + + public void Start(ApplicationContext appContext) { + if (appContext == null) return; + using (DisposableTimer.DebugDuration(() => "Scheduled publishing executing", () => "Scheduled publishing complete")) - { - //NOTE: sender will be the umbraco ApplicationContext - - var appContext = sender as ApplicationContext; - if (appContext == null) return; - + { if (_isPublishingRunning) return; _isPublishingRunning = true; diff --git a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs index 526ebb0abb..6bfb6168c2 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs @@ -12,18 +12,17 @@ namespace Umbraco.Web.Scheduling { //TODO: No scheduled task (i.e. URL) would be secured, so if people are actually using these each task // would need to be a publicly available task (URL) which isn't really very good :( + // We should really be using the AdminTokenAuthorizeAttribute for this stuff internal class ScheduledTasks { private static readonly Hashtable ScheduledTaskTimes = new Hashtable(); private static bool _isPublishingRunning = false; - public void Start(object sender) + public void Start(ApplicationContext appContext) { using (DisposableTimer.DebugDuration(() => "Scheduled tasks executing", () => "Scheduled tasks complete")) { - //NOTE: sender will be the umbraco ApplicationContext - if (_isPublishingRunning) return; _isPublishingRunning = true; diff --git a/src/Umbraco.Web/Scheduling/Scheduler.cs b/src/Umbraco.Web/Scheduling/Scheduler.cs index aab1e6c666..e18e41aeda 100644 --- a/src/Umbraco.Web/Scheduling/Scheduler.cs +++ b/src/Umbraco.Web/Scheduling/Scheduler.cs @@ -79,11 +79,11 @@ namespace Umbraco.Web.Scheduling //do the scheduled publishing var scheduledPublishing = new ScheduledPublishing(); - scheduledPublishing.Start(sender); + scheduledPublishing.Start(ApplicationContext.Current); //do the scheduled tasks var scheduledTasks = new ScheduledTasks(); - scheduledTasks.Start(sender); + scheduledTasks.Start(ApplicationContext.Current); break; case CurrentServerEnvironmentStatus.Slave: From 7cf7ac687c80a5817e83aa09e2f15e9e427b29f1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 12:03:31 +1000 Subject: [PATCH 066/189] last issue with state/keep alive timer --- src/Umbraco.Web/Scheduling/KeepAlive.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs index e5092c2a7b..0ebce6878b 100644 --- a/src/Umbraco.Web/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -11,12 +11,7 @@ namespace Umbraco.Web.Scheduling public static void Start(object sender) { using (DisposableTimer.DebugDuration(() => "Keep alive executing", () => "Keep alive complete")) - { - //NOTE: sender will be the umbraco ApplicationContext - - var appContext = sender as ApplicationContext; - if (appContext == null) return; - + { var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(); var url = string.Format("{0}/ping.aspx", umbracoBaseUrl); From a18a2198a89c172bc3991beb96d5c8f0f1ea12d6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 12:25:27 +1000 Subject: [PATCH 067/189] removes comment --- src/Umbraco.Web/WebBootManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 5a313b2350..45eb2676c6 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -276,7 +276,6 @@ namespace Umbraco.Web DefaultRenderMvcControllerResolver.Current = new DefaultRenderMvcControllerResolver(typeof(RenderMvcController)); //Override the ServerMessengerResolver to set a username/password for the distributed calls - //ServerMessengerResolver.Current.SetServerMessenger(new DefaultServerMessenger(() => ServerMessengerResolver.Current.SetServerMessenger(new BatchedServerMessenger(() => { //we should not proceed to change this if the app/database is not configured since there will From bef6ce66f18e975d1ed733b8a0fd1a04def4984d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 14:19:06 +1000 Subject: [PATCH 068/189] Updated core nuspec to include Examine --- build/NuSpecs/UmbracoCms.Core.nuspec | 1 + 1 file changed, 1 insertion(+) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 83408646a1..bdd352b8f3 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -29,6 +29,7 @@ + From a4bf4b0977fa8c5842c05d5341917b58fe1fc6bc Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 14:19:31 +1000 Subject: [PATCH 069/189] Fixes: U4-5118 Examine not storing XML fragments - updates to latest examine --- src/Umbraco.Tests/Umbraco.Tests.csproj | 4 ++-- src/Umbraco.Tests/packages.config | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 5 +++-- src/Umbraco.Web.UI/packages.config | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 6 +++--- src/Umbraco.Web/packages.config | 2 +- src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj | 6 +++--- src/UmbracoExamine.Azure/packages.config | 2 +- .../UmbracoExamine.PDF.Azure.csproj | 5 +++-- src/UmbracoExamine.PDF.Azure/packages.config | 2 +- src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj | 6 +++--- src/UmbracoExamine.PDF/packages.config | 2 +- src/UmbracoExamine/UmbracoExamine.csproj | 7 +++---- src/UmbracoExamine/packages.config | 2 +- src/umbraco.MacroEngines/packages.config | 2 +- src/umbraco.MacroEngines/umbraco.MacroEngines.csproj | 6 +++--- 16 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index b7c5d9e9a1..e97b913c04 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -48,9 +48,9 @@ 4 - + False - ..\packages\Examine.0.1.56.2941\lib\Examine.dll + ..\packages\Examine.0.1.57.2941\lib\Examine.dll False diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index a9e31b4e57..ec6e559acf 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index fbc922fc2c..e86ecfccc6 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -109,8 +109,9 @@ False ..\packages\ClientDependency-Mvc.1.7.0.4\lib\ClientDependency.Core.Mvc.dll - - ..\packages\Examine.0.1.56.2941\lib\Examine.dll + + False + ..\packages\Examine.0.1.57.2941\lib\Examine.dll True diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index ff2f6ab5cd..10e705ab8d 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index bca502ff8b..cb019563b4 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -101,9 +101,9 @@ False ..\packages\xmlrpcnet.2.5.0\lib\net20\CookComputing.XmlRpcV2.dll - - ..\packages\Examine.0.1.56.2941\lib\Examine.dll - True + + False + ..\packages\Examine.0.1.57.2941\lib\Examine.dll False diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index 474c7c8973..96e9bf3d0b 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj b/src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj index 5571697c65..5818caf636 100644 --- a/src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj +++ b/src/UmbracoExamine.Azure/UmbracoExamine.Azure.csproj @@ -1,4 +1,4 @@ - + Debug @@ -37,9 +37,9 @@ ..\packages\AzureDirectory.1.0.5\lib\AzureDirectory.dll - + False - ..\packages\Examine.0.1.56.2941\lib\Examine.dll + ..\packages\Examine.0.1.57.2941\lib\Examine.dll False diff --git a/src/UmbracoExamine.Azure/packages.config b/src/UmbracoExamine.Azure/packages.config index 05e36b051e..0d4b103fae 100644 --- a/src/UmbracoExamine.Azure/packages.config +++ b/src/UmbracoExamine.Azure/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/UmbracoExamine.PDF.Azure/UmbracoExamine.PDF.Azure.csproj b/src/UmbracoExamine.PDF.Azure/UmbracoExamine.PDF.Azure.csproj index 0aafee84d7..f897a9f5e5 100644 --- a/src/UmbracoExamine.PDF.Azure/UmbracoExamine.PDF.Azure.csproj +++ b/src/UmbracoExamine.PDF.Azure/UmbracoExamine.PDF.Azure.csproj @@ -37,8 +37,9 @@ ..\packages\AzureDirectory.1.0.5\lib\AzureDirectory.dll - - ..\packages\Examine.0.1.56.2941\lib\Examine.dll + + False + ..\packages\Examine.0.1.57.2941\lib\Examine.dll False diff --git a/src/UmbracoExamine.PDF.Azure/packages.config b/src/UmbracoExamine.PDF.Azure/packages.config index 05e36b051e..0d4b103fae 100644 --- a/src/UmbracoExamine.PDF.Azure/packages.config +++ b/src/UmbracoExamine.PDF.Azure/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj b/src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj index 5b01d4d38c..96eced121f 100644 --- a/src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj +++ b/src/UmbracoExamine.PDF/UmbracoExamine.PDF.csproj @@ -1,4 +1,4 @@ - + Debug @@ -44,9 +44,9 @@ bin\Release\UmbracoExamine.PDF.XML - + False - ..\packages\Examine.0.1.56.2941\lib\Examine.dll + ..\packages\Examine.0.1.57.2941\lib\Examine.dll ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll diff --git a/src/UmbracoExamine.PDF/packages.config b/src/UmbracoExamine.PDF/packages.config index 4e1b27d4f3..adb682b11d 100644 --- a/src/UmbracoExamine.PDF/packages.config +++ b/src/UmbracoExamine.PDF/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/UmbracoExamine/UmbracoExamine.csproj b/src/UmbracoExamine/UmbracoExamine.csproj index 8047ce4d96..4a4438a356 100644 --- a/src/UmbracoExamine/UmbracoExamine.csproj +++ b/src/UmbracoExamine/UmbracoExamine.csproj @@ -1,4 +1,4 @@ - + Debug @@ -80,10 +80,9 @@ ..\Solution Items\TheFARM-Public.snk - + False - ..\packages\Examine.0.1.56.2941\lib\Examine.dll - True + ..\packages\Examine.0.1.57.2941\lib\Examine.dll False diff --git a/src/UmbracoExamine/packages.config b/src/UmbracoExamine/packages.config index 6045ab5822..7e2ea1daf3 100644 --- a/src/UmbracoExamine/packages.config +++ b/src/UmbracoExamine/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/umbraco.MacroEngines/packages.config b/src/umbraco.MacroEngines/packages.config index bdd36b16f7..c48a99b992 100644 --- a/src/umbraco.MacroEngines/packages.config +++ b/src/umbraco.MacroEngines/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj index b2b0cb5b4b..78f932f2a1 100644 --- a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj +++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj @@ -1,4 +1,4 @@ - + Debug @@ -42,9 +42,9 @@ bin\Release\umbraco.MacroEngines.xml - + False - ..\packages\Examine.0.1.56.2941\lib\Examine.dll + ..\packages\Examine.0.1.57.2941\lib\Examine.dll False From c659d5b85e923daa1ea34e1664ced71a9c958da8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 14:35:14 +1000 Subject: [PATCH 070/189] fixes dll ref --- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e97b913c04..2d00ca0d41 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -48,7 +48,7 @@ 4 - + False ..\packages\Examine.0.1.57.2941\lib\Examine.dll From 72991334c4b7c1cd8afa78d917233219dfc0f2a6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 15:55:52 +1000 Subject: [PATCH 071/189] little bit of code cleanup --- .../businesslogic/Packager/Installer.cs | 219 ++---------------- .../businesslogic/Packager/Package.cs | 127 ++++++++++ .../PackageInstance/PackageActions.cs | 2 +- src/umbraco.cms/umbraco.cms.csproj | 3 +- 4 files changed, 146 insertions(+), 205 deletions(-) create mode 100644 src/umbraco.cms/businesslogic/Packager/Package.cs diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index 102a74b2fc..e531b692ac 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Xml; -using System.Runtime.CompilerServices; using System.Linq; using ICSharpCode.SharpZipLib.Zip; using Umbraco.Core; @@ -12,7 +11,6 @@ using Umbraco.Core.Logging; using umbraco.cms.businesslogic.web; using umbraco.cms.businesslogic.propertytype; using umbraco.BusinessLogic; -using umbraco.DataLayer; using System.Diagnostics; using umbraco.cms.businesslogic.macro; using umbraco.cms.businesslogic.template; @@ -391,6 +389,7 @@ namespace umbraco.cms.businesslogic.packager #endregion #region Package Actions + foreach (XmlNode n in _packageConfig.DocumentElement.SelectNodes("Actions/Action")) { if (n.Attributes["undo"] == null || n.Attributes["undo"].Value == "true") @@ -398,15 +397,15 @@ namespace umbraco.cms.businesslogic.packager insPack.Data.Actions += n.OuterXml; } + //Run the actions tagged only for 'install' + if (n.Attributes["runat"] != null && n.Attributes["runat"].Value == "install") { - try - { - PackageAction.RunPackageAction(insPack.Data.Name, n.Attributes["alias"].Value, n); - } - catch - { + var alias = n.Attributes["alias"] != null ? n.Attributes["alias"].Value : ""; + if (alias.IsNullOrWhiteSpace() == false) + { + PackageAction.RunPackageAction(insPack.Data.Name, alias, n); } } } @@ -440,6 +439,7 @@ namespace umbraco.cms.businesslogic.packager /// Temporary folder where the package's content are extracted to /// /// + [Obsolete("This method is no longer used and will be removed in the future.")] public void Install(string tempDir, string guid, string repoGuid) { //PPH added logging of installs, this adds all install info in the installedPackages config file. @@ -669,13 +669,15 @@ namespace umbraco.cms.businesslogic.packager #endregion #region Install Actions + + //Install package actions for anything that is NOT uninstall foreach (XmlNode n in _packageConfig.DocumentElement.SelectNodes("Actions/Action [@runat != 'uninstall']")) { - try + var alias = n.Attributes["alias"] != null ? n.Attributes["alias"].Value : ""; + if (alias.IsNullOrWhiteSpace() == false) { - PackageAction.RunPackageAction(packName, n.Attributes["alias"].Value, n); + PackageAction.RunPackageAction(packName, alias, n); } - catch { } } //saving the uninstall actions untill the package is uninstalled. @@ -817,9 +819,9 @@ namespace umbraco.cms.businesslogic.packager wc.DownloadFile( "http://" + UmbracoSettings.PackageServer + "/fetch?package=" + Package.ToString(), - IOHelper.MapPath(SystemDirectories.Packages + "/" + Package.ToString() + ".umb")); + IOHelper.MapPath(SystemDirectories.Packages + "/" + Package + ".umb")); - return "packages\\" + Package.ToString() + ".umb"; + return "packages\\" + Package + ".umb"; } #endregion @@ -900,19 +902,7 @@ namespace umbraco.cms.businesslogic.packager return path + Path.DirectorySeparatorChar + fileName; } } - - private static int FindDataTypeDefinitionFromType(ref Guid dtId) - { - int dfId = 0; - foreach (datatype.DataTypeDefinition df in datatype.DataTypeDefinition.GetAll()) - if (df.DataType.Id == dtId) - { - dfId = df.Id; - break; - } - return dfId; - } - + private static string UnPack(string zipName) { // Unzip @@ -960,181 +950,4 @@ namespace umbraco.cms.businesslogic.packager #endregion } - - public class Package - { - protected static ISqlHelper SqlHelper - { - get { return Application.SqlHelper; } - } - - public Package() - { - } - - /// - /// Initialize package install status object by specifying the internal id of the installation. - /// The id is specific to the local umbraco installation and cannot be used to identify the package in general. - /// Use the Package(Guid) constructor to check whether a package has been installed - /// - /// The internal id. - public Package(int Id) - { - initialize(Id); - } - - public Package(Guid Id) - { - int installStatusId = SqlHelper.ExecuteScalar( - "select id from umbracoInstalledPackages where package = @package and upgradeId = 0", - SqlHelper.CreateParameter("@package", Id)); - - if (installStatusId > 0) - initialize(installStatusId); - else - throw new ArgumentException("Package with id '" + Id.ToString() + "' is not installed"); - } - - private void initialize(int id) - { - - IRecordsReader dr = - SqlHelper.ExecuteReader( - "select id, uninstalled, upgradeId, installDate, userId, package, versionMajor, versionMinor, versionPatch from umbracoInstalledPackages where id = @id", - SqlHelper.CreateParameter("@id", id)); - - if (dr.Read()) - { - Id = id; - Uninstalled = dr.GetBoolean("uninstalled"); - UpgradeId = dr.GetInt("upgradeId"); - InstallDate = dr.GetDateTime("installDate"); - User = User.GetUser(dr.GetInt("userId")); - PackageId = dr.GetGuid("package"); - VersionMajor = dr.GetInt("versionMajor"); - VersionMinor = dr.GetInt("versionMinor"); - VersionPatch = dr.GetInt("versionPatch"); - } - dr.Close(); - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public void Save() - { - - IParameter[] values = { - SqlHelper.CreateParameter("@uninstalled", Uninstalled), - SqlHelper.CreateParameter("@upgradeId", UpgradeId), - SqlHelper.CreateParameter("@installDate", InstallDate), - SqlHelper.CreateParameter("@userId", User.Id), - SqlHelper.CreateParameter("@versionMajor", VersionMajor), - SqlHelper.CreateParameter("@versionMinor", VersionMinor), - SqlHelper.CreateParameter("@versionPatch", VersionPatch), - SqlHelper.CreateParameter("@id", Id) - }; - - // check if package status exists - if (Id == 0) - { - // The method is synchronized - SqlHelper.ExecuteNonQuery("INSERT INTO umbracoInstalledPackages (uninstalled, upgradeId, installDate, userId, versionMajor, versionMinor, versionPatch) VALUES (@uninstalled, @upgradeId, @installDate, @userId, @versionMajor, @versionMinor, @versionPatch)", values); - Id = SqlHelper.ExecuteScalar("SELECT MAX(id) FROM umbracoInstalledPackages"); - } - - SqlHelper.ExecuteNonQuery( - "update umbracoInstalledPackages set " + - "uninstalled = @uninstalled, " + - "upgradeId = @upgradeId, " + - "installDate = @installDate, " + - "userId = @userId, " + - "versionMajor = @versionMajor, " + - "versionMinor = @versionMinor, " + - "versionPatch = @versionPatch " + - "where id = @id", - values); - } - - private bool _uninstalled; - - public bool Uninstalled - { - get { return _uninstalled; } - set { _uninstalled = value; } - } - - - private User _user; - - public User User - { - get { return _user; } - set { _user = value; } - } - - - private DateTime _installDate; - - public DateTime InstallDate - { - get { return _installDate; } - set { _installDate = value; } - } - - - private int _id; - - public int Id - { - get { return _id; } - set { _id = value; } - } - - - private int _upgradeId; - - public int UpgradeId - { - get { return _upgradeId; } - set { _upgradeId = value; } - } - - - private Guid _packageId; - - public Guid PackageId - { - get { return _packageId; } - set { _packageId = value; } - } - - - private int _versionPatch; - - public int VersionPatch - { - get { return _versionPatch; } - set { _versionPatch = value; } - } - - - private int _versionMinor; - - public int VersionMinor - { - get { return _versionMinor; } - set { _versionMinor = value; } - } - - - private int _versionMajor; - - public int VersionMajor - { - get { return _versionMajor; } - set { _versionMajor = value; } - } - - - - } } diff --git a/src/umbraco.cms/businesslogic/Packager/Package.cs b/src/umbraco.cms/businesslogic/Packager/Package.cs new file mode 100644 index 0000000000..a12e2160bc --- /dev/null +++ b/src/umbraco.cms/businesslogic/Packager/Package.cs @@ -0,0 +1,127 @@ +using System; +using System.Runtime.CompilerServices; +using umbraco.BusinessLogic; +using umbraco.DataLayer; + +namespace umbraco.cms.businesslogic.packager +{ + public class Package + { + protected static ISqlHelper SqlHelper + { + get { return Application.SqlHelper; } + } + + public Package() + { + } + + /// + /// Initialize package install status object by specifying the internal id of the installation. + /// The id is specific to the local umbraco installation and cannot be used to identify the package in general. + /// Use the Package(Guid) constructor to check whether a package has been installed + /// + /// The internal id. + public Package(int Id) + { + Initialize(Id); + } + + public Package(Guid Id) + { + int installStatusId = SqlHelper.ExecuteScalar( + "select id from umbracoInstalledPackages where package = @package and upgradeId = 0", + SqlHelper.CreateParameter("@package", Id)); + + if (installStatusId > 0) + Initialize(installStatusId); + else + throw new ArgumentException("Package with id '" + Id.ToString() + "' is not installed"); + } + + private void Initialize(int id) + { + + IRecordsReader dr = + SqlHelper.ExecuteReader( + "select id, uninstalled, upgradeId, installDate, userId, package, versionMajor, versionMinor, versionPatch from umbracoInstalledPackages where id = @id", + SqlHelper.CreateParameter("@id", id)); + + if (dr.Read()) + { + Id = id; + Uninstalled = dr.GetBoolean("uninstalled"); + UpgradeId = dr.GetInt("upgradeId"); + InstallDate = dr.GetDateTime("installDate"); + User = User.GetUser(dr.GetInt("userId")); + PackageId = dr.GetGuid("package"); + VersionMajor = dr.GetInt("versionMajor"); + VersionMinor = dr.GetInt("versionMinor"); + VersionPatch = dr.GetInt("versionPatch"); + } + dr.Close(); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void Save() + { + + IParameter[] values = { + SqlHelper.CreateParameter("@uninstalled", Uninstalled), + SqlHelper.CreateParameter("@upgradeId", UpgradeId), + SqlHelper.CreateParameter("@installDate", InstallDate), + SqlHelper.CreateParameter("@userId", User.Id), + SqlHelper.CreateParameter("@versionMajor", VersionMajor), + SqlHelper.CreateParameter("@versionMinor", VersionMinor), + SqlHelper.CreateParameter("@versionPatch", VersionPatch), + SqlHelper.CreateParameter("@id", Id) + }; + + // check if package status exists + if (Id == 0) + { + // The method is synchronized + SqlHelper.ExecuteNonQuery("INSERT INTO umbracoInstalledPackages (uninstalled, upgradeId, installDate, userId, versionMajor, versionMinor, versionPatch) VALUES (@uninstalled, @upgradeId, @installDate, @userId, @versionMajor, @versionMinor, @versionPatch)", values); + Id = SqlHelper.ExecuteScalar("SELECT MAX(id) FROM umbracoInstalledPackages"); + } + + SqlHelper.ExecuteNonQuery( + "update umbracoInstalledPackages set " + + "uninstalled = @uninstalled, " + + "upgradeId = @upgradeId, " + + "installDate = @installDate, " + + "userId = @userId, " + + "versionMajor = @versionMajor, " + + "versionMinor = @versionMinor, " + + "versionPatch = @versionPatch " + + "where id = @id", + values); + } + + public bool Uninstalled { get; set; } + + + public User User { get; set; } + + + public DateTime InstallDate { get; set; } + + + public int Id { get; set; } + + + public int UpgradeId { get; set; } + + + public Guid PackageId { get; set; } + + + public int VersionPatch { get; set; } + + + public int VersionMinor { get; set; } + + + public int VersionMajor { get; set; } + } +} \ No newline at end of file diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageActions.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageActions.cs index eaf27ec2ef..bd312343e4 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageActions.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageActions.cs @@ -27,7 +27,7 @@ namespace umbraco.cms.businesslogic.packager /// Name of the package. /// The action alias. /// The action XML. - public static void RunPackageAction(string packageName, string actionAlias, System.Xml.XmlNode actionXml) + public static void RunPackageAction(string packageName, string actionAlias, XmlNode actionXml) { foreach (var ipa in PackageActionsResolver.Current.PackageActions) diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index f44c856634..3dc0894887 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -1,4 +1,4 @@ - + Local @@ -245,6 +245,7 @@ True PackageFiles.resx + From 5fdcec6904dd018f8d37dad67f243f667f49f06e Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 18:56:51 +1000 Subject: [PATCH 072/189] Fixes: U4-5121 umbraco.content launches threadpool threads to reload the xml cache which causes lots of other issues --- src/Umbraco.Web/UmbracoModule.cs | 6 +- .../umbraco.presentation/content.cs | 213 +++++------------- 2 files changed, 53 insertions(+), 166 deletions(-) diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 31aacc9e2a..ebfa604a3d 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -584,7 +584,6 @@ namespace Umbraco.Web app.BeginRequest += (sender, e) => { var httpContext = ((HttpApplication)sender).Context; - httpContext.Trace.Write("UmbracoModule", "Umbraco request begins"); LogHelper.Debug("Begin request: {0}.", () => httpContext.Request.Url); BeginRequest(new HttpContextWrapper(httpContext)); }; @@ -609,9 +608,8 @@ namespace Umbraco.Web var httpContext = ((HttpApplication)sender).Context; if (UmbracoContext.Current != null && UmbracoContext.Current.IsFrontEndUmbracoRequest) { - //write the trace output for diagnostics at the end of the request - httpContext.Trace.Write("UmbracoModule", "Umbraco request completed"); - LogHelper.Debug("Total milliseconds for umbraco request to process: " + DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds); + LogHelper.Debug( + "Total milliseconds for umbraco request to process: {0}", () => DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds); } OnEndRequest(new EventArgs()); diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 1a10045d75..9d658125c9 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -207,7 +207,6 @@ namespace umbraco FireAfterRefreshContent(new RefreshContentEventArgs()); // Only save new XML cache to disk if we just repopulated it - // TODO: Re-architect this so that a call to this method doesn't invoke a new thread for saving disk cache if (UmbracoConfig.For.UmbracoSettings().Content.XmlCacheEnabled && !IsValidDiskCachePresent()) { QueueXmlForPersistence(); @@ -216,7 +215,9 @@ namespace umbraco } } } - Trace.WriteLine("Content initialized (was already in context)"); + + LogHelper.Debug(() => "Content initialized (was already in context)"); + return false; } @@ -289,32 +290,33 @@ namespace umbraco #endregion - /// - /// Load content from database in a background thread - /// Replaces active content when done. - /// + [Obsolete("This is no longer used and will be removed in future versions, if you use this method it will not refresh 'async' it will perform the refresh on the current thread which is how it should be doing it")] public virtual void RefreshContentFromDatabaseAsync() + { + RefreshContentFromDatabase(); + } + + /// + /// Load content from database and replaces active content when done. + /// + public virtual void RefreshContentFromDatabase() { var e = new RefreshContentEventArgs(); FireBeforeRefreshContent(e); if (!e.Cancel) { - ThreadPool.QueueUserWorkItem( - delegate - { - XmlDocument xmlDoc = LoadContentFromDatabase(); - XmlContentInternal = xmlDoc; + XmlDocument xmlDoc = LoadContentFromDatabase(); + XmlContentInternal = xmlDoc; - // It is correct to manually call PersistXmlToFile here event though the setter of XmlContentInternal - // queues this up, because this delegate is executing on a different thread and may complete - // after the request which invoked it (which would normally persist the file on completion) - // So we are responsible for ensuring the content is persisted in this case. - if (UmbracoConfig.For.UmbracoSettings().Content.XmlCacheEnabled && UmbracoConfig.For.UmbracoSettings().Content.ContinouslyUpdateXmlDiskCache) - PersistXmlToFile(xmlDoc); - }); - - FireAfterRefreshContent(e); + // It is correct to manually call PersistXmlToFile here event though the setter of XmlContentInternal + // queues this up, because this delegate is executing on a different thread and may complete + // after the request which invoked it (which would normally persist the file on completion) + // So we are responsible for ensuring the content is persisted in this case. + if (UmbracoConfig.For.UmbracoSettings().Content.XmlCacheEnabled && UmbracoConfig.For.UmbracoSettings().Content.ContinouslyUpdateXmlDiskCache) + { + PersistXmlToFile(xmlDoc); + } } } @@ -552,33 +554,15 @@ namespace umbraco } } - /// - /// Updates the document cache async. - /// - /// The document id. [Obsolete("Method obsolete in version 4.1 and later, please use UpdateDocumentCache", true)] public virtual void UpdateDocumentCacheAsync(int documentId) { - //SD: WE've obsoleted this but then didn't make it call the method it should! So we've just - // left a bug behind...???? ARGH. - //.... changed now. - //ThreadPool.QueueUserWorkItem(delegate { UpdateDocumentCache(documentId); }); - UpdateDocumentCache(documentId); } - - /// - /// Clears the document cache async. - /// - /// The document id. + [Obsolete("Method obsolete in version 4.1 and later, please use ClearDocumentCache", true)] public virtual void ClearDocumentCacheAsync(int documentId) { - //SD: WE've obsoleted this but then didn't make it call the method it should! So we've just - // left a bug behind...???? ARGH. - //.... changed now. - //ThreadPool.QueueUserWorkItem(delegate { ClearDocumentCache(documentId); }); - ClearDocumentCache(documentId); } @@ -653,69 +637,6 @@ namespace umbraco ClearDocumentCache(documentId); } - ///// - ///// Uns the publish node async. - ///// - ///// The document id. - //[Obsolete("Please use: umbraco.content.ClearDocumentCacheAsync", true)] - //public virtual void UnPublishNodeAsync(int documentId) - //{ - - // ThreadPool.QueueUserWorkItem(delegate { ClearDocumentCache(documentId); }); - //} - - - ///// - ///// Legacy method - you should use the overloaded publishnode(document d) method whenever possible - ///// - ///// - //[Obsolete("Please use: umbraco.content.UpdateDocumentCache", true)] - //public virtual void PublishNode(int documentId) - //{ - - // // Get the document - // var d = new Document(documentId); - // PublishNode(d); - //} - - - ///// - ///// Publishes the node async. - ///// - ///// The document id. - //[Obsolete("Please use: umbraco.content.UpdateDocumentCacheAsync", true)] - //public virtual void PublishNodeAsync(int documentId) - //{ - - // UpdateDocumentCacheAsync(documentId); - //} - - - ///// - ///// Publishes the node. - ///// - ///// The documents. - //[Obsolete("Please use: umbraco.content.UpdateDocumentCache", true)] - //public virtual void PublishNode(List Documents) - //{ - - // UpdateDocumentCache(Documents); - //} - - - - ///// - ///// Publishes the node. - ///// - ///// The document. - //[Obsolete("Please use: umbraco.content.UpdateDocumentCache", true)] - //public virtual void PublishNode(Document d) - //{ - - // UpdateDocumentCache(d); - //} - - /// /// Occurs when [before document cache update]. /// @@ -933,9 +854,10 @@ namespace umbraco RemoveXmlFilePersistenceQueue(); } } - catch + catch (Exception ex) { // Nothing to catch here - we'll just persist + LogHelper.Error("An error occurred checking if xml file is queued for persistence", ex); } } } @@ -1037,22 +959,8 @@ namespace umbraco /// private XmlDocument LoadContentFromDatabase() { - // Alex N - 2010 06 - Very generic try-catch simply because at the moment, unfortunately, this method gets called inside a ThreadPool thread - // and we need to guarantee it won't tear down the app pool by throwing an unhandled exception try { - // Moved User to a local variable - why are we causing user 0 to load from the DB though? - // Alex N 20100212 - User staticUser = null; - try - { - staticUser = User.GetCurrent(); //User.GetUser(0); - } - catch - { - /* We don't care later if the staticUser is null */ - } - // Try to log to the DB LogHelper.Info("Loading content from database..."); @@ -1236,19 +1144,10 @@ order by umbracoNode.level, umbracoNode.sortOrder"; { if (xmlDoc != null) { - Trace.Write(string.Format("Saving content to disk on thread '{0}' (Threadpool? {1})", - Thread.CurrentThread.Name, Thread.CurrentThread.IsThreadPoolThread)); - - // Moved the user into a variable and avoided it throwing an error if one can't be loaded (e.g. empty / corrupt db on initial install) - User staticUser = null; - try - { - staticUser = User.GetCurrent(); - } - catch - { - } - + LogHelper.Debug("Saving content to disk on thread '{0}' (Threadpool? {1})", + () => Thread.CurrentThread.Name, + () => Thread.CurrentThread.IsThreadPoolThread); + try { Stopwatch stopWatch = Stopwatch.StartNew(); @@ -1264,23 +1163,20 @@ order by umbracoNode.level, umbracoNode.sortOrder"; } xmlDoc.Save(UmbracoXmlDiskCacheFileName); - - Trace.Write(string.Format("Saved content on thread '{0}' in {1} (Threadpool? {2})", - Thread.CurrentThread.Name, stopWatch.Elapsed, - Thread.CurrentThread.IsThreadPoolThread)); - - LogHelper.Debug(string.Format("Xml saved in {0}", stopWatch.Elapsed)); + + LogHelper.Debug("Saved content on thread '{0}' in {1} (Threadpool? {2})", + () => Thread.CurrentThread.Name, + () => stopWatch.Elapsed, + () => Thread.CurrentThread.IsThreadPoolThread); } catch (Exception ee) { // If for whatever reason something goes wrong here, invalidate disk cache DeleteXmlCache(); - - Trace.Write(string.Format( + + LogHelper.Error(string.Format( "Error saving content on thread '{0}' due to '{1}' (Threadpool? {2})", - Thread.CurrentThread.Name, ee.Message, Thread.CurrentThread.IsThreadPoolThread)); - - LogHelper.Error("Xml wasn't saved", ee); + Thread.CurrentThread.Name, ee.Message, Thread.CurrentThread.IsThreadPoolThread), ee); } } } @@ -1289,36 +1185,29 @@ order by umbracoNode.level, umbracoNode.sortOrder"; /// /// Marks a flag in the HttpContext so that, upon page execution completion, the Xml cache will /// get persisted to disk. Ensure this method is only called from a thread executing a page request - /// since umbraco.presentation.requestModule is the only monitor of this flag and is responsible + /// since UmbracoModule is the only monitor of this flag and is responsible /// for enacting the persistence at the PostRequestHandlerExecute stage of the page lifecycle. /// private void QueueXmlForPersistence() { - /* Alex Norcliffe 2010 06 03 - removing all launching of ThreadPool threads, instead we just - * flag on the context that the Xml should be saved and an event in the requestModule - * will check for this and call PersistXmlToFile() if necessary */ + //if this is called outside a web request we cannot queue it. + if (HttpContext.Current != null) { HttpContext.Current.Application.Lock(); - if (HttpContext.Current.Application[PersistenceFlagContextKey] != null) - HttpContext.Current.Application.Add(PersistenceFlagContextKey, null); - HttpContext.Current.Application[PersistenceFlagContextKey] = DateTime.UtcNow; - HttpContext.Current.Application.UnLock(); - } - else - { - //// Save copy of content - if (UmbracoConfig.For.UmbracoSettings().Content.CloneXmlContent) + try { - XmlDocument xmlContentCopy = CloneXmlDoc(_xmlContent); - - ThreadPool.QueueUserWorkItem( - delegate { PersistXmlToFile(xmlContentCopy); }); + if (HttpContext.Current.Application[PersistenceFlagContextKey] != null) + { + HttpContext.Current.Application.Add(PersistenceFlagContextKey, null); + } + HttpContext.Current.Application[PersistenceFlagContextKey] = DateTime.UtcNow; } - else - ThreadPool.QueueUserWorkItem( - delegate { PersistXmlToFile(); }); - } + finally + { + HttpContext.Current.Application.UnLock(); + } + } } internal DateTime GetCacheFileUpdateTime() From 27764a2c82582bbddeca8e3694abfd336c2fcf90 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 18:58:29 +1000 Subject: [PATCH 073/189] merge issue --- src/umbraco.cms/businesslogic/Packager/Installer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index 2cd0489b29..49541df9b3 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -437,7 +437,6 @@ namespace umbraco.cms.businesslogic.packager /// /// The folder to which the contents of the package is extracted public void LoadConfig(string tempDir) - [Obsolete("This method is no longer used and will be removed in the future.")] { Config = new XmlDocument(); Config.Load(tempDir + Path.DirectorySeparatorChar + "package.xml"); From 859fbaaa058794dd67d1f09f1902146556b275ce Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Jun 2014 19:02:49 +1000 Subject: [PATCH 074/189] Fixes: U4-5121 umbraco.content launches threadpool threads to reload the xml cache which causes lots of other issues Conflicts: src/Umbraco.Web/umbraco.presentation/content.cs --- src/Umbraco.Web/UmbracoModule.cs | 6 +- .../umbraco.presentation/content.cs | 211 +++++------------- 2 files changed, 52 insertions(+), 165 deletions(-) diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 9c34af7ab8..86ede93ae1 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -498,7 +498,6 @@ namespace Umbraco.Web app.BeginRequest += (sender, e) => { var httpContext = ((HttpApplication)sender).Context; - httpContext.Trace.Write("UmbracoModule", "Umbraco request begins"); LogHelper.Debug("Begin request: {0}.", () => httpContext.Request.Url); BeginRequest(new HttpContextWrapper(httpContext)); }; @@ -523,9 +522,8 @@ namespace Umbraco.Web var httpContext = ((HttpApplication)sender).Context; if (UmbracoContext.Current != null && UmbracoContext.Current.IsFrontEndUmbracoRequest) { - //write the trace output for diagnostics at the end of the request - httpContext.Trace.Write("UmbracoModule", "Umbraco request completed"); - LogHelper.Debug("Total milliseconds for umbraco request to process: " + DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds); + LogHelper.Debug( + "Total milliseconds for umbraco request to process: {0}", () => DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds); } OnEndRequest(new EventArgs()); diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index ab611bda87..78a5fe7023 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -206,7 +206,6 @@ namespace umbraco FireAfterRefreshContent(new RefreshContentEventArgs()); // Only save new XML cache to disk if we just repopulated it - // TODO: Re-architect this so that a call to this method doesn't invoke a new thread for saving disk cache if (!UmbracoSettings.isXmlContentCacheDisabled && !IsValidDiskCachePresent()) { QueueXmlForPersistence(); @@ -215,7 +214,9 @@ namespace umbraco } } } - Trace.WriteLine("Content initialized (was already in context)"); + + LogHelper.Debug(() => "Content initialized (was already in context)"); + return false; } @@ -288,32 +289,33 @@ namespace umbraco #endregion - /// - /// Load content from database in a background thread - /// Replaces active content when done. - /// + [Obsolete("This is no longer used and will be removed in future versions, if you use this method it will not refresh 'async' it will perform the refresh on the current thread which is how it should be doing it")] public virtual void RefreshContentFromDatabaseAsync() + { + RefreshContentFromDatabase(); + } + + /// + /// Load content from database and replaces active content when done. + /// + public virtual void RefreshContentFromDatabase() { var e = new RefreshContentEventArgs(); FireBeforeRefreshContent(e); if (!e.Cancel) { - ThreadPool.QueueUserWorkItem( - delegate - { - XmlDocument xmlDoc = LoadContentFromDatabase(); - XmlContentInternal = xmlDoc; + XmlDocument xmlDoc = LoadContentFromDatabase(); + XmlContentInternal = xmlDoc; - // It is correct to manually call PersistXmlToFile here event though the setter of XmlContentInternal - // queues this up, because this delegate is executing on a different thread and may complete - // after the request which invoked it (which would normally persist the file on completion) - // So we are responsible for ensuring the content is persisted in this case. + // It is correct to manually call PersistXmlToFile here event though the setter of XmlContentInternal + // queues this up, because this delegate is executing on a different thread and may complete + // after the request which invoked it (which would normally persist the file on completion) + // So we are responsible for ensuring the content is persisted in this case. if (!UmbracoSettings.isXmlContentCacheDisabled && UmbracoSettings.continouslyUpdateXmlDiskCache) - PersistXmlToFile(xmlDoc); - }); - - FireAfterRefreshContent(e); + { + PersistXmlToFile(xmlDoc); + } } } @@ -551,33 +553,15 @@ namespace umbraco } } - /// - /// Updates the document cache async. - /// - /// The document id. [Obsolete("Method obsolete in version 4.1 and later, please use UpdateDocumentCache", true)] public virtual void UpdateDocumentCacheAsync(int documentId) { - //SD: WE've obsoleted this but then didn't make it call the method it should! So we've just - // left a bug behind...???? ARGH. - //.... changed now. - //ThreadPool.QueueUserWorkItem(delegate { UpdateDocumentCache(documentId); }); - UpdateDocumentCache(documentId); } - - /// - /// Clears the document cache async. - /// - /// The document id. + [Obsolete("Method obsolete in version 4.1 and later, please use ClearDocumentCache", true)] public virtual void ClearDocumentCacheAsync(int documentId) { - //SD: WE've obsoleted this but then didn't make it call the method it should! So we've just - // left a bug behind...???? ARGH. - //.... changed now. - //ThreadPool.QueueUserWorkItem(delegate { ClearDocumentCache(documentId); }); - ClearDocumentCache(documentId); } @@ -652,69 +636,6 @@ namespace umbraco ClearDocumentCache(documentId); } - ///// - ///// Uns the publish node async. - ///// - ///// The document id. - //[Obsolete("Please use: umbraco.content.ClearDocumentCacheAsync", true)] - //public virtual void UnPublishNodeAsync(int documentId) - //{ - - // ThreadPool.QueueUserWorkItem(delegate { ClearDocumentCache(documentId); }); - //} - - - ///// - ///// Legacy method - you should use the overloaded publishnode(document d) method whenever possible - ///// - ///// - //[Obsolete("Please use: umbraco.content.UpdateDocumentCache", true)] - //public virtual void PublishNode(int documentId) - //{ - - // // Get the document - // var d = new Document(documentId); - // PublishNode(d); - //} - - - ///// - ///// Publishes the node async. - ///// - ///// The document id. - //[Obsolete("Please use: umbraco.content.UpdateDocumentCacheAsync", true)] - //public virtual void PublishNodeAsync(int documentId) - //{ - - // UpdateDocumentCacheAsync(documentId); - //} - - - ///// - ///// Publishes the node. - ///// - ///// The documents. - //[Obsolete("Please use: umbraco.content.UpdateDocumentCache", true)] - //public virtual void PublishNode(List Documents) - //{ - - // UpdateDocumentCache(Documents); - //} - - - - ///// - ///// Publishes the node. - ///// - ///// The document. - //[Obsolete("Please use: umbraco.content.UpdateDocumentCache", true)] - //public virtual void PublishNode(Document d) - //{ - - // UpdateDocumentCache(d); - //} - - /// /// Occurs when [before document cache update]. /// @@ -932,9 +853,10 @@ namespace umbraco RemoveXmlFilePersistenceQueue(); } } - catch + catch (Exception ex) { // Nothing to catch here - we'll just persist + LogHelper.Error("An error occurred checking if xml file is queued for persistence", ex); } } } @@ -1036,22 +958,8 @@ namespace umbraco /// private XmlDocument LoadContentFromDatabase() { - // Alex N - 2010 06 - Very generic try-catch simply because at the moment, unfortunately, this method gets called inside a ThreadPool thread - // and we need to guarantee it won't tear down the app pool by throwing an unhandled exception try { - // Moved User to a local variable - why are we causing user 0 to load from the DB though? - // Alex N 20100212 - User staticUser = null; - try - { - staticUser = User.GetCurrent(); //User.GetUser(0); - } - catch - { - /* We don't care later if the staticUser is null */ - } - // Try to log to the DB LogHelper.Info("Loading content from database..."); @@ -1235,19 +1143,10 @@ order by umbracoNode.level, umbracoNode.sortOrder"; { if (xmlDoc != null) { - Trace.Write(string.Format("Saving content to disk on thread '{0}' (Threadpool? {1})", - Thread.CurrentThread.Name, Thread.CurrentThread.IsThreadPoolThread)); - - // Moved the user into a variable and avoided it throwing an error if one can't be loaded (e.g. empty / corrupt db on initial install) - User staticUser = null; - try - { - staticUser = User.GetCurrent(); - } - catch - { - } - + LogHelper.Debug("Saving content to disk on thread '{0}' (Threadpool? {1})", + () => Thread.CurrentThread.Name, + () => Thread.CurrentThread.IsThreadPoolThread); + try { Stopwatch stopWatch = Stopwatch.StartNew(); @@ -1263,23 +1162,20 @@ order by umbracoNode.level, umbracoNode.sortOrder"; } xmlDoc.Save(UmbracoXmlDiskCacheFileName); - - Trace.Write(string.Format("Saved content on thread '{0}' in {1} (Threadpool? {2})", - Thread.CurrentThread.Name, stopWatch.Elapsed, - Thread.CurrentThread.IsThreadPoolThread)); - - LogHelper.Debug(string.Format("Xml saved in {0}", stopWatch.Elapsed)); + + LogHelper.Debug("Saved content on thread '{0}' in {1} (Threadpool? {2})", + () => Thread.CurrentThread.Name, + () => stopWatch.Elapsed, + () => Thread.CurrentThread.IsThreadPoolThread); } catch (Exception ee) { // If for whatever reason something goes wrong here, invalidate disk cache DeleteXmlCache(); - - Trace.Write(string.Format( + + LogHelper.Error(string.Format( "Error saving content on thread '{0}' due to '{1}' (Threadpool? {2})", - Thread.CurrentThread.Name, ee.Message, Thread.CurrentThread.IsThreadPoolThread)); - - LogHelper.Error("Xml wasn't saved", ee); + Thread.CurrentThread.Name, ee.Message, Thread.CurrentThread.IsThreadPoolThread), ee); } } } @@ -1288,36 +1184,29 @@ order by umbracoNode.level, umbracoNode.sortOrder"; /// /// Marks a flag in the HttpContext so that, upon page execution completion, the Xml cache will /// get persisted to disk. Ensure this method is only called from a thread executing a page request - /// since umbraco.presentation.requestModule is the only monitor of this flag and is responsible + /// since UmbracoModule is the only monitor of this flag and is responsible /// for enacting the persistence at the PostRequestHandlerExecute stage of the page lifecycle. /// private void QueueXmlForPersistence() { - /* Alex Norcliffe 2010 06 03 - removing all launching of ThreadPool threads, instead we just - * flag on the context that the Xml should be saved and an event in the requestModule - * will check for this and call PersistXmlToFile() if necessary */ + //if this is called outside a web request we cannot queue it. + if (HttpContext.Current != null) { HttpContext.Current.Application.Lock(); - if (HttpContext.Current.Application[PersistenceFlagContextKey] != null) - HttpContext.Current.Application.Add(PersistenceFlagContextKey, null); - HttpContext.Current.Application[PersistenceFlagContextKey] = DateTime.UtcNow; - HttpContext.Current.Application.UnLock(); - } - else - { - //// Save copy of content - if (UmbracoSettings.CloneXmlCacheOnPublish) + try { - XmlDocument xmlContentCopy = CloneXmlDoc(_xmlContent); - - ThreadPool.QueueUserWorkItem( - delegate { PersistXmlToFile(xmlContentCopy); }); + if (HttpContext.Current.Application[PersistenceFlagContextKey] != null) + { + HttpContext.Current.Application.Add(PersistenceFlagContextKey, null); + } + HttpContext.Current.Application[PersistenceFlagContextKey] = DateTime.UtcNow; } - else - ThreadPool.QueueUserWorkItem( - delegate { PersistXmlToFile(); }); - } + finally + { + HttpContext.Current.Application.UnLock(); + } + } } internal DateTime GetCacheFileUpdateTime() From 12cdd3326aa18932e4ce043f0385b284ebc43c91 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 25 Jun 2014 16:00:21 +0200 Subject: [PATCH 075/189] The CopyFile extension method was not disposing the file stream, so any attempt to delete the source file afterwards would fail. Removing unused using statements. --- src/Umbraco.Core/IO/FileSystemExtensions.cs | 6 ++++-- src/Umbraco.Core/IO/FileSystemWrapper.cs | 1 - src/Umbraco.Core/IO/IFileSystem.cs | 1 - src/Umbraco.Core/IO/PhysicalFileSystem.cs | 3 --- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index a4a40d37ba..6edc52eeb1 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using Umbraco.Core.CodeAnnotations; namespace Umbraco.Core.IO { @@ -19,7 +18,10 @@ namespace Umbraco.Core.IO public static void CopyFile(this IFileSystem fs, string path, string newPath) { - fs.AddFile(newPath, fs.OpenFile(path)); + using (var fileStream = fs.OpenFile(path)) + { + fs.AddFile(newPath, fileStream); + } } public static string GetExtension(this IFileSystem fs, string path) diff --git a/src/Umbraco.Core/IO/FileSystemWrapper.cs b/src/Umbraco.Core/IO/FileSystemWrapper.cs index 924485db43..ba2ad8f48b 100644 --- a/src/Umbraco.Core/IO/FileSystemWrapper.cs +++ b/src/Umbraco.Core/IO/FileSystemWrapper.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using Umbraco.Core.CodeAnnotations; namespace Umbraco.Core.IO { diff --git a/src/Umbraco.Core/IO/IFileSystem.cs b/src/Umbraco.Core/IO/IFileSystem.cs index 113f63565c..82baae6fb8 100644 --- a/src/Umbraco.Core/IO/IFileSystem.cs +++ b/src/Umbraco.Core/IO/IFileSystem.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using Umbraco.Core.CodeAnnotations; namespace Umbraco.Core.IO { diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 74d7f27671..bdba541ea3 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -3,10 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Web; -using Umbraco.Core.CodeAnnotations; using Umbraco.Core.Logging; -using Umbraco.Core.Publishing; namespace Umbraco.Core.IO { From 582bafdefd790cc9737b6f32d0b8712734c64cc3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 26 Jun 2014 10:02:09 +1000 Subject: [PATCH 076/189] Updates version --- build/Build.bat | 2 +- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/Build.bat b/build/Build.bat index a758bb5722..48d3439a47 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -1,5 +1,5 @@ @ECHO OFF -SET release=6.2.1 +SET release=6.2.2 SET comment= SET version=%release% diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index b88f880021..50ee876c64 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("6.2.1"); + private static readonly Version Version = new Version("6.2.2"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index e86ecfccc6..dc6b9c6884 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2677,7 +2677,7 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" True 6210 / - http://localhost:6210 + http://localhost:6220 False False From 70514b88c54a76ab08dfca5a312690d5e6c1d314 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Thu, 26 Jun 2014 02:57:47 +0200 Subject: [PATCH 077/189] Fix for U4-3953 Installing package - installing message progressbar causes horizontal scrollbar --- .../umbraco/developer/Packages/installer.aspx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx b/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx index 1f0c4a873e..405661465f 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx @@ -74,9 +74,9 @@ - @@ -251,7 +251,7 @@
-
- - internal interface IPackageExtraction + internal interface IPackageExtraction { /// /// Returns the content of the file with the given filename diff --git a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs b/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs index 2ef4dfec9d..a3c394946a 100644 --- a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs +++ b/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Core.Models; @@ -9,36 +10,35 @@ namespace Umbraco.Core.Packaging.Models internal class InstallationSummary { public MetaData MetaData { get; set; } - public IDataTypeDefinition[] DataTypesInstalled { get; set; } - public ILanguage[] LanguagesInstalled { get; set; } - public IDictionaryItem[] DictionaryItemsInstalled { get; set; } - public IMacro[] MacrosInstalled { get; set; } - public string[] FilesInstalled { get; set; } - public ITemplate[] TemplatesInstalled { get; set; } - public IContentType[] ContentTypesInstalled { get; set; } - public IFile[] StylesheetsInstalled { get; set; } - public IContent[] ContentInstalled { get; set; } - public PackageAction[] Actions { get; set; } + public IEnumerable DataTypesInstalled { get; set; } + public IEnumerable LanguagesInstalled { get; set; } + public IEnumerable DictionaryItemsInstalled { get; set; } + public IEnumerable MacrosInstalled { get; set; } + public IEnumerable FilesInstalled { get; set; } + public IEnumerable TemplatesInstalled { get; set; } + public IEnumerable ContentTypesInstalled { get; set; } + public IEnumerable StylesheetsInstalled { get; set; } + public IEnumerable ContentInstalled { get; set; } + public IEnumerable Actions { get; set; } public bool PackageInstalled { get; set; } } - - + internal static class InstallationSummaryExtentions { - public static InstallationSummary InitEmpty(this InstallationSummary @this) + public static InstallationSummary InitEmpty(this InstallationSummary summary) { - @this.Actions = new PackageAction[0]; - @this.ContentInstalled = new IContent[0]; - @this.ContentTypesInstalled = new IContentType[0]; - @this.DataTypesInstalled = new IDataTypeDefinition[0]; - @this.DictionaryItemsInstalled = new IDictionaryItem[0]; - @this.FilesInstalled = new string[0]; - @this.LanguagesInstalled = new ILanguage[0]; - @this.MacrosInstalled = new IMacro[0]; - @this.MetaData = new MetaData(); - @this.TemplatesInstalled = new ITemplate[0]; - @this.PackageInstalled = false; - return @this; + summary.Actions = new List(); + summary.ContentInstalled = new List(); + summary.ContentTypesInstalled = new List(); + summary.DataTypesInstalled = new List(); + summary.DictionaryItemsInstalled = new List(); + summary.FilesInstalled = new List(); + summary.LanguagesInstalled = new List(); + summary.MacrosInstalled = new List(); + summary.MetaData = new MetaData(); + summary.TemplatesInstalled = new List(); + summary.PackageInstalled = false; + return summary; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/Models/PackageAction.cs b/src/Umbraco.Core/Packaging/Models/PackageAction.cs index 08c586c7f4..0e20786a72 100644 --- a/src/Umbraco.Core/Packaging/Models/PackageAction.cs +++ b/src/Umbraco.Core/Packaging/Models/PackageAction.cs @@ -1,18 +1,16 @@ -using System; -using System.Runtime.Serialization; -using System.Xml; +using System; +using System.Runtime.Serialization; using System.Xml.Linq; -namespace Umbraco.Core.Packaging.Models +namespace Umbraco.Core.Packaging.Models { internal enum ActionRunAt { Undefined = 0, Install, Uninstall - } - - + } + [Serializable] [DataContract(IsReference = true)] internal class PackageAction @@ -34,8 +32,7 @@ namespace Umbraco.Core.Packaging.Models get { return _undo ?? true; } set { _undo = value; } } - - public XElement XmlData { get; set; } - } + public XElement XmlData { get; set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs b/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs index 383c9bd26b..05330ce043 100644 --- a/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs +++ b/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs @@ -6,14 +6,14 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Packaging.Models { [Serializable] - [DataContract(IsReference = true)] + [DataContract(IsReference = true)] internal class PreInstallWarnings { public KeyValuePair[] UnsecureFiles { get; set; } public KeyValuePair[] FilesReplaced { get; set; } - public IMacro[] ConflictingMacroAliases { get; set; } - public ITemplate[] ConflictingTemplateAliases { get; set; } - public IFile[] ConflictingStylesheetNames { get; set; } - public string[] AssembliesWithLegacyPropertyEditors { get; set; } + public IEnumerable ConflictingMacroAliases { get; set; } + public IEnumerable ConflictingTemplateAliases { get; set; } + public IEnumerable ConflictingStylesheetNames { get; set; } + public IEnumerable AssembliesWithLegacyPropertyEditors { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs index 2b9ada9278..ccf1345074 100644 --- a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs +++ b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs @@ -5,9 +5,6 @@ using System.Linq; using System.Reflection; using System.Security; using System.Security.Permissions; -using System.Text; -using System.Threading.Tasks; -using System.Web; using Umbraco.Core.Logging; namespace Umbraco.Core.Packaging @@ -216,13 +213,5 @@ namespace Umbraco.Core.Packaging domainSetup, new PermissionSet(PermissionState.Unrestricted)); } - - private static string GetAssemblyPath(Assembly a) - { - var codeBase = a.CodeBase; - var uri = new Uri(codeBase); - return uri.LocalPath; - } - } } diff --git a/src/Umbraco.Core/Packaging/PackageExtraction.cs b/src/Umbraco.Core/Packaging/PackageExtraction.cs index 6550fd435d..1f34933020 100644 --- a/src/Umbraco.Core/Packaging/PackageExtraction.cs +++ b/src/Umbraco.Core/Packaging/PackageExtraction.cs @@ -4,9 +4,9 @@ using System.IO; using System.Linq; using ICSharpCode.SharpZipLib.Zip; -namespace Umbraco.Core.Packaging +namespace Umbraco.Core.Packaging { - internal class PackageExtraction : IPackageExtraction + internal class PackageExtraction : IPackageExtraction { public string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage) { @@ -66,9 +66,7 @@ namespace Umbraco.Core.Packaging string.Format("Error - file isn't a package. only extentions: \"{0}\" is allowed", string.Join(", ", alowedExtension))); } } - - public void CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath) { CopyFilesFromArchive(packageFilePath, new[]{new KeyValuePair(fileInPackageName, destinationfilePath) } ); @@ -199,5 +197,5 @@ namespace Umbraco.Core.Packaging fs.Close(); } } - } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index 57f04914d3..8ca115ea8b 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -8,71 +8,77 @@ using System.Xml.XPath; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Models; -using Umbraco.Core.Packaging.Models; +using Umbraco.Core.Packaging.Models; using Umbraco.Core.Services; using umbraco.interfaces; using File = System.IO.File; -namespace Umbraco.Core.Packaging +namespace Umbraco.Core.Packaging { - internal class PackageInstallation : IPackageInstallation - { + internal class PackageInstallation : IPackageInstallation + { private readonly IFileService _fileService; private readonly IMacroService _macroService; private readonly IPackagingService _packagingService; - private IConflictingPackageContentFinder _conflictingPackageContentFinder; + private IConflictingPackageData _conflictingPackageData; private readonly IPackageExtraction _packageExtraction; + private string _fullPathToRoot; public PackageInstallation(IPackagingService packagingService, IMacroService macroService, IFileService fileService, IPackageExtraction packageExtraction) + : this(packagingService, macroService, fileService, packageExtraction, GlobalSettings.FullpathToRoot) + {} + + public PackageInstallation(IPackagingService packagingService, IMacroService macroService, + IFileService fileService, IPackageExtraction packageExtraction, string fullPathToRoot) { if (packageExtraction != null) _packageExtraction = packageExtraction; else throw new ArgumentNullException("packageExtraction"); + if (macroService != null) _macroService = macroService; else throw new ArgumentNullException("macroService"); + if (fileService != null) _fileService = fileService; else throw new ArgumentNullException("fileService"); + if (packagingService != null) _packagingService = packagingService; else throw new ArgumentNullException("packagingService"); + + _fullPathToRoot = fullPathToRoot; } - - - public IConflictingPackageContentFinder ConflictingPackageContentFinder + + public IConflictingPackageData ConflictingPackageData { private get { - return _conflictingPackageContentFinder ?? - (_conflictingPackageContentFinder = new ConflictingPackageContentFinder(_macroService, _fileService)); + return _conflictingPackageData ?? + (_conflictingPackageData = new ConflictingPackageData(_macroService, _fileService)); } set { - if (_conflictingPackageContentFinder != null) + if (_conflictingPackageData != null) { throw new PropertyConstraintException("This property already have a value"); } - _conflictingPackageContentFinder = value; + _conflictingPackageData = value; } } - - - private string _fullpathToRoot; - public string FullpathToRoot + public string FullPathToRoot { - private get { return _fullpathToRoot ?? (_fullpathToRoot = GlobalSettings.FullpathToRoot); } + private get { return _fullPathToRoot; } set { - if (_fullpathToRoot != null) + if (_fullPathToRoot != null) { throw new PropertyConstraintException("This property already have a value"); } - _fullpathToRoot = value; + _fullPathToRoot = value; } } - - + public MetaData GetMetaData(string packageFilePath) { try @@ -99,7 +105,6 @@ namespace Umbraco.Core.Packaging } } - public InstallationSummary InstallPackage(string packageFile, int userId) { XElement dataTypes; @@ -141,34 +146,34 @@ namespace Umbraco.Core.Packaging try { - var dataTypeDefinitions = EmptyArrayIfNull(dataTypes) ?? InstallDataTypes(dataTypes, userId); + var dataTypeDefinitions = EmptyEnumerableIfNull(dataTypes) ?? InstallDataTypes(dataTypes, userId); installationSummary.DataTypesInstalled = dataTypeDefinitions; - var languagesInstalled = EmptyArrayIfNull(languages) ?? InstallLanguages(languages, userId); + var languagesInstalled = EmptyEnumerableIfNull(languages) ?? InstallLanguages(languages, userId); installationSummary.LanguagesInstalled = languagesInstalled; - var dictionaryInstalled = EmptyArrayIfNull(dictionaryItems) ?? InstallDictionaryItems(dictionaryItems); + var dictionaryInstalled = EmptyEnumerableIfNull(dictionaryItems) ?? InstallDictionaryItems(dictionaryItems); installationSummary.DictionaryItemsInstalled = dictionaryInstalled; - var macros = EmptyArrayIfNull(macroes)?? InstallMacros(macroes, userId); + var macros = EmptyEnumerableIfNull(macroes) ?? InstallMacros(macroes, userId); installationSummary.MacrosInstalled = macros; - var keyValuePairs = EmptyArrayIfNull(packageFile) ?? InstallFiles(packageFile, files); + var keyValuePairs = EmptyEnumerableIfNull(packageFile) ?? InstallFiles(packageFile, files); installationSummary.FilesInstalled = keyValuePairs; - - var templatesInstalled = EmptyArrayIfNull(templates) ?? InstallTemplats(templates, userId); + + var templatesInstalled = EmptyEnumerableIfNull(templates) ?? InstallTemplats(templates, userId); installationSummary.TemplatesInstalled = templatesInstalled; - var documentTypesInstalled = EmptyArrayIfNull(documentTypes) ?? InstallDocumentTypes(documentTypes, userId); + var documentTypesInstalled = EmptyEnumerableIfNull(documentTypes) ?? InstallDocumentTypes(documentTypes, userId); installationSummary.ContentTypesInstalled =documentTypesInstalled; - var stylesheetsInstalled = EmptyArrayIfNull(styleSheets) ?? InstallStylesheets(styleSheets, userId); + var stylesheetsInstalled = EmptyEnumerableIfNull(styleSheets) ?? InstallStylesheets(styleSheets, userId); installationSummary.StylesheetsInstalled = stylesheetsInstalled; - var documentsInstalled = EmptyArrayIfNull(documentSet) ?? InstallDocuments(documentSet, userId); + var documentsInstalled = EmptyEnumerableIfNull(documentSet) ?? InstallDocuments(documentSet, userId); installationSummary.ContentInstalled = documentsInstalled; - var packageActions = EmptyArrayIfNull(actions) ?? GetPackageActions(actions, metaData.Name); + var packageActions = EmptyEnumerableIfNull(actions) ?? GetPackageActions(actions, metaData.Name); installationSummary.Actions = packageActions; installationSummary.PackageInstalled = true; @@ -190,7 +195,6 @@ namespace Umbraco.Core.Packaging XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); if (styleSheets != null && styleSheets.Elements().Any()) throw new NotSupportedException("Stylesheets is not suported in this version of umbraco"); - } private static T[] EmptyArrayIfNull(object obj) @@ -198,6 +202,10 @@ namespace Umbraco.Core.Packaging return obj == null ? new T[0] : null; } + private static IEnumerable EmptyEnumerableIfNull(object obj) + { + return obj == null ? Enumerable.Empty() : null; + } private XDocument GetConfigXmlDoc(string packageFilePath) { @@ -208,7 +216,6 @@ namespace Umbraco.Core.Packaging return XDocument.Parse(configXmlContent); } - public XElement GetConfigXmlElement(string packageFilePath) { XDocument document = GetConfigXmlDoc(packageFilePath); @@ -220,7 +227,6 @@ namespace Umbraco.Core.Packaging return document.Root; } - internal void PackageStructureSanetyCheck(string packageFilePath) { XElement rootElement = GetConfigXmlElement(packageFilePath); @@ -229,7 +235,6 @@ namespace Umbraco.Core.Packaging private void PackageStructureSanetyCheck(string packageFilePath, XElement rootElement) { - XElement filesElement = rootElement.Element(Constants.Packaging.FilesNodeName); if (filesElement != null) { @@ -259,10 +264,7 @@ namespace Umbraco.Core.Packaging } } - - - - private static PackageAction[] GetPackageActions(XElement actionsElement, string packageName) + private static IEnumerable GetPackageActions(XElement actionsElement, string packageName) { if (actionsElement == null) { return new PackageAction[0]; } @@ -301,20 +303,20 @@ namespace Umbraco.Core.Packaging return packageAction; - }).ToArray(); + }); } - private IContent[] InstallDocuments(XElement documentsElement, int userId = 0) + private IEnumerable InstallDocuments(XElement documentsElement, int userId = 0) { if (string.Equals(Constants.Packaging.DocumentSetNodeName, documentsElement.Name.LocalName) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.DocumentSetNodeName + "\" as root", "documentsElement"); } - return _packagingService.ImportContent(documentsElement, -1, userId).ToArray(); + return _packagingService.ImportContent(documentsElement, -1, userId); } - private IFile[] InstallStylesheets(XElement styleSheetsElement, int userId = 0) + private IEnumerable InstallStylesheets(XElement styleSheetsElement, int userId = 0) { if (string.Equals(Constants.Packaging.StylesheetsNodeName, styleSheetsElement.Name.LocalName) == false) { @@ -323,12 +325,12 @@ namespace Umbraco.Core.Packaging } // TODO: Call _packagingService when import stylesheets import has been implimentet - if (styleSheetsElement.HasElements == false) { return new IFile[0]; } + if (styleSheetsElement.HasElements == false) { return new List(); } throw new NotImplementedException("The packaging service do not yes have a method for importing stylesheets"); } - private IContentType[] InstallDocumentTypes(XElement documentTypes, int userId = 0) + private IEnumerable InstallDocumentTypes(XElement documentTypes, int userId = 0) { if (string.Equals(Constants.Packaging.DocumentTypesNodeName, documentTypes.Name.LocalName) == false) { @@ -339,28 +341,27 @@ namespace Umbraco.Core.Packaging documentTypes = new XElement(Constants.Packaging.DocumentTypesNodeName, documentTypes); } - return _packagingService.ImportContentTypes(documentTypes, userId).ToArray(); + return _packagingService.ImportContentTypes(documentTypes, userId); } - private ITemplate[] InstallTemplats(XElement templateElement, int userId = 0) + private IEnumerable InstallTemplats(XElement templateElement, int userId = 0) { if (string.Equals(Constants.Packaging.TemplatesNodeName, templateElement.Name.LocalName) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.TemplatesNodeName + "\" as root", "templateElement"); } - return _packagingService.ImportTemplates(templateElement, userId).ToArray(); + return _packagingService.ImportTemplates(templateElement, userId); } - - private string[] InstallFiles(string packageFilePath, XElement filesElement) + private IEnumerable InstallFiles(string packageFilePath, XElement filesElement) { var sourceDestination = ExtractSourceDestinationFileInformation(filesElement); - sourceDestination = AppendRootToDestination(FullpathToRoot, sourceDestination); + sourceDestination = AppendRootToDestination(FullPathToRoot, sourceDestination); _packageExtraction.CopyFilesFromArchive(packageFilePath, sourceDestination); - return sourceDestination.Select(sd => sd.Value).ToArray(); + return sourceDestination.Select(sd => sd.Value); } private KeyValuePair[] AppendRootToDestination(string fullpathToRoot, IEnumerable> sourceDestination) @@ -370,17 +371,17 @@ namespace Umbraco.Core.Packaging sd => new KeyValuePair(sd.Key, Path.Combine(fullpathToRoot, sd.Value))).ToArray(); } - private IMacro[] InstallMacros(XElement macroElements, int userId = 0) + private IEnumerable InstallMacros(XElement macroElements, int userId = 0) { if (string.Equals(Constants.Packaging.MacrosNodeName, macroElements.Name.LocalName) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.MacrosNodeName + "\" as root", "macroElements"); } - return _packagingService.ImportMacros(macroElements, userId).ToArray(); + return _packagingService.ImportMacros(macroElements, userId); } - private IDictionaryItem[] InstallDictionaryItems(XElement dictionaryItemsElement) + private IEnumerable InstallDictionaryItems(XElement dictionaryItemsElement) { if (string.Equals(Constants.Packaging.DictionaryItemsNodeName, dictionaryItemsElement.Name.LocalName) == false) @@ -388,19 +389,19 @@ namespace Umbraco.Core.Packaging throw new ArgumentException("Must be \"" + Constants.Packaging.DictionaryItemsNodeName + "\" as root", "dictionaryItemsElement"); } - return _packagingService.ImportDictionaryItems(dictionaryItemsElement).ToArray(); + return _packagingService.ImportDictionaryItems(dictionaryItemsElement); } - private ILanguage[] InstallLanguages(XElement languageElement, int userId = 0) + private IEnumerable InstallLanguages(XElement languageElement, int userId = 0) { if (string.Equals(Constants.Packaging.LanguagesNodeName, languageElement.Name.LocalName) == false) { throw new ArgumentException("Must be \"" + Constants.Packaging.LanguagesNodeName + "\" as root", "languageElement"); } - return _packagingService.ImportLanguages(languageElement, userId).ToArray(); + return _packagingService.ImportLanguages(languageElement, userId); } - private IDataTypeDefinition[] InstallDataTypes(XElement dataTypeElements, int userId = 0) + private IEnumerable InstallDataTypes(XElement dataTypeElements, int userId = 0) { if (string.Equals(Constants.Packaging.DataTypesNodeName, dataTypeElements.Name.LocalName) == false) { @@ -409,7 +410,7 @@ namespace Umbraco.Core.Packaging throw new ArgumentException("Must be \"" + Constants.Packaging.DataTypeNodeName + "\" as root", "dataTypeElements"); } } - return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId).ToArray(); + return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId); } private PreInstallWarnings GetPreInstallWarnings(string packagePath, XElement rootElement) @@ -423,13 +424,13 @@ namespace Umbraco.Core.Packaging var installWarnings = new PreInstallWarnings(); - var macroAliases = EmptyArrayIfNull(alias) ?? ConflictingPackageContentFinder.FindConflictingMacros(alias); + var macroAliases = EmptyEnumerableIfNull(alias) ?? ConflictingPackageData.FindConflictingMacros(alias); installWarnings.ConflictingMacroAliases = macroAliases; - var templateAliases = EmptyArrayIfNull(templates) ?? ConflictingPackageContentFinder.FindConflictingTemplates(templates); + var templateAliases = EmptyEnumerableIfNull(templates) ?? ConflictingPackageData.FindConflictingTemplates(templates); installWarnings.ConflictingTemplateAliases = templateAliases; - var stylesheetNames = EmptyArrayIfNull(styleSheets) ?? ConflictingPackageContentFinder.FindConflictingStylesheets(styleSheets); + var stylesheetNames = EmptyEnumerableIfNull(styleSheets) ?? ConflictingPackageData.FindConflictingStylesheets(styleSheets); installWarnings.ConflictingStylesheetNames = stylesheetNames; installWarnings.UnsecureFiles = FindUnsecureFiles(sourceDestination); @@ -441,22 +442,20 @@ namespace Umbraco.Core.Packaging private KeyValuePair[] FindFilesToBeReplaced(IEnumerable> sourceDestination) { - return sourceDestination.Where(sd => File.Exists(Path.Combine(FullpathToRoot, sd.Value))).ToArray(); + return sourceDestination.Where(sd => File.Exists(Path.Combine(FullPathToRoot, sd.Value))).ToArray(); } - private string[] FindLegacyPropertyEditors(string packagePath, IEnumerable> sourceDestinationPair) + private IEnumerable FindLegacyPropertyEditors(string packagePath, IEnumerable> sourceDestinationPair) { var dlls = sourceDestinationPair.Where( sd => (Path.GetExtension(sd.Value) ?? string.Empty).Equals(".dll", StringComparison.InvariantCultureIgnoreCase)).Select(sd => sd.Key).ToArray(); - if (dlls.Any() == false) { return new string[0]; } - + if (dlls.Any() == false) { return new List(); } // Now we want to see if the DLLs contain any legacy data types since we want to warn people about that string[] assemblyErrors; IEnumerable assemblyesToScan =_packageExtraction.ReadFilesFromArchive(packagePath, dlls); - return PackageBinaryByteInspector.ScanAssembliesForTypeReference(assemblyesToScan, out assemblyErrors).ToArray(); - + return PackageBinaryByteInspector.ScanAssembliesForTypeReference(assemblyesToScan, out assemblyErrors).ToArray(); } private KeyValuePair[] FindUnsecureFiles(IEnumerable> sourceDestinationPair) @@ -473,8 +472,7 @@ namespace Umbraco.Core.Packaging string extension = Path.GetExtension(destination); return extension != null && extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase); } - - + private KeyValuePair[] ExtractSourceDestinationFileInformation(XElement filesElement) { if (string.Equals(Constants.Packaging.FilesNodeName, filesElement.Name.LocalName) == false) @@ -521,7 +519,6 @@ namespace Umbraco.Core.Packaging return pathElement.TrimStart(new[] {'\\', '/', '~'}).Replace("/", "\\"); } - private MetaData GetMetaData(XElement xRootElement) { XElement infoElement = xRootElement.Element(Constants.Packaging.InfoNodeName); @@ -546,21 +543,20 @@ namespace Umbraco.Core.Packaging XElement controlElement = xRootElement.Element(Constants.Packaging.ControlNodeName); return new MetaData - { - Name = StringValue(nameElement), - Version = StringValue(versionElement), - Url = StringValue(urlElement), - License = StringValue(licenseElement), - LicenseUrl = StringAttribute(licenseElement, Constants.Packaging.PackageLicenseXpathUrlAttribute), - AuthorName = StringValue(authorNameElement), - AuthorUrl = StringValue(authorUrlElement), - Readme = StringValue(readmeElement), - Control = StringValue(controlElement), - ReqMajor = IntValue(majorElement), - ReqMinor = IntValue(minorElement), - ReqPatch = IntValue(patchElement), - - }; + { + Name = StringValue(nameElement), + Version = StringValue(versionElement), + Url = StringValue(urlElement), + License = StringValue(licenseElement), + LicenseUrl = StringAttribute(licenseElement, Constants.Packaging.PackageLicenseXpathUrlAttribute), + AuthorName = StringValue(authorNameElement), + AuthorUrl = StringValue(authorUrlElement), + Readme = StringValue(readmeElement), + Control = StringValue(controlElement), + ReqMajor = IntValue(majorElement), + ReqMinor = IntValue(minorElement), + ReqPatch = IntValue(patchElement) + }; } private static string StringValue(XElement xElement, string defaultValue = "") @@ -575,14 +571,12 @@ namespace Umbraco.Core.Packaging : xElement.HasAttributes ? xElement.AttributeValue(attribute) : defaultValue; } - private static int IntValue(XElement xElement, int defaultValue = 0) { int val; return xElement == null ? defaultValue : int.TryParse(xElement.Value, out val) ? val : defaultValue; } - - + private static string UpdatePathPlaceholders(string path) { if (path.Contains("[$")) diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index b279f422fb..c8fe4e781b 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Configuration; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; @@ -1416,9 +1415,6 @@ namespace Umbraco.Core.Services internal IPackageInstallation PackageInstallation { - //NOTE The PackageInstallation class should be passed as IPackageInstallation through the - //constructor (probably as an overload to avoid breaking stuff), so that its extendable. - // NOTE COMMENT: But is is not a service? and all other parced in constructor is services... private get { return _packageInstallation ?? new PackageInstallation(this, _macroService, _fileService, new PackageExtraction()); } set { _packageInstallation = value; } } @@ -1442,7 +1438,6 @@ namespace Umbraco.Core.Services ImportedPackage.RaiseEvent(new ImportPackageEventArgs(installationSummary, false), this); } - return installationSummary; } @@ -1600,7 +1595,6 @@ namespace Umbraco.Core.Services /// public static event TypedEventHandler> ImportingStylesheets; - /// /// Occurs after Template is Imported and Saved /// @@ -1615,8 +1609,7 @@ namespace Umbraco.Core.Services /// Occurs after Template is Exported to Xml ///
public static event TypedEventHandler> ExportedTemplate; - - + /// /// Occurs before Importing umbraco package /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index dc606a41ce..2ff13b5ada 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1047,7 +1047,7 @@ - + @@ -1062,7 +1062,7 @@ - + From 097014454f9da44f85fc4487e4883a3fd2c7ce7f Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Fri, 27 Jun 2014 12:42:15 +0200 Subject: [PATCH 094/189] Fixing issue in MacroRepository which was causing a test to fail. Changing the alias of a macro property didn't alias work, so added a fallback to the Id of the property in the cases where the Alias wouldn't work. --- .../Repositories/MacroRepository.cs | 18 ++++++++++-- .../Services/MacroServiceTests.cs | 29 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs index c8ffcbe68a..f8c6955ada 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs @@ -188,10 +188,22 @@ namespace Umbraco.Core.Persistence.Repositories } else { - //only update if it's dirty - if (macro.Properties[propDto.Alias].IsDirty()) + //This will only work if the Alias hasn't been changed + if (macro.Properties.ContainsKey(propDto.Alias)) { - Database.Update(propDto); + //only update if it's dirty + if (macro.Properties[propDto.Alias].IsDirty()) + { + Database.Update(propDto); + } + } + else + { + var property = macro.Properties.FirstOrDefault(x => x.Id == propDto.Id); + if (property != null && property.IsDirty()) + { + Database.Update(propDto); + } } } } diff --git a/src/Umbraco.Tests/Services/MacroServiceTests.cs b/src/Umbraco.Tests/Services/MacroServiceTests.cs index d88dd26dd4..9f81ffab9a 100644 --- a/src/Umbraco.Tests/Services/MacroServiceTests.cs +++ b/src/Umbraco.Tests/Services/MacroServiceTests.cs @@ -150,6 +150,35 @@ namespace Umbraco.Tests.Services } + [Test] + public void Can_Update_Remove_Property() + { + // Arrange + var macroService = ServiceContext.MacroService; + IMacro macro = new Macro("test", "Test", scriptPath: "~/Views/MacroPartials/Test.cshtml", cacheDuration: 1234); + macro.Properties.Add(new MacroProperty("blah1", "Blah1", 0, "blah1")); + macro.Properties.Add(new MacroProperty("blah2", "Blah2", 1, "blah2")); + macro.Properties.Add(new MacroProperty("blah3", "Blah3", 2, "blah3")); + macroService.Save(macro); + + // Act + macro.Properties["blah1"].Alias = "newAlias"; + macro.Properties["blah1"].Name = "new Name"; + macro.Properties["blah1"].SortOrder = 1; + macro.Properties["blah1"].EditorAlias = "new"; + macro.Properties.Remove("blah3"); + macroService.Save(macro); + + macro = macroService.GetById(macro.Id); + + //assert + Assert.AreEqual(2, macro.Properties.Count()); + Assert.AreEqual("newAlias", macro.Properties["newAlias"].Alias); + Assert.AreEqual("new Name", macro.Properties["newAlias"].Name); + Assert.AreEqual(1, macro.Properties["newAlias"].SortOrder); + Assert.AreEqual("new", macro.Properties["newAlias"].EditorAlias); + } + [Test] public void Can_Add_And_Remove_Properties() { From 9f28aea10dcdf65f1d67c1001716b95e35b69db5 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Fri, 27 Jun 2014 13:23:54 +0200 Subject: [PATCH 095/189] This Can_Find_Targetted_Migrations test needed a valid database to pass. This update ensures that the database is created with the old version 4.8 db schema. --- .../TargetVersionSixthMigrationsTest.cs | 70 +++++++++++++++++-- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs b/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs index 8aad190fea..9a9b2922d4 100644 --- a/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs +++ b/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs @@ -1,7 +1,11 @@ using System; using System.Collections.Generic; +using System.Data.SqlServerCe; using System.Linq; +using System.Text.RegularExpressions; using NUnit.Framework; +using SQLCE4Umbraco; +using Umbraco.Core; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Migrations; @@ -13,13 +17,20 @@ using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; namespace Umbraco.Tests.Migrations { [TestFixture] - public class TargetVersionSixthMigrationsTest + public class TargetVersionSixthMigrationsTest : BaseDatabaseFactoryTest { + /// Regular expression that finds multiline block comments. + private static readonly Regex FindComments = new Regex(@"\/\*.*?\*\/", RegexOptions.Singleline | RegexOptions.Compiled); + [SetUp] - public void Initialize() + public override void Initialize() { TestHelper.SetupLog4NetForTests(); + TestHelper.InitializeContentDirectories(); + Path = TestHelper.CurrentAssemblyDirectory; + AppDomain.CurrentDomain.SetData("DataDirectory", Path); + MigrationResolver.Current = new MigrationResolver(() => new List { typeof (Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero.RemoveUmbracoAppConstraints), @@ -36,22 +47,39 @@ namespace Umbraco.Tests.Migrations typeof (UpdateCmsPropertyTypeGroupTable) }.OrderByDescending(x => x.Name)); - Resolution.Freeze(); + Resolution.Freeze(); SqlSyntaxContext.SqlSyntaxProvider = SqlCeSyntax.Provider; + + var engine = new SqlCeEngine("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;"); + engine.CreateDatabase(); } - + [Test] public void Can_Find_Targetted_Migrations() { + var db = GetConfiguredDatabase(); + + //Create db schema and data from old Total.sql file for Sql Ce + string statements = GetDatabaseSpecificSqlScript(); + // replace block comments by whitespace + statements = FindComments.Replace(statements, " "); + // execute all non-empty statements + foreach (string statement in statements.Split(";".ToCharArray())) + { + string rawStatement = statement.Replace("GO", "").Trim(); + if (rawStatement.Length > 0) + db.Execute(new Sql(rawStatement)); + } + var configuredVersion = new Version("4.8.0"); var targetVersion = new Version("6.0.0"); - var foundMigrations = MigrationResolver.Current.Migrations; + var foundMigrations = MigrationResolver.Current.Migrations; var migrationRunner = new MigrationRunner(configuredVersion, targetVersion, GlobalSettings.UmbracoMigrationName); var migrations = migrationRunner.OrderedUpgradeMigrations(foundMigrations).ToList(); - var context = new MigrationContext(DatabaseProviders.SqlServerCE, null); + var context = new MigrationContext(DatabaseProviders.SqlServerCE, db); foreach (MigrationBase migration in migrations) { migration.GetUpExpressions(context); @@ -66,9 +94,37 @@ namespace Umbraco.Tests.Migrations } [TearDown] - public void TearDown() + public override void TearDown() { + base.TearDown(); + + PluginManager.Current = null; + SqlSyntaxContext.SqlSyntaxProvider = null; MigrationResolver.Reset(); + + TestHelper.CleanContentDirectories(); + + Path = TestHelper.CurrentAssemblyDirectory; + AppDomain.CurrentDomain.SetData("DataDirectory", null); + + SqlCeContextGuardian.CloseBackgroundConnection(); } + + public string Path { get; set; } + + public UmbracoDatabase GetConfiguredDatabase() + { + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0"); + } + + public DatabaseProviders GetDatabaseProvider() + { + return DatabaseProviders.SqlServerCE; + } + + public string GetDatabaseSpecificSqlScript() + { + return SqlScripts.SqlResources.SqlCeTotal_480; + } } } \ No newline at end of file From 966a29954c8a04db94fd93eb6f74dece8b3f4db3 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 27 Jun 2014 16:25:57 +0200 Subject: [PATCH 096/189] #U4-5010 fixed Package editing view missing in distribution --- .../umbraco.presentation/umbraco/Trees/loadPackager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackager.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackager.cs index 196c5ab46d..b47db683f6 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackager.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackager.cs @@ -119,6 +119,9 @@ namespace umbraco { xNode.Text = ""; } + + xNode.Action = "javascript:void(0);"; + break; @@ -161,6 +164,7 @@ namespace umbraco xNode.Menu.Add(umbraco.BusinessLogic.Actions.ActionRefresh.Instance); xNode.Text = ui.Text("treeHeaders", "createdPackages"); xNode.HasChildren = true; + xNode.Action = "javascript:void(0);"; break; From c53d812742824b446f4595b831fbfa2a5d1cb5d8 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 27 Jun 2014 16:26:22 +0200 Subject: [PATCH 097/189] #U4-3154 Fixed Deleting single media item in recycle bin does not use the FileSystemProvider --- src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 789a583510..d916c91e96 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -369,7 +369,7 @@ namespace Umbraco.Core.Persistence.Repositories { if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias && string.IsNullOrEmpty(property.Value.ToString()) == false - && fs.FileExists(IOHelper.MapPath(property.Value.ToString()))) + && fs.FileExists(fs.GetRelativePath(property.Value.ToString()))) { var relativeFilePath = fs.GetRelativePath(property.Value.ToString()); var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath); From 5bee398d09adb0023d676496efc3933a381c8df6 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 27 Jun 2014 17:22:51 +0200 Subject: [PATCH 098/189] #U4-3838 fixed Due in version 7.1.5 Fixes ContentService Copy uses IOHelper.MapPath but should use fs.GetFullPath and a few other instances where IOHelper was used --- src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs | 2 +- src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs | 2 +- src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs | 2 +- src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index b38b19a2d8..08c159481b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -498,7 +498,7 @@ namespace Umbraco.Core.Persistence.Repositories { if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias && property.Value != null && string.IsNullOrEmpty(property.Value.ToString()) == false - && fs.FileExists(IOHelper.MapPath(property.Value.ToString()))) + && fs.FileExists(fs.GetRelativePath(property.Value.ToString()))) { var relativeFilePath = fs.GetRelativePath(property.Value.ToString()); var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath); diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 595fcb033d..2bbb3439ca 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -398,7 +398,7 @@ namespace Umbraco.Core.Persistence.Repositories { if (property.PropertyType.PropertyEditorAlias == uploadFieldAlias && string.IsNullOrEmpty(property.Value.ToString()) == false - && fs.FileExists(IOHelper.MapPath(property.Value.ToString()))) + && fs.FileExists(fs.GetRelativePath(property.Value.ToString()))) { var relativeFilePath = fs.GetRelativePath(property.Value.ToString()); var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath); diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index 4934a457d8..28da565b9e 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -71,7 +71,7 @@ namespace Umbraco.Web.PropertyEditors && x.Value != null && string.IsNullOrEmpty(x.Value.ToString()) == false)) { - if (fs.FileExists(IOHelper.MapPath(property.Value.ToString()))) + if (fs.FileExists(fs.GetRelativePath(property.Value.ToString()))) { var currentPath = fs.GetRelativePath(property.Value.ToString()); var propertyId = e.Copy.Properties.First(x => x.Alias == property.Alias).Id; diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index bb6aff2e95..468d039936 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -89,7 +89,7 @@ namespace Umbraco.Web.PropertyEditors if (json["src"] != null && json["src"].ToString().IsNullOrWhiteSpace() == false) { - if (fs.FileExists(IOHelper.MapPath(json["src"].ToString()))) + if (fs.FileExists(fs.GetRelativePath(json["src"].ToString()))) { var currentPath = fs.GetRelativePath(json["src"].ToString()); var propertyId = e.Copy.Properties.First(x => x.Alias == property.Alias).Id; From 45e6127d0063db87bc8ce4d3a4a41a5d8480b274 Mon Sep 17 00:00:00 2001 From: Stephan Date: Sat, 28 Jun 2014 16:26:27 +0200 Subject: [PATCH 099/189] U4-4866 - fix the build --- .../businesslogic/Packager/PackageInstance/CreatedPackage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs index 2747c89c3d..49f8485161 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs @@ -118,7 +118,7 @@ namespace umbraco.cms.businesslogic.packager { int _contentNodeID = 0; if (!String.IsNullOrEmpty(pack.ContentNodeId) && int.TryParse(pack.ContentNodeId, out _contentNodeID)) { - if (contentNodeId > 0) + if (_contentNodeID > 0) { XmlNode documents = _packageManifest.CreateElement("Documents"); From 496beca8f286c918e59030aa252c00a8ee80607d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Jun 2014 14:02:43 +1000 Subject: [PATCH 100/189] This Can_Find_Targetted_Migrations test needed a valid database to pass. This update ensures that the database is created with the old version 4.8 db schema. --- .../TargetVersionSixthMigrationsTest.cs | 70 +++++++++++++++++-- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs b/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs index 8aad190fea..9a9b2922d4 100644 --- a/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs +++ b/src/Umbraco.Tests/Migrations/TargetVersionSixthMigrationsTest.cs @@ -1,7 +1,11 @@ using System; using System.Collections.Generic; +using System.Data.SqlServerCe; using System.Linq; +using System.Text.RegularExpressions; using NUnit.Framework; +using SQLCE4Umbraco; +using Umbraco.Core; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Migrations; @@ -13,13 +17,20 @@ using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; namespace Umbraco.Tests.Migrations { [TestFixture] - public class TargetVersionSixthMigrationsTest + public class TargetVersionSixthMigrationsTest : BaseDatabaseFactoryTest { + /// Regular expression that finds multiline block comments. + private static readonly Regex FindComments = new Regex(@"\/\*.*?\*\/", RegexOptions.Singleline | RegexOptions.Compiled); + [SetUp] - public void Initialize() + public override void Initialize() { TestHelper.SetupLog4NetForTests(); + TestHelper.InitializeContentDirectories(); + Path = TestHelper.CurrentAssemblyDirectory; + AppDomain.CurrentDomain.SetData("DataDirectory", Path); + MigrationResolver.Current = new MigrationResolver(() => new List { typeof (Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero.RemoveUmbracoAppConstraints), @@ -36,22 +47,39 @@ namespace Umbraco.Tests.Migrations typeof (UpdateCmsPropertyTypeGroupTable) }.OrderByDescending(x => x.Name)); - Resolution.Freeze(); + Resolution.Freeze(); SqlSyntaxContext.SqlSyntaxProvider = SqlCeSyntax.Provider; + + var engine = new SqlCeEngine("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;"); + engine.CreateDatabase(); } - + [Test] public void Can_Find_Targetted_Migrations() { + var db = GetConfiguredDatabase(); + + //Create db schema and data from old Total.sql file for Sql Ce + string statements = GetDatabaseSpecificSqlScript(); + // replace block comments by whitespace + statements = FindComments.Replace(statements, " "); + // execute all non-empty statements + foreach (string statement in statements.Split(";".ToCharArray())) + { + string rawStatement = statement.Replace("GO", "").Trim(); + if (rawStatement.Length > 0) + db.Execute(new Sql(rawStatement)); + } + var configuredVersion = new Version("4.8.0"); var targetVersion = new Version("6.0.0"); - var foundMigrations = MigrationResolver.Current.Migrations; + var foundMigrations = MigrationResolver.Current.Migrations; var migrationRunner = new MigrationRunner(configuredVersion, targetVersion, GlobalSettings.UmbracoMigrationName); var migrations = migrationRunner.OrderedUpgradeMigrations(foundMigrations).ToList(); - var context = new MigrationContext(DatabaseProviders.SqlServerCE, null); + var context = new MigrationContext(DatabaseProviders.SqlServerCE, db); foreach (MigrationBase migration in migrations) { migration.GetUpExpressions(context); @@ -66,9 +94,37 @@ namespace Umbraco.Tests.Migrations } [TearDown] - public void TearDown() + public override void TearDown() { + base.TearDown(); + + PluginManager.Current = null; + SqlSyntaxContext.SqlSyntaxProvider = null; MigrationResolver.Reset(); + + TestHelper.CleanContentDirectories(); + + Path = TestHelper.CurrentAssemblyDirectory; + AppDomain.CurrentDomain.SetData("DataDirectory", null); + + SqlCeContextGuardian.CloseBackgroundConnection(); } + + public string Path { get; set; } + + public UmbracoDatabase GetConfiguredDatabase() + { + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0"); + } + + public DatabaseProviders GetDatabaseProvider() + { + return DatabaseProviders.SqlServerCE; + } + + public string GetDatabaseSpecificSqlScript() + { + return SqlScripts.SqlResources.SqlCeTotal_480; + } } } \ No newline at end of file From 525280d4667d561461178d84449a4e96f45260dd Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Jun 2014 14:09:36 +1000 Subject: [PATCH 101/189] fixed some tests --- src/Umbraco.Tests/Sync/DistributedCacheTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Sync/DistributedCacheTests.cs b/src/Umbraco.Tests/Sync/DistributedCacheTests.cs index 5e667645b9..a897f59dae 100644 --- a/src/Umbraco.Tests/Sync/DistributedCacheTests.cs +++ b/src/Umbraco.Tests/Sync/DistributedCacheTests.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.Sync [Test] public void RefreshIntId() { - for (var i = 0; i < 10; i++) + for (var i = 1; i < 11; i++) { DistributedCache.Instance.Refresh(Guid.Parse("E0F452CB-DCB2-4E84-B5A5-4F01744C5C73"), i); } @@ -71,7 +71,7 @@ namespace Umbraco.Tests.Sync [Test] public void RemoveIds() { - for (var i = 0; i < 12; i++) + for (var i = 1; i < 13; i++) { DistributedCache.Instance.Remove(Guid.Parse("E0F452CB-DCB2-4E84-B5A5-4F01744C5C73"), i); } From 1be2706c3025026f28639810a61830b5462fa930 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Jun 2014 15:09:41 +1000 Subject: [PATCH 102/189] fix merge --- .../businesslogic/Packager/PackageInstance/CreatedPackage.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs index 65b558b429..2a4c155efb 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs @@ -125,8 +125,6 @@ namespace umbraco.cms.businesslogic.packager if (string.IsNullOrEmpty(pack.ContentNodeId) == false && int.TryParse(pack.ContentNodeId, out contentNodeId)) { if (contentNodeId > 0) - { - if (_contentNodeID > 0) { XmlNode documents = _packageManifest.CreateElement("Documents"); From 1e1d49f3050ad28fa5785be591424da385048f9e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Jun 2014 16:43:51 +1000 Subject: [PATCH 103/189] fixes U4-5140 Package installation for content does not respect tags, however need to check with Morten if it's required because if we never actually publish content on package install then this wouldn't really matter. But surely we must publish on package install for some thing because starter kits are published... hrm. --- src/Umbraco.Core/Models/Tag.cs | 27 ------- src/Umbraco.Core/Models/TaggedEntity.cs | 16 ++++ src/Umbraco.Core/Models/TaggedProperty.cs | 18 +++++ src/Umbraco.Core/Services/PackagingService.cs | 77 ++++++++++++++----- src/Umbraco.Core/Umbraco.Core.csproj | 2 + .../PackageInstance/CreatedPackage.cs | 73 +++++++++++++++++- src/umbraco.cms/packages.config | 1 + src/umbraco.cms/umbraco.cms.csproj | 4 + 8 files changed, 168 insertions(+), 50 deletions(-) create mode 100644 src/Umbraco.Core/Models/TaggedEntity.cs create mode 100644 src/Umbraco.Core/Models/TaggedProperty.cs diff --git a/src/Umbraco.Core/Models/Tag.cs b/src/Umbraco.Core/Models/Tag.cs index 959d2f1aa9..1bd5bff76d 100644 --- a/src/Umbraco.Core/Models/Tag.cs +++ b/src/Umbraco.Core/Models/Tag.cs @@ -1,38 +1,11 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { - public class TaggedEntity - { - public TaggedEntity(int entityId, IEnumerable taggedProperties) - { - EntityId = entityId; - TaggedProperties = taggedProperties; - } - - public int EntityId { get; private set; } - public IEnumerable TaggedProperties { get; private set; } - } - - public class TaggedProperty - { - public TaggedProperty(int propertyTypeId, string propertyTypeAlias, IEnumerable tags) - { - PropertyTypeId = propertyTypeId; - PropertyTypeAlias = propertyTypeAlias; - Tags = tags; - } - - public int PropertyTypeId { get; private set; } - public string PropertyTypeAlias { get; private set; } - public IEnumerable Tags { get; private set; } - } - [Serializable] [DataContract(IsReference = true)] public class Tag : Entity, ITag diff --git a/src/Umbraco.Core/Models/TaggedEntity.cs b/src/Umbraco.Core/Models/TaggedEntity.cs new file mode 100644 index 0000000000..decd4220fe --- /dev/null +++ b/src/Umbraco.Core/Models/TaggedEntity.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Models +{ + public class TaggedEntity + { + public TaggedEntity(int entityId, IEnumerable taggedProperties) + { + EntityId = entityId; + TaggedProperties = taggedProperties; + } + + public int EntityId { get; private set; } + public IEnumerable TaggedProperties { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/TaggedProperty.cs b/src/Umbraco.Core/Models/TaggedProperty.cs new file mode 100644 index 0000000000..3b92413cdb --- /dev/null +++ b/src/Umbraco.Core/Models/TaggedProperty.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Models +{ + public class TaggedProperty + { + public TaggedProperty(int propertyTypeId, string propertyTypeAlias, IEnumerable tags) + { + PropertyTypeId = propertyTypeId; + PropertyTypeAlias = propertyTypeAlias; + Tags = tags; + } + + public int PropertyTypeId { get; private set; } + public string PropertyTypeAlias { get; private set; } + public IEnumerable Tags { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index c8fe4e781b..9c4b989dbf 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -4,6 +4,8 @@ using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Xml.Linq; +using System.Xml.XPath; +using Newtonsoft.Json; using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.IO; @@ -106,6 +108,17 @@ namespace Umbraco.Core.Services return Enumerable.Empty(); } + //check for tag properties element at root + XElement tagProperties = null; + if (element.Document != null && element.Document.Root != null) + { + var found = element.Document.Root.XPathSelectElement("/umbPackage/TagProperties"); + if (found != null) + { + tagProperties = found; + } + } + var name = element.Name.LocalName; if (name.Equals("DocumentSet")) { @@ -114,7 +127,7 @@ namespace Umbraco.Core.Services where (string)doc.Attribute("isDoc") == "" select doc; - var contents = ParseDocumentRootXml(roots, parentId); + var contents = ParseDocumentRootXml(roots, parentId, tagProperties); if (contents.Any()) _contentService.Save(contents, userId); @@ -128,7 +141,7 @@ namespace Umbraco.Core.Services { //This is a single doc import var elements = new List { element }; - var contents = ParseDocumentRootXml(elements, parentId); + var contents = ParseDocumentRootXml(elements, parentId, tagProperties); if (contents.Any()) _contentService.Save(contents, userId); @@ -142,7 +155,7 @@ namespace Umbraco.Core.Services "'DocumentSet' (for structured imports) nor is the first element a Document (for single document import)."); } - private IEnumerable ParseDocumentRootXml(IEnumerable roots, int parentId) + private IEnumerable ParseDocumentRootXml(IEnumerable roots, int parentId, XElement tagProperties) { var contents = new List(); foreach (var root in roots) @@ -158,19 +171,19 @@ namespace Umbraco.Core.Services _importedContentTypes.Add(contentTypeAlias, contentType); } - var content = CreateContentFromXml(root, _importedContentTypes[contentTypeAlias], null, parentId, isLegacySchema); + var content = CreateContentFromXml(root, _importedContentTypes[contentTypeAlias], null, parentId, isLegacySchema, tagProperties); contents.Add(content); var children = from child in root.Elements() where (string)child.Attribute("isDoc") == "" select child; if (children.Any()) - contents.AddRange(CreateContentFromXml(children, content, isLegacySchema)); + contents.AddRange(CreateContentFromXml(children, content, isLegacySchema, tagProperties)); } return contents; } - private IEnumerable CreateContentFromXml(IEnumerable children, IContent parent, bool isLegacySchema) + private IEnumerable CreateContentFromXml(IEnumerable children, IContent parent, bool isLegacySchema, XElement tagProperties) { var list = new List(); foreach (var child in children) @@ -186,7 +199,7 @@ namespace Umbraco.Core.Services } //Create and add the child to the list - var content = CreateContentFromXml(child, _importedContentTypes[contentTypeAlias], parent, default(int), isLegacySchema); + var content = CreateContentFromXml(child, _importedContentTypes[contentTypeAlias], parent, default(int), isLegacySchema, tagProperties); list.Add(content); //Recursive call @@ -196,13 +209,13 @@ namespace Umbraco.Core.Services select grand; if (grandChildren.Any()) - list.AddRange(CreateContentFromXml(grandChildren, content, isLegacySchema)); + list.AddRange(CreateContentFromXml(grandChildren, content, isLegacySchema, tagProperties)); } return list; } - private IContent CreateContentFromXml(XElement element, IContentType contentType, IContent parent, int parentId, bool isLegacySchema) + private IContent CreateContentFromXml(XElement element, IContentType contentType, IContent parent, int parentId, bool isLegacySchema, XElement tagProperties) { var id = element.Attribute("id").Value; var level = element.Attribute("level").Value; @@ -235,21 +248,47 @@ namespace Umbraco.Core.Services var propertyValue = property.Value; var propertyType = contentType.PropertyTypes.FirstOrDefault(pt => pt.Alias == propertyTypeAlias); - if (propertyType != null && propertyType.PropertyEditorAlias == Constants.PropertyEditors.CheckBoxListAlias) - { - var database = ApplicationContext.Current.DatabaseContext.Database; - var dtos = database.Fetch("WHERE datatypeNodeId = @Id", new { Id = propertyType.DataTypeDefinitionId }); - var propertyValueList = new List(); - foreach (var preValue in propertyValue.Split(',')) + //TODO: It would be heaps nicer if we didn't have to hard code references to specific property editors + // we'd have to modify the packaging format to denote how to parse/store the value instead of relying on this + + if (propertyType != null) + { + if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.CheckBoxListAlias) { - propertyValueList.Add(dtos.Single(x => x.Value == preValue).Id.ToString(CultureInfo.InvariantCulture)); + var database = ApplicationContext.Current.DatabaseContext.Database; + var dtos = database.Fetch("WHERE datatypeNodeId = @Id", new {Id = propertyType.DataTypeDefinitionId}); + + var propertyValueList = new List(); + foreach (var preValue in propertyValue.Split(',')) + { + propertyValueList.Add(dtos.Single(x => x.Value == preValue).Id.ToString(CultureInfo.InvariantCulture)); + } + + propertyValue = string.Join(",", propertyValueList.ToArray()); + + //set value as per normal + content.SetValue(propertyTypeAlias, propertyValue); + } + else + { + //check if this exists in tagProperties + var hasTags = tagProperties.XPathSelectElement(string.Format("//TagProperty[@docId=\"{0}\" and @propertyAlias=\"{1}\"]", id, propertyType.Alias)); + if (hasTags != null) + { + var tags = JsonConvert.DeserializeObject(hasTags.Value); + content.SetTags(propertyTypeAlias, tags, true, hasTags.Attribute("group").Value); + } + } - propertyValue = string.Join(",", propertyValueList.ToArray()); } - - content.SetValue(propertyTypeAlias, propertyValue); + else + { + //set value as per normal + content.SetValue(propertyTypeAlias, propertyValue); + } + } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2ff13b5ada..9149248e18 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -364,6 +364,8 @@ + + diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs index 2a4c155efb..5bc792799a 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs @@ -1,14 +1,20 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; +using System.Linq; using System.Text; using System.Xml; using System.Web; +using Newtonsoft.Json; +using Umbraco.Core; using Umbraco.Core.Logging; -using umbraco.cms.businesslogic.template; using umbraco.cms.businesslogic.web; using umbraco.cms.businesslogic.macro; using Umbraco.Core.IO; +using Umbraco.Core.Models; +using File = System.IO.File; +using Template = umbraco.cms.businesslogic.template.Template; namespace umbraco.cms.businesslogic.packager @@ -120,14 +126,14 @@ namespace umbraco.cms.businesslogic.packager //Info section.. AppendElement(utill.PackageInfo(pack, _packageManifest)); - //Documents... + //Documents and tags... var contentNodeId = 0; if (string.IsNullOrEmpty(pack.ContentNodeId) == false && int.TryParse(pack.ContentNodeId, out contentNodeId)) { if (contentNodeId > 0) { + //Create the Documents/DocumentSet node XmlNode documents = _packageManifest.CreateElement("Documents"); - XmlNode documentSet = _packageManifest.CreateElement("DocumentSet"); XmlAttribute importMode = _packageManifest.CreateAttribute("importMode", ""); importMode.Value = "root"; @@ -139,7 +145,66 @@ namespace umbraco.cms.businesslogic.packager documentSet.AppendChild(umbDocument.ToXml(_packageManifest, pack.ContentLoadChildNodes)); - AppendElement(documents); + AppendElement(documents); + + //Create the TagProperties node - this is used to store a definition for all + // document properties that are tags, this ensures that we can re-import tags properly + XmlNode tagProps = _packageManifest.CreateElement("TagProperties"); + + //before we try to populate this, we'll do a quick lookup to see if any of the documents + // being exported contain published tags. + var allExportedIds = documents.SelectNodes("//@id").Cast() + .Select(x => x.Value.TryConvertTo()) + .Where(x => x.Success) + .Select(x => x.Result) + .ToArray(); + var allContentTags = new List(); + foreach (var exportedId in allExportedIds) + { + allContentTags.AddRange( + ApplicationContext.Current.Services.TagService.GetTagsForEntity(exportedId)); + } + + //This is pretty round-about but it works. Essentially we need to get the properties that are tagged + // but to do that we need to lookup by a tag (string) + var allTaggedEntities = new List(); + foreach (var group in allContentTags.Select(x => x.Group).Distinct()) + { + allTaggedEntities.AddRange( + ApplicationContext.Current.Services.TagService.GetTaggedContentByTagGroup(group)); + } + + //Now, we have all property Ids/Aliases and their referenced document Ids and tags + var allExportedTaggedEntities = allTaggedEntities.Where(x => allExportedIds.Contains(x.EntityId)) + .DistinctBy(x => x.EntityId) + .OrderBy(x => x.EntityId); + + foreach (var taggedEntity in allExportedTaggedEntities) + { + foreach (var taggedProperty in taggedEntity.TaggedProperties.Where(x => x.Tags.Any())) + { + XmlNode tagProp = _packageManifest.CreateElement("TagProperty"); + var docId = _packageManifest.CreateAttribute("docId", ""); + docId.Value = taggedEntity.EntityId.ToString(CultureInfo.InvariantCulture); + tagProp.Attributes.Append(docId); + + var propertyAlias = _packageManifest.CreateAttribute("propertyAlias", ""); + propertyAlias.Value = taggedProperty.PropertyTypeAlias; + tagProp.Attributes.Append(propertyAlias); + + var group = _packageManifest.CreateAttribute("group", ""); + group.Value = taggedProperty.Tags.First().Group; + tagProp.Attributes.Append(group); + + tagProp.AppendChild(_packageManifest.CreateCDataSection( + JsonConvert.SerializeObject(taggedProperty.Tags.Select(x => x.Text).ToArray()))); + + tagProps.AppendChild(tagProp); + } + } + + AppendElement(tagProps); + } } diff --git a/src/umbraco.cms/packages.config b/src/umbraco.cms/packages.config index fb9ebcc90d..4d7778d690 100644 --- a/src/umbraco.cms/packages.config +++ b/src/umbraco.cms/packages.config @@ -2,6 +2,7 @@ + \ No newline at end of file diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index 2cff54744b..75afd7f433 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -118,6 +118,10 @@ False ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + + False + ..\packages\Newtonsoft.Json.6.0.2\lib\net45\Newtonsoft.Json.dll + System From a66670263f219ace131b7d52f8bbc94886148c66 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Jun 2014 17:04:59 +1000 Subject: [PATCH 104/189] fixes null check --- src/umbraco.cms/businesslogic/Packager/data.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/umbraco.cms/businesslogic/Packager/data.cs b/src/umbraco.cms/businesslogic/Packager/data.cs index a950212c47..2d153daaa5 100644 --- a/src/umbraco.cms/businesslogic/Packager/data.cs +++ b/src/umbraco.cms/businesslogic/Packager/data.cs @@ -262,8 +262,12 @@ namespace umbraco.cms.businesslogic.packager //p.Folder XmlNode n = data.GetFromId(Id, dataSource, true); - data.Source.SelectSingleNode("/packages").RemoveChild(n); - data.Source.Save(dataSource); + if (n != null) + { + data.Source.SelectSingleNode("/packages").RemoveChild(n); + data.Source.Save(dataSource); + } + } public static void UpdateValue(XmlNode n, string Value) From 59116fc7fa47aebecb4112a6776cedf564359db5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Jun 2014 17:05:34 +1000 Subject: [PATCH 105/189] un-does the tag export/import, instead need to look at the internal of a simple publish (without save) to see if tags get written or not. --- src/Umbraco.Core/Services/PackagingService.cs | 20 ++-- .../PackageInstance/CreatedPackage.cs | 96 +++++++++---------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 9c4b989dbf..bb3f703c73 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -270,17 +270,17 @@ namespace Umbraco.Core.Services //set value as per normal content.SetValue(propertyTypeAlias, propertyValue); } - else - { - //check if this exists in tagProperties - var hasTags = tagProperties.XPathSelectElement(string.Format("//TagProperty[@docId=\"{0}\" and @propertyAlias=\"{1}\"]", id, propertyType.Alias)); - if (hasTags != null) - { - var tags = JsonConvert.DeserializeObject(hasTags.Value); - content.SetTags(propertyTypeAlias, tags, true, hasTags.Attribute("group").Value); - } + //else + //{ + // //check if this exists in tagProperties + // var hasTags = tagProperties.XPathSelectElement(string.Format("//TagProperty[@docId=\"{0}\" and @propertyAlias=\"{1}\"]", id, propertyType.Alias)); + // if (hasTags != null) + // { + // var tags = JsonConvert.DeserializeObject(hasTags.Value); + // content.SetTags(propertyTypeAlias, tags, true, hasTags.Attribute("group").Value); + // } - } + //} } else diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs index 5bc792799a..29eaaf25ea 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs @@ -147,63 +147,63 @@ namespace umbraco.cms.businesslogic.packager AppendElement(documents); - //Create the TagProperties node - this is used to store a definition for all - // document properties that are tags, this ensures that we can re-import tags properly - XmlNode tagProps = _packageManifest.CreateElement("TagProperties"); + ////Create the TagProperties node - this is used to store a definition for all + //// document properties that are tags, this ensures that we can re-import tags properly + //XmlNode tagProps = _packageManifest.CreateElement("TagProperties"); - //before we try to populate this, we'll do a quick lookup to see if any of the documents - // being exported contain published tags. - var allExportedIds = documents.SelectNodes("//@id").Cast() - .Select(x => x.Value.TryConvertTo()) - .Where(x => x.Success) - .Select(x => x.Result) - .ToArray(); - var allContentTags = new List(); - foreach (var exportedId in allExportedIds) - { - allContentTags.AddRange( - ApplicationContext.Current.Services.TagService.GetTagsForEntity(exportedId)); - } + ////before we try to populate this, we'll do a quick lookup to see if any of the documents + //// being exported contain published tags. + //var allExportedIds = documents.SelectNodes("//@id").Cast() + // .Select(x => x.Value.TryConvertTo()) + // .Where(x => x.Success) + // .Select(x => x.Result) + // .ToArray(); + //var allContentTags = new List(); + //foreach (var exportedId in allExportedIds) + //{ + // allContentTags.AddRange( + // ApplicationContext.Current.Services.TagService.GetTagsForEntity(exportedId)); + //} - //This is pretty round-about but it works. Essentially we need to get the properties that are tagged - // but to do that we need to lookup by a tag (string) - var allTaggedEntities = new List(); - foreach (var group in allContentTags.Select(x => x.Group).Distinct()) - { - allTaggedEntities.AddRange( - ApplicationContext.Current.Services.TagService.GetTaggedContentByTagGroup(group)); - } + ////This is pretty round-about but it works. Essentially we need to get the properties that are tagged + //// but to do that we need to lookup by a tag (string) + //var allTaggedEntities = new List(); + //foreach (var group in allContentTags.Select(x => x.Group).Distinct()) + //{ + // allTaggedEntities.AddRange( + // ApplicationContext.Current.Services.TagService.GetTaggedContentByTagGroup(group)); + //} - //Now, we have all property Ids/Aliases and their referenced document Ids and tags - var allExportedTaggedEntities = allTaggedEntities.Where(x => allExportedIds.Contains(x.EntityId)) - .DistinctBy(x => x.EntityId) - .OrderBy(x => x.EntityId); + ////Now, we have all property Ids/Aliases and their referenced document Ids and tags + //var allExportedTaggedEntities = allTaggedEntities.Where(x => allExportedIds.Contains(x.EntityId)) + // .DistinctBy(x => x.EntityId) + // .OrderBy(x => x.EntityId); - foreach (var taggedEntity in allExportedTaggedEntities) - { - foreach (var taggedProperty in taggedEntity.TaggedProperties.Where(x => x.Tags.Any())) - { - XmlNode tagProp = _packageManifest.CreateElement("TagProperty"); - var docId = _packageManifest.CreateAttribute("docId", ""); - docId.Value = taggedEntity.EntityId.ToString(CultureInfo.InvariantCulture); - tagProp.Attributes.Append(docId); + //foreach (var taggedEntity in allExportedTaggedEntities) + //{ + // foreach (var taggedProperty in taggedEntity.TaggedProperties.Where(x => x.Tags.Any())) + // { + // XmlNode tagProp = _packageManifest.CreateElement("TagProperty"); + // var docId = _packageManifest.CreateAttribute("docId", ""); + // docId.Value = taggedEntity.EntityId.ToString(CultureInfo.InvariantCulture); + // tagProp.Attributes.Append(docId); - var propertyAlias = _packageManifest.CreateAttribute("propertyAlias", ""); - propertyAlias.Value = taggedProperty.PropertyTypeAlias; - tagProp.Attributes.Append(propertyAlias); + // var propertyAlias = _packageManifest.CreateAttribute("propertyAlias", ""); + // propertyAlias.Value = taggedProperty.PropertyTypeAlias; + // tagProp.Attributes.Append(propertyAlias); - var group = _packageManifest.CreateAttribute("group", ""); - group.Value = taggedProperty.Tags.First().Group; - tagProp.Attributes.Append(group); + // var group = _packageManifest.CreateAttribute("group", ""); + // group.Value = taggedProperty.Tags.First().Group; + // tagProp.Attributes.Append(group); - tagProp.AppendChild(_packageManifest.CreateCDataSection( - JsonConvert.SerializeObject(taggedProperty.Tags.Select(x => x.Text).ToArray()))); + // tagProp.AppendChild(_packageManifest.CreateCDataSection( + // JsonConvert.SerializeObject(taggedProperty.Tags.Select(x => x.Text).ToArray()))); - tagProps.AppendChild(tagProp); - } - } + // tagProps.AppendChild(tagProp); + // } + //} - AppendElement(tagProps); + //AppendElement(tagProps); } } From f3c7533796d82f1d30a7a1b79959f5c180213905 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 1 Jul 2014 10:43:44 +1000 Subject: [PATCH 106/189] Potentially fixes U4-5069 Deadlock occurring in MemberService.GetByUsername --- .../Security/MembershipProviderExtensions.cs | 20 ++++++++++++++++++- src/Umbraco.Web/Security/MembershipHelper.cs | 4 ++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs b/src/Umbraco.Core/Security/MembershipProviderExtensions.cs index 5796680027..f59f4d1169 100644 --- a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs +++ b/src/Umbraco.Core/Security/MembershipProviderExtensions.cs @@ -62,7 +62,12 @@ namespace Umbraco.Core.Security return Membership.Providers[UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider]; } - public static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider) + /// + /// Returns the currently logged in MembershipUser and flags them as being online - use sparingly (i.e. login) + /// + /// + /// + public static MembershipUser GetCurrentUserOnline(this MembershipProvider membershipProvider) { var username = membershipProvider.GetCurrentUserName(); return username.IsNullOrWhiteSpace() @@ -70,6 +75,19 @@ namespace Umbraco.Core.Security : membershipProvider.GetUser(username, true); } + /// + /// Returns the currently logged in MembershipUser + /// + /// + /// + public static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider) + { + var username = membershipProvider.GetCurrentUserName(); + return username.IsNullOrWhiteSpace() + ? null + : membershipProvider.GetUser(username, false); + } + /// /// Just returns the current user's login name (just a wrapper). /// diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 5e64f33a16..5146b48141 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -312,7 +312,7 @@ namespace Umbraco.Web.Security if (provider.IsUmbracoMembershipProvider()) { - var membershipUser = provider.GetCurrentUser(); + var membershipUser = provider.GetCurrentUserOnline(); var member = GetCurrentPersistedMember(); //this shouldn't happen but will if the member is deleted in the back office while the member is trying // to use the front-end! @@ -476,7 +476,7 @@ namespace Umbraco.Web.Security } else { - var member = provider.GetCurrentUser(); + var member = provider.GetCurrentUserOnline(); //this shouldn't happen but will if the member is deleted in the back office while the member is trying // to use the front-end! if (member == null) From 7a12060c37e958cb9c3a16a7c99bc619c36e931c Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 1 Jul 2014 10:46:52 +1000 Subject: [PATCH 107/189] Potentially fixes U4-5069 Deadlock occurring in MemberService.GetByUsername Conflicts: src/Umbraco.Web/Security/MembershipHelper.cs --- .../Security/MembershipProviderExtensions.cs | 20 ++++++++++++++++++- src/Umbraco.Web/Security/MembershipHelper.cs | 4 ++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs b/src/Umbraco.Core/Security/MembershipProviderExtensions.cs index 68505e5669..091d981c73 100644 --- a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs +++ b/src/Umbraco.Core/Security/MembershipProviderExtensions.cs @@ -62,7 +62,12 @@ namespace Umbraco.Core.Security return Membership.Providers[UmbracoSettings.DefaultBackofficeProvider]; } - public static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider) + /// + /// Returns the currently logged in MembershipUser and flags them as being online - use sparingly (i.e. login) + /// + /// + /// + public static MembershipUser GetCurrentUserOnline(this MembershipProvider membershipProvider) { var username = membershipProvider.GetCurrentUserName(); return username.IsNullOrWhiteSpace() @@ -70,6 +75,19 @@ namespace Umbraco.Core.Security : membershipProvider.GetUser(username, true); } + /// + /// Returns the currently logged in MembershipUser + /// + /// + /// + public static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider) + { + var username = membershipProvider.GetCurrentUserName(); + return username.IsNullOrWhiteSpace() + ? null + : membershipProvider.GetUser(username, false); + } + /// /// Just returns the current user's login name (just a wrapper). /// diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 253fc7c3d4..fa3332d70d 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -274,7 +274,7 @@ namespace Umbraco.Web.Security if (provider.IsUmbracoMembershipProvider()) { - var membershipUser = provider.GetCurrentUser(); + var membershipUser = provider.GetCurrentUserOnline(); var member = GetCurrentMember(); //this shouldn't happen but will if the member is deleted in the back office while the member is trying // to use the front-end! @@ -438,7 +438,7 @@ namespace Umbraco.Web.Security } else { - var member = provider.GetCurrentUser(); + var member = provider.GetCurrentUserOnline(); //this shouldn't happen but will if the member is deleted in the back office while the member is trying // to use the front-end! if (member == null) From 9b2b961f83d8d4213f77b5befd2f4f9dcb7776fa Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 1 Jul 2014 10:51:56 +1000 Subject: [PATCH 108/189] Ensures no membership user writing when checking public access --- src/umbraco.cms/businesslogic/web/Access.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/umbraco.cms/businesslogic/web/Access.cs b/src/umbraco.cms/businesslogic/web/Access.cs index 9e009e8b9d..c7054dd923 100644 --- a/src/umbraco.cms/businesslogic/web/Access.cs +++ b/src/umbraco.cms/businesslogic/web/Access.cs @@ -451,7 +451,7 @@ namespace umbraco.cms.businesslogic.web { var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - return provider.GetUser(currentNode.Attributes.GetNamedItem("memberId").Value, true); + return provider.GetUser(currentNode.Attributes.GetNamedItem("memberId").Value, false); } } @@ -506,7 +506,7 @@ namespace umbraco.cms.businesslogic.web var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - var member = provider.GetUser(memberId, true); + var member = provider.GetUser(memberId, false); var currentNode = GetPage(GetProtectedPage(node.Path)); if (member != null) From 68b17bff08ce4462e56247f0a001201f0f714004 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 1 Jul 2014 10:52:29 +1000 Subject: [PATCH 109/189] Ensures no membership user writing when checking public access --- src/umbraco.cms/businesslogic/web/Access.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/umbraco.cms/businesslogic/web/Access.cs b/src/umbraco.cms/businesslogic/web/Access.cs index 9e009e8b9d..c7054dd923 100644 --- a/src/umbraco.cms/businesslogic/web/Access.cs +++ b/src/umbraco.cms/businesslogic/web/Access.cs @@ -451,7 +451,7 @@ namespace umbraco.cms.businesslogic.web { var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - return provider.GetUser(currentNode.Attributes.GetNamedItem("memberId").Value, true); + return provider.GetUser(currentNode.Attributes.GetNamedItem("memberId").Value, false); } } @@ -506,7 +506,7 @@ namespace umbraco.cms.businesslogic.web var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - var member = provider.GetUser(memberId, true); + var member = provider.GetUser(memberId, false); var currentNode = GetPage(GetProtectedPage(node.Path)); if (member != null) From 8917701fa3d34fd0b995b9feaa1bf3382f43cd8b Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 1 Jul 2014 11:03:47 +1000 Subject: [PATCH 110/189] ignores failing test - need stephane to look --- src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index a1f63aa452..47ff544fed 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -90,9 +90,9 @@ namespace Umbraco.Tests.Routing } //test all template name styles to match the ActionName - [TestCase("home-\\234^^*32page")] + + //[TestCase("home-\\234^^*32page")] //TODO: This fails! [TestCase("home-page")] - [TestCase("home-\\234^^*32page")] [TestCase("home-page")] [TestCase("home-page")] [TestCase("Home-Page")] From a89b9e9da683cb0b606ededb92652d4133c62168 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 1 Jul 2014 11:16:57 +1000 Subject: [PATCH 111/189] ignores log4net tests --- src/Umbraco.Tests/AsynchronousRollingFileAppenderTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Tests/AsynchronousRollingFileAppenderTests.cs b/src/Umbraco.Tests/AsynchronousRollingFileAppenderTests.cs index 9e49bb3290..2f1a8d99d3 100644 --- a/src/Umbraco.Tests/AsynchronousRollingFileAppenderTests.cs +++ b/src/Umbraco.Tests/AsynchronousRollingFileAppenderTests.cs @@ -15,6 +15,8 @@ using log4net.Repository; namespace Umbraco.Tests { + //Ignore this test, it fails sometimes on the build server - pretty sure it's a threading issue with this test class + [Ignore] [TestFixture] public class AsynchronousRollingFileAppenderTests { From 8645e0b19354a7919756e642c3888af84163764b Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 1 Jul 2014 12:53:07 +1000 Subject: [PATCH 112/189] Fixes: U4-5140 Tags are not respected with bulk publish operations - just need to ensure all tests are passing and run some local tests --- src/Umbraco.Core/Models/ContentExtensions.cs | 6 + .../Models/Rdbms/PropertyDataDto.cs | 18 +++ .../Repositories/VersionableRepositoryBase.cs | 59 +++++++++- src/Umbraco.Core/Services/TagExtractor.cs | 104 ++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Services/ContentServiceTests.cs | 54 +++++++++ .../Editors/ContentControllerBase.cs | 3 +- src/Umbraco.Web/Editors/TagExtractor.cs | 79 ------------- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 9 files changed, 242 insertions(+), 83 deletions(-) create mode 100644 src/Umbraco.Core/Services/TagExtractor.cs delete mode 100644 src/Umbraco.Web/Editors/TagExtractor.cs diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index 84cf2869e7..768ae37c9b 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -590,6 +590,12 @@ namespace Umbraco.Core.Models { throw new IndexOutOfRangeException("No property exists with name " + propertyTypeAlias); } + property.SetTags(storageType, propertyTypeAlias, tags, replaceTags, tagGroup); + } + + internal static void SetTags(this Property property, TagCacheStorageType storageType, string propertyTypeAlias, IEnumerable tags, bool replaceTags, string tagGroup = "default") + { + if (property == null) throw new ArgumentNullException("property"); var trimmedTags = tags.Select(x => x.Trim()).ToArray(); diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs index 29e4c472b7..7c16f920bf 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs @@ -78,5 +78,23 @@ namespace Umbraco.Core.Models.Rdbms return string.Empty; } } + + protected bool Equals(PropertyDataDto other) + { + return Id == other.Id; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((PropertyDataDto) obj); + } + + public override int GetHashCode() + { + return Id; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 175ac87218..da9e2680e9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -2,11 +2,14 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Caching; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -135,14 +138,33 @@ namespace Umbraco.Core.Persistence.Repositories protected PropertyCollection GetPropertyCollection(int id, Guid versionId, IContentTypeComposition contentType, DateTime createDate, DateTime updateDate) { var sql = new Sql(); - sql.Select("*") + sql.Select("cmsPropertyData.*, cmsDataTypePreValues.id as preValId, cmsDataTypePreValues.value, cmsDataTypePreValues.sortorder, cmsDataTypePreValues.alias, cmsDataTypePreValues.datatypeNodeId") .From() .InnerJoin() .On(left => left.PropertyTypeId, right => right.Id) + + .LeftOuterJoin() + .On(left => left.DataTypeId, right => right.DataTypeNodeId) + .Where(x => x.NodeId == id) .Where(x => x.VersionId == versionId); - var propertyDataDtos = Database.Fetch(sql); + var allData = Database.Fetch(sql); + + var propertyDataDtos = allData.Select(x => new PropertyDataDto + { + Date = x.dataDate, + Id = x.id, + Integer = x.dataInt, + NodeId = x.contentNodeId, + Text = x.dataNtext, + VarChar = x.dataNvarchar, + VersionId = x.versionId, + PropertyTypeId = x.propertytypeid, + //NOTE: This get's used for nothing so we don't need to map it + //PropertyTypeDto = new PropertyTypeDto() + }).Distinct(); + var propertyFactory = new PropertyFactory(contentType, versionId, id, createDate, updateDate); var properties = propertyFactory.BuildEntity(propertyDataDtos).ToArray(); @@ -156,6 +178,39 @@ namespace Umbraco.Core.Persistence.Repositories property.Id = primaryKey; } + foreach (var property in properties) + { + var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); + //TODO: Should this be cached somehow? Might need to benchmark this + var tagSupport = TagExtractor.GetAttribute(editor); + + if (tagSupport != null) + { + + //this property has tags, so we need to extract them and for that we need the prevals which we've already looked up + + var preValData = allData.Where(x => x.propertytypeid == property.PropertyTypeId && x.preValId != null) + .Select(x => new DataTypePreValueDto + { + Alias = x.alias, + DataTypeNodeId = x.datatypeNodeId, + Id = x.preValId, + SortOrder = x.sortorder, + Value = x.value + }) + .ToDictionary(x => x.Alias, x => new PreValue(x.Id, x.Value, x.SortOrder)); + + var preVals = new PreValueCollection(preValData); + + var d = new ContentPropertyData(property.Value, + preVals, + new Dictionary()); + + TagExtractor.SetPropertyTags(property, d, property.Value, tagSupport); + } + } + + return new PropertyCollection(properties); } } diff --git a/src/Umbraco.Core/Services/TagExtractor.cs b/src/Umbraco.Core/Services/TagExtractor.cs new file mode 100644 index 0000000000..8de0c6d230 --- /dev/null +++ b/src/Umbraco.Core/Services/TagExtractor.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Services +{ + /// + /// A simple utility class to extract the tag values from a property/property editor and set them on the content + /// + internal class TagExtractor + { + public static SupportTagsAttribute GetAttribute(PropertyEditor propEd) + { + var tagSupport = propEd.GetType().GetCustomAttribute(false); + return tagSupport; + } + + /// + /// Sets the tag values on the content property based on the property editor's tags attribute + /// + /// + /// + /// + /// + public static void SetPropertyTags(Property property, ContentPropertyData propertyData, object convertedPropertyValue, SupportTagsAttribute attribute) + { + //check for a custom definition + if (attribute.TagPropertyDefinitionType != null) + { + //try to create it + TagPropertyDefinition def; + try + { + def = (TagPropertyDefinition) Activator.CreateInstance(attribute.TagPropertyDefinitionType, propertyData, attribute); + } + catch (Exception ex) + { + LogHelper.Error("Could not create custom " + attribute.TagPropertyDefinitionType + " tag definition", ex); + throw; + } + SetPropertyTags(property, convertedPropertyValue, def.Delimiter, def.ReplaceTags, def.TagGroup, attribute.ValueType, def.StorageType); + } + else + { + SetPropertyTags(property, convertedPropertyValue, attribute.Delimiter, attribute.ReplaceTags, attribute.TagGroup, attribute.ValueType, attribute.StorageType); + } + } + + public static void SetPropertyTags(Property property, object convertedPropertyValue, string delimiter, bool replaceTags, string tagGroup, TagValueType valueType, TagCacheStorageType storageType) + { + if (convertedPropertyValue == null) + { + convertedPropertyValue = ""; + } + + switch (valueType) + { + case TagValueType.FromDelimitedValue: + var tags = convertedPropertyValue.ToString().Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries); + property.SetTags(storageType, property.Alias, tags, replaceTags, tagGroup); + break; + case TagValueType.CustomTagList: + //for this to work the object value must be IENumerable + var stringList = convertedPropertyValue as IEnumerable; + if (stringList != null) + { + property.SetTags(storageType, property.Alias, stringList, replaceTags, tagGroup); + } + else + { + //it's not enumerable string, so lets see if we can automatically make it that way based on the current storage type + switch (storageType) + { + case TagCacheStorageType.Csv: + var split = convertedPropertyValue.ToString().Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries); + //recurse with new value + SetPropertyTags(property, split, delimiter, replaceTags, tagGroup, valueType, storageType); + break; + case TagCacheStorageType.Json: + try + { + var parsedJson = JsonConvert.DeserializeObject>(convertedPropertyValue.ToString()); + //recurse with new value + SetPropertyTags(property, parsedJson, delimiter, replaceTags, tagGroup, valueType, storageType); + } + catch (Exception ex) + { + LogHelper.WarnWithException("Could not automatically convert stored json value to an enumerable string", ex); + } + break; + default: + throw new ArgumentOutOfRangeException("storageType"); + } + } + break; + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 9149248e18..94df94f57f 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1070,6 +1070,7 @@ + diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index b522d97261..7f0d222308 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -39,6 +39,60 @@ namespace Umbraco.Tests.Services //TODO Add test to verify there is only ONE newest document/content in cmsDocument table after updating. //TODO Add test to delete specific version (with and without deleting prior versions) and versions by date. + [Test] + public void Create_Tag_Data_Bulk_Publish_Operation() + { + //Arrange + var contentService = ServiceContext.ContentService; + var contentTypeService = ServiceContext.ContentTypeService; + var dataTypeService = ServiceContext.DataTypeService; + //set the pre-values + dataTypeService.SavePreValues(1041, new Dictionary + { + {"group", new PreValue("test")}, + {"storageType", new PreValue("Csv")} + }); + var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true); + contentType.PropertyGroups.First().PropertyTypes.Add( + new PropertyType("test", DataTypeDatabaseType.Ntext) + { + Alias = "tags", + DataTypeDefinitionId = 1041 + }); + contentTypeService.Save(contentType); + contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy(() => contentType.Id), 0, contentType.Alias) }; + + var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1); + content.SetTags("tags", new[] { "hello", "world", "some", "tags" }, true); + contentService.Save(content); + + var child1 = MockedContent.CreateSimpleContent(contentType, "child 1 content", content.Id); + child1.SetTags("tags", new[] { "hello1", "world1", "some1" }, true); + contentService.Save(child1); + + var child2 = MockedContent.CreateSimpleContent(contentType, "child 2 content", content.Id); + child2.SetTags("tags", new[] { "hello2", "world2" }, true); + contentService.Save(child2); + + // Act + contentService.PublishWithChildrenWithStatus(content, includeUnpublished: true); + + // Assert + var propertyTypeId = contentType.PropertyTypes.Single(x => x.Alias == "tags").Id; + + Assert.AreEqual(4, DatabaseContext.Database.ExecuteScalar( + "SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId", + new { nodeId = content.Id, propTypeId = propertyTypeId })); + + Assert.AreEqual(3, DatabaseContext.Database.ExecuteScalar( + "SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId", + new { nodeId = child1.Id, propTypeId = propertyTypeId })); + + Assert.AreEqual(2, DatabaseContext.Database.ExecuteScalar( + "SELECT COUNT(*) FROM cmsTagRelationship WHERE nodeId=@nodeId AND propertyTypeId=@propTypeId", + new { nodeId = child2.Id, propTypeId = propertyTypeId })); + } + [Test] public void Does_Not_Create_Tag_Data_For_Non_Published_Version() { diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index 12f829fe49..5f02819f4d 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -9,6 +9,7 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; @@ -117,7 +118,7 @@ namespace Umbraco.Web.Editors var supportTagsAttribute = TagExtractor.GetAttribute(p.PropertyEditor); if (supportTagsAttribute != null) { - TagExtractor.SetPropertyTags(contentItem.PersistedContent, dboProperty, data, propVal, supportTagsAttribute); + TagExtractor.SetPropertyTags(dboProperty, data, propVal, supportTagsAttribute); } else { diff --git a/src/Umbraco.Web/Editors/TagExtractor.cs b/src/Umbraco.Web/Editors/TagExtractor.cs deleted file mode 100644 index 361e7700c9..0000000000 --- a/src/Umbraco.Web/Editors/TagExtractor.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Editors; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.Editors -{ - /// - /// A simple utility class to extract the tag values from a property/property editor and set them on the content - /// - internal class TagExtractor - { - public static SupportTagsAttribute GetAttribute(PropertyEditor propEd) - { - var tagSupport = propEd.GetType().GetCustomAttribute(false); - return tagSupport; - } - - /// - /// Sets the tag values on the content property based on the property editor's tags attribute - /// - /// - /// - /// - /// - /// - public static void SetPropertyTags(IContentBase content, Property property, ContentPropertyData propertyData, object convertedPropertyValue, SupportTagsAttribute attribute) - { - //check for a custom definition - if (attribute.TagPropertyDefinitionType != null) - { - //try to create it - TagPropertyDefinition def; - try - { - def = (TagPropertyDefinition) Activator.CreateInstance(attribute.TagPropertyDefinitionType, propertyData, attribute); - } - catch (Exception ex) - { - LogHelper.Error("Could not create custom " + attribute.TagPropertyDefinitionType + " tag definition", ex); - throw; - } - SetPropertyTags(content, property, convertedPropertyValue, def.Delimiter, def.ReplaceTags, def.TagGroup, attribute.ValueType, def.StorageType); - } - else - { - SetPropertyTags(content, property, convertedPropertyValue, attribute.Delimiter, attribute.ReplaceTags, attribute.TagGroup, attribute.ValueType, attribute.StorageType); - } - } - - public static void SetPropertyTags(IContentBase content, Property property, object convertedPropertyValue, string delimiter, bool replaceTags, string tagGroup, TagValueType valueType, TagCacheStorageType storageType) - { - if (convertedPropertyValue == null) - { - convertedPropertyValue = ""; - } - - switch (valueType) - { - case TagValueType.FromDelimitedValue: - var tags = convertedPropertyValue.ToString().Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries); - content.SetTags(storageType, property.Alias, tags, replaceTags, tagGroup); - break; - case TagValueType.CustomTagList: - //for this to work the object value must be IENumerable - var stringList = convertedPropertyValue as IEnumerable; - if (stringList != null) - { - content.SetTags(storageType, property.Alias, stringList, replaceTags, tagGroup); - } - break; - } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e89fa232da..fdd1719ae3 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -376,7 +376,6 @@ - From efda233fcfbfa3ec4545915ac65e5878def77fe9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 1 Jul 2014 17:40:41 +1000 Subject: [PATCH 113/189] super quick performance win in the case where people don't pass in distinct values --- src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs | 3 +++ src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs | 3 +++ .../Persistence/Repositories/StylesheetRepository.cs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index de20cc4fe8..0669db97a5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -133,6 +133,9 @@ namespace Umbraco.Core.Persistence.Repositories /// public IEnumerable GetAll(params TId[] ids) { + //ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries + ids = ids.Distinct().ToArray(); + if (ids.Any()) { var entities = _cache.GetByIds( diff --git a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs index f6af195b94..b09930ae3f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs @@ -64,6 +64,9 @@ namespace Umbraco.Core.Persistence.Repositories public override IEnumerable + From 6167d1584ab76cdd782529e2ed7a8a50a46abb4c Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 15 Jul 2014 11:37:31 +1000 Subject: [PATCH 136/189] Fixes an xss vulnerability --- .../umbraco/developer/Packages/proxy.htm | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/developer/Packages/proxy.htm b/src/Umbraco.Web.UI/umbraco/developer/Packages/proxy.htm index 4d89a2c063..b65ea2d674 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Packages/proxy.htm +++ b/src/Umbraco.Web.UI/umbraco/developer/Packages/proxy.htm @@ -5,9 +5,57 @@ - + From e22282c1e830c0e17943cbd25c7e0f6d8e391cc8 Mon Sep 17 00:00:00 2001 From: Jeroen Breuer Date: Tue, 15 Jul 2014 14:24:27 +0200 Subject: [PATCH 137/189] Fix for U4-5223 --- src/Umbraco.Core/Persistence/Factories/MacroFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs index dec58a09f1..0f6788a33e 100644 --- a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs @@ -42,7 +42,7 @@ namespace Umbraco.Core.Persistence.Factories }; if (entity.HasIdentity) - dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); + dto.Id = int.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); return dto; } @@ -69,4 +69,4 @@ namespace Umbraco.Core.Persistence.Factories return list; } } -} \ No newline at end of file +} From 39ee22cbd69f9d5f385dfd0c78e5e8910a7cd887 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Jul 2014 13:41:02 +1000 Subject: [PATCH 138/189] fixes a single vs first check with render route handler when there might be duplicate routes assigned to a controller --- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 04ae377027..0d0e012f1a 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -228,8 +228,9 @@ namespace Umbraco.Web.Mvc // If more than one route is found, find one with a matching action if (surfaceRoutes.Count() > 1) { - surfaceRoute = surfaceRoutes.SingleOrDefault(x => - x.Defaults["action"].ToString().InvariantEquals(postedInfo.ActionName)); + surfaceRoute = surfaceRoutes.FirstOrDefault(x => + x.Defaults["action"] != null && + x.Defaults["action"].ToString().InvariantEquals(postedInfo.ActionName)); } else { From fdf1218a9ec960f375dc8beb1f827972ebed57a8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Jul 2014 13:43:08 +1000 Subject: [PATCH 139/189] fixes another short/int parse --- .../Persistence/Factories/ServerRegistrationFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs b/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs index d8ddc1fc70..5f93bf56d3 100644 --- a/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs @@ -28,7 +28,7 @@ namespace Umbraco.Core.Persistence.Factories ComputerName = entity.ComputerName }; if (entity.HasIdentity) - dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); + dto.Id = int.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); return dto; } From 388eea95e51069ac46a090f32878ee3c3630a9f7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Jul 2014 13:46:15 +1000 Subject: [PATCH 140/189] fixes a single vs first check with render route handler when there might be duplicate routes assigned to a controller, fixes short/int parse --- .../Persistence/Factories/ServerRegistrationFactory.cs | 2 +- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs b/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs index fc065102e0..790106dbee 100644 --- a/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core.Persistence.Factories ComputerName = entity.ComputerName }; if (entity.HasIdentity) - dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); + dto.Id = int.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); return dto; } diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 04ae377027..0d0e012f1a 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -228,8 +228,9 @@ namespace Umbraco.Web.Mvc // If more than one route is found, find one with a matching action if (surfaceRoutes.Count() > 1) { - surfaceRoute = surfaceRoutes.SingleOrDefault(x => - x.Defaults["action"].ToString().InvariantEquals(postedInfo.ActionName)); + surfaceRoute = surfaceRoutes.FirstOrDefault(x => + x.Defaults["action"] != null && + x.Defaults["action"].ToString().InvariantEquals(postedInfo.ActionName)); } else { From 1a75e7beb114157d75c177c0787451621e1145b1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Jul 2014 14:10:24 +1000 Subject: [PATCH 141/189] Fixes: U4-5207 User Type with a space in the name breaks User Content --- .../Models/Membership/UserType.cs | 57 ++++++++++++++++--- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Models/Membership/UserType.cs b/src/Umbraco.Core/Models/Membership/UserType.cs index b5553c10d2..915943be43 100644 --- a/src/Umbraco.Core/Models/Membership/UserType.cs +++ b/src/Umbraco.Core/Models/Membership/UserType.cs @@ -1,27 +1,55 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Strings; namespace Umbraco.Core.Models.Membership { /// /// Represents the Type for a Backoffice User - /// - /// - /// Should be internal until a proper user/membership implementation - /// is part of the roadmap. - /// + ///
[Serializable] [DataContract(IsReference = true)] internal class UserType : Entity, IUserType { - [DataMember] - public string Alias { get; set; } + private string _alias; + private string _name; + private IEnumerable _permissions; + + private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + private static readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); [DataMember] - public string Name { get; set; } + public string Alias + { + get { return _alias; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); + return _alias; + }, _alias, AliasSelector); + } + } + + [DataMember] + public string Name + { + get { return _name; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _name = value; + return _name; + }, _name, NameSelector); + } + } /// /// The set of default permissions for the user type @@ -30,6 +58,17 @@ namespace Umbraco.Core.Models.Membership /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. /// [DataMember] - public IEnumerable Permissions { get; set; } + public IEnumerable Permissions + { + get { return _permissions; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _permissions = value; + return _permissions; + }, _permissions, PermissionsSelector); + } + } } } \ No newline at end of file From 7848a3095d2df87975382832f341a60baeeeb0cb Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Jul 2014 14:12:04 +1000 Subject: [PATCH 142/189] Fixes: U4-5207 User Type with a space in the name breaks User Content --- .../Models/Membership/UserType.cs | 57 ++++++++++++++++--- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Models/Membership/UserType.cs b/src/Umbraco.Core/Models/Membership/UserType.cs index b5553c10d2..915943be43 100644 --- a/src/Umbraco.Core/Models/Membership/UserType.cs +++ b/src/Umbraco.Core/Models/Membership/UserType.cs @@ -1,27 +1,55 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Strings; namespace Umbraco.Core.Models.Membership { /// /// Represents the Type for a Backoffice User - /// - /// - /// Should be internal until a proper user/membership implementation - /// is part of the roadmap. - /// + /// [Serializable] [DataContract(IsReference = true)] internal class UserType : Entity, IUserType { - [DataMember] - public string Alias { get; set; } + private string _alias; + private string _name; + private IEnumerable _permissions; + + private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + private static readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); [DataMember] - public string Name { get; set; } + public string Alias + { + get { return _alias; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); + return _alias; + }, _alias, AliasSelector); + } + } + + [DataMember] + public string Name + { + get { return _name; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _name = value; + return _name; + }, _name, NameSelector); + } + } /// /// The set of default permissions for the user type @@ -30,6 +58,17 @@ namespace Umbraco.Core.Models.Membership /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. /// [DataMember] - public IEnumerable Permissions { get; set; } + public IEnumerable Permissions + { + get { return _permissions; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _permissions = value; + return _permissions; + }, _permissions, PermissionsSelector); + } + } } } \ No newline at end of file From 6e78a9f98f4483c8d9f9a7e76a74d90383b577b3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Jul 2014 14:14:02 +1000 Subject: [PATCH 143/189] fix merge --- src/Umbraco.Core/IO/UmbracoMediaFile.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Umbraco.Core/IO/UmbracoMediaFile.cs b/src/Umbraco.Core/IO/UmbracoMediaFile.cs index b85483e2f4..a8a55e5d8a 100644 --- a/src/Umbraco.Core/IO/UmbracoMediaFile.cs +++ b/src/Umbraco.Core/IO/UmbracoMediaFile.cs @@ -163,11 +163,6 @@ namespace Umbraco.Core.IO } } else - { - _size = new Size(-1, -1); - } - } - else { _size = new Size(-1, -1); } From 4693fb23c8dc0638db70ab35b061e247865b83d4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 18 Jul 2014 16:28:54 +1000 Subject: [PATCH 144/189] removes unneeded lock on master controller factory - must have been left over from some v5 stuff for some reason :( this will speed things up a bit! --- src/Umbraco.Web/Mvc/MasterControllerFactory.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs index 100b192bf8..c60e9d883e 100644 --- a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs +++ b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs @@ -18,9 +18,6 @@ namespace Umbraco.Web.Mvc internal class MasterControllerFactory : DefaultControllerFactory { private readonly FilteredControllerFactoriesResolver _slaveFactories; - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); - - private readonly ConcurrentDictionary _controllerCache = new ConcurrentDictionary(); public MasterControllerFactory(FilteredControllerFactoriesResolver factoryResolver) { @@ -87,9 +84,7 @@ namespace Umbraco.Web.Mvc /// public override void ReleaseController(IController controller) { - using (new WriteLock(_locker)) - { - bool released = false; + bool released = false; if (controller is Controller) { var requestContext = ((Controller)controller).ControllerContext.RequestContext; @@ -101,7 +96,6 @@ namespace Umbraco.Web.Mvc } } if (!released) base.ReleaseController(controller); - } } } } \ No newline at end of file From a9261d217ea1d2b54f85c6e8140937ad9343207d Mon Sep 17 00:00:00 2001 From: arknu Date: Fri, 18 Jul 2014 17:00:59 +0200 Subject: [PATCH 145/189] U4-5194: Fix handling of multiple words in backoffice search --- src/Umbraco.Web/Editors/EntityController.cs | 26 +++++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 507c229122..81029b9bbc 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -288,23 +288,39 @@ namespace Umbraco.Web.Editors // then __nodeName will be matched normally with wildcards // the rest will be normal without wildcards var sb = new StringBuilder(); - + + var querywords = query.Split(' '); + //node name exactly boost x 10 sb.Append("+(__nodeName:"); + sb.Append("\""); sb.Append(query.ToLower()); + sb.Append("\""); sb.Append("^10.0 "); //node name normally with wildcards - sb.Append(" __nodeName:"); - sb.Append(query.ToLower()); - sb.Append("* "); + sb.Append(" __nodeName:"); + sb.Append("("); + foreach (var w in querywords) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + sb.Append(") "); + foreach (var f in fields) { //additional fields normally sb.Append(f); sb.Append(":"); - sb.Append(query); + sb.Append("("); + foreach (var w in querywords) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + sb.Append(")"); sb.Append(" "); } From 188613e5e785c7c19c6368efaf7c72964fe05e92 Mon Sep 17 00:00:00 2001 From: arknu Date: Fri, 18 Jul 2014 17:42:47 +0200 Subject: [PATCH 146/189] U4-5167: Handle quoted searches in the backoffice correctly --- src/Umbraco.Web/Editors/EntityController.cs | 79 +++++++++++++++------ 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 81029b9bbc..bb094fa30d 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -24,6 +24,7 @@ using Examine.LuceneEngine.SearchCriteria; using Examine.SearchCriteria; using Umbraco.Web.Dynamics; using umbraco; +using System.Text.RegularExpressions; namespace Umbraco.Web.Editors { @@ -289,45 +290,77 @@ namespace Umbraco.Web.Editors // the rest will be normal without wildcards var sb = new StringBuilder(); - var querywords = query.Split(' '); - - //node name exactly boost x 10 - sb.Append("+(__nodeName:"); - sb.Append("\""); - sb.Append(query.ToLower()); - sb.Append("\""); - sb.Append("^10.0 "); - - //node name normally with wildcards - sb.Append(" __nodeName:"); - sb.Append("("); - foreach (var w in querywords) + bool hasSingleQuotes = Regex.IsMatch(query, "\"[^\"]*"); + if (hasSingleQuotes) { - sb.Append(w.ToLower()); - sb.Append("* "); + //we cannot search for single qoutes, so we ignore them + query = query.Replace("\"", ""); } - sb.Append(") "); + var querywords = query.Split(' '); + bool hasDoubleQuotes = Regex.IsMatch(query, "\"[^\"]*\""); - foreach (var f in fields) + if (string.IsNullOrWhiteSpace(query)) { - //additional fields normally - sb.Append(f); - sb.Append(":"); + return new List(); + } + if (hasDoubleQuotes) + { + + //node name exactly boost x 10 + sb.Append("+(__nodeName: ("); + sb.Append(query.ToLower()); + sb.Append(")^10.0 "); + + foreach (var f in fields) + { + //additional fields normally + sb.Append(f); + sb.Append(": ("); + sb.Append(query); + sb.Append(") "); + } + } + else + { + //node name exactly boost x 10 + sb.Append("+(__nodeName:"); + sb.Append("\""); + sb.Append(query.ToLower()); + sb.Append("\""); + sb.Append("^10.0 "); + + //node name normally with wildcards + sb.Append(" __nodeName:"); sb.Append("("); foreach (var w in querywords) { sb.Append(w.ToLower()); sb.Append("* "); } - sb.Append(")"); - sb.Append(" "); + sb.Append(") "); + + + foreach (var f in fields) + { + //additional fields normally + sb.Append(f); + sb.Append(":"); + sb.Append("("); + foreach (var w in querywords) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + sb.Append(")"); + sb.Append(" "); + } } //must match index type sb.Append(") +__IndexType:"); sb.Append(type); - + var raw = internalSearcher.CreateSearchCriteria().RawQuery(sb.ToString()); var result = internalSearcher.Search(raw); From 0370a1bcf245e677b23dcbcdb12a8402ca4e4e59 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 21 Jul 2014 15:32:16 +0100 Subject: [PATCH 147/189] PublishedContentModelFactory: Changes the private 'constructors' dictionary to be case-insensitive. --- .../Models/PublishedContent/PublishedContentModelFactory.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs index d0874dd58f..b3ad56d959 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Models.PublishedContent public PublishedContentModelFactory(IEnumerable types) { var ctorArgTypes = new[] { typeof(IPublishedContent) }; - var constructors = new Dictionary>(); + var constructors = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); foreach (var type in types) { @@ -40,7 +40,6 @@ namespace Umbraco.Core.Models.PublishedContent throw new InvalidOperationException(string.Format("Type {0} is missing a public constructor with one argument of type IPublishedContent.", type.FullName)); var attribute = type.GetCustomAttribute(false); var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; - typeName = typeName.ToLowerInvariant(); if (constructors.ContainsKey(typeName)) throw new InvalidOperationException(string.Format("More that one type want to be a model for content type {0}.", typeName)); @@ -70,7 +69,7 @@ namespace Umbraco.Core.Models.PublishedContent return content; // be case-insensitive - var contentTypeAlias = content.DocumentTypeAlias.ToLowerInvariant(); + var contentTypeAlias = content.DocumentTypeAlias; //ConstructorInfo constructor; //return _constructors.TryGetValue(contentTypeAlias, out constructor) From 8d1d2f241498027ccae27b24ef44cf1fac92e59d Mon Sep 17 00:00:00 2001 From: AndyButland Date: Mon, 21 Jul 2014 22:27:04 +0200 Subject: [PATCH 148/189] Initialised slider scope model value if not already set from a previous save --- .../slider/slider.controller.js | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/slider.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/slider.controller.js index 692d2dbc99..2f26572fde 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/slider.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/slider.controller.js @@ -69,6 +69,11 @@ } } + // Initialise model value if not set + if (!$scope.model.value) { + setModelValueFromSlider(sliderVal); + } + //initiate slider, add event handler and get the instance reference (stored in data) var slider = $element.find('.slider-item').slider({ max: $scope.model.config.maxVal, @@ -81,16 +86,21 @@ value: sliderVal }).on('slideStop', function () { angularHelper.safeApply($scope, function () { - //Get the value from the slider and format it correctly, if it is a range we want a comma delimited value - if ($scope.model.config.enableRange === "1") { - $scope.model.value = slider.getValue().join(","); - } - else { - $scope.model.value = slider.getValue().toString(); - } + setModelValueFromSlider(slider.getValue()); }); }).data('slider'); + } + /** Called on start-up when no model value has been applied and on change of the slider via the UI - updates + the model with the currently selected slider value(s) **/ + function setModelValueFromSlider(sliderVal) { + //Get the value from the slider and format it correctly, if it is a range we want a comma delimited value + if ($scope.model.config.enableRange === "1") { + $scope.model.value = sliderVal.join(","); + } + else { + $scope.model.value = sliderVal.toString(); + } } //tell the assetsService to load the bootstrap slider From 626e155c07a0b957dbef8546e18b352c0de3a4da Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Jul 2014 03:54:27 +1000 Subject: [PATCH 149/189] Fixes SO issue when tags contain an empty string --- src/Umbraco.Core/Services/TagExtractor.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Services/TagExtractor.cs b/src/Umbraco.Core/Services/TagExtractor.cs index 3e04517f80..0e6d605e41 100644 --- a/src/Umbraco.Core/Services/TagExtractor.cs +++ b/src/Umbraco.Core/Services/TagExtractor.cs @@ -85,8 +85,11 @@ namespace Umbraco.Core.Services try { var parsedJson = JsonConvert.DeserializeObject>(convertedPropertyValue.ToString()); - //recurse with new value - SetPropertyTags(property, parsedJson, delimiter, replaceTags, tagGroup, valueType, storageType); + if (parsedJson != null) + { + //recurse with new value + SetPropertyTags(property, parsedJson, delimiter, replaceTags, tagGroup, valueType, storageType); + } } catch (Exception ex) { From e98ed8561a94ef7b0d2b5dec03219ff025a5e24a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Jul 2014 04:10:48 +1000 Subject: [PATCH 150/189] Fixes: U4-5252 --- .../umbraco/ClientRedirect.aspx | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/ClientRedirect.aspx b/src/Umbraco.Web.UI/umbraco/ClientRedirect.aspx index fd9c5182f2..72232c99cd 100644 --- a/src/Umbraco.Web.UI/umbraco/ClientRedirect.aspx +++ b/src/Umbraco.Web.UI/umbraco/ClientRedirect.aspx @@ -1,4 +1,4 @@ -<%@ Page Language="C#" AutoEventWireup="true" Inherits="System.Web.UI.Page" %> +<%@ Page Language="C#" AutoEventWireup="true" Inherits="Umbraco.Web.UI.Pages.UmbracoEnsuredPage" %> <%-- This page is required because we cannot reload the angular app with a changed Hash since it just detects the hash and doesn't reload. So this is used purely for a full reload of an angular app with a changed hash. @@ -8,12 +8,46 @@ Redirecting... From cd8b8dc7048672cd4016c5f3cbcef92e69faefcc Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Jul 2014 11:33:58 -0700 Subject: [PATCH 151/189] Fixes: U4-5212 Preview button only greyed and not disabled while previewing. (was save button) --- .../views/content/content.edit.controller.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index b5c72005e8..90b1b513a3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -262,14 +262,20 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS }; $scope.preview = function (content) { - // Chromes popup blocker will kick in if a window is opened - // outwith the initial scoped request. This trick will fix that. - var previewWindow = $window.open("/umbraco/views/content/umbpreview.html", "umbpreview"); - $scope.save().then(function (data) { - // Build the correct path so both /#/ and #/ work. - var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/dialogs/preview.aspx?id=' + data.id; - previewWindow.location.href = redirect; - }); + + if (!$scope.busy) { + $scope.save().then(function (data) { + + // Chromes popup blocker will kick in if a window is opened + // outwith the initial scoped request. This trick will fix that. + var previewWindow = $window.open("/umbraco/views/content/umbpreview.html", "umbpreview"); + + + // Build the correct path so both /#/ and #/ work. + var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/dialogs/preview.aspx?id=' + data.id; + previewWindow.location.href = redirect; + }); + } }; /** this method is called for all action buttons and then we proxy based on the btn definition */ From e8eea039e14dfe24b57ca62813f9c3326e0d9475 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Tue, 22 Jul 2014 22:35:58 +0100 Subject: [PATCH 152/189] Reset page number to one when applying filter on list view --- .../src/views/propertyeditors/listview/listview.controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 8a765bb0f6..578d7a464d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -94,7 +94,8 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific }; //assign debounce method to the search to limit the queries - $scope.search = _.debounce(function() { + $scope.search = _.debounce(function () { + $scope.options.pageNumber = 1; $scope.reloadView($scope.contentId); }, 100); From 8445c9c29b1a67af7361667ce2ac9a4329f63b52 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Tue, 22 Jul 2014 23:11:22 +0100 Subject: [PATCH 153/189] Applied styling to list view paging (active class on page numbers, disabled on prev and next when not available --- .../src/less/listview.less | 4 ++++ .../listview/listview.controller.js | 2 +- .../propertyeditors/listview/listview.html | 18 +++++++++++------- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index ce1a73fe6d..13e3853458 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -166,6 +166,10 @@ color: @black; } +.umb-listview .pagination ul > li.disabled > a, .umb-listview .pagination ul > li.disabled > a:hover { + color: @grayLight; +} + /* TEMP */ .umb-listview .table-striped tbody td { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 578d7a464d..220aa070db 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -80,7 +80,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific contentResource.getChildren(id, $scope.options).then(function (data) { $scope.listViewResultSet = data; - $scope.pagination = []; + $scope.pagination = []; for (var i = $scope.listViewResultSet.totalPages - 1; i >= 0; i--) { $scope.pagination[i] = { index: i, name: i + 1 }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 01ed21d42f..a9521865d6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -91,18 +91,22 @@
From 05be2a5e865e7508b1c536286e84650fece470a6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Jul 2014 15:13:01 -0700 Subject: [PATCH 154/189] adds test --- .../Services/MemberServiceTests.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 22abab7786..9609110308 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; @@ -340,6 +341,29 @@ namespace Umbraco.Tests.Services Assert.IsFalse(ServiceContext.MemberService.Exists(9876)); } + [Test] + public void Tracks_Dirty_Changes() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); + ServiceContext.MemberService.Save(member); + + var resolved = ServiceContext.MemberService.GetByEmail(member.Email); + + //NOTE: This will not trigger a property isDirty because this is not based on a 'Property', it is + // just a c# property of the Member object + resolved.Email = "changed@test.com"; + //NOTE: this WILL trigger a property isDirty because setting this c# property actually sets a value of + // the underlying 'Property' + resolved.FailedPasswordAttempts = 1234; + + var dirtyMember = (ICanBeDirty)resolved; + var dirtyProperties = resolved.Properties.Where(x => x.IsDirty()).ToList(); + Assert.IsTrue(dirtyMember.IsDirty()); + Assert.AreEqual(1, dirtyProperties.Count()); + } + [Test] public void Get_By_Email() { From 851181f86150ffb33c293f90092b507815c3b0b1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Jul 2014 15:39:07 -0700 Subject: [PATCH 155/189] Microsoft.Net.Http required a reinstall command for the UI project as noted by it's persisted 'requiresReinstall' attribute in the packages.config - and then the project wouldn't actually build anymore. --- src/Umbraco.Tests/Umbraco.Tests.csproj | 12 +++++++----- src/Umbraco.Tests/packages.config | 6 +++--- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 6 +++--- src/Umbraco.Web.UI/packages.config | 6 +++--- src/Umbraco.Web/Umbraco.Web.csproj | 12 +++++++----- src/Umbraco.Web/packages.config | 4 ++-- src/umbraco.MacroEngines/packages.config | 4 ++-- src/umbraco.MacroEngines/umbraco.MacroEngines.csproj | 6 +++--- 8 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e2f95ac8c9..e87cbe5462 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -107,13 +107,15 @@ - + + False ..\packages\Microsoft.Net.Http.2.2.15\lib\net45\System.Net.Http.Extensions.dll ..\packages\Microsoft.AspNet.WebApi.Client.4.0.30506.0\lib\net40\System.Net.Http.Formatting.dll - + + False ..\packages\Microsoft.Net.Http.2.2.15\lib\net45\System.Net.Http.Primitives.dll @@ -711,10 +713,10 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" /Y /F /E /D - + - - + + - + - - + + \ No newline at end of file diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index 41acee96bb..980fd5822b 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -14,8 +14,8 @@ - - + + diff --git a/src/umbraco.MacroEngines/packages.config b/src/umbraco.MacroEngines/packages.config index f88a052ce9..0f4be17c9a 100644 --- a/src/umbraco.MacroEngines/packages.config +++ b/src/umbraco.MacroEngines/packages.config @@ -10,8 +10,8 @@ - - + + diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj index 3c49a04763..f2a4f9c060 100644 --- a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj +++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj @@ -239,10 +239,10 @@ - + - - + + cXAl(hKgdcVGV_xZnJD(df8GYUnRkKCT>1!C1{ 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

S>iHS|@46sMM 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%0Dbb%>LJ;tb|o`Kp8Evufeht>W*BXmS?4^&TB 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%0A{4Qko^d0b`ex!LBAWYhRF*1EWi7u)hrY#_Gf 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 z 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 From ea56363f6d9ef8e6da70d143b02a18ae906ff2a8 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 23 Jul 2014 14:04:50 +0200 Subject: [PATCH 161/189] Adds logging to the RteEmbedController when there's an exception, instead of silently swallowing the error --- src/Umbraco.Web/PropertyEditors/RteEmbedController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PropertyEditors/RteEmbedController.cs b/src/Umbraco.Web/PropertyEditors/RteEmbedController.cs index 7181780b32..6e6417615d 100644 --- a/src/Umbraco.Web/PropertyEditors/RteEmbedController.cs +++ b/src/Umbraco.Web/PropertyEditors/RteEmbedController.cs @@ -5,7 +5,9 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web.Http; using System.Xml; +using umbraco.BusinessLogic; using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; using Umbraco.Web.Editors; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; @@ -65,8 +67,9 @@ namespace Umbraco.Web.PropertyEditors result.Markup = prov.GetMarkup(url, width, height); result.Status = Status.Success; } - catch + catch(Exception ex) { + LogHelper.Error(string.Format("Error embedding url {0} - width: {1} height: {2}", url, width, height), ex); result.Status = Status.Error; } From dd83dced4c91a781a4577fc378f74148adb5188c Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Jul 2014 09:03:54 -0700 Subject: [PATCH 162/189] Fixes up back office search, U4-5048, U4-5021, U4-5167, U4-5194 --- src/Umbraco.Web/Editors/EntityController.cs | 41 +++++++++++++-------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index bb094fa30d..f98c3d051a 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -262,7 +262,7 @@ namespace Umbraco.Web.Editors { string type; var searcher = Constants.Examine.InternalSearcher; - var fields = new[] { "id", "bodyText" }; + var fields = new[] { "id" }; //TODO: WE should really just allow passing in a lucene raw query switch (entityType) @@ -289,23 +289,25 @@ namespace Umbraco.Web.Editors // then __nodeName will be matched normally with wildcards // the rest will be normal without wildcards var sb = new StringBuilder(); - - bool hasSingleQuotes = Regex.IsMatch(query, "\"[^\"]*"); - if (hasSingleQuotes) + + //check if text is surrounded by single or double quotes, if so, then exact match + var surroundedByQuotes = Regex.IsMatch(query, "^\".*?\"$") + || Regex.IsMatch(query, "^\'.*?\'$"); + + if (surroundedByQuotes) { - //we cannot search for single qoutes, so we ignore them - query = query.Replace("\"", ""); - } + //strip quotes, escape string, the replace again + query = query.Trim(new[] { '\"', '\'' }); - var querywords = query.Split(' '); - bool hasDoubleQuotes = Regex.IsMatch(query, "\"[^\"]*\""); + query = Lucene.Net.QueryParsers.QueryParser.Escape(query); - if (string.IsNullOrWhiteSpace(query)) - { - return new List(); - } - if (hasDoubleQuotes) - { + if (query.IsNullOrWhiteSpace()) + { + return new List(); + } + + //add back the surrounding quotes + query = string.Format("{0}{1}{0}", "\"", query); //node name exactly boost x 10 sb.Append("+(__nodeName: ("); @@ -323,6 +325,15 @@ namespace Umbraco.Web.Editors } else { + if (query.Trim(new[] { '\"', '\'' }).IsNullOrWhiteSpace()) + { + return new List(); + } + + query = Lucene.Net.QueryParsers.QueryParser.Escape(query); + + var querywords = query.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + //node name exactly boost x 10 sb.Append("+(__nodeName:"); sb.Append("\""); From c24775adb0bec69278617379cc6e4673cd88681a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Jul 2014 10:03:26 -0700 Subject: [PATCH 163/189] re-installs mvc, webapi and ensures running the latest 2.0.x versions of webpages and razor, now all proj's are consistent with correct versions referenced and copy local set correctly. --- src/SQLCE4Umbraco/SqlCE4Umbraco.csproj | 1 + src/SQLCE4Umbraco/app.config | 15 +++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 22 ++++++---------- src/Umbraco.Core/app.config | 15 +++++++++++ src/Umbraco.Tests/App.config | 8 ++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 11 ++++---- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 25 ++++++------------- src/Umbraco.Web/Umbraco.Web.csproj | 25 +++++++------------ src/Umbraco.Web/app.config | 4 +++ src/umbraco.MacroEngines/App.Config | 8 ++++++ src/umbraco.businesslogic/app.config | 15 +++++++++++ .../umbraco.businesslogic.csproj | 2 ++ src/umbraco.datalayer/app.config | 15 +++++++++++ .../umbraco.datalayer.csproj | 1 + src/umbraco.editorControls/app.config | 4 +++ 15 files changed, 118 insertions(+), 53 deletions(-) create mode 100644 src/SQLCE4Umbraco/app.config create mode 100644 src/Umbraco.Core/app.config create mode 100644 src/umbraco.businesslogic/app.config create mode 100644 src/umbraco.datalayer/app.config diff --git a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj index db3caf4b82..4113225994 100644 --- a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj +++ b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj @@ -80,6 +80,7 @@ + diff --git a/src/SQLCE4Umbraco/app.config b/src/SQLCE4Umbraco/app.config new file mode 100644 index 0000000000..e72c720717 --- /dev/null +++ b/src/SQLCE4Umbraco/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 672caf2ffd..fe511d3077 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -39,9 +39,8 @@ ..\packages\log4net-mediumtrust.2.0.0\lib\log4net.dll - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - False True + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll @@ -72,37 +71,31 @@ - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll - False True + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll - ..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll - False True + ..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll - ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll - False True + ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll False - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - False True + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll - False True + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll - False True + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll @@ -937,6 +930,7 @@ + diff --git a/src/Umbraco.Core/app.config b/src/Umbraco.Core/app.config new file mode 100644 index 0000000000..e72c720717 --- /dev/null +++ b/src/Umbraco.Core/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 9e4b0ac66d..1ced772e3b 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -121,6 +121,14 @@ + + + + + + + + diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 2d00ca0d41..6c2ee7c94d 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -122,17 +122,16 @@ True ..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll - - ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll + True - + + True ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - True - - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll + True + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll True diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 2804823ddc..de024526ff 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -122,9 +122,8 @@ ..\packages\Microsoft.Web.Helpers.1.0.0\lib\Microsoft.Web.Helpers.dll - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - False True + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll False @@ -172,16 +171,14 @@ - False + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll ..\packages\Microsoft.AspNet.WebApi.Client.4.0.30506.0\lib\net40\System.Net.Http.Formatting.dll False - False ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll - True System.Web @@ -195,9 +192,8 @@ - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll - False True + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll ..\packages\Microsoft.AspNet.WebApi.Core.4.0.30506.0\lib\net40\System.Web.Http.dll @@ -208,32 +204,27 @@ False - ..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll - False True + ..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll - ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll - False True + ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll System.Web.Services - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - False True + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll - False True + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll - False True + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll System.XML diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 66a990468c..d57d7107de 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -119,9 +119,8 @@ - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - False True + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll False @@ -163,14 +162,14 @@ - False + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll ..\packages\Microsoft.AspNet.WebApi.Client.4.0.30506.0\lib\net40\System.Net.Http.Formatting.dll False - False + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll @@ -183,9 +182,8 @@ - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll - False True + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll ..\packages\Microsoft.AspNet.WebApi.Core.4.0.30506.0\lib\net40\System.Web.Http.dll @@ -196,22 +194,19 @@ False - ..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll - False True + ..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll - ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll - False True + ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll System.Web.Services - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - False True + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll ..\packages\CodeSharp.Package.AspNetWebPage.1.0\lib\net40\System.Web.WebPages.Administration.dll @@ -219,14 +214,12 @@ True - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll - False True + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll - ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll - False True + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll diff --git a/src/Umbraco.Web/app.config b/src/Umbraco.Web/app.config index 568750da9c..f82303ad3e 100644 --- a/src/Umbraco.Web/app.config +++ b/src/Umbraco.Web/app.config @@ -14,6 +14,10 @@ + + + + \ No newline at end of file diff --git a/src/umbraco.MacroEngines/App.Config b/src/umbraco.MacroEngines/App.Config index 794c0925aa..f81fbde12c 100644 --- a/src/umbraco.MacroEngines/App.Config +++ b/src/umbraco.MacroEngines/App.Config @@ -61,6 +61,14 @@ + + + + + + + + diff --git a/src/umbraco.businesslogic/app.config b/src/umbraco.businesslogic/app.config new file mode 100644 index 0000000000..e72c720717 --- /dev/null +++ b/src/umbraco.businesslogic/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/umbraco.businesslogic/umbraco.businesslogic.csproj b/src/umbraco.businesslogic/umbraco.businesslogic.csproj index 7d1e446b27..f087884a3c 100644 --- a/src/umbraco.businesslogic/umbraco.businesslogic.csproj +++ b/src/umbraco.businesslogic/umbraco.businesslogic.csproj @@ -106,6 +106,7 @@ True + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll @@ -271,6 +272,7 @@ + diff --git a/src/umbraco.datalayer/app.config b/src/umbraco.datalayer/app.config new file mode 100644 index 0000000000..e72c720717 --- /dev/null +++ b/src/umbraco.datalayer/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/umbraco.datalayer/umbraco.datalayer.csproj b/src/umbraco.datalayer/umbraco.datalayer.csproj index ccb4dbcbe1..6e5065cb38 100644 --- a/src/umbraco.datalayer/umbraco.datalayer.csproj +++ b/src/umbraco.datalayer/umbraco.datalayer.csproj @@ -150,6 +150,7 @@ + diff --git a/src/umbraco.editorControls/app.config b/src/umbraco.editorControls/app.config index 568750da9c..f82303ad3e 100644 --- a/src/umbraco.editorControls/app.config +++ b/src/umbraco.editorControls/app.config @@ -14,6 +14,10 @@ + + + + \ No newline at end of file From fd7930256b3019ea6d47cb3cb7a2336c2539a9ee Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Jul 2014 10:53:30 -0700 Subject: [PATCH 164/189] Uninstalls package CodeSharp.Package.AspNetWebPage and removes refs to Nuget.Core and Webpages.Administration - neither of which are used anywhere... but we've been shipping Umbraco with them for some reason. --- src/Umbraco.Web/Umbraco.Web.csproj | 10 ---------- src/Umbraco.Web/packages.config | 1 - 2 files changed, 11 deletions(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d57d7107de..eb28b27feb 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -133,11 +133,6 @@ ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll - - ..\packages\CodeSharp.Package.AspNetWebPage.1.0\lib\net40\NuGet.Core.dll - False - True - ..\packages\uGoLive.1.4.0\lib\Our.Umbraco.uGoLive.dll @@ -208,11 +203,6 @@ True ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - - ..\packages\CodeSharp.Package.AspNetWebPage.1.0\lib\net40\System.Web.WebPages.Administration.dll - False - True - True ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index 96e9bf3d0b..85d33f5f6f 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -1,7 +1,6 @@  - From 84fad1300b81107c5d8bea3974a7f486859e2743 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Jul 2014 12:19:40 -0700 Subject: [PATCH 165/189] All nuget refs are now up to date and installed correctly with copy local set correctly for all non gac 4.5 assemblies as well as the specific version attribute. --- src/Umbraco.Core/Umbraco.Core.csproj | 5 +++ src/Umbraco.Core/packages.config | 4 +- src/Umbraco.Tests/App.config | 8 ++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 15 +------ src/Umbraco.Tests/packages.config | 14 +++---- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 25 +++--------- src/Umbraco.Web.UI/packages.config | 14 +++---- src/Umbraco.Web/Umbraco.Web.csproj | 40 +++++-------------- src/Umbraco.Web/app.config | 4 ++ src/Umbraco.Web/packages.config | 15 +++---- src/umbraco.MacroEngines/app.config | 4 ++ src/umbraco.MacroEngines/packages.config | 14 +++---- .../umbraco.MacroEngines.csproj | 13 +----- src/umbraco.businesslogic/app.config | 4 ++ src/umbraco.editorControls/app.config | 4 ++ 15 files changed, 73 insertions(+), 110 deletions(-) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index a4f009a229..eb757727b8 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1197,6 +1197,11 @@ + + + + + - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web/app.config b/src/Umbraco.Web/app.config index 64bd723ac9..957569042f 100644 --- a/src/Umbraco.Web/app.config +++ b/src/Umbraco.Web/app.config @@ -47,6 +47,10 @@ + + + + \ No newline at end of file diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index 980fd5822b..37b4c51632 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -2,22 +2,19 @@ - - - - - + + + + - - - - + + diff --git a/src/umbraco.MacroEngines/app.config b/src/umbraco.MacroEngines/app.config index a2f25c8809..900c3903d5 100644 --- a/src/umbraco.MacroEngines/app.config +++ b/src/umbraco.MacroEngines/app.config @@ -22,6 +22,10 @@ + + + + \ No newline at end of file diff --git a/src/umbraco.MacroEngines/packages.config b/src/umbraco.MacroEngines/packages.config index 0f4be17c9a..e299f1e7b4 100644 --- a/src/umbraco.MacroEngines/packages.config +++ b/src/umbraco.MacroEngines/packages.config @@ -5,15 +5,13 @@ - - - - + + + + - - - - + + \ No newline at end of file diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj index f2a4f9c060..47ca32fcf0 100644 --- a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj +++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj @@ -65,7 +65,7 @@ True ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - + False ..\packages\Newtonsoft.Json.6.0.2\lib\net45\Newtonsoft.Json.dll @@ -74,15 +74,9 @@ - - ..\packages\Microsoft.Net.Http.2.2.15\lib\net45\System.Net.Http.Extensions.dll - ..\packages\Microsoft.AspNet.WebApi.Client.4.0.30506.0\lib\net40\System.Net.Http.Formatting.dll - - ..\packages\Microsoft.Net.Http.2.2.15\lib\net45\System.Net.Http.Primitives.dll - @@ -239,11 +233,6 @@ - - - - -