626 lines
28 KiB
C#
626 lines
28 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
using Umbraco.Core.Configuration;
|
|
using Umbraco.Core.IO;
|
|
using Umbraco.Core.Logging;
|
|
using Umbraco.Core.Models;
|
|
using Umbraco.Core.Models.Packaging;
|
|
using Umbraco.Core.Services;
|
|
using File = System.IO.File;
|
|
|
|
namespace Umbraco.Core.Packaging
|
|
{
|
|
/// <summary>
|
|
/// Manages the storage of installed/created package definitions
|
|
/// </summary>
|
|
internal class PackagesRepository : ICreatedPackagesRepository, IInstalledPackagesRepository
|
|
{
|
|
private readonly IContentService _contentService;
|
|
private readonly IContentTypeService _contentTypeService;
|
|
private readonly IDataTypeService _dataTypeService;
|
|
private readonly IFileService _fileService;
|
|
private readonly IMacroService _macroService;
|
|
private readonly ILocalizationService _languageService;
|
|
private readonly IEntityXmlSerializer _serializer;
|
|
private readonly ILogger _logger;
|
|
private readonly string _packageRepositoryFileName;
|
|
private readonly string _mediaFolderPath;
|
|
private readonly string _packagesFolderPath;
|
|
private readonly string _tempFolderPath;
|
|
private readonly PackageDefinitionXmlParser _parser;
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="contentService"></param>
|
|
/// <param name="contentTypeService"></param>
|
|
/// <param name="dataTypeService"></param>
|
|
/// <param name="fileService"></param>
|
|
/// <param name="macroService"></param>
|
|
/// <param name="languageService"></param>
|
|
/// <param name="serializer"></param>
|
|
/// <param name="logger"></param>
|
|
/// <param name="packageRepositoryFileName">
|
|
/// The file name for storing the package definitions (i.e. "createdPackages.config")
|
|
/// </param>
|
|
/// <param name="tempFolderPath"></param>
|
|
/// <param name="packagesFolderPath"></param>
|
|
/// <param name="mediaFolderPath"></param>
|
|
public PackagesRepository(IContentService contentService, IContentTypeService contentTypeService,
|
|
IDataTypeService dataTypeService, IFileService fileService, IMacroService macroService,
|
|
ILocalizationService languageService,
|
|
IEntityXmlSerializer serializer, ILogger logger,
|
|
string packageRepositoryFileName,
|
|
string tempFolderPath = null, string packagesFolderPath = null, string mediaFolderPath = null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(packageRepositoryFileName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(packageRepositoryFileName));
|
|
_contentService = contentService;
|
|
_contentTypeService = contentTypeService;
|
|
_dataTypeService = dataTypeService;
|
|
_fileService = fileService;
|
|
_macroService = macroService;
|
|
_languageService = languageService;
|
|
_serializer = serializer;
|
|
_logger = logger;
|
|
_packageRepositoryFileName = packageRepositoryFileName;
|
|
|
|
_tempFolderPath = tempFolderPath ?? SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles";
|
|
_packagesFolderPath = packagesFolderPath ?? SystemDirectories.Packages;
|
|
_mediaFolderPath = mediaFolderPath ?? SystemDirectories.Media + "/created-packages";
|
|
|
|
_parser = new PackageDefinitionXmlParser(logger);
|
|
}
|
|
|
|
private string CreatedPackagesFile => _packagesFolderPath.EnsureEndsWith('/') + _packageRepositoryFileName;
|
|
|
|
public IEnumerable<PackageDefinition> GetAll()
|
|
{
|
|
var packagesXml = EnsureStorage(out _);
|
|
if (packagesXml?.Root == null)
|
|
yield break;;
|
|
|
|
foreach (var packageXml in packagesXml.Root.Elements("package"))
|
|
yield return _parser.ToPackageDefinition(packageXml);
|
|
}
|
|
|
|
public PackageDefinition GetById(int id)
|
|
{
|
|
var packagesXml = EnsureStorage(out _);
|
|
var packageXml = packagesXml?.Root?.Elements("package").FirstOrDefault(x => x.AttributeValue<int>("id") == id);
|
|
return packageXml == null ? null : _parser.ToPackageDefinition(packageXml);
|
|
}
|
|
|
|
public void Delete(int id)
|
|
{
|
|
var packagesXml = EnsureStorage(out var packagesFile);
|
|
var packageXml = packagesXml?.Root?.Elements("package").FirstOrDefault(x => x.AttributeValue<int>("id") == id);
|
|
if (packageXml == null) return;
|
|
|
|
packageXml.Remove();
|
|
|
|
packagesXml.Save(packagesFile);
|
|
}
|
|
|
|
public bool SavePackage(PackageDefinition definition)
|
|
{
|
|
if (definition == null) throw new ArgumentNullException(nameof(definition));
|
|
|
|
var packagesXml = EnsureStorage(out var packagesFile);
|
|
|
|
if (packagesXml?.Root == null)
|
|
return false;
|
|
|
|
//ensure it's valid
|
|
ValidatePackage(definition);
|
|
|
|
if (definition.Id == default)
|
|
{
|
|
//need to gen an id and persist
|
|
// Find max id
|
|
var maxId = packagesXml.Root.Elements("package").Max(x => x.AttributeValue<int?>("id")) ?? 0;
|
|
var newId = maxId + 1;
|
|
definition.Id = newId;
|
|
definition.PackageId = definition.PackageId == default ? Guid.NewGuid() : definition.PackageId;
|
|
var packageXml = _parser.ToXml(definition);
|
|
packagesXml.Root.Add(packageXml);
|
|
}
|
|
else
|
|
{
|
|
//existing
|
|
var packageXml = packagesXml.Root.Elements("package").FirstOrDefault(x => x.AttributeValue<int>("id") == definition.Id);
|
|
if (packageXml == null)
|
|
return false;
|
|
|
|
var updatedXml = _parser.ToXml(definition);
|
|
packageXml.ReplaceWith(updatedXml);
|
|
}
|
|
|
|
packagesXml.Save(packagesFile);
|
|
|
|
return true;
|
|
}
|
|
|
|
public string ExportPackage(PackageDefinition definition)
|
|
{
|
|
if (definition.Id == default) throw new ArgumentException("The package definition does not have an ID, it must be saved before being exported");
|
|
if (definition.PackageId == default) throw new ArgumentException("the package definition does not have a GUID, it must be saved before being exported");
|
|
|
|
//ensure it's valid
|
|
ValidatePackage(definition);
|
|
|
|
//Create a folder for building this package
|
|
var temporaryPath = IOHelper.MapPath(_tempFolderPath.EnsureEndsWith('/') + Guid.NewGuid());
|
|
if (Directory.Exists(temporaryPath) == false)
|
|
Directory.CreateDirectory(temporaryPath);
|
|
|
|
try
|
|
{
|
|
//Init package file
|
|
var compiledPackageXml = CreateCompiledPackageXml(out var root, out var filesXml);
|
|
|
|
//Info section
|
|
root.Add(GetPackageInfoXml(definition));
|
|
|
|
PackageDocumentsAndTags(definition, root);
|
|
PackageDocumentTypes(definition, root);
|
|
PackageTemplates(definition, root);
|
|
PackageStylesheets(definition, root);
|
|
PackageMacros(definition, root, filesXml, temporaryPath);
|
|
PackageDictionaryItems(definition, root);
|
|
PackageLanguages(definition, root);
|
|
PackageDataTypes(definition, root);
|
|
|
|
//Files
|
|
foreach (var fileName in definition.Files)
|
|
AppendFileToPackage(fileName, temporaryPath, filesXml);
|
|
|
|
//Load view on install...
|
|
if (!string.IsNullOrEmpty(definition.PackageView))
|
|
{
|
|
var control = new XElement("view", definition.PackageView);
|
|
AppendFileToPackage(definition.PackageView, temporaryPath, filesXml);
|
|
root.Add(control);
|
|
}
|
|
|
|
//Actions
|
|
if (string.IsNullOrEmpty(definition.Actions) == false)
|
|
{
|
|
var actionsXml = new XElement("Actions");
|
|
try
|
|
{
|
|
//this will be formatted like a full xml block like <actions>...</actions> and we want the child nodes
|
|
var parsed = XElement.Parse(definition.Actions);
|
|
actionsXml.Add(parsed.Elements());
|
|
root.Add(actionsXml);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.Warn<PackagesRepository>(e, "Could not add package actions to the package, the xml did not parse");
|
|
}
|
|
}
|
|
|
|
var packageXmlFileName = temporaryPath + "/package.xml";
|
|
|
|
if (File.Exists(packageXmlFileName))
|
|
File.Delete(packageXmlFileName);
|
|
|
|
compiledPackageXml.Save(packageXmlFileName);
|
|
|
|
// check if there's a packages directory below media
|
|
|
|
if (Directory.Exists(IOHelper.MapPath(_mediaFolderPath)) == false)
|
|
Directory.CreateDirectory(IOHelper.MapPath(_mediaFolderPath));
|
|
|
|
var packPath = _mediaFolderPath.EnsureEndsWith('/') + (definition.Name + "_" + definition.Version).Replace(' ', '_') + ".zip";
|
|
ZipPackage(temporaryPath, IOHelper.MapPath(packPath));
|
|
|
|
//we need to update the package path and save it
|
|
definition.PackagePath = packPath;
|
|
SavePackage(definition);
|
|
|
|
return packPath;
|
|
}
|
|
finally
|
|
{
|
|
//Clean up
|
|
Directory.Delete(temporaryPath, true);
|
|
}
|
|
}
|
|
|
|
private void ValidatePackage(PackageDefinition definition)
|
|
{
|
|
//ensure it's valid
|
|
var context = new ValidationContext(definition, serviceProvider: null, items: null);
|
|
var results = new List<ValidationResult>();
|
|
var isValid = Validator.TryValidateObject(definition, context, results);
|
|
if (!isValid)
|
|
throw new InvalidOperationException("Validation failed, there is invalid data on the model: " + string.Join(", ", results.Select(x => x.ErrorMessage)));
|
|
}
|
|
|
|
private void PackageDataTypes(PackageDefinition definition, XContainer root)
|
|
{
|
|
var dataTypes = new XElement("DataTypes");
|
|
foreach (var dtId in definition.DataTypes)
|
|
{
|
|
if (!int.TryParse(dtId, out var outInt)) continue;
|
|
var dataType = _dataTypeService.GetDataType(outInt);
|
|
if (dataType == null) continue;
|
|
dataTypes.Add(_serializer.Serialize(dataType));
|
|
}
|
|
root.Add(dataTypes);
|
|
}
|
|
|
|
private void PackageLanguages(PackageDefinition definition, XContainer root)
|
|
{
|
|
var languages = new XElement("Languages");
|
|
foreach (var langId in definition.Languages)
|
|
{
|
|
if (!int.TryParse(langId, out var outInt)) continue;
|
|
var lang = _languageService.GetLanguageById(outInt);
|
|
if (lang == null) continue;
|
|
languages.Add(_serializer.Serialize(lang));
|
|
}
|
|
root.Add(languages);
|
|
}
|
|
|
|
private void PackageDictionaryItems(PackageDefinition definition, XContainer root)
|
|
{
|
|
var dictionaryItems = new XElement("DictionaryItems");
|
|
foreach (var dictionaryId in definition.DictionaryItems)
|
|
{
|
|
if (!int.TryParse(dictionaryId, out var outInt)) continue;
|
|
var di = _languageService.GetDictionaryItemById(outInt);
|
|
if (di == null) continue;
|
|
dictionaryItems.Add(_serializer.Serialize(di, false));
|
|
}
|
|
root.Add(dictionaryItems);
|
|
}
|
|
|
|
private void PackageMacros(PackageDefinition definition, XContainer root, XContainer filesXml, string temporaryPath)
|
|
{
|
|
var macros = new XElement("Macros");
|
|
foreach (var macroId in definition.Macros)
|
|
{
|
|
if (!int.TryParse(macroId, out var outInt)) continue;
|
|
|
|
var macroXml = GetMacroXml(outInt, out var macro);
|
|
if (macroXml == null) continue;
|
|
macros.Add(macroXml);
|
|
//if the macro has a file copy it to the xml
|
|
if (!string.IsNullOrEmpty(macro.MacroSource))
|
|
AppendFileToPackage(macro.MacroSource, temporaryPath, filesXml);
|
|
}
|
|
root.Add(macros);
|
|
}
|
|
|
|
private void PackageStylesheets(PackageDefinition definition, XContainer root)
|
|
{
|
|
var stylesheetsXml = new XElement("Stylesheets");
|
|
foreach (var stylesheetName in definition.Stylesheets)
|
|
{
|
|
if (stylesheetName.IsNullOrWhiteSpace()) continue;
|
|
var xml = GetStylesheetXml(stylesheetName, true);
|
|
if (xml != null)
|
|
stylesheetsXml.Add(xml);
|
|
}
|
|
root.Add(stylesheetsXml);
|
|
}
|
|
|
|
private void PackageTemplates(PackageDefinition definition, XContainer root)
|
|
{
|
|
var templatesXml = new XElement("Templates");
|
|
foreach (var templateId in definition.Templates)
|
|
{
|
|
if (!int.TryParse(templateId, out var outInt)) continue;
|
|
var template = _fileService.GetTemplate(outInt);
|
|
if (template == null) continue;
|
|
templatesXml.Add(_serializer.Serialize(template));
|
|
}
|
|
root.Add(templatesXml);
|
|
}
|
|
|
|
private void PackageDocumentTypes(PackageDefinition definition, XContainer root)
|
|
{
|
|
var contentTypes = new HashSet<IContentType>();
|
|
var docTypesXml = new XElement("DocumentTypes");
|
|
foreach (var dtId in definition.DocumentTypes)
|
|
{
|
|
if (!int.TryParse(dtId, out var outInt)) continue;
|
|
var contentType = _contentTypeService.Get(outInt);
|
|
if (contentType == null) continue;
|
|
AddDocumentType(contentType, contentTypes);
|
|
}
|
|
foreach (var contentType in contentTypes)
|
|
docTypesXml.Add(_serializer.Serialize(contentType));
|
|
|
|
root.Add(docTypesXml);
|
|
}
|
|
|
|
private void PackageDocumentsAndTags(PackageDefinition definition, XContainer root)
|
|
{
|
|
//Documents and tags
|
|
if (string.IsNullOrEmpty(definition.ContentNodeId) == false && int.TryParse(definition.ContentNodeId, out var contentNodeId))
|
|
{
|
|
if (contentNodeId > 0)
|
|
{
|
|
//load content from umbraco.
|
|
var content = _contentService.GetById(contentNodeId);
|
|
if (content != null)
|
|
{
|
|
var contentXml = definition.ContentLoadChildNodes ? content.ToDeepXml(_serializer) : content.ToXml(_serializer);
|
|
|
|
//Create the Documents/DocumentSet node
|
|
|
|
root.Add(
|
|
new XElement("Documents",
|
|
new XElement("DocumentSet",
|
|
new XAttribute("importMode", "root"),
|
|
contentXml)));
|
|
|
|
// TODO: I guess tags has been broken for a very long time for packaging, we should get this working again sometime
|
|
////Create the TagProperties node - this is used to store a definition for all
|
|
//// document properties that are tags, this ensures that we can re-import tags properly
|
|
//XmlNode tagProps = new XElement("TagProperties");
|
|
|
|
////before we try to populate this, we'll do a quick lookup to see if any of the documents
|
|
//// being exported contain published tags.
|
|
//var allExportedIds = documents.SelectNodes("//@id").Cast<XmlNode>()
|
|
// .Select(x => x.Value.TryConvertTo<int>())
|
|
// .Where(x => x.Success)
|
|
// .Select(x => x.Result)
|
|
// .ToArray();
|
|
//var allContentTags = new List<ITag>();
|
|
//foreach (var exportedId in allExportedIds)
|
|
//{
|
|
// allContentTags.AddRange(
|
|
// Current.Services.TagService.GetTagsForEntity(exportedId));
|
|
//}
|
|
|
|
////This is pretty round-about but it works. Essentially we need to get the properties that are tagged
|
|
//// but to do that we need to lookup by a tag (string)
|
|
//var allTaggedEntities = new List<TaggedEntity>();
|
|
//foreach (var group in allContentTags.Select(x => x.Group).Distinct())
|
|
//{
|
|
// allTaggedEntities.AddRange(
|
|
// Current.Services.TagService.GetTaggedContentByTagGroup(group));
|
|
//}
|
|
|
|
////Now, we have all property Ids/Aliases and their referenced document Ids and tags
|
|
//var allExportedTaggedEntities = allTaggedEntities.Where(x => allExportedIds.Contains(x.EntityId))
|
|
// .DistinctBy(x => x.EntityId)
|
|
// .OrderBy(x => x.EntityId);
|
|
|
|
//foreach (var taggedEntity in allExportedTaggedEntities)
|
|
//{
|
|
// foreach (var taggedProperty in taggedEntity.TaggedProperties.Where(x => x.Tags.Any()))
|
|
// {
|
|
// XmlNode tagProp = new XElement("TagProperty");
|
|
// var docId = packageManifest.CreateAttribute("docId", "");
|
|
// docId.Value = taggedEntity.EntityId.ToString(CultureInfo.InvariantCulture);
|
|
// tagProp.Attributes.Append(docId);
|
|
|
|
// var propertyAlias = packageManifest.CreateAttribute("propertyAlias", "");
|
|
// propertyAlias.Value = taggedProperty.PropertyTypeAlias;
|
|
// tagProp.Attributes.Append(propertyAlias);
|
|
|
|
// var group = packageManifest.CreateAttribute("group", "");
|
|
// group.Value = taggedProperty.Tags.First().Group;
|
|
// tagProp.Attributes.Append(group);
|
|
|
|
// tagProp.AppendChild(packageManifest.CreateCDataSection(
|
|
// JsonConvert.SerializeObject(taggedProperty.Tags.Select(x => x.Text).ToArray())));
|
|
|
|
// tagProps.AppendChild(tagProp);
|
|
// }
|
|
//}
|
|
|
|
//manifestRoot.Add(tagProps);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Zips the package.
|
|
/// </summary>
|
|
/// <param name="path">The path.</param>
|
|
/// <param name="savePath">The save path.</param>
|
|
private static void ZipPackage(string path, string savePath)
|
|
{
|
|
if (File.Exists(savePath))
|
|
File.Delete(savePath);
|
|
ZipFile.CreateFromDirectory(path, savePath);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends a file to package and copies the file to the correct folder.
|
|
/// </summary>
|
|
/// <param name="path">The path.</param>
|
|
/// <param name="packageDirectory">The package directory.</param>
|
|
/// <param name="filesXml">The files xml node</param>
|
|
private static void AppendFileToPackage(string path, string packageDirectory, XContainer filesXml)
|
|
{
|
|
if (!path.StartsWith("~/") && !path.StartsWith("/"))
|
|
path = "~/" + path;
|
|
|
|
var serverPath = IOHelper.MapPath(path);
|
|
|
|
if (File.Exists(serverPath))
|
|
AppendFileXml(new FileInfo(serverPath), path, packageDirectory, filesXml);
|
|
else if (Directory.Exists(serverPath))
|
|
ProcessDirectory(new DirectoryInfo(serverPath), path, packageDirectory, filesXml);
|
|
}
|
|
|
|
//Process files in directory and add them to package
|
|
private static void ProcessDirectory(DirectoryInfo directory, string dirPath, string packageDirectory, XContainer filesXml)
|
|
{
|
|
if (directory == null) throw new ArgumentNullException(nameof(directory));
|
|
if (string.IsNullOrWhiteSpace(packageDirectory)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(packageDirectory));
|
|
if (string.IsNullOrWhiteSpace(dirPath)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(dirPath));
|
|
if (!directory.Exists) return;
|
|
|
|
foreach (var file in directory.GetFiles())
|
|
AppendFileXml(new FileInfo(Path.Combine(directory.FullName, file.Name)), dirPath + "/" + file.Name, packageDirectory, filesXml);
|
|
|
|
foreach (var dir in directory.GetDirectories())
|
|
ProcessDirectory(dir, dirPath + "/" + dir.Name, packageDirectory, filesXml);
|
|
}
|
|
|
|
private static void AppendFileXml(FileInfo file, string filePath, string packageDirectory, XContainer filesXml)
|
|
{
|
|
if (file == null) throw new ArgumentNullException(nameof(file));
|
|
if (string.IsNullOrWhiteSpace(filePath)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(filePath));
|
|
if (string.IsNullOrWhiteSpace(packageDirectory)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(packageDirectory));
|
|
|
|
var orgPath = filePath.Substring(0, (filePath.LastIndexOf('/')));
|
|
var orgName = filePath.Substring((filePath.LastIndexOf('/') + 1));
|
|
var newFileName = orgName;
|
|
|
|
if (File.Exists(packageDirectory.EnsureEndsWith('/') + orgName))
|
|
newFileName = Guid.NewGuid() + "_" + newFileName;
|
|
|
|
//Copy file to directory for zipping...
|
|
File.Copy(file.FullName, packageDirectory + "/" + newFileName, true);
|
|
|
|
filesXml.Add(new XElement("file",
|
|
new XElement("guid", newFileName),
|
|
new XElement("orgPath", orgPath == "" ? "/" : orgPath),
|
|
new XElement("orgName", orgName)));
|
|
}
|
|
|
|
private XElement GetMacroXml(int macroId, out IMacro macro)
|
|
{
|
|
macro = _macroService.GetById(macroId);
|
|
if (macro == null) return null;
|
|
var xml = _serializer.Serialize(macro);
|
|
return xml;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a umbraco stylesheet to a package xml node
|
|
/// </summary>
|
|
/// <param name="name">The name of the stylesheet.</param>
|
|
/// <param name="includeProperties">if set to <c>true</c> [include properties].</param>
|
|
/// <returns></returns>
|
|
private XElement GetStylesheetXml(string name, bool includeProperties)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name));
|
|
;
|
|
var sts = _fileService.GetStylesheetByName(name);
|
|
if (sts == null) return null;
|
|
var stylesheetXml = new XElement("Stylesheet");
|
|
stylesheetXml.Add(new XElement("Name", sts.Alias));
|
|
stylesheetXml.Add(new XElement("FileName", sts.Name));
|
|
stylesheetXml.Add(new XElement("Content", new XCData(sts.Content)));
|
|
|
|
if (!includeProperties) return stylesheetXml;
|
|
|
|
var properties = new XElement("Properties");
|
|
foreach (var ssP in sts.Properties)
|
|
{
|
|
var property = new XElement("Property");
|
|
property.Add(new XElement("Name", ssP.Name));
|
|
property.Add(new XElement("Alias", ssP.Alias));
|
|
property.Add(new XElement("Value", ssP.Value));
|
|
}
|
|
stylesheetXml.Add(properties);
|
|
return stylesheetXml;
|
|
}
|
|
|
|
private void AddDocumentType(IContentType dt, HashSet<IContentType> dtl)
|
|
{
|
|
if (dt.ParentId > 0)
|
|
{
|
|
var parent = _contentTypeService.Get(dt.ParentId);
|
|
if (parent != null) // could be a container
|
|
AddDocumentType(parent, dtl);
|
|
}
|
|
|
|
if (!dtl.Contains(dt))
|
|
dtl.Add(dt);
|
|
}
|
|
|
|
private static XElement GetPackageInfoXml(PackageDefinition definition)
|
|
{
|
|
var info = new XElement("info");
|
|
|
|
//Package info
|
|
var package = new XElement("package");
|
|
package.Add(new XElement("name", definition.Name));
|
|
package.Add(new XElement("version", definition.Version));
|
|
package.Add(new XElement("iconUrl", definition.IconUrl));
|
|
|
|
var license = new XElement("license", definition.License);
|
|
license.Add(new XAttribute("url", definition.LicenseUrl));
|
|
package.Add(license);
|
|
|
|
package.Add(new XElement("url", definition.Url));
|
|
|
|
var requirements = new XElement("requirements");
|
|
|
|
requirements.Add(new XElement("major", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Major.ToInvariantString() : definition.UmbracoVersion.Major.ToInvariantString()));
|
|
requirements.Add(new XElement("minor", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Minor.ToInvariantString() : definition.UmbracoVersion.Minor.ToInvariantString()));
|
|
requirements.Add(new XElement("patch", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Patch.ToInvariantString() : definition.UmbracoVersion.Build.ToInvariantString()));
|
|
|
|
if (definition.UmbracoVersion != null)
|
|
requirements.Add(new XAttribute("type", RequirementsType.Strict.ToString()));
|
|
|
|
package.Add(requirements);
|
|
info.Add(package);
|
|
|
|
// Author
|
|
var author = new XElement("author", "");
|
|
author.Add(new XElement("name", definition.Author));
|
|
author.Add(new XElement("website", definition.AuthorUrl));
|
|
info.Add(author);
|
|
|
|
// Contributors
|
|
var contributors = new XElement("contributors", "");
|
|
|
|
if (definition.Contributors != null && definition.Contributors.Any())
|
|
{
|
|
foreach (var contributor in definition.Contributors)
|
|
{
|
|
contributors.Add(new XElement("contributor", contributor));
|
|
}
|
|
}
|
|
|
|
info.Add(contributors);
|
|
|
|
info.Add(new XElement("readme", new XCData(definition.Readme)));
|
|
|
|
return info;
|
|
}
|
|
|
|
private static XDocument CreateCompiledPackageXml(out XElement root, out XElement files)
|
|
{
|
|
files = new XElement("files");
|
|
root = new XElement("umbPackage", files);
|
|
var compiledPackageXml = new XDocument(root);
|
|
return compiledPackageXml;
|
|
}
|
|
|
|
private XDocument EnsureStorage(out string packagesFile)
|
|
{
|
|
var packagesFolder = IOHelper.MapPath(_packagesFolderPath);
|
|
//ensure it exists
|
|
Directory.CreateDirectory(packagesFolder);
|
|
|
|
packagesFile = IOHelper.MapPath(CreatedPackagesFile);
|
|
if (!File.Exists(packagesFile))
|
|
{
|
|
var xml = new XDocument(new XElement("packages"));
|
|
xml.Save(packagesFile);
|
|
}
|
|
|
|
var packagesXml = XDocument.Load(packagesFile);
|
|
return packagesXml;
|
|
}
|
|
}
|
|
}
|