429 lines
17 KiB
C#
429 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Web.Http;
|
|
using System.Web.UI.WebControls;
|
|
using System.Xml;
|
|
using System.Xml.Linq;
|
|
using umbraco;
|
|
using Umbraco.Core.Auditing;
|
|
using umbraco.BusinessLogic;
|
|
using umbraco.cms.businesslogic.packager;
|
|
using umbraco.cms.businesslogic.packager.repositories;
|
|
using umbraco.cms.businesslogic.web;
|
|
using umbraco.cms.presentation.Trees;
|
|
using umbraco.presentation.developer.packages;
|
|
using umbraco.webservices;
|
|
using Umbraco.Core;
|
|
using Umbraco.Core.Configuration;
|
|
using Umbraco.Core.IO;
|
|
using Umbraco.Core.Logging;
|
|
using Umbraco.Core.Models;
|
|
using Umbraco.Core.Packaging.Models;
|
|
using Umbraco.Core.Services;
|
|
using Umbraco.Web.Models;
|
|
using Umbraco.Web.Models.ContentEditing;
|
|
using Umbraco.Web.Mvc;
|
|
using Umbraco.Web.UI;
|
|
using Umbraco.Web.WebApi;
|
|
using Umbraco.Web.WebApi.Filters;
|
|
using File = System.IO.File;
|
|
using Notification = Umbraco.Web.Models.ContentEditing.Notification;
|
|
using Settings = umbraco.cms.businesslogic.packager.Settings;
|
|
|
|
namespace Umbraco.Web.Editors
|
|
{
|
|
[PluginController("UmbracoApi")]
|
|
[UmbracoApplicationAuthorize(Core.Constants.Applications.Developer)]
|
|
public class PackageInstallController : UmbracoAuthorizedJsonController
|
|
{
|
|
[HttpPost]
|
|
public IHttpActionResult Uninstall(int packageId)
|
|
{
|
|
var pack = InstalledPackage.GetById(packageId);
|
|
if (pack == null) return NotFound();
|
|
|
|
PerformUninstall(pack);
|
|
|
|
return Ok();
|
|
}
|
|
|
|
/// <summary>
|
|
/// SORRY :( I didn't have time to put this in a service somewhere - the old packager did this all manually too
|
|
/// </summary>
|
|
/// <param name="pack"></param>
|
|
protected void PerformUninstall(InstalledPackage pack)
|
|
{
|
|
if (pack == null) throw new ArgumentNullException("pack");
|
|
|
|
var refreshCache = false;
|
|
|
|
//Uninstall templates
|
|
foreach (var item in pack.Data.Templates.ToArray())
|
|
{
|
|
int nId;
|
|
if (int.TryParse(item, out nId) == false) continue;
|
|
var found = Services.FileService.GetTemplate(nId);
|
|
if (found != null)
|
|
{
|
|
ApplicationContext.Services.FileService.DeleteTemplate(found.Alias, Security.GetUserId());
|
|
}
|
|
pack.Data.Templates.Remove(nId.ToString());
|
|
}
|
|
|
|
//Uninstall macros
|
|
foreach (var item in pack.Data.Macros.ToArray())
|
|
{
|
|
int nId;
|
|
if (int.TryParse(item, out nId) == false) continue;
|
|
var macro = Services.MacroService.GetById(nId);
|
|
if (macro != null)
|
|
{
|
|
Services.MacroService.Delete(macro);
|
|
}
|
|
pack.Data.Macros.Remove(nId.ToString());
|
|
}
|
|
|
|
//Remove Document Types
|
|
var contentTypes = new List<IContentType>();
|
|
var contentTypeService = Services.ContentTypeService;
|
|
foreach (var item in pack.Data.Documenttypes.ToArray())
|
|
{
|
|
int nId;
|
|
if (int.TryParse(item, out nId) == false) continue;
|
|
var contentType = contentTypeService.GetContentType(nId);
|
|
if (contentType == null) continue;
|
|
contentTypes.Add(contentType);
|
|
pack.Data.Documenttypes.Remove(nId.ToString(CultureInfo.InvariantCulture));
|
|
// refresh content cache when document types are removed
|
|
refreshCache = true;
|
|
}
|
|
|
|
//Order the DocumentTypes before removing them
|
|
if (contentTypes.Any())
|
|
{
|
|
var orderedTypes = from contentType in contentTypes
|
|
orderby contentType.ParentId descending, contentType.Id descending
|
|
select contentType;
|
|
foreach (var contentType in orderedTypes)
|
|
{
|
|
contentTypeService.Delete(contentType);
|
|
}
|
|
}
|
|
|
|
//Remove Dictionary items
|
|
foreach (var item in pack.Data.DictionaryItems.ToArray())
|
|
{
|
|
int nId;
|
|
if (int.TryParse(item, out nId) == false) continue;
|
|
var di = Services.LocalizationService.GetDictionaryItemById(nId);
|
|
if (di != null)
|
|
{
|
|
Services.LocalizationService.Delete(di);
|
|
}
|
|
pack.Data.DictionaryItems.Remove(nId.ToString());
|
|
}
|
|
|
|
//Remove Data types
|
|
foreach (var item in pack.Data.DataTypes.ToArray())
|
|
{
|
|
int nId;
|
|
if (int.TryParse(item, out nId) == false) continue;
|
|
var dtd = Services.DataTypeService.GetDataTypeDefinitionById(nId);
|
|
if (dtd != null)
|
|
{
|
|
Services.DataTypeService.Delete(dtd);
|
|
}
|
|
pack.Data.DataTypes.Remove(nId.ToString());
|
|
}
|
|
|
|
pack.Save();
|
|
|
|
// uninstall actions
|
|
//TODO: We should probably report errors to the UI!!
|
|
// This never happened before though, but we should do something now
|
|
if (pack.Data.Actions.IsNullOrWhiteSpace() == false)
|
|
{
|
|
try
|
|
{
|
|
var actionsXml = new XmlDocument();
|
|
actionsXml.LoadXml("<Actions>" + pack.Data.Actions + "</Actions>");
|
|
|
|
LogHelper.Debug<installedPackage>("executing undo actions: {0}", () => actionsXml.OuterXml);
|
|
|
|
foreach (XmlNode n in actionsXml.DocumentElement.SelectNodes("//Action"))
|
|
{
|
|
try
|
|
{
|
|
global::umbraco.cms.businesslogic.packager.PackageAction
|
|
.UndoPackageAction(pack.Data.Name, n.Attributes["alias"].Value, n);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.Error<installedPackage>("An error occurred running undo actions", ex);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.Error<installedPackage>("An error occurred running undo actions", ex);
|
|
}
|
|
}
|
|
|
|
//moved remove of files here so custom package actions can still undo
|
|
//Remove files
|
|
foreach (var item in pack.Data.Files.ToArray())
|
|
{
|
|
//here we need to try to find the file in question as most packages does not support the tilde char
|
|
var file = IOHelper.FindFile(item);
|
|
if (file != null)
|
|
{
|
|
var filePath = IOHelper.MapPath(file);
|
|
if (File.Exists(filePath))
|
|
{
|
|
File.Delete(filePath);
|
|
|
|
}
|
|
}
|
|
pack.Data.Files.Remove(file);
|
|
}
|
|
pack.Save();
|
|
pack.Delete(Security.GetUserId());
|
|
|
|
//TODO: Legacy - probably not needed
|
|
if (refreshCache)
|
|
{
|
|
library.RefreshContent();
|
|
}
|
|
TreeDefinitionCollection.Instance.ReRegisterTrees();
|
|
global::umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers();
|
|
}
|
|
|
|
|
|
public IEnumerable<InstalledPackageModel> GetInstalled()
|
|
{
|
|
return data.GetAllPackages(IOHelper.MapPath(Settings.InstalledPackagesSettings))
|
|
.Select(pack => new InstalledPackageModel
|
|
{
|
|
Name = pack.Name,
|
|
Id = pack.Id,
|
|
Author = pack.Author,
|
|
Version = pack.Version,
|
|
Url = pack.Url,
|
|
License = pack.License,
|
|
LicenseUrl = pack.LicenseUrl
|
|
}).ToList();
|
|
}
|
|
|
|
[HttpPost]
|
|
[FileUploadCleanupFilter(false)]
|
|
public async Task<LocalPackageInstallModel> UploadLocalPackage()
|
|
{
|
|
if (Request.Content.IsMimeMultipartContent() == false)
|
|
{
|
|
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
|
|
}
|
|
|
|
var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads");
|
|
//ensure it exists
|
|
Directory.CreateDirectory(root);
|
|
var provider = new MultipartFormDataStreamProvider(root);
|
|
|
|
var result = await Request.Content.ReadAsMultipartAsync(provider);
|
|
|
|
//must have a file
|
|
if (result.FileData.Count == 0)
|
|
{
|
|
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
|
|
}
|
|
|
|
//TODO: App/Tree Permissions?
|
|
var model = new LocalPackageInstallModel
|
|
{
|
|
PackageGuid = Guid.NewGuid()
|
|
};
|
|
|
|
//get the files
|
|
foreach (var file in result.FileData)
|
|
{
|
|
var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' });
|
|
var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower();
|
|
|
|
//TODO: Only allow .zip
|
|
if (ext.InvariantEquals("zip") || ext.InvariantEquals("umb"))
|
|
{
|
|
//TODO: Currently it has to be here, it's not ideal but that's the way it is right now
|
|
var packageTempDir = IOHelper.MapPath(SystemDirectories.Data);
|
|
|
|
//ensure it's there
|
|
Directory.CreateDirectory(packageTempDir);
|
|
|
|
//copy it - must always be '.umb' for the installer thing to work
|
|
//the file name must be a GUID - this is what the packager expects (strange yes)
|
|
//because essentially this is creating a temporary package Id that will be used
|
|
//for unpacking/installing/etc...
|
|
var packageTempFileName = model.PackageGuid + ".umb";
|
|
var packageTempFileLocation = Path.Combine(packageTempDir, packageTempFileName);
|
|
File.Copy(file.LocalFileName, packageTempFileLocation, true);
|
|
|
|
try
|
|
{
|
|
var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id);
|
|
//this will load in all the metadata too
|
|
var tempDir = ins.Import(packageTempFileName);
|
|
model.TemporaryDirectoryPath = Path.Combine(SystemDirectories.Data, tempDir);
|
|
model.Id = ins.CreateManifest(
|
|
IOHelper.MapPath(model.TemporaryDirectoryPath),
|
|
model.PackageGuid.ToString(),
|
|
//TODO: Does this matter? we're installing a local package
|
|
string.Empty);
|
|
|
|
model.Name = ins.Name;
|
|
model.Author = ins.Author;
|
|
model.AuthorUrl = ins.AuthorUrl;
|
|
model.License = ins.License;
|
|
model.LicenseUrl = ins.LicenseUrl;
|
|
model.ReadMe = ins.ReadMe;
|
|
model.ConflictingMacroAliases = ins.ConflictingMacroAliases;
|
|
model.ConflictingStyleSheetNames = ins.ConflictingStyleSheetNames;
|
|
model.ConflictingTemplateAliases = ins.ConflictingTemplateAliases;
|
|
model.ContainsBinaryFileErrors = ins.ContainsBinaryFileErrors;
|
|
model.ContainsLegacyPropertyEditors = ins.ContainsLegacyPropertyEditors;
|
|
model.ContainsMacroConflict = ins.ContainsMacroConflict;
|
|
model.ContainsStyleSheetConflicts = ins.ContainsStyleSheeConflicts;
|
|
model.ContainsTemplateConflicts = ins.ContainsTemplateConflicts;
|
|
model.ContainsUnsecureFiles = ins.ContainsUnsecureFiles;
|
|
|
|
model.Url = ins.Url;
|
|
model.Version = ins.Version;
|
|
//TODO: We need to add the 'strict' requirement to the installer
|
|
}
|
|
finally
|
|
{
|
|
//Cleanup file
|
|
if (File.Exists(packageTempFileLocation))
|
|
{
|
|
File.Delete(packageTempFileLocation);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
model.Notifications.Add(new Notification(
|
|
Services.TextService.Localize("speechBubbles/operationFailedHeader"),
|
|
Services.TextService.Localize("media/disallowedFileType"),
|
|
SpeechBubbleIcon.Warning));
|
|
}
|
|
|
|
}
|
|
|
|
return model;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the package from Our to install
|
|
/// </summary>
|
|
/// <param name="packageGuid"></param>
|
|
/// <returns></returns>
|
|
[HttpGet]
|
|
public PackageInstallModel Fetch(string packageGuid)
|
|
{
|
|
//Default path
|
|
string path = Path.Combine("packages", packageGuid + ".umb");
|
|
if (File.Exists(IOHelper.MapPath(Path.Combine(SystemDirectories.Data, path))) == false)
|
|
{
|
|
//our repo guid
|
|
using (var our = Repository.getByGuid("65194810-1f85-11dd-bd0b-0800200c9a66"))
|
|
{
|
|
path = our.fetch(packageGuid, Security.CurrentUser.Id);
|
|
}
|
|
}
|
|
|
|
var p = new PackageInstallModel();
|
|
p.PackageGuid = Guid.Parse(packageGuid);
|
|
p.RepositoryGuid = Guid.Parse("65194810-1f85-11dd-bd0b-0800200c9a66");
|
|
p.ZipFilePath = path;
|
|
//p.ZipFilePath = Path.Combine("temp", "package.umb");
|
|
|
|
return p;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts the package zip and gets the packages information
|
|
/// </summary>
|
|
/// <param name="model"></param>
|
|
/// <returns></returns>
|
|
[HttpPost]
|
|
public PackageInstallModel Import(PackageInstallModel model)
|
|
{
|
|
var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id);
|
|
model.TemporaryDirectoryPath = Path.Combine(SystemDirectories.Data, ins.Import(model.ZipFilePath));
|
|
model.Id = ins.CreateManifest( IOHelper.MapPath(model.TemporaryDirectoryPath), model.PackageGuid.ToString(), model.RepositoryGuid.ToString());
|
|
|
|
return model;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Installs the package files
|
|
/// </summary>
|
|
/// <param name="model"></param>
|
|
/// <returns></returns>
|
|
[HttpPost]
|
|
public PackageInstallModel InstallFiles(PackageInstallModel model)
|
|
{
|
|
var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id);
|
|
ins.LoadConfig(IOHelper.MapPath(model.TemporaryDirectoryPath));
|
|
ins.InstallFiles(model.Id, IOHelper.MapPath(model.TemporaryDirectoryPath));
|
|
return model;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Installs the packages data/business logic
|
|
/// </summary>
|
|
/// <param name="model"></param>
|
|
/// <returns></returns>
|
|
[HttpPost]
|
|
public PackageInstallModel InstallData(PackageInstallModel model)
|
|
{
|
|
var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id);
|
|
ins.LoadConfig(IOHelper.MapPath(model.TemporaryDirectoryPath));
|
|
ins.InstallBusinessLogic(model.Id, IOHelper.MapPath(model.TemporaryDirectoryPath));
|
|
return model;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans up the package installation
|
|
/// </summary>
|
|
/// <param name="model"></param>
|
|
/// <returns></returns>
|
|
[HttpPost]
|
|
public PackageInstallModel CleanUp(PackageInstallModel model)
|
|
{
|
|
var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id);
|
|
ins.LoadConfig(IOHelper.MapPath(model.TemporaryDirectoryPath));
|
|
ins.InstallCleanUp(model.Id, IOHelper.MapPath(model.TemporaryDirectoryPath));
|
|
|
|
var clientDependencyConfig = new Umbraco.Core.Configuration.ClientDependencyConfiguration(ApplicationContext.ProfilingLogger.Logger);
|
|
var clientDependencyUpdated = clientDependencyConfig.IncreaseVersionNumber();
|
|
|
|
//clear the tree cache - we'll do this here even though the browser will reload, but just in case it doesn't can't hurt.
|
|
//these bits are super old, but cant find another way to do this currently
|
|
global::umbraco.cms.presentation.Trees.TreeDefinitionCollection.Instance.ReRegisterTrees();
|
|
global::umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers();
|
|
|
|
|
|
return model;
|
|
}
|
|
|
|
|
|
}
|
|
}
|