2021-06-09 16:56:42 -06:00
|
|
|
using System.Xml.Linq;
|
2023-02-23 14:36:21 +01:00
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
using Microsoft.Extensions.FileProviders;
|
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
using Umbraco.Cms.Core.DependencyInjection;
|
2021-02-09 10:22:42 +01:00
|
|
|
using Umbraco.Cms.Core.Events;
|
2021-06-16 15:34:20 -06:00
|
|
|
using Umbraco.Cms.Core.Manifest;
|
2021-02-09 10:22:42 +01:00
|
|
|
using Umbraco.Cms.Core.Models;
|
|
|
|
|
using Umbraco.Cms.Core.Models.Packaging;
|
2021-05-11 14:33:49 +02:00
|
|
|
using Umbraco.Cms.Core.Notifications;
|
2021-02-09 10:22:42 +01:00
|
|
|
using Umbraco.Cms.Core.Packaging;
|
2023-02-23 14:36:21 +01:00
|
|
|
using Umbraco.Cms.Core.Services.OperationStatus;
|
2021-06-16 15:43:36 -06:00
|
|
|
using Umbraco.Extensions;
|
2023-02-23 14:36:21 +01:00
|
|
|
using Umbraco.New.Cms.Core.Models;
|
2022-06-02 08:18:31 +02:00
|
|
|
using File = System.IO.File;
|
2017-12-28 09:18:09 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
namespace Umbraco.Cms.Core.Services.Implement;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Represents the Packaging Service, which provides import/export functionality for the Core models of the API
|
|
|
|
|
/// using xml representation. This is primarily used by the Package functionality.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class PackagingService : IPackagingService
|
2017-12-28 09:18:09 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
private readonly IAuditService _auditService;
|
|
|
|
|
private readonly ICreatedPackagesRepository _createdPackages;
|
|
|
|
|
private readonly IEventAggregator _eventAggregator;
|
|
|
|
|
private readonly IKeyValueService _keyValueService;
|
2023-02-15 14:57:21 +01:00
|
|
|
private readonly ILegacyManifestParser _legacyManifestParser;
|
2022-06-02 08:18:31 +02:00
|
|
|
private readonly IPackageInstallation _packageInstallation;
|
|
|
|
|
private readonly PackageMigrationPlanCollection _packageMigrationPlans;
|
2023-02-23 14:36:21 +01:00
|
|
|
private readonly IHostEnvironment _hostEnvironment;
|
2023-03-21 12:41:20 +01:00
|
|
|
private readonly IUserService _userService;
|
2022-06-02 08:18:31 +02:00
|
|
|
|
|
|
|
|
public PackagingService(
|
|
|
|
|
IAuditService auditService,
|
|
|
|
|
ICreatedPackagesRepository createdPackages,
|
|
|
|
|
IPackageInstallation packageInstallation,
|
|
|
|
|
IEventAggregator eventAggregator,
|
2023-02-15 14:57:21 +01:00
|
|
|
ILegacyManifestParser legacyManifestParser,
|
2022-06-02 08:18:31 +02:00
|
|
|
IKeyValueService keyValueService,
|
2023-02-23 14:36:21 +01:00
|
|
|
PackageMigrationPlanCollection packageMigrationPlans,
|
2023-03-21 12:41:20 +01:00
|
|
|
IHostEnvironment hostEnvironment,
|
|
|
|
|
IUserService userService)
|
2017-12-28 09:18:09 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
_auditService = auditService;
|
|
|
|
|
_createdPackages = createdPackages;
|
|
|
|
|
_packageInstallation = packageInstallation;
|
|
|
|
|
_eventAggregator = eventAggregator;
|
2023-02-15 14:57:21 +01:00
|
|
|
_legacyManifestParser = legacyManifestParser;
|
2022-06-02 08:18:31 +02:00
|
|
|
_keyValueService = keyValueService;
|
|
|
|
|
_packageMigrationPlans = packageMigrationPlans;
|
2023-02-23 14:36:21 +01:00
|
|
|
_hostEnvironment = hostEnvironment;
|
2023-03-21 12:41:20 +01:00
|
|
|
_userService = userService;
|
2023-02-23 14:36:21 +01:00
|
|
|
}
|
|
|
|
|
|
2023-03-21 12:41:20 +01:00
|
|
|
[Obsolete("Use constructor that also takes an IHostEnvironment and IUserService instead. Scheduled for removal in V15")]
|
2023-02-23 14:36:21 +01:00
|
|
|
public PackagingService(
|
|
|
|
|
IAuditService auditService,
|
|
|
|
|
ICreatedPackagesRepository createdPackages,
|
|
|
|
|
IPackageInstallation packageInstallation,
|
|
|
|
|
IEventAggregator eventAggregator,
|
2023-02-23 15:36:59 +01:00
|
|
|
ILegacyManifestParser manifestParser,
|
2023-02-23 14:36:21 +01:00
|
|
|
IKeyValueService keyValueService,
|
|
|
|
|
PackageMigrationPlanCollection packageMigrationPlans)
|
|
|
|
|
: this(
|
|
|
|
|
auditService,
|
|
|
|
|
createdPackages,
|
|
|
|
|
packageInstallation,
|
|
|
|
|
eventAggregator,
|
|
|
|
|
manifestParser,
|
|
|
|
|
keyValueService,
|
|
|
|
|
packageMigrationPlans,
|
2023-03-21 12:41:20 +01:00
|
|
|
StaticServiceProvider.Instance.GetRequiredService<IHostEnvironment>(),
|
|
|
|
|
StaticServiceProvider.Instance.GetRequiredService<IUserService>())
|
2023-02-23 14:36:21 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2017-12-28 09:18:09 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
#region Installation
|
2017-12-28 09:18:09 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
public CompiledPackage GetCompiledPackageInfo(XDocument? xml) => _packageInstallation.ReadPackage(xml);
|
2019-01-10 12:44:57 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
public InstallationSummary InstallCompiledPackageData(
|
|
|
|
|
XDocument? packageXml,
|
|
|
|
|
int userId = Constants.Security.SuperUserId)
|
|
|
|
|
{
|
|
|
|
|
CompiledPackage compiledPackage = GetCompiledPackageInfo(packageXml);
|
2021-06-09 16:56:42 -06:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
if (compiledPackage == null)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("Could not read the package file " + packageXml);
|
|
|
|
|
}
|
2019-01-14 14:28:00 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// Trigger the Importing Package Notification and stop execution if event/user is cancelling it
|
|
|
|
|
var importingPackageNotification = new ImportingPackageNotification(compiledPackage.Name);
|
|
|
|
|
if (_eventAggregator.PublishCancelable(importingPackageNotification))
|
|
|
|
|
{
|
|
|
|
|
return new InstallationSummary(compiledPackage.Name);
|
|
|
|
|
}
|
2019-01-14 14:28:00 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
InstallationSummary summary = _packageInstallation.InstallPackageData(compiledPackage, userId, out _);
|
2019-01-14 17:46:12 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
_auditService.Add(AuditType.PackagerInstall, userId, -1, "Package", $"Package data installed for package '{compiledPackage.Name}'.");
|
2019-01-14 14:28:00 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// trigger the ImportedPackage event
|
|
|
|
|
_eventAggregator.Publish(new ImportedPackageNotification(summary).WithStateFrom(importingPackageNotification));
|
2019-01-15 22:08:08 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
return summary;
|
|
|
|
|
}
|
2019-01-15 22:08:08 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
public InstallationSummary InstallCompiledPackageData(
|
|
|
|
|
FileInfo packageXmlFile,
|
|
|
|
|
int userId = Constants.Security.SuperUserId)
|
|
|
|
|
{
|
|
|
|
|
XDocument xml;
|
|
|
|
|
using (StreamReader streamReader = File.OpenText(packageXmlFile.FullName))
|
2021-06-09 16:56:42 -06:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
xml = XDocument.Load(streamReader);
|
2021-06-09 16:56:42 -06:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
return InstallCompiledPackageData(xml, userId);
|
|
|
|
|
}
|
2017-12-28 09:18:09 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
#endregion
|
2019-01-10 12:44:57 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
#region Created/Installed Package Repositories
|
2019-01-11 14:30:04 +11:00
|
|
|
|
2023-02-23 14:36:21 +01:00
|
|
|
[Obsolete("Use DeleteCreatedPackageAsync instead. Scheduled for removal in Umbraco 15.")]
|
2022-06-02 08:18:31 +02:00
|
|
|
public void DeleteCreatedPackage(int id, int userId = Constants.Security.SuperUserId)
|
|
|
|
|
{
|
|
|
|
|
PackageDefinition? package = GetCreatedPackageById(id);
|
2023-02-23 14:36:21 +01:00
|
|
|
Guid key = package?.PackageId ?? Guid.Empty;
|
2023-03-21 12:41:20 +01:00
|
|
|
Guid currentUserKey = _userService.GetUserById(id)?.Key ?? Constants.Security.SuperUserKey;
|
2023-02-23 14:36:21 +01:00
|
|
|
|
2023-03-21 12:41:20 +01:00
|
|
|
DeleteCreatedPackageAsync(key, currentUserKey).GetAwaiter().GetResult();
|
2023-02-23 14:36:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
2023-03-21 12:41:20 +01:00
|
|
|
public async Task<Attempt<PackageDefinition?, PackageOperationStatus>> DeleteCreatedPackageAsync(Guid key, Guid userKey)
|
2023-02-23 14:36:21 +01:00
|
|
|
{
|
|
|
|
|
PackageDefinition? package = await GetCreatedPackageByKeyAsync(key);
|
2022-06-02 08:18:31 +02:00
|
|
|
if (package == null)
|
|
|
|
|
{
|
2023-02-23 14:36:21 +01:00
|
|
|
return Attempt.FailWithStatus<PackageDefinition?, PackageOperationStatus>(PackageOperationStatus.NotFound, null);
|
2019-01-11 14:30:04 +11:00
|
|
|
}
|
2019-01-10 17:18:47 +11:00
|
|
|
|
2023-03-21 12:41:20 +01:00
|
|
|
int currentUserId = _userService.GetAsync(userKey).Result?.Id ?? Constants.Security.SuperUserId;
|
|
|
|
|
_auditService.Add(AuditType.Delete, currentUserId, -1, "Package", $"Created package '{package.Name}' deleted. Package key: {key}");
|
2023-02-23 14:36:21 +01:00
|
|
|
_createdPackages.Delete(package.Id);
|
|
|
|
|
|
|
|
|
|
return Attempt.SucceedWithStatus<PackageDefinition?, PackageOperationStatus>(PackageOperationStatus.Success, package);
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
|
|
|
|
|
2023-03-24 11:47:59 +01:00
|
|
|
[Obsolete("Use GetCreatedPackagesAsync instead. Scheduled for removal in Umbraco 15.")]
|
2022-06-02 08:18:31 +02:00
|
|
|
public IEnumerable<PackageDefinition?> GetAllCreatedPackages() => _createdPackages.GetAll();
|
2019-01-10 17:18:47 +11:00
|
|
|
|
2023-03-24 11:47:59 +01:00
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public async Task<PagedModel<PackageDefinition>> GetCreatedPackagesAsync(int skip, int take)
|
|
|
|
|
{
|
|
|
|
|
PackageDefinition[] packages = _createdPackages.GetAll().WhereNotNull().ToArray();
|
|
|
|
|
var pagedModel = new PagedModel<PackageDefinition>(packages.Length, packages.Skip(skip).Take(take));
|
|
|
|
|
return await Task.FromResult(pagedModel);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
public PackageDefinition? GetCreatedPackageById(int id) => _createdPackages.GetById(id);
|
2019-01-10 17:18:47 +11:00
|
|
|
|
2023-02-23 14:36:21 +01:00
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public Task<PackageDefinition?> GetCreatedPackageByKeyAsync(Guid key) => Task.FromResult(_createdPackages.GetByKey(key));
|
2019-01-10 17:18:47 +11:00
|
|
|
|
2023-02-23 14:36:21 +01:00
|
|
|
[Obsolete("Use CreateCreatedPackageAsync or UpdateCreatedPackageAsync instead. Scheduled for removal in Umbraco 15.")]
|
|
|
|
|
public bool SaveCreatedPackage(PackageDefinition definition) => _createdPackages.SavePackage(definition);
|
2021-06-18 12:45:22 -06:00
|
|
|
|
2023-02-23 14:36:21 +01:00
|
|
|
/// <inheritdoc/>
|
2023-03-21 12:41:20 +01:00
|
|
|
public async Task<Attempt<PackageDefinition, PackageOperationStatus>> CreateCreatedPackageAsync(PackageDefinition package, Guid userKey)
|
2022-06-02 08:18:31 +02:00
|
|
|
{
|
2023-02-23 14:36:21 +01:00
|
|
|
if (_createdPackages.SavePackage(package) == false)
|
2021-06-16 15:34:20 -06:00
|
|
|
{
|
2023-02-23 14:36:21 +01:00
|
|
|
if (string.IsNullOrEmpty(package.Name))
|
2022-06-02 08:18:31 +02:00
|
|
|
{
|
2023-02-23 14:36:21 +01:00
|
|
|
return Attempt.FailWithStatus(PackageOperationStatus.InvalidName, package);
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2021-06-16 15:34:20 -06:00
|
|
|
|
2023-02-23 14:36:21 +01:00
|
|
|
return Attempt.FailWithStatus(PackageOperationStatus.DuplicateItemName, package);
|
|
|
|
|
}
|
2021-06-16 15:34:20 -06:00
|
|
|
|
2023-03-21 12:41:20 +01:00
|
|
|
int currentUserId = _userService.GetAsync(userKey).Result?.Id ?? Constants.Security.SuperUserId;
|
|
|
|
|
_auditService.Add(AuditType.New, currentUserId, -1, "Package", $"Created package '{package.Name}' created. Package key: {package.PackageId}");
|
2023-02-23 14:36:21 +01:00
|
|
|
return await Task.FromResult(Attempt.SucceedWithStatus(PackageOperationStatus.Success, package));
|
|
|
|
|
|
|
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
|
2023-02-23 14:36:21 +01:00
|
|
|
/// <inheritdoc/>
|
2023-03-21 12:41:20 +01:00
|
|
|
public async Task<Attempt<PackageDefinition, PackageOperationStatus>> UpdateCreatedPackageAsync(PackageDefinition package, Guid userKey)
|
2023-02-23 14:36:21 +01:00
|
|
|
{
|
|
|
|
|
if (_createdPackages.SavePackage(package) == false)
|
|
|
|
|
{
|
|
|
|
|
return Attempt.FailWithStatus(PackageOperationStatus.NotFound, package);
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
|
|
|
|
|
2023-03-21 12:41:20 +01:00
|
|
|
int currentUserId = _userService.GetAsync(userKey).Result?.Id ?? Constants.Security.SuperUserId;
|
|
|
|
|
_auditService.Add(AuditType.New, currentUserId, -1, "Package", $"Created package '{package.Name}' updated. Package key: {package.PackageId}");
|
2023-02-23 14:36:21 +01:00
|
|
|
return await Task.FromResult(Attempt.SucceedWithStatus(PackageOperationStatus.Success, package));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string ExportCreatedPackage(PackageDefinition definition) => _createdPackages.ExportPackage(definition);
|
|
|
|
|
|
|
|
|
|
public InstalledPackage? GetInstalledPackageByName(string packageName)
|
|
|
|
|
=> GetAllInstalledPackages().Where(x => x.PackageName?.InvariantEquals(packageName) ?? false).FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
public IEnumerable<InstalledPackage> GetAllInstalledPackages()
|
|
|
|
|
{
|
|
|
|
|
// Collect the packages from the package migration plans
|
|
|
|
|
var installedPackages = GetInstalledPackagesFromMigrationPlansAsync(0, int.MaxValue)
|
|
|
|
|
.GetAwaiter()
|
|
|
|
|
.GetResult()
|
|
|
|
|
.Items.ToDictionary(package => package.PackageName!, package => package); // PackageName cannot be null here
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// Collect and merge the packages from the manifests
|
2023-02-15 14:57:21 +01:00
|
|
|
foreach (LegacyPackageManifest package in _legacyManifestParser.GetManifests())
|
2022-06-02 08:18:31 +02:00
|
|
|
{
|
|
|
|
|
if (package.PackageName is null)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
2021-06-16 15:34:20 -06:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
if (!installedPackages.TryGetValue(package.PackageName, out InstalledPackage? installedPackage))
|
2021-06-16 15:34:20 -06:00
|
|
|
{
|
2023-02-23 14:36:21 +01:00
|
|
|
installedPackage = new InstalledPackage
|
|
|
|
|
{
|
2022-08-15 10:29:41 +01:00
|
|
|
PackageName = package.PackageName,
|
|
|
|
|
Version = string.IsNullOrEmpty(package.Version) ? "Unknown" : package.Version,
|
|
|
|
|
};
|
2022-06-02 08:18:31 +02:00
|
|
|
|
|
|
|
|
installedPackages.Add(package.PackageName, installedPackage);
|
2021-06-16 15:34:20 -06:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
installedPackage.PackageView = package.PackageView;
|
2021-06-16 15:34:20 -06:00
|
|
|
}
|
2019-01-11 14:30:04 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// Return all packages with a name in the package.manifest or package migrations
|
|
|
|
|
return installedPackages.Values;
|
2017-12-28 09:18:09 +01:00
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
|
|
|
|
|
#endregion
|
2023-02-23 14:36:21 +01:00
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public async Task<PagedModel<InstalledPackage>> GetInstalledPackagesFromMigrationPlansAsync(int skip, int take)
|
|
|
|
|
{
|
|
|
|
|
IReadOnlyDictionary<string, string?>? keyValues =
|
|
|
|
|
_keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix);
|
|
|
|
|
|
|
|
|
|
InstalledPackage[] installedPackages = _packageMigrationPlans
|
|
|
|
|
.GroupBy(plan => plan.PackageName)
|
|
|
|
|
.Select(group =>
|
|
|
|
|
{
|
|
|
|
|
var package = new InstalledPackage
|
|
|
|
|
{
|
|
|
|
|
PackageName = group.Key,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var currentState = keyValues?
|
|
|
|
|
.GetValueOrDefault(Constants.Conventions.Migrations.KeyValuePrefix + package.PackageName);
|
|
|
|
|
|
|
|
|
|
package.PackageMigrationPlans = group
|
|
|
|
|
.Select(plan => new InstalledPackageMigrationPlans
|
|
|
|
|
{
|
|
|
|
|
CurrentMigrationId = currentState,
|
|
|
|
|
FinalMigrationId = plan.FinalState,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return package;
|
|
|
|
|
}).ToArray();
|
|
|
|
|
|
|
|
|
|
return await Task.FromResult(new PagedModel<InstalledPackage>
|
|
|
|
|
{
|
|
|
|
|
Total = installedPackages.Count(),
|
|
|
|
|
Items = installedPackages.Skip(skip).Take(take),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public Stream? GetPackageFileStream(PackageDefinition definition)
|
|
|
|
|
{
|
|
|
|
|
// Removing the ContentRootPath from the package path as a sub path is required for GetFileInfo()
|
|
|
|
|
var subPath = definition.PackagePath.Replace(_hostEnvironment.ContentRootPath, string.Empty);
|
|
|
|
|
|
|
|
|
|
IFileInfo packageFile = _hostEnvironment.ContentRootFileProvider.GetFileInfo(subPath);
|
|
|
|
|
|
|
|
|
|
if (packageFile.Exists == false)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return packageFile.CreateReadStream();
|
|
|
|
|
}
|
2017-12-28 09:18:09 +01:00
|
|
|
}
|