using System.Xml.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Packaging; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Packaging; using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Extensions; using Umbraco.New.Cms.Core.Models; using File = System.IO.File; namespace Umbraco.Cms.Core.Services.Implement; /// /// 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. /// public class PackagingService : IPackagingService { private readonly IAuditService _auditService; private readonly ICreatedPackagesRepository _createdPackages; private readonly IEventAggregator _eventAggregator; private readonly IKeyValueService _keyValueService; private readonly ILegacyManifestParser _legacyManifestParser; private readonly IPackageInstallation _packageInstallation; private readonly PackageMigrationPlanCollection _packageMigrationPlans; private readonly IHostEnvironment _hostEnvironment; private readonly IUserService _userService; public PackagingService( IAuditService auditService, ICreatedPackagesRepository createdPackages, IPackageInstallation packageInstallation, IEventAggregator eventAggregator, ILegacyManifestParser legacyManifestParser, IKeyValueService keyValueService, PackageMigrationPlanCollection packageMigrationPlans, IHostEnvironment hostEnvironment, IUserService userService) { _auditService = auditService; _createdPackages = createdPackages; _packageInstallation = packageInstallation; _eventAggregator = eventAggregator; _legacyManifestParser = legacyManifestParser; _keyValueService = keyValueService; _packageMigrationPlans = packageMigrationPlans; _hostEnvironment = hostEnvironment; _userService = userService; } [Obsolete("Use constructor that also takes an IHostEnvironment and IUserService instead. Scheduled for removal in V15")] public PackagingService( IAuditService auditService, ICreatedPackagesRepository createdPackages, IPackageInstallation packageInstallation, IEventAggregator eventAggregator, ILegacyManifestParser manifestParser, IKeyValueService keyValueService, PackageMigrationPlanCollection packageMigrationPlans) : this( auditService, createdPackages, packageInstallation, eventAggregator, manifestParser, keyValueService, packageMigrationPlans, StaticServiceProvider.Instance.GetRequiredService(), StaticServiceProvider.Instance.GetRequiredService()) { } #region Installation public CompiledPackage GetCompiledPackageInfo(XDocument? xml) => _packageInstallation.ReadPackage(xml); public InstallationSummary InstallCompiledPackageData( XDocument? packageXml, int userId = Constants.Security.SuperUserId) { CompiledPackage compiledPackage = GetCompiledPackageInfo(packageXml); if (compiledPackage == null) { throw new InvalidOperationException("Could not read the package file " + packageXml); } // 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); } InstallationSummary summary = _packageInstallation.InstallPackageData(compiledPackage, userId, out _); _auditService.Add(AuditType.PackagerInstall, userId, -1, "Package", $"Package data installed for package '{compiledPackage.Name}'."); // trigger the ImportedPackage event _eventAggregator.Publish(new ImportedPackageNotification(summary).WithStateFrom(importingPackageNotification)); return summary; } public InstallationSummary InstallCompiledPackageData( FileInfo packageXmlFile, int userId = Constants.Security.SuperUserId) { XDocument xml; using (StreamReader streamReader = File.OpenText(packageXmlFile.FullName)) { xml = XDocument.Load(streamReader); } return InstallCompiledPackageData(xml, userId); } #endregion #region Created/Installed Package Repositories [Obsolete("Use DeleteCreatedPackageAsync instead. Scheduled for removal in Umbraco 15.")] public void DeleteCreatedPackage(int id, int userId = Constants.Security.SuperUserId) { PackageDefinition? package = GetCreatedPackageById(id); Guid key = package?.PackageId ?? Guid.Empty; Guid currentUserKey = _userService.GetUserById(id)?.Key ?? Constants.Security.SuperUserKey; DeleteCreatedPackageAsync(key, currentUserKey).GetAwaiter().GetResult(); } /// public async Task> DeleteCreatedPackageAsync(Guid key, Guid userKey) { PackageDefinition? package = await GetCreatedPackageByKeyAsync(key); if (package == null) { return Attempt.FailWithStatus(PackageOperationStatus.NotFound, null); } 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}"); _createdPackages.Delete(package.Id); return Attempt.SucceedWithStatus(PackageOperationStatus.Success, package); } [Obsolete("Use GetCreatedPackagesAsync instead. Scheduled for removal in Umbraco 15.")] public IEnumerable GetAllCreatedPackages() => _createdPackages.GetAll(); /// public async Task> GetCreatedPackagesAsync(int skip, int take) { PackageDefinition[] packages = _createdPackages.GetAll().WhereNotNull().ToArray(); var pagedModel = new PagedModel(packages.Length, packages.Skip(skip).Take(take)); return await Task.FromResult(pagedModel); } public PackageDefinition? GetCreatedPackageById(int id) => _createdPackages.GetById(id); /// public Task GetCreatedPackageByKeyAsync(Guid key) => Task.FromResult(_createdPackages.GetByKey(key)); [Obsolete("Use CreateCreatedPackageAsync or UpdateCreatedPackageAsync instead. Scheduled for removal in Umbraco 15.")] public bool SaveCreatedPackage(PackageDefinition definition) => _createdPackages.SavePackage(definition); /// public async Task> CreateCreatedPackageAsync(PackageDefinition package, Guid userKey) { if (_createdPackages.SavePackage(package) == false) { if (string.IsNullOrEmpty(package.Name)) { return Attempt.FailWithStatus(PackageOperationStatus.InvalidName, package); } return Attempt.FailWithStatus(PackageOperationStatus.DuplicateItemName, package); } 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}"); return await Task.FromResult(Attempt.SucceedWithStatus(PackageOperationStatus.Success, package)); } /// public async Task> UpdateCreatedPackageAsync(PackageDefinition package, Guid userKey) { if (_createdPackages.SavePackage(package) == false) { return Attempt.FailWithStatus(PackageOperationStatus.NotFound, package); } 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}"); 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 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 // Collect and merge the packages from the manifests foreach (LegacyPackageManifest package in _legacyManifestParser.GetManifests()) { if (package.PackageName is null) { continue; } if (!installedPackages.TryGetValue(package.PackageName, out InstalledPackage? installedPackage)) { installedPackage = new InstalledPackage { PackageName = package.PackageName, Version = string.IsNullOrEmpty(package.Version) ? "Unknown" : package.Version, }; installedPackages.Add(package.PackageName, installedPackage); } installedPackage.PackageView = package.PackageView; } // Return all packages with a name in the package.manifest or package migrations return installedPackages.Values; } #endregion /// public async Task> GetInstalledPackagesFromMigrationPlansAsync(int skip, int take) { IReadOnlyDictionary? 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 { Total = installedPackages.Count(), Items = installedPackages.Skip(skip).Take(take), }); } /// 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(); } }