diff --git a/Composite.Workflows/Plugins/Elements/ElementProviders/PackageElementProvider/ViewAvailablePackageInfoWorkflowWorkflow.cs b/Composite.Workflows/Plugins/Elements/ElementProviders/PackageElementProvider/ViewAvailablePackageInfoWorkflowWorkflow.cs index 7320384bc1..beb8a44218 100644 --- a/Composite.Workflows/Plugins/Elements/ElementProviders/PackageElementProvider/ViewAvailablePackageInfoWorkflowWorkflow.cs +++ b/Composite.Workflows/Plugins/Elements/ElementProviders/PackageElementProvider/ViewAvailablePackageInfoWorkflowWorkflow.cs @@ -1,20 +1,46 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Workflow.Activities; using Composite.C1Console.Events; using Composite.C1Console.Forms.DataServices; using Composite.Core.Configuration; -using Composite.Core.Extensions; using Composite.Core.PackageSystem; using Composite.C1Console.Users; using Composite.C1Console.Workflow; +using Texts = Composite.Core.ResourceSystem.LocalizationFiles.Composite_Plugins_PackageElementProvider; namespace Composite.Plugins.Elements.ElementProviders.PackageElementProvider { [AllowPersistingWorkflow(WorkflowPersistingType.Idle)] public sealed partial class ViewAvailablePackageInfoWorkflowWorkflow : Composite.C1Console.Workflow.Activities.FormsWorkflow { + const string CustomToolbarFormPath = @"\Administrative\PackageElementProviderViewAvailablePackageInformationToolbar.xml"; + + static class BindingNames + { + public const string PackageDescription = nameof(PackageDescription); + public const string DocumentTitle = nameof(DocumentTitle); + public const string AddOnServerSource = nameof(AddOnServerSource); + public const string HasOwnPrice = nameof(HasOwnPrice); + public const string PriceText = nameof(PriceText); + public const string IsInPurchasableSubscriptions = nameof(IsInPurchasableSubscriptions); + public const string PurchasableSubscriptions = nameof(PurchasableSubscriptions); + public const string ShowTrialInfo = nameof(ShowTrialInfo); + public const string ShowSubscriptionLicense = nameof(ShowSubscriptionLicense); + public const string SubscriptionName = nameof(SubscriptionName); + public const string LicenseExpirationDate = nameof(LicenseExpirationDate); + } + + class SubscriptionLicense + { + public string Name { get; set; } + public DateTime ExpirationDate { get; set; } + public bool Expired { get; set; } + } + + public ViewAvailablePackageInfoWorkflowWorkflow() { InitializeComponent(); @@ -25,7 +51,7 @@ public ViewAvailablePackageInfoWorkflowWorkflow() private void AddOnDescriptionExists(object sender, ConditionalEventArgs e) { PackageDescription packageDescription; - this.TryGetBinding("PackageDescription", out packageDescription); + this.TryGetBinding(BindingNames.PackageDescription, out packageDescription); e.Result = packageDescription != null; } @@ -33,56 +59,96 @@ private void AddOnDescriptionExists(object sender, ConditionalEventArgs e) private void viewStateCodeActivity_Initialize_ExecuteCode(object sender, EventArgs e) { - if (this.BindingExist("PackageDescription") == false) + this.SetCustomToolbarDefinition(new FormDefinitionFileMarkupProvider(CustomToolbarFormPath)); + + + if (this.BindingExist(BindingNames.PackageDescription)) { - PackageElementProviderAvailablePackagesItemEntityToken castedToken = (PackageElementProviderAvailablePackagesItemEntityToken)this.EntityToken; + return; + } + + var castedToken = (PackageElementProviderAvailablePackagesItemEntityToken)this.EntityToken; + + PackageDescription packageDescription = + (from description in PackageSystemServices.GetFilteredAllAvailablePackages() + where description.Id.ToString() == castedToken.Id + select description).SingleOrDefault(); - PackageDescription packageDescription = - (from description in PackageSystemServices.GetFilteredAllAvailablePackages() - where description.Id.ToString() == castedToken.Id - select description).SingleOrDefault(); + if (packageDescription == null) + { + return; + } - this.Bindings.Add("PackageDescription", packageDescription); + string packageSourceName = PackageSystemServices.GetPackageSourceNameByPackageId(packageDescription.Id, + InstallationInformationFacade.InstallationId, UserSettings.CultureInfo); - if (packageDescription != null) + var purchasableSubscriptions = packageDescription.AvailableInSubscriptions.Where(f => f.Purchasable).ToList(); + + bool showTrialInformation = false; + var licenses = GetSubscriptionLicenses(packageDescription.AvailableInSubscriptions); + + var validLicense = licenses.Where(l => !l.Expired) + .OrderByDescending(l => l.ExpirationDate) + .FirstOrDefault(); + + this.Bindings = new Dictionary + { + {BindingNames.PackageDescription, packageDescription}, + {BindingNames.DocumentTitle, GetDocumentTitle(packageDescription)}, + {BindingNames.AddOnServerSource, packageSourceName}, + {BindingNames.HasOwnPrice, packageDescription.PriceAmmount > 0}, + {BindingNames.PriceText, $"{packageDescription.PriceAmmount} {packageDescription.PriceCurrency}"}, + {BindingNames.IsInPurchasableSubscriptions, purchasableSubscriptions.Any()}, + {BindingNames.PurchasableSubscriptions, + string.Join(", \n", purchasableSubscriptions.Select(f => f.Name))}, + {BindingNames.ShowTrialInfo, packageDescription.IsTrial && validLicense == null}, + {BindingNames.ShowSubscriptionLicense, validLicense != null}, + {BindingNames.SubscriptionName, validLicense?.Name}, + {BindingNames.LicenseExpirationDate, validLicense?.ExpirationDate.ToLocalTime().ToString()} + }; + } + + private SubscriptionLicense[] GetSubscriptionLicenses(IEnumerable availableInSubscriptions) + { + return (from subscription in availableInSubscriptions + let license = PackageLicenseHelper.GetLicenseDefinition(subscription.Id) + where license != null + select new SubscriptionLicense + { + Name = subscription.Name, + ExpirationDate = license.Expires, + Expired = !license.Permanent && license.Expires < DateTime.Now + }).ToArray(); + } + + private string GetDocumentTitle(PackageDescription packageDescription) + { + // Valid package names: + // "Composite.Community.Versioning" + // "Composite C1 3.0" + string name = packageDescription.Name.Trim(); + + string documentTitle = name; + + if (name.Contains(".") && !name.EndsWith(".")) + { + string packageName = name.Substring(name.LastIndexOf('.') + 1); + string packageNamespace = name.Substring(0, name.LastIndexOf('.')); + + int temp; + if (!int.TryParse(packageName, out temp)) { - // Valid package names: - // "Composite.Community.Versioning" - // "Composite C1 3.0" - string name = packageDescription.Name.Trim(); - - string documentTitle = name; - - if (name.Contains(".") && !name.EndsWith(".")) - { - string packageName = name.Substring(name.LastIndexOf('.') + 1); - string packageNamespace = name.Substring(0, name.LastIndexOf('.')); - - int temp; - if (!int.TryParse(packageName, out temp)) - { - documentTitle = "{0} ({1})".FormatWith(packageName, packageNamespace); - } - } - - this.Bindings.Add("DocumentTitle", documentTitle); - this.Bindings.Add("AddOnServerSource", PackageSystemServices.GetPackageSourceNameByPackageId(packageDescription.Id, InstallationInformationFacade.InstallationId, UserSettings.CultureInfo)); - this.Bindings.Add("HasOwnPrice", packageDescription.PriceAmmount > 0); - this.Bindings.Add("PriceText", string.Format("{0} {1}", packageDescription.PriceAmmount, packageDescription.PriceCurrency)); - this.Bindings.Add("IsInPurchasableSubscriptions", packageDescription.AvailableInSubscriptions.Any(f => f.Purchasable)); - this.Bindings.Add("PurchasableSubscriptions", - string.Join(", \n", packageDescription.AvailableInSubscriptions.Where(f => f.Purchasable).Select(f => f.Name))); + documentTitle = $"{packageName} ({packageNamespace})"; } } - this.SetCustomToolbarDefinition(new FormDefinitionFileMarkupProvider(@"\Administrative\PackageElementProviderViewAvailablePackageInformationToolbar.xml")); + return documentTitle; } - private void installAddOnCodeActivity_Execute_ExecuteCode(object sender, EventArgs e) { - PackageElementProviderAvailablePackagesItemEntityToken castedToken = (PackageElementProviderAvailablePackagesItemEntityToken)this.EntityToken; + var castedToken = (PackageElementProviderAvailablePackagesItemEntityToken)this.EntityToken; PackageDescription packageDescription = (from description in PackageSystemServices.GetFilteredAllAvailablePackages() @@ -97,20 +163,18 @@ where description.Id.ToString() == castedToken.Id } else { - this.ShowMessage( - DialogType.Message, - "${Composite.Plugins.PackageElementProvider, ViewAvailableInformation.ShowError.MessageTitle}", - "${Composite.Plugins.PackageElementProvider, ViewAvailableInformation.ShowError.MessageMessage}"); + this.ShowMessage(DialogType.Message, + Texts.ViewAvailableInformation_ShowError_MessageTitle, + Texts.ViewAvailableInformation_ShowError_MessageMessage); } } private void viewCodeActivity_ShowMessage_ExecuteCode(object sender, EventArgs e) { - this.ShowMessage( - DialogType.Error, - "${Composite.Plugins.PackageElementProvider, ViewAvailableInformation.ShowServerError.MessageTitle}", - "${Composite.Plugins.PackageElementProvider, ViewAvailableInformation.ShowServerError.MessageMessage}"); + this.ShowMessage(DialogType.Error, + Texts.ViewAvailableInformation_ShowServerError_MessageTitle, + Texts.ViewAvailableInformation_ShowServerError_MessageMessage); } } } diff --git a/Composite.Workflows/Plugins/Elements/ElementProviders/PackageElementProvider/ViewInstalledPackageInfoWorkflow.cs b/Composite.Workflows/Plugins/Elements/ElementProviders/PackageElementProvider/ViewInstalledPackageInfoWorkflow.cs index b2f95256a7..b30ebd5f3e 100644 --- a/Composite.Workflows/Plugins/Elements/ElementProviders/PackageElementProvider/ViewInstalledPackageInfoWorkflow.cs +++ b/Composite.Workflows/Plugins/Elements/ElementProviders/PackageElementProvider/ViewInstalledPackageInfoWorkflow.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Composite.C1Console.Users; using Composite.Core.Configuration; @@ -14,77 +15,165 @@ namespace Composite.Plugins.Elements.ElementProviders.PackageElementProvider [AllowPersistingWorkflow(WorkflowPersistingType.Idle)] public sealed partial class ViewInstalledPackageInfoWorkflow : Composite.C1Console.Workflow.Activities.FormsWorkflow { + const string CustomToolbarDefinitionPath = @"\Administrative\PackageElementProviderViewInstalledPackageInformationToolbar.xml"; + public ViewInstalledPackageInfoWorkflow() { InitializeComponent(); } + private static class BindingNames + { + public const string DocumentTitle = nameof(DocumentTitle); + public const string InstalledPackageInformation = nameof(InstalledPackageInformation); + public const string InstallDate = nameof(InstallDate); + public const string IsTrial = nameof(IsTrial); + public const string TrialPurchaseUrl = nameof(TrialPurchaseUrl); + public const string ReadMoreUrl = nameof(ReadMoreUrl); + + public const string IsSubscription = nameof(IsSubscription); + public const string SubscriptionName = nameof(SubscriptionName); + public const string LicenseExpirationDate = nameof(LicenseExpirationDate); + + public const string ShowPurchaseThisButton = nameof(ShowPurchaseThisButton); + } + private class LicenseInformation + { + public bool IsSubscription { get; set; } + public bool IsTrial { get; set; } + public bool HasExpired { get; set; } + public string Name { get; set; } + public string PurchaseUrl { get; set; } + public DateTime ExpirationDate { get; set; } + } private void viewStateCodeActivity_Initialize_ExecuteCode(object sender, EventArgs e) { var castedToken = (PackageElementProviderInstalledPackageItemEntityToken)this.EntityToken; + Guid packageId = castedToken.PackageId; - if (!this.BindingExist("InstalledPackageInformation")) - { - InstalledPackageInformation installedAddOnInformation = - (from info in PackageManager.GetInstalledPackages() - where info.Id == castedToken.PackageId - select info).Single(); + if (castedToken.CanBeUninstalled) + { + this.SetCustomToolbarDefinition(new FormDefinitionFileMarkupProvider(CustomToolbarDefinitionPath)); + } - string name = installedAddOnInformation.Name; - string documentTitle = (name.Contains('.') && !name.EndsWith(".") ? - string.Format("{0} ({1})", name.Substring(name.LastIndexOf('.') + 1), name.Substring(0,name.LastIndexOf('.'))) : - name); + if (this.BindingExist(BindingNames.InstalledPackageInformation)) + { + return; + } - this.Bindings.Add("DocumentTitle", documentTitle); - this.Bindings.Add("InstalledPackageInformation", installedAddOnInformation); - this.Bindings.Add("InstallDate", installedAddOnInformation.InstallDate.ToLocalTime().ToString()); + InstalledPackageInformation installedAddOnInformation = + PackageManager.GetInstalledPackages().Single(info => info.Id == packageId); - PackageLicenseDefinition licenseInfo = PackageLicenseHelper.GetLicenseDefinition(installedAddOnInformation.Id); - bool isTrial = (licenseInfo != null && !licenseInfo.Permanent); - this.Bindings.Add("IsTrial", isTrial); + string name = installedAddOnInformation.Name; + string documentTitle = GetDocumentTitle(name); + DateTime installationDate = installedAddOnInformation.InstallDate.ToLocalTime(); - this.Bindings.Add("ShowPurchaseThisButton", isTrial && !string.IsNullOrWhiteSpace(licenseInfo.PurchaseUrl)); + this.Bindings = new Dictionary + { + {BindingNames.DocumentTitle, documentTitle}, + {BindingNames.InstalledPackageInformation, installedAddOnInformation}, + {BindingNames.InstallDate, installationDate.ToString()} + }; - PackageDescription packageDescription = null; - try - { - Guid packageId = new Guid(castedToken.Id); + PackageDescription packageDescription = null; + try + { + var allPackages = PackageServerFacade.GetAllPackageDescriptions(InstallationInformationFacade.InstallationId, UserSettings.CultureInfo); - var allPackages = PackageServerFacade.GetAllPackageDescriptions(InstallationInformationFacade.InstallationId, - UserSettings.CultureInfo); + packageDescription = allPackages.FirstOrDefault(p => p.Id == packageId); + } + catch + { + } - packageDescription = allPackages.FirstOrDefault(p => p.Id == packageId); - } - catch - { - } + if (!string.IsNullOrEmpty(packageDescription?.ReadMoreUrl)) + { + this.Bindings[BindingNames.ReadMoreUrl] = packageDescription.ReadMoreUrl; + } + + var licenses = GetRelatedLicenses(packageId, packageDescription); + var actualLicense = licenses.OrderBy(l => l.HasExpired).ThenByDescending(l => l.IsSubscription).FirstOrDefault(); + + if (actualLicense != null) + { + Bindings[BindingNames.LicenseExpirationDate] = actualLicense.ExpirationDate.ToLocalTime().ToString(); + } + + bool isTrial = actualLicense != null && actualLicense.IsTrial; + this.Bindings[BindingNames.IsTrial] = isTrial; - if (packageDescription != null && !string.IsNullOrEmpty(packageDescription.ReadMoreUrl)) + + bool showPurchaseButton = false; + + if (isTrial && !string.IsNullOrWhiteSpace(actualLicense.PurchaseUrl)) + { + string url = actualLicense.PurchaseUrl; + this.Bindings[BindingNames.TrialPurchaseUrl] = url; + + showPurchaseButton = true; + } + this.Bindings[BindingNames.ShowPurchaseThisButton] = showPurchaseButton; + + bool isSubscription = actualLicense != null && actualLicense.IsSubscription; + this.Bindings[BindingNames.IsSubscription] = isSubscription; + if (isSubscription) + { + Bindings[BindingNames.SubscriptionName] = actualLicense.Name; + } + } + + private LicenseInformation[] GetRelatedLicenses(Guid packageId, PackageDescription packageDescription) + { + var result = new List(); + + var licenseInfo = PackageLicenseHelper.GetLicenseDefinition(packageId); + if (licenseInfo != null) + { + result.Add(new LicenseInformation { - this.Bindings.Add("ReadMoreUrl", packageDescription.ReadMoreUrl); - } + Name = licenseInfo.ProductName, + IsSubscription = false, + ExpirationDate = licenseInfo.Expires, + HasExpired = !licenseInfo.Permanent && licenseInfo.Expires < DateTime.Now, + IsTrial = !licenseInfo.Permanent, + PurchaseUrl = licenseInfo.PurchaseUrl + }); + } - if (isTrial) + if (packageDescription != null) + { + foreach (var subscription in packageDescription.AvailableInSubscriptions) { - this.Bindings.Add("TrialExpire", licenseInfo.Expires.ToLocalTime().ToString()); - if (!string.IsNullOrWhiteSpace(licenseInfo.PurchaseUrl)) + var subscriptionLicense = PackageLicenseHelper.GetLicenseDefinition(subscription.Id); + if (subscriptionLicense == null) continue; + + result.Add(new LicenseInformation { - //string url = string.Format("{0}{1}installationId={2}", licenseInfo.PurchaseUrl, (licenseInfo.PurchaseUrl.Contains('?') ? "&" : "?"), Composite.Core.Configuration.InstallationInformationFacade.InstallationId); - string url = licenseInfo.PurchaseUrl; - this.Bindings.Add("TrialPurchaseUrl", url); - } - + Name = subscription.Name, + IsSubscription = true, + ExpirationDate = subscriptionLicense.Expires, + HasExpired = !subscriptionLicense.Permanent && subscriptionLicense.Expires < DateTime.Now, + IsTrial = false, + PurchaseUrl = subscription.DetailsUrl, + }); } } - if (castedToken.CanBeUninstalled) + return result.ToArray(); + } + + private string GetDocumentTitle(string name) + { + if (!name.Contains('.') || name.EndsWith(".")) { - this.SetCustomToolbarDefinition(new FormDefinitionFileMarkupProvider(@"\Administrative\PackageElementProviderViewInstalledPackageInformationToolbar.xml")); + return name; } - } + int nameOffset = name.LastIndexOf('.'); + return $"{name.Substring(nameOffset + 1)} ({name.Substring(0, nameOffset)})"; + } private void uninstallCodeActivity_ExecuteCode(object sender, EventArgs e) @@ -98,14 +187,10 @@ private void uninstallCodeActivity_ExecuteCode(object sender, EventArgs e) if (installedAddOnInformation != null) { - if (installedAddOnInformation.IsLocalInstalled) - { - this.ExecuteWorklow(this.EntityToken, typeof(UninstallLocalPackageWorkflow)); - } - else - { - this.ExecuteWorklow(this.EntityToken, typeof(UninstallRemotePackageWorkflow)); - } + this.ExecuteWorklow(this.EntityToken, + installedAddOnInformation.IsLocalInstalled + ? typeof (UninstallLocalPackageWorkflow) + : typeof (UninstallRemotePackageWorkflow)); } else { diff --git a/Composite.Workflows/Plugins/Elements/ElementProviders/PageTypeElementProvider/AddNewPageTypeWorkflow.cs b/Composite.Workflows/Plugins/Elements/ElementProviders/PageTypeElementProvider/AddNewPageTypeWorkflow.cs index f1eaf3abb1..842586c9c7 100644 --- a/Composite.Workflows/Plugins/Elements/ElementProviders/PageTypeElementProvider/AddNewPageTypeWorkflow.cs +++ b/Composite.Workflows/Plugins/Elements/ElementProviders/PageTypeElementProvider/AddNewPageTypeWorkflow.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Composite.Data.Types; using Composite.Data; using System.Workflow.Activities; @@ -24,7 +23,7 @@ private void initializeCodeActivity_UpdateBindings_ExecuteCode(object sender, Ev pageType.Id = Guid.NewGuid(); pageType.Available = true; pageType.PresetMenuTitle = true; - pageType.HomepageRelation = PageTypeHomepageRelation.NoRestriction.ToPageTypeHomepageRelationString(); + pageType.HomepageRelation = nameof(PageTypeHomepageRelation.NoRestriction); pageType.DefaultTemplateId = Guid.Empty; pageType.DefaultChildPageType = Guid.Empty; diff --git a/Composite.Workflows/Plugins/Elements/ElementProviders/PageTypeElementProvider/EditPageTypeWorkflow.cs b/Composite.Workflows/Plugins/Elements/ElementProviders/PageTypeElementProvider/EditPageTypeWorkflow.cs index 6475ab435a..323fd18ec9 100644 --- a/Composite.Workflows/Plugins/Elements/ElementProviders/PageTypeElementProvider/EditPageTypeWorkflow.cs +++ b/Composite.Workflows/Plugins/Elements/ElementProviders/PageTypeElementProvider/EditPageTypeWorkflow.cs @@ -73,7 +73,7 @@ private void initializeCodeActivity_UpdateBindings_ExecuteCode(object sender, Ev Func > getOption = relation => new KeyValuePair( - relation.ToPageTypeHomepageRelationString(), + relation.ToString(), GetText($"PageType.EditPageTypeWorkflow.HomepageRelationKeySelector.{relation}Label")); this.Bindings.Add("HomepageRelationOptions", new List> { @@ -88,14 +88,14 @@ private void initializeCodeActivity_UpdateBindings_ExecuteCode(object sender, Ev OrderBy(f => f.Title). ToList(f => new KeyValuePair(f.Id, f.Title)); - var defaultPageTempatesOptions = new List> + var defaultPageTemplateOptions = new List> { new KeyValuePair(Guid.Empty, Texts.PageType_EditPageTypeWorkflow_DefaultPageTemplateKeySelector_NoneSelectedLabel) }; - defaultPageTempatesOptions.AddRange(pageTemplates); + defaultPageTemplateOptions.AddRange(pageTemplates); - this.Bindings.Add("DefaultTemplateOptions", defaultPageTempatesOptions); + this.Bindings.Add("DefaultTemplateOptions", defaultPageTemplateOptions); this.Bindings.Add("TemplateRestrictionOptions", pageTemplates); @@ -180,7 +180,8 @@ private void editCodeActivity_ValidateBindings(object sender, ConditionalEventAr return; } - if ((pageType.HomepageRelation == PageTypeHomepageRelation.OnlyHomePages.ToPageTypeHomepageRelationString()) && (selectedPageTypeParentRestrictions.Count > 0)) + if (pageType.HomepageRelation == nameof(PageTypeHomepageRelation.OnlyHomePages) + && selectedPageTypeParentRestrictions.Count > 0) { this.ShowFieldMessage("PageType.HomepageRelation", Texts.PageType_EditPageTypeWorkflow_ValidationError_HomepageRelationConflictsWithParentRestrictions); SetSaveStatus(false); diff --git a/Composite/C1Console/Elements/UrlToEntityTokenFacade.cs b/Composite/C1Console/Elements/UrlToEntityTokenFacade.cs index 089b22f973..b0ef80fc03 100644 --- a/Composite/C1Console/Elements/UrlToEntityTokenFacade.cs +++ b/Composite/C1Console/Elements/UrlToEntityTokenFacade.cs @@ -68,9 +68,10 @@ private static string ProcessUrlWithServiceMappers(string url, EntityToken entit public static EntityToken TryGetEntityToken(string url) { var originalUrl = url; - url = _serviceMappers.Select(sm => sm.CleanUrl(ref url)).Last(); + _serviceMappers.Select(sm => sm.CleanUrl(ref url)); var baseEntityToken = _mappers.Select(mapper => mapper.TryGetEntityToken(url)).FirstOrDefault(entityToken => entityToken != null); - return _serviceMappers.Select(sm => sm.TryGetEntityToken(originalUrl, ref baseEntityToken)).Last(); + _serviceMappers.Select(sm => sm.TryGetEntityToken(originalUrl, ref baseEntityToken)); + return baseEntityToken; } /// diff --git a/Composite/C1Console/Trees/Foundation/AttachmentPoints/DynamicDataItemAttachmentPoint.cs b/Composite/C1Console/Trees/Foundation/AttachmentPoints/DynamicDataItemAttachmentPoint.cs index f93b3d8ac7..e2cb0ad510 100644 --- a/Composite/C1Console/Trees/Foundation/AttachmentPoints/DynamicDataItemAttachmentPoint.cs +++ b/Composite/C1Console/Trees/Foundation/AttachmentPoints/DynamicDataItemAttachmentPoint.cs @@ -2,11 +2,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; using Composite.Data; using Composite.C1Console.Security; using Composite.Data.ProcessControlled; - namespace Composite.C1Console.Trees.Foundation.AttachmentPoints { /// @@ -69,7 +69,7 @@ public override IEnumerable GetEntityTokens(EntityToken childEntity private EntityToken GetEntityTokensImpl() { - IData data = DataFacade.TryGetDataByUniqueKey(this.InterfaceType, KeyValue); + IData data = DataFacade.TryGetDataVersionsByUniqueKey(this.InterfaceType, KeyValue).FirstOrDefault(); return data == null ? null : data.GetDataEntityToken(); } diff --git a/Composite/Composite.csproj b/Composite/Composite.csproj index 37fe2b4819..c1cc186267 100644 --- a/Composite/Composite.csproj +++ b/Composite/Composite.csproj @@ -1448,7 +1448,6 @@ - @@ -2534,4 +2533,4 @@ copy "$(TargetPath)" "$(ProjectDir)..\bin\" copy "$(TargetPath)" "$(ProjectDir)..\Website\bin\" - + \ No newline at end of file diff --git a/Composite/Core/Application/AppDomainLocker.cs b/Composite/Core/Application/AppDomainLocker.cs index 7ce9c52974..2f9a2f2d41 100644 --- a/Composite/Core/Application/AppDomainLocker.cs +++ b/Composite/Core/Application/AppDomainLocker.cs @@ -1,10 +1,5 @@ using System; -using System.Threading; -using Composite.Core.Logging; -using Composite.Core.Types; using System.Diagnostics; -using System.IO; -using Composite.Core.IO; namespace Composite.Core.Application @@ -15,9 +10,9 @@ namespace Composite.Core.Application /// internal static class AppDomainLocker { - private static readonly SystemGlobalSemaphore _systemGlobalEventWaitHandle = new SystemGlobalSemaphore(EventWaitHandleId); - private static int _numberOfLocksAcquried = 0; - private static readonly object _numberOfLocksAquriedLock = new object(); + private static readonly SystemGlobalSemaphore _semaphore = new SystemGlobalSemaphore(EventWaitHandleId); + private static int _numberOfLocksAcquired; + private static readonly object _numberOfLocksAcquiredLock = new object(); private const string _verboseLogEntryTitle = "RGB(205, 92, 92)AppDomainLocker"; @@ -47,14 +42,7 @@ public static IDisposable NewLock(bool verbose = false) /// /// Returns true if the calling app domain has the lock. /// - public static bool CurrentAppDomainHasLock - { - get - { - return IsCurrentAppDomainLockingAppDomain(); - } - } - + public static bool CurrentAppDomainHasLock => IsCurrentAppDomainLockingAppDomain(); /// @@ -64,46 +52,53 @@ public static bool CurrentAppDomainHasLock /// /// Acquire lock timeout in milliseconds. /// If this is false, no logging will be done. - /// True if the lock was acuired. + /// True if the lock was acquired. public static bool AcquireLock(int timeout = 30000, bool verbose = true) { - if (RuntimeInformation.AppDomainLockingDisabled) return true; + if (RuntimeInformation.AppDomainLockingDisabled) return true; - lock (_numberOfLocksAquriedLock) + + var appDomainId = AppDomain.CurrentDomain.Id; + var processId = Process.GetCurrentProcess().Id; + + lock (_numberOfLocksAcquiredLock) { if (!IsCurrentAppDomainLockingAppDomain()) { - if (verbose) Log.LogVerbose(_verboseLogEntryTitle, string.Format("The AppDomain '{0}', Process '{1}': Are going to acquire the system wide lock with the key '{2}'...", AppDomain.CurrentDomain.Id, Process.GetCurrentProcess().Id, _systemGlobalEventWaitHandle.Id)); + if (verbose) Log.LogVerbose(_verboseLogEntryTitle, + $"The AppDomain '{appDomainId}', Process '{processId}': Are going to acquire the system wide lock with the key '{_semaphore.Id}'..."); - bool entered = _systemGlobalEventWaitHandle.Enter(timeout); + bool entered = _semaphore.Enter(timeout); if (!entered) { - string message = string.Format("The AppDomain '{0}', Process '{1}': Failed to aqruie the system wide lock with the key '{2}' within the timeout period of {3} ms!!!", AppDomain.CurrentDomain.Id, Process.GetCurrentProcess().Id, _systemGlobalEventWaitHandle.Id, timeout); + string message = $"The AppDomain '{appDomainId}', Process '{processId}': Failed to acquire the system wide lock with the key '{_semaphore.Id}' within the timeout period of {timeout} ms!!!"; Log.LogWarning(_warningLogEntryTitle, message); //throw new WaitHandleCannotBeOpenedException(message); return false; } - if (verbose) Log.LogVerbose(_verboseLogEntryTitle, string.Format("The AppDomain '{0}', Process '{1}': Acquired the system wide lock with the key '{2}'!", AppDomain.CurrentDomain.Id, Process.GetCurrentProcess().Id, _systemGlobalEventWaitHandle.Id)); + if (verbose) Log.LogVerbose(_verboseLogEntryTitle, + $"The AppDomain '{appDomainId}', Process '{processId}': Acquired the system wide lock with the key '{_semaphore.Id}'!"); } else { - if (verbose) Log.LogVerbose(_verboseLogEntryTitle, string.Format("The AppDomain '{0}', Process '{1}': Aquiring the lock that it is allready holding the system wide lock with the key '{2}' (Number of inner locks {3})", AppDomain.CurrentDomain.Id, Process.GetCurrentProcess().Id, _systemGlobalEventWaitHandle.Id, _numberOfLocksAcquried + 1)); + if (verbose) Log.LogVerbose(_verboseLogEntryTitle, + $"The AppDomain '{appDomainId}', Process '{processId}': Acquiring the lock that it is already holding the system wide lock with the key '{_semaphore.Id}' (Number of inner locks {_numberOfLocksAcquired + 1})"); } - _numberOfLocksAcquried++; + _numberOfLocksAcquired++; return true; - } + } } /// /// Releases the acquired system wide lock. - /// If the same thread has aqired the lock more than once, only the last call to this method + /// If the same thread has acquired the lock more than once, only the last call to this method /// from that thread will release the lock. /// /// @@ -113,34 +108,42 @@ public static bool ReleaseLock(bool verbose = true, bool forceRelease = false) { if (RuntimeInformation.AppDomainLockingDisabled) return true; - lock (_numberOfLocksAquriedLock) + var appDomainId = AppDomain.CurrentDomain.Id; + var processId = Process.GetCurrentProcess().Id; + + lock (_numberOfLocksAcquiredLock) { if (!forceRelease && IsAllReleased()) { - Log.LogWarning(_warningLogEntryTitle, string.Format("The AppDomain '{0}', Process '{1}': Is trying to release a system wide lock with the key '{2}' that it does not hold! Release ignored!", AppDomain.CurrentDomain.Id, Process.GetCurrentProcess().Id, _systemGlobalEventWaitHandle.Id)); + Log.LogWarning(_warningLogEntryTitle, + $"The AppDomain '{appDomainId}', Process '{processId}': Is trying to release a system wide lock with the key '{_semaphore.Id}' that it does not hold! Release ignored!"); return true; } - else if (forceRelease || IsLastReleaseForLockHoldingAppDomain()) + + if (forceRelease || IsLastReleaseForLockHoldingAppDomain()) { - if (verbose) Log.LogVerbose(_verboseLogEntryTitle, string.Format("The AppDomain '{0}', Process '{1}': Are going to release the system wide lock with the key '{2}' (force: {3})...", AppDomain.CurrentDomain.Id, Process.GetCurrentProcess().Id, _systemGlobalEventWaitHandle.Id, forceRelease)); + if (verbose) Log.LogVerbose(_verboseLogEntryTitle, + $"The AppDomain '{appDomainId}', Process '{processId}': Are going to release the system wide lock with the key '{_semaphore.Id}' (force: {forceRelease})..."); try { - _systemGlobalEventWaitHandle.Leave(); + _semaphore.Leave(); } catch(Exception) { return false; } - if (verbose) Log.LogVerbose(_verboseLogEntryTitle, string.Format("The AppDomain '{0}', Process '{1}': Released the system wide lock with the key '{2}' (force: {3})...", AppDomain.CurrentDomain.Id, Process.GetCurrentProcess().Id, _systemGlobalEventWaitHandle.Id, forceRelease)); + if (verbose) Log.LogVerbose(_verboseLogEntryTitle, + $"The AppDomain '{appDomainId}', Process '{processId}': Released the system wide lock with the key '{_semaphore.Id}' (force: {forceRelease})..."); } else { - if (verbose) Log.LogVerbose(_verboseLogEntryTitle, string.Format("The AppDomain '{0}', Process '{1}': Releasing a lock it has aqruired more than once with the key '{2}'. Lock not released. (Number of inner locks {3})", AppDomain.CurrentDomain.Id, Process.GetCurrentProcess().Id, _systemGlobalEventWaitHandle.Id, _numberOfLocksAcquried - 1)); + if (verbose) Log.LogVerbose(_verboseLogEntryTitle, + $"The AppDomain '{appDomainId}', Process '{processId}': Releasing a lock it has aqruired more than once with the key '{_semaphore.Id}'. Lock not released. (Number of inner locks {_numberOfLocksAcquired - 1})"); } - if (!forceRelease) _numberOfLocksAcquried--; + if (!forceRelease) _numberOfLocksAcquired--; return true; } @@ -152,25 +155,14 @@ public static bool ReleaseLock(bool verbose = true, bool forceRelease = false) /// /// Used to name the EventWaitHandle, making it a system wide EventWaitHandle /// - private static string EventWaitHandleId - { - get - { - return RuntimeInformation.UniqueInstanceName; - } - } - + private static string EventWaitHandleId => RuntimeInformation.UniqueInstanceName; /// /// Returns true if the current thread is the thread holding the lock /// /// - private static bool IsCurrentAppDomainLockingAppDomain() - { - return _numberOfLocksAcquried > 0; - } - + private static bool IsCurrentAppDomainLockingAppDomain() => _numberOfLocksAcquired > 0; @@ -178,10 +170,7 @@ private static bool IsCurrentAppDomainLockingAppDomain() /// Returns true if there will be no more releases for the lock holding thread. /// /// - private static bool IsLastReleaseForLockHoldingAppDomain() - { - return _numberOfLocksAcquried == 1; - } + private static bool IsLastReleaseForLockHoldingAppDomain() => _numberOfLocksAcquired == 1; @@ -189,10 +178,8 @@ private static bool IsLastReleaseForLockHoldingAppDomain() /// Returns true if all locks have been released. /// /// - private static bool IsAllReleased() - { - return _numberOfLocksAcquried == 0; - } + private static bool IsAllReleased() => _numberOfLocksAcquired == 0; + /// @@ -200,7 +187,7 @@ private static bool IsAllReleased() /// private class DisposableLock : IDisposable { - private bool _disposed = false; + private bool _disposed; private readonly bool _verbose; public DisposableLock(bool verbose = true) @@ -218,7 +205,7 @@ public void Dispose() - protected virtual void Dispose(bool disposing) + void Dispose(bool disposing) { if (!disposing || _disposed) return; diff --git a/Composite/Core/Application/SystemGlobalSemaphore.cs b/Composite/Core/Application/SystemGlobalSemaphore.cs index de88423227..a2ed871f74 100644 --- a/Composite/Core/Application/SystemGlobalSemaphore.cs +++ b/Composite/Core/Application/SystemGlobalSemaphore.cs @@ -41,7 +41,8 @@ public bool Enter(int timeout, bool throwOnTimeout = false) { bool entered = _eventWaitHandle.WaitOne(timeout); - if (!entered && throwOnTimeout) throw new TimeoutException(string.Format("Failed to obtain the system global semaphore with id '{0}'", _id)); + if (!entered && throwOnTimeout) throw new TimeoutException( + $"Failed to obtain the system global semaphore with id '{_id}'"); return entered; } @@ -60,12 +61,6 @@ public void Leave() /// /// The used id used for naming the semaphore. /// - public string Id - { - get - { - return _id; - } - } + public string Id => _id; } } diff --git a/Composite/Core/Extensions/IQueryableExtensionMethods.cs b/Composite/Core/Extensions/IQueryableExtensionMethods.cs index 64a18b7853..d0d2831864 100644 --- a/Composite/Core/Extensions/IQueryableExtensionMethods.cs +++ b/Composite/Core/Extensions/IQueryableExtensionMethods.cs @@ -47,12 +47,7 @@ public static bool IsEnumerableQuery(this IQueryable query) return (query as DataFacadeQueryable).IsEnumerableQuery; } - if (query is CachingQueryable) - { - return true; - } - - return query.GetType().GetGenericTypeDefinition() == typeof(EnumerableQuery<>); + return query is CachingQueryable || query is EnumerableQuery; } diff --git a/Composite/Core/PackageSystem/PackageDescription.cs b/Composite/Core/PackageSystem/PackageDescription.cs index 78e6d83cb4..6e603f3927 100644 --- a/Composite/Core/PackageSystem/PackageDescription.cs +++ b/Composite/Core/PackageSystem/PackageDescription.cs @@ -4,8 +4,6 @@ namespace Composite.Core.PackageSystem { - /// - /// /// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [SerializerHandler(typeof(PropertySerializerHandler))] @@ -82,6 +80,8 @@ public sealed class PackageDescription /// public sealed class Subscription { + /// + public Guid Id { get; set; } /// public string Name { get; set; } /// diff --git a/Composite/Core/PackageSystem/PackageServerFacadeImpl.cs b/Composite/Core/PackageSystem/PackageServerFacadeImpl.cs index 58f454538f..ca4d4f5f08 100644 --- a/Composite/Core/PackageSystem/PackageServerFacadeImpl.cs +++ b/Composite/Core/PackageSystem/PackageServerFacadeImpl.cs @@ -12,6 +12,8 @@ namespace Composite.Core.PackageSystem { internal sealed class PackageServerFacadeImpl : IPackageServerFacade { + const string LogTitle = nameof(PackageServerFacade); + private readonly PackageServerFacadeImplCache _packageServerFacadeImplCache = new PackageServerFacadeImplCache(); @@ -19,8 +21,11 @@ public ServerUrlValidationResult ValidateServerUrl(string packageServerUrl) { try { - var basicHttpBinding = new BasicHttpBinding { MaxReceivedMessageSize = int.MaxValue }; - basicHttpBinding.Security.Mode = BasicHttpSecurityMode.Transport; + var basicHttpBinding = new BasicHttpBinding + { + MaxReceivedMessageSize = int.MaxValue, + Security = {Mode = BasicHttpSecurityMode.Transport} + }; var client = new PackagesSoapClient(basicHttpBinding, new EndpointAddress($"https://{packageServerUrl}")); @@ -62,20 +67,27 @@ public IEnumerable GetPackageDescriptions(string packageServ } catch (Exception ex) { - Log.LogError("PackageServerFacade", ex); + Log.LogError(LogTitle, ex); } packageDescriptions = new List(); if (packageDescriptors != null) { - foreach (PackageDescriptor packageDescriptor in packageDescriptors) + foreach (var packageDescriptor in packageDescriptors) { if (ValidatePackageDescriptor(packageDescriptor)) { var subscriptionList = new List(); if (packageDescriptor.Subscriptions !=null) { - subscriptionList = packageDescriptor.Subscriptions.Select(f => new Subscription { Name = f.Name, DetailsUrl = f.DetailsUrl, Purchasable = f.Purchasable }).ToList(); + subscriptionList = packageDescriptor.Subscriptions.Select( + f => new Subscription + { + Id = f.Id, + Name = f.Name, + DetailsUrl = f.DetailsUrl, + Purchasable = f.Purchasable + }).ToList(); } packageDescriptions.Add(new PackageDescription @@ -125,7 +137,7 @@ public string GetEulaText(string packageServerUrl, Guid eulaId, CultureInfo user public Stream GetInstallFileStream(string packageFileDownloadUrl) { - Log.LogVerbose("PackageServerFacade", $"Downloading file: {packageFileDownloadUrl}"); + Log.LogVerbose(LogTitle, $"Downloading file: {packageFileDownloadUrl}"); var client = new System.Net.WebClient(); return client.OpenRead(packageFileDownloadUrl); @@ -172,7 +184,7 @@ private bool ValidatePackageDescriptor(PackageDescriptor packageDescriptor) string newVersion; if (!VersionStringHelper.ValidateVersion(packageDescriptor.PackageVersion, out newVersion)) { - Log.LogWarning("PackageServerFacade", + Log.LogWarning(LogTitle, $"The package '{packageDescriptor.Name}' ({packageDescriptor.Id}) did not validate and is skipped"); return false; } @@ -181,7 +193,7 @@ private bool ValidatePackageDescriptor(PackageDescriptor packageDescriptor) if (!VersionStringHelper.ValidateVersion(packageDescriptor.MinCompositeVersionSupported, out newVersion)) { - Log.LogWarning("PackageServerFacade", + Log.LogWarning(LogTitle, $"The package '{packageDescriptor.Name}' ({packageDescriptor.Id}) did not validate and is skipped"); return false; } @@ -190,7 +202,7 @@ private bool ValidatePackageDescriptor(PackageDescriptor packageDescriptor) if (!VersionStringHelper.ValidateVersion(packageDescriptor.MaxCompositeVersionSupported, out newVersion)) { - Log.LogWarning("PackageServerFacade", + Log.LogWarning(LogTitle, $"The package '{packageDescriptor.Name}' ({packageDescriptor.Id}) did not validate and is skipped"); return false; } diff --git a/Composite/Core/Threading/ThreadDataManager.cs b/Composite/Core/Threading/ThreadDataManager.cs index 526ea1ce70..47d132e9d1 100644 --- a/Composite/Core/Threading/ThreadDataManager.cs +++ b/Composite/Core/Threading/ThreadDataManager.cs @@ -141,14 +141,6 @@ public static IDisposable EnsureInitialize() /// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public static void InitializeThroughHttpContext() - { - InitializeThroughHttpContext(false); - } - - - /// - [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public static void InitializeThroughHttpContext(bool forceUserValidation) { var httpContext = HttpContext.Current; Verify.IsNotNull(httpContext, "This can only be called from a thread started with a current http context"); @@ -164,6 +156,15 @@ public static void InitializeThroughHttpContext(bool forceUserValidation) var threadData = new ThreadDataManagerData(); httpContext.Items[c_HttpContextItemsId] = threadData; } + } + + + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [Obsolete("Use the overload taking no parameters instead")] + public static void InitializeThroughHttpContext(bool forceUserValidation) + { + InitializeThroughHttpContext(); if (forceUserValidation) { diff --git a/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs b/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs index 04d1027266..78804628f0 100644 --- a/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs +++ b/Composite/Core/WebClient/ApplicationLevelEventHandlers.cs @@ -163,7 +163,7 @@ public static void Application_BeginRequest(object sender, EventArgs e) { var context = (sender as HttpApplication).Context; - ThreadDataManager.InitializeThroughHttpContext(true); + ThreadDataManager.InitializeThroughHttpContext(); ServiceLocator.CreateRequestServicesScope(context); diff --git a/Composite/Core/WebClient/HttpModules/AdministrativeAuthorizationHttpModule.cs b/Composite/Core/WebClient/HttpModules/AdministrativeAuthorizationHttpModule.cs index 7132541119..98131037c6 100644 --- a/Composite/Core/WebClient/HttpModules/AdministrativeAuthorizationHttpModule.cs +++ b/Composite/Core/WebClient/HttpModules/AdministrativeAuthorizationHttpModule.cs @@ -25,7 +25,7 @@ internal class AdministrativeAuthorizationHttpModule : IHttpModule private static string _adminRootPath; private static string _servicesPath; private static string _loginPagePath; - private static object _lock = new object(); + private static readonly object _lock = new object(); private static bool _allowC1ConsoleRequests; private static bool _forceHttps = true; private static bool _allowFallbackToHttp = true; @@ -54,7 +54,7 @@ static AdministrativeAuthorizationHttpModule() { _allowC1ConsoleRequests = false; string adminRootPath = HostingEnvironment.MapPath(UrlUtils.AdminRootPath); - bool adminFolderExists = false; + bool adminFolderExists; try { @@ -62,7 +62,7 @@ static AdministrativeAuthorizationHttpModule() } catch (Exception) { - // we fail misserably here when write permissions are missing, also the default exception is exceptionally crappy + // we fail miserably here when write permissions are missing, also the default exception is exceptionally crappy throw new IOException("Please ensure that the web application process has permissions to read and modify the entire web app directory structure."); } @@ -83,12 +83,12 @@ public void Dispose() public void Init(HttpApplication context) { - context.AuthenticateRequest += context_AuthenticateRequest; + context.AuthorizeRequest += context_AuthorizeRequest; } - private bool AlwaysAllowUnsecured(string requestPath) + private static bool AlwaysAllowUnsecured(string requestPath) { string fileName = Path.GetFileName(requestPath); return fileName.StartsWith("unsecure") || @@ -97,12 +97,11 @@ private bool AlwaysAllowUnsecured(string requestPath) fileName == "button.png" || fileName == "startcomposite.png" || fileName == "default.css.aspx"; - } - private void context_AuthenticateRequest(object sender, EventArgs e) + private void context_AuthorizeRequest(object sender, EventArgs e) { var application = (HttpApplication)sender; HttpContext context = application.Context; @@ -124,12 +123,15 @@ private void context_AuthenticateRequest(object sender, EventArgs e) return; } + var url = context.Request.Url; + // https check - if (_forceHttps && context.Request.Url.Scheme != "https") + if (_forceHttps && url.Scheme != "https") { - if (!AlwaysAllowUnsecured(context.Request.Url.LocalPath) && !UserOptedOutOfHttps(context)) + if (!AlwaysAllowUnsecured(url.LocalPath) && !UserOptedOutOfHttps(context)) { - context.Response.Redirect(string.Format("{0}?fallback={1}&httpsport={2}", unsecureRedirectRelativePath, _allowFallbackToHttp.ToString().ToLower(), _customHttpsPortNumber)); + context.Response.Redirect( + $"{unsecureRedirectRelativePath}?fallback={_allowFallbackToHttp.ToString().ToLower()}&httpsport={_customHttpsPortNumber}"); } } @@ -145,7 +147,7 @@ private void context_AuthenticateRequest(object sender, EventArgs e) } Log.LogWarning("Authorization", "DENIED {0} access to {1}", context.Request.UserHostAddress, currentPath); - string redirectUrl =$"{_loginPagePath}?ReturnUrl={HttpUtility.UrlEncode(context.Request.Url.PathAndQuery, Encoding.UTF8)}"; + string redirectUrl =$"{_loginPagePath}?ReturnUrl={HttpUtility.UrlEncode(url.PathAndQuery, Encoding.UTF8)}"; context.Response.Redirect(redirectUrl, true); return; @@ -174,7 +176,7 @@ private bool UserOptedOutOfHttps(HttpContext context) } HttpCookie cookie = context.Request.Cookies["avoidc1consolehttps"]; - return cookie != null && cookie.Value == "true"; + return cookie?.Value == "true"; } @@ -244,7 +246,7 @@ private static void LoadAllowedPaths() if (!C1File.Exists(webauthorizationConfigPath)) { - Log.LogInformation("AdministrativeAuthorizationHttpModule ", "File '{0}' not found - all access to the ~/Composite folder will be blocked", webauthorizationConfigPath); + Log.LogInformation(nameof(AdministrativeAuthorizationHttpModule), "File '{0}' not found - all access to the ~/Composite folder will be blocked", webauthorizationConfigPath); return; } diff --git a/Composite/Core/WebClient/HttpModules/AdministrativeCultureSetterHttpModule.cs b/Composite/Core/WebClient/HttpModules/AdministrativeCultureSetterHttpModule.cs index 39ce78a75c..ad292d10b1 100644 --- a/Composite/Core/WebClient/HttpModules/AdministrativeCultureSetterHttpModule.cs +++ b/Composite/Core/WebClient/HttpModules/AdministrativeCultureSetterHttpModule.cs @@ -1,7 +1,10 @@ using System; using System.Globalization; +using System.Threading; using System.Web; using Composite.C1Console.Security; +using Composite.C1Console.Users; +using Composite.Core.Configuration; namespace Composite.Core.WebClient.HttpModules { @@ -9,11 +12,11 @@ internal class AdministrativeCultureSetterHttpModule : IHttpModule { public void Init(HttpApplication context) { - context.AuthenticateRequest += context_AuthenticateRequest; + context.AuthorizeRequest += context_AuthorizeRequest; } - void context_AuthenticateRequest(object sender, EventArgs e) + void context_AuthorizeRequest(object sender, EventArgs e) { HttpApplication application = (HttpApplication)sender; HttpContext context = application.Context; @@ -24,13 +27,15 @@ void context_AuthenticateRequest(object sender, EventArgs e) { if (UserValidationFacade.IsLoggedIn()) { - System.Threading.Thread.CurrentThread.CurrentCulture = C1Console.Users.UserSettings.CultureInfo; - System.Threading.Thread.CurrentThread.CurrentUICulture = C1Console.Users.UserSettings.C1ConsoleUiLanguage; + Thread.CurrentThread.CurrentCulture = UserSettings.CultureInfo; + Thread.CurrentThread.CurrentUICulture = UserSettings.C1ConsoleUiLanguage; } else { - System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(Composite.Core.Configuration.GlobalSettingsFacade.DefaultCultureName); - System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(Composite.Core.Configuration.GlobalSettingsFacade.DefaultCultureName); + var defaultCulture = CultureInfo.CreateSpecificCulture(GlobalSettingsFacade.DefaultCultureName); + + Thread.CurrentThread.CurrentCulture = defaultCulture; + Thread.CurrentThread.CurrentUICulture = defaultCulture; } } } diff --git a/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs b/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs index c2cb5bae39..8d1ea71671 100644 --- a/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs +++ b/Composite/Core/WebClient/HttpModules/AdministrativeDataScopeSetterHttpModule.cs @@ -10,36 +10,37 @@ namespace Composite.Core.WebClient.HttpModules { internal class AdministrativeDataScopeSetterHttpModule : IHttpModule { - private const string HttpContextItemsKey = nameof(AdministrativeDataScopeSetterHttpModule) + ":DataScope"; - public void Init(HttpApplication context) { - context.AuthenticateRequest += AuthenticateRequest; - context.EndRequest += EndRequest; + var moduleInstance = new ModuleInstance(); + context.AuthorizeRequest += moduleInstance.AuthorizeRequest; + context.EndRequest += moduleInstance.EndRequest; } - private static void AuthenticateRequest(object sender, EventArgs e) + private class ModuleInstance { - if (!SystemSetupFacade.IsSystemFirstTimeInitialized) return; - - HttpContext context = ((HttpApplication)sender).Context; - - bool adminRootRequest = UrlUtils.IsAdminConsoleRequest(context); + private DataScope _dataScope; - if (adminRootRequest && UserValidationFacade.IsLoggedIn()) + public void AuthorizeRequest(object sender, EventArgs e) { - var dataScope = new DataScope(DataScopeIdentifier.Administrated, UserSettings.ActiveLocaleCultureInfo); - context.Items[HttpContextItemsKey] = dataScope; - } + if (!SystemSetupFacade.IsSystemFirstTimeInitialized) return; - } + HttpApplication application = (HttpApplication)sender; + HttpContext context = application.Context; - private static void EndRequest(object sender, EventArgs e) - { - HttpContext context = ((HttpApplication)sender).Context; - var dataScope = context.Items[HttpContextItemsKey] as DataScope; + bool adminRootRequest = UrlUtils.IsAdminConsoleRequest(context); + + if (adminRootRequest && UserValidationFacade.IsLoggedIn()) + { + _dataScope = new DataScope(DataScopeIdentifier.Administrated, UserSettings.ActiveLocaleCultureInfo); + } + } - dataScope?.Dispose(); + public void EndRequest(object sender, EventArgs e) + { + _dataScope?.Dispose(); + _dataScope = null; + } } public void Dispose() diff --git a/Composite/Core/WebClient/HttpModules/AdministrativeOutputGZipperHttpModule.cs b/Composite/Core/WebClient/HttpModules/AdministrativeOutputGZipperHttpModule.cs deleted file mode 100644 index 19d6a282c3..0000000000 --- a/Composite/Core/WebClient/HttpModules/AdministrativeOutputGZipperHttpModule.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.IO; -using System.Reflection; -using System.Web; - - -namespace Composite.Core.WebClient.HttpModules -{ - [Obsolete("This feature is build-in on IIS7", true)] - internal class AdministrativeOutputGZipperHttpModule : IHttpModule - { - private static PropertyInfo _headersWrittenPropertyInfo; - - public void Init(HttpApplication context) - { - context.AuthenticateRequest += context_AuthenticateRequest; - context.EndRequest += context_EndRequest; - } - - public void context_EndRequest(object sender, EventArgs e) - { - HttpApplication application = (HttpApplication) sender; - HttpContext context = application.Context; - bool adminRootRequest = UrlUtils.IsAdminConsoleRequest(context); - - if (!adminRootRequest) return; - - - string requestPathExtension = Path.GetExtension(context.Request.Path).ToLowerInvariant(); - - if (requestPathExtension == ".aspx" || requestPathExtension == ".asmx" || requestPathExtension == ".cshtml") - { - SetResponseHeaders(); - } - } - - - void context_AuthenticateRequest(object sender, EventArgs e) - { - HttpApplication application = (HttpApplication)sender; - HttpContext context = application.Context; - - bool adminRootRequest = UrlUtils.IsAdminConsoleRequest(context); - - if (adminRootRequest) - { - string requestPathExtension = Path.GetExtension(context.Request.Path).ToLowerInvariant(); - - if (requestPathExtension == ".aspx" || requestPathExtension == ".asmx") - { - GZipEncodePage(); - } - } - } - - - public void Dispose() - { - } - - - /// - /// Determines if GZip is supported - /// - /// - public static bool IsGZipSupported() - { - string AcceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"]; - if (!string.IsNullOrEmpty(AcceptEncoding) && - (AcceptEncoding.Contains("gzip") || AcceptEncoding.Contains("deflate"))) - return true; - return false; - } - - - /// - /// Sets up the current page or handler to use GZip through a Response.Filter - /// IMPORTANT: - /// You have to call this method before any output is generated! - /// - public static void GZipEncodePage() - { - if (IsGZipSupported()) - { - HttpResponse Response = HttpContext.Current.Response; - - string AcceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"]; - if (AcceptEncoding.Contains("gzip")) - { - Response.Filter = new System.IO.Compression.GZipStream(Response.Filter, - System.IO.Compression.CompressionMode.Compress); - } - else - { - Response.Filter = new System.IO.Compression.DeflateStream(Response.Filter, - System.IO.Compression.CompressionMode.Compress); - } - } - } - - - public static void SetResponseHeaders() - { - if (!IsGZipSupported()) - { - return; - } - - var httpContext = HttpContext.Current; - - if (HeadersWritten(httpContext.Response)) - { - return; - } - - string AcceptEncoding = httpContext.Request.Headers["Accept-Encoding"]; - - string encoding = AcceptEncoding.Contains("gzip") ? "gzip" : "deflate"; - - httpContext.Response.AppendHeader("Content-Encoding", encoding); - } - - - private static bool HeadersWritten(HttpResponse response) - { - return (bool)HeadersWrittenPropertyInfo.GetValue(response, new object[0]); - } - - private static PropertyInfo HeadersWrittenPropertyInfo - { - get - { - if(_headersWrittenPropertyInfo == null) - { - _headersWrittenPropertyInfo = typeof (HttpResponse).GetProperty("HeadersWritten", BindingFlags.Instance |BindingFlags.NonPublic); - } - return _headersWrittenPropertyInfo; - } - } - - } -} diff --git a/Composite/Core/WebClient/Renderings/RequestInterceptorHttpModule.cs b/Composite/Core/WebClient/Renderings/RequestInterceptorHttpModule.cs index 85a5383b99..55488d1f50 100644 --- a/Composite/Core/WebClient/Renderings/RequestInterceptorHttpModule.cs +++ b/Composite/Core/WebClient/Renderings/RequestInterceptorHttpModule.cs @@ -23,7 +23,7 @@ void context_BeginRequest(object sender, EventArgs e) { if (!SystemSetupFacade.IsSystemFirstTimeInitialized) return; - ThreadDataManager.InitializeThroughHttpContext(true); + ThreadDataManager.InitializeThroughHttpContext(); var httpContext = (sender as HttpApplication).Context; diff --git a/Composite/Data/DataFacadeImpl.cs b/Composite/Data/DataFacadeImpl.cs index f0206692ef..3b01ed5faf 100644 --- a/Composite/Data/DataFacadeImpl.cs +++ b/Composite/Data/DataFacadeImpl.cs @@ -168,8 +168,12 @@ public IEnumerable GetDataInterceptors(Type dataType) DataInterceptor globalDataInterceptor = GlobalDataInterceptors .FirstOrDefault(kvp => kvp.Key.IsAssignableFrom(dataType)).Value; - DataInterceptor threadedDataInterceptor; - this.DataInterceptors.TryGetValue(dataType, out threadedDataInterceptor); + DataInterceptor threadedDataInterceptor = null; + var threadData = ThreadDataManager.Current; + if (threadData != null) + { + GetDataInterceptors(threadData).TryGetValue(dataType, out threadedDataInterceptor); + } if (threadedDataInterceptor == null && globalDataInterceptor == null) { @@ -237,27 +241,25 @@ public void ClearGlobalDataInterceptor() where T : class, IData } } - private Dictionary DataInterceptors + private Dictionary GetDataInterceptors(ThreadDataManagerData threadData) { - get - { - const string threadDataKey = "DataFacade:DataInterceptors"; + Verify.ArgumentNotNull(threadData, nameof(threadData)); + const string threadDataKey = "DataFacade:DataInterceptors"; - var threadData = ThreadDataManager.GetCurrentNotNull(); + var dataInterceptors = threadData.GetValue(threadDataKey) as Dictionary; - var dataInterceptors = threadData.GetValue(threadDataKey) as Dictionary; - - if (dataInterceptors == null) - { - dataInterceptors = new Dictionary(); - threadData.SetValue(threadDataKey, dataInterceptors); - } - - return dataInterceptors; + if (dataInterceptors == null) + { + dataInterceptors = new Dictionary(); + threadData.SetValue(threadDataKey, dataInterceptors); } + + return dataInterceptors; } - + private Dictionary DataInterceptors + => GetDataInterceptors(ThreadDataManager.Current); + public void Update(IEnumerable dataset, bool suppressEventing, bool performForeignKeyIntegrityCheck, bool performValidation) { diff --git a/Composite/Data/Foundation/DataInterfaceAutoUpdater.cs b/Composite/Data/Foundation/DataInterfaceAutoUpdater.cs index d53b495da7..e712da2ae9 100644 --- a/Composite/Data/Foundation/DataInterfaceAutoUpdater.cs +++ b/Composite/Data/Foundation/DataInterfaceAutoUpdater.cs @@ -1,9 +1,9 @@ using System; using System.Linq; using Composite.Core; +using Composite.Core.Application; using Composite.Data.DynamicTypes; using Composite.Core.Instrumentation; -using Composite.Data.Types; namespace Composite.Data.Foundation @@ -14,17 +14,13 @@ internal static class DataInterfaceAutoUpdater internal static bool EnsureUpdateAllInterfaces() { + using (AppDomainLocker.NewLock()) using (TimerProfilerFacade.CreateTimerProfiler()) { bool doFlush = false; var knownInterfaces = DataProviderRegistry.AllKnownInterfaces.ToList(); - if(!knownInterfaces.Contains(typeof(IXmlPageTemplate))) - { - knownInterfaces.Insert(0, typeof(IXmlPageTemplate)); - } - foreach (Type interfaceType in knownInterfaces) { if (!interfaceType.IsAutoUpdateble() || interfaceType.IsGenerated()) diff --git a/Composite/Data/PageFolderFacade.cs b/Composite/Data/PageFolderFacade.cs index ca5476967f..46b87445a7 100644 --- a/Composite/Data/PageFolderFacade.cs +++ b/Composite/Data/PageFolderFacade.cs @@ -4,6 +4,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Transactions; +using Composite.Core.Extensions; using Composite.Data.DynamicTypes; using Composite.Data.Foundation; using Composite.Data.Types; @@ -43,9 +44,15 @@ public static IEnumerable GetAllFolderTypes() /// public static bool HasFolderDefinitions(this IPage page) { - Verify.ArgumentNotNull(page, "page"); + Verify.ArgumentNotNull(page, nameof(page)); + + Guid pageId = page.Id; - return DataFacade.GetData().Any(f => f.PageId == page.Id); + var definitions = DataFacade.GetData(); + + return definitions.IsEnumerableQuery() + ? definitions.AsEnumerable().Any(f => f.PageId == pageId) + : definitions.Any(f => f.PageId == pageId); } @@ -57,19 +64,32 @@ public static bool HasFolderDefinitions(this IPage page) /// public static IEnumerable GetDefinedFolderTypes(this IPage page) { - Verify.ArgumentNotNull(page, "page"); + Verify.ArgumentNotNull(page, nameof(page)); - IEnumerable typeIds = - DataFacade.GetData(). - Where(f => f.PageId == page.Id). - Select(f => f.FolderTypeId). - Evaluate(); + var folderDefinitions = DataFacade.GetData(); + + IEnumerable typeIds; + + if (folderDefinitions.IsEnumerableQuery()) + { + typeIds = folderDefinitions + .Evaluate() + .Where(f => f.PageId == page.Id) + .Select(f => f.FolderTypeId); + } + else + { + typeIds = folderDefinitions + .Where(f => f.PageId == page.Id) + .Select(f => f.FolderTypeId) + .Evaluate(); + } foreach (Guid typeId in typeIds) { var dataTypeDescriptor = DynamicTypeManager.GetDataTypeDescriptor(typeId); Verify.IsNotNull(dataTypeDescriptor, "Missing a page data folder type with id '{0}', referenced by a IPageFolderDefinition record", typeId); - + yield return TypeManager.GetType(dataTypeDescriptor.TypeManagerTypeName); } } diff --git a/Composite/Data/Types/IPageType.cs b/Composite/Data/Types/IPageType.cs index 179cee8bdd..3bc4c034df 100644 --- a/Composite/Data/Types/IPageType.cs +++ b/Composite/Data/Types/IPageType.cs @@ -36,12 +36,12 @@ public static class PageTypeHomepageRelationExtensionMethods /// public static PageTypeHomepageRelation GetPageTypeHomepageRelation(this string value) { - if (string.IsNullOrEmpty(value)) throw new ArgumentNullException("value"); + if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); PageTypeHomepageRelation result; - if (Enum.TryParse(value, out result) == false) + if (!Enum.TryParse(value, out result)) { - throw new ArgumentException(string.Format("The argument is wrongly formattet")); + throw new ArgumentException("The argument is wrongly formatted"); } return result; @@ -50,6 +50,7 @@ public static PageTypeHomepageRelation GetPageTypeHomepageRelation(this string v /// + [Obsolete("Use nameof() keyword instead", true)] public static string ToPageTypeHomepageRelationString(this PageTypeHomepageRelation pageTypeHomepageRelation) { return pageTypeHomepageRelation.ToString(); @@ -59,7 +60,7 @@ public static string ToPageTypeHomepageRelationString(this PageTypeHomepageRelat - /// + /// /// /// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] @@ -68,47 +69,37 @@ public static class PageTypeExtensionMethods /// public static IEnumerable GetChildPageSelectablePageTypes(this IPage parentPage, IPage childPage = null) { + var pageTypes = DataFacade.GetData().AsEnumerable() + .Where(pt => pt.Available); + + pageTypes = pageTypes.OrderBy(f => f.Name); + if (parentPage == null) { - return - DataFacade.GetData(). - Where(f => f.Available && f.HomepageRelation != PageTypeHomepageRelation.OnlySubPages.ToString()). - OrderBy(f => f.Name). - Evaluate(); + return pageTypes + .Where(f => f.HomepageRelation != nameof(PageTypeHomepageRelation.OnlySubPages)) + .Evaluate(); } - IEnumerable pageTypes; - if (childPage == null) - { - pageTypes = - DataFacade.GetData(). - Where(f => f.Available && f.HomepageRelation != PageTypeHomepageRelation.OnlyHomePages.ToString()). - OrderBy(f => f.Name). - Evaluate(); - } - else - { - pageTypes = - DataFacade.GetData(). - Where(f => - f.Available && - (f.HomepageRelation != PageTypeHomepageRelation.OnlyHomePages.ToString() || f.Id == childPage.PageTypeId)). - OrderBy(f => f.Name). - Evaluate(); - } + pageTypes = pageTypes + .Where(f => f.HomepageRelation != nameof(PageTypeHomepageRelation.OnlyHomePages) + && (childPage == null || f.Id == childPage.PageTypeId)) + .Evaluate(); + + ICollection parentRestrictions = null; var result = new List(); foreach (IPageType pageType in pageTypes) { if (childPage != null && pageType.Id == childPage.PageTypeId) { - result.Add(pageType); + result.Add(pageType); continue; } - var parentRestrictions = DataFacade.GetData() - .Where(f => f.PageTypeId == pageType.Id) - .ToList(); + parentRestrictions = parentRestrictions ?? + DataFacade.GetData().AsEnumerable() + .Where(f => f.PageTypeId == pageType.Id).ToList(); if (parentRestrictions.Count == 0 || parentRestrictions.Any(f => f.AllowedParentPageTypeId == parentPage.PageTypeId)) { @@ -122,7 +113,7 @@ public static IEnumerable GetChildPageSelectablePageTypes(this IPage - /// + /// /// /// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] @@ -149,7 +140,7 @@ public interface IPageType : IData /// [StoreFieldType(PhysicalStoreFieldType.LargeString)] - [ImmutableFieldId("{CCAA5F15-63E4-42BF-8CDA-3AD0407520A7}")] + [ImmutableFieldId("{CCAA5F15-63E4-42BF-8CDA-3AD0407520A7}")] string Description { get; set; } diff --git a/Composite/Data/Types/PageServices.cs b/Composite/Data/Types/PageServices.cs index c1304f40cd..4c00679d4c 100644 --- a/Composite/Data/Types/PageServices.cs +++ b/Composite/Data/Types/PageServices.cs @@ -4,6 +4,7 @@ using System.Linq; using Composite.C1Console.Trees; using Composite.C1Console.Users; +using Composite.Core.Extensions; using Composite.Core.Linq; using Composite.Data.ProcessControlled; using Composite.Data.Transactions; @@ -58,11 +59,23 @@ public static IQueryable GetChildren(this IPage page) /// public static IQueryable GetChildren(Guid parentId) { - return from ps in DataFacade.GetData() - join p in DataFacade.GetData() on ps.Id equals p.Id - where ps.ParentId == parentId - orderby ps.LocalOrdering - select p; + var structure = DataFacade.GetData(); + var pages = DataFacade.GetData(); + + if (structure.IsEnumerableQuery() && pages.IsEnumerableQuery()) + { + return (from ps in structure.AsEnumerable() + where ps.ParentId == parentId + join p in pages.AsEnumerable() on ps.Id equals p.Id + orderby ps.LocalOrdering + select p).AsQueryable(); + } + + return from ps in structure + where ps.ParentId == parentId + join p in pages on ps.Id equals p.Id + orderby ps.LocalOrdering + select p; #warning revisit this - we return all versions (by design so far). Any ordering on page versions? - check history for original intent } diff --git a/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/XmlDataProviderStoreManipulator.cs b/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/XmlDataProviderStoreManipulator.cs index 185f07a33f..e4c1d76e84 100644 --- a/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/XmlDataProviderStoreManipulator.cs +++ b/Composite/Plugins/Data/DataProviders/XmlDataProvider/Foundation/XmlDataProviderStoreManipulator.cs @@ -253,12 +253,14 @@ private static void CopyData(string providerName, DataTypeChangeDescriptor dataT { if (addingVersionId && fieldDescriptor.Name == nameof(IVersioned.VersionId) && versionIdSourceFieldName != null) { - var existingField = (Guid)oldElement.Attribute(versionIdSourceFieldName); - if (existingField != null) + if (!oldElement.Attributes().Any(a => a.Name.LocalName == nameof(IVersioned.VersionId))) { - newChildAttributes.Add(new XAttribute(fieldDescriptor.Name, existingField)); - continue; + var sourceFieldValue = (Guid)oldElement.Attribute(versionIdSourceFieldName); + + newChildAttributes.Add(new XAttribute(fieldDescriptor.Name, sourceFieldValue)); } + + continue; } if (!fieldDescriptor.IsNullable @@ -290,11 +292,9 @@ private static void CopyData(string providerName, DataTypeChangeDescriptor dataT C1File.Delete(oldFilename); } - XElement newRoot = new XElement(XmlDataProviderDocumentWriter.GetRootElementName(newDataScopeConfigurationElement.ElementName)); - newRoot.Add(newElements); - - XDocument newDocument = new XDocument(); - newDocument.Add(newRoot); + var newDocument = new XDocument( + new XElement(XmlDataProviderDocumentWriter.GetRootElementName(newDataScopeConfigurationElement.ElementName), + newElements)); XDocumentUtils.Save(newDocument, newFilename); } diff --git a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs index 0d43b4bc1c..669d215340 100644 --- a/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs +++ b/Composite/Plugins/Elements/ElementProviders/PageElementProvider/PageElementProvider.cs @@ -128,11 +128,11 @@ public IEnumerable GetRoots(SearchToken searchToken) } }; - var allPageTypes = DataFacade.GetData(); + var allPageTypes = DataFacade.GetData().AsEnumerable(); foreach ( var pageType in - allPageTypes.Where(f => f.HomepageRelation != PageTypeHomepageRelation.OnlySubPages.ToPageTypeHomepageRelationString()) + allPageTypes.Where(f => f.HomepageRelation != nameof(PageTypeHomepageRelation.OnlySubPages)) .OrderByDescending(f=>f.Id)) { element.AddAction( @@ -670,7 +670,7 @@ private List GetElements(List> pag string localizePageLabel = StringResourceSystemFacade.GetString("Composite.Plugins.PageElementProvider", "PageElementProvider.LocalizePage"); string localizePageToolTip = StringResourceSystemFacade.GetString("Composite.Plugins.PageElementProvider", "PageElementProvider.LocalizePageToolTip"); string addNewPageLabel = StringResourceSystemFacade.GetString("Composite.Plugins.PageElementProvider", "PageElementProvider.AddSubPageFormat"); - string addNewPageToolTip = StringResourceSystemFacade.GetString("Composite.Plugins.PageElementProvider", "PageElementProvider.AddSubPageToolTip"); + //string addNewPageToolTip = StringResourceSystemFacade.GetString("Composite.Plugins.PageElementProvider", "PageElementProvider.AddSubPageToolTip"); string deletePageLabel = StringResourceSystemFacade.GetString("Composite.Plugins.PageElementProvider", "PageElementProvider.Delete"); string deletePageToolTip = StringResourceSystemFacade.GetString("Composite.Plugins.PageElementProvider", "PageElementProvider.DeleteToolTip"); @@ -681,7 +681,7 @@ private List GetElements(List> pag } var elements = new Element[pages.Count]; - var allPageTypes = DataFacade.GetData(); + var allPageTypes = DataFacade.GetData().AsEnumerable(); ParallelFacade.For("PageElementProvider. Getting elements", 0, pages.Count, i => { @@ -696,7 +696,7 @@ private List GetElements(List> pag var element = new Element(_context.CreateElementHandle(entityToken), MakeVisualData(page, kvp.Key, urlMappingName, rootPages), dragAndDropInfo); - element.PropertyBag.Add("Uri", "~/page({0})".FormatWith(page.Id)); + element.PropertyBag.Add("Uri", $"~/page({page.Id})"); element.PropertyBag.Add("ElementType", "application/x-composite-page"); element.PropertyBag.Add("DataId", page.Id.ToString()); diff --git a/Website/Composite/content/forms/Administrative/PackageElementProviderViewAvailablePackageInformation.xml b/Website/Composite/content/forms/Administrative/PackageElementProviderViewAvailablePackageInformation.xml index 4c916e4851..3d4e0eb718 100644 --- a/Website/Composite/content/forms/Administrative/PackageElementProviderViewAvailablePackageInformation.xml +++ b/Website/Composite/content/forms/Administrative/PackageElementProviderViewAvailablePackageInformation.xml @@ -8,6 +8,11 @@ + + + + + @@ -45,7 +50,7 @@ - + @@ -83,12 +88,36 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Website/Composite/content/forms/Administrative/PackageElementProviderViewInstalledPackageInformation.xml b/Website/Composite/content/forms/Administrative/PackageElementProviderViewInstalledPackageInformation.xml index 2a0a887528..b807657bfc 100644 --- a/Website/Composite/content/forms/Administrative/PackageElementProviderViewInstalledPackageInformation.xml +++ b/Website/Composite/content/forms/Administrative/PackageElementProviderViewInstalledPackageInformation.xml @@ -5,7 +5,9 @@ - + + + @@ -53,13 +55,33 @@