using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Xml.XPath; using Examine; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Extensions; using Umbraco.Web.Composing; namespace Umbraco.Tests.LegacyXmlPublishedCache { /// /// An IPublishedContent that is represented all by a dictionary. /// /// /// This is a helper class and definitely not intended for public use, it expects that all of the values required /// to create an IPublishedContent exist in the dictionary by specific aliases. /// internal class DictionaryPublishedContent : PublishedContentBase { // note: I'm not sure this class fully complies with IPublishedContent rules especially // I'm not sure that _properties contains all properties including those without a value, // neither that GetProperty will return a property without a value vs. null... @zpqrtbnk // List of properties that will appear in the XML and do not match // anything in the ContentType, so they must be ignored. private static readonly string[] IgnoredKeys = { "version", "isDoc" }; public DictionaryPublishedContent( IReadOnlyDictionary valueDictionary, Func getParent, Func> getChildren, Func getProperty, IAppCache appCache, IVariationContextAccessor variationContextAccessor, PublishedContentTypeCache contentTypeCache, XPathNavigator nav, bool fromExamine):base(variationContextAccessor) { if (valueDictionary == null) throw new ArgumentNullException(nameof(valueDictionary)); if (getParent == null) throw new ArgumentNullException(nameof(getParent)); if (getProperty == null) throw new ArgumentNullException(nameof(getProperty)); _getParent = new Lazy(() => getParent(ParentId)); _getChildren = new Lazy>(() => getChildren(Id, nav)); _getProperty = getProperty; _appCache = appCache; LoadedFromExamine = fromExamine; ValidateAndSetProperty(valueDictionary, val => _id = Int32.Parse(val), "id", "nodeId", "__NodeId"); //should validate the int! ValidateAndSetProperty(valueDictionary, val => _key = Guid.Parse(val), "key", "__key", "__Key"); //ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId"); ValidateAndSetProperty(valueDictionary, val => _sortOrder = Int32.Parse(val), "sortOrder"); ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName"); ValidateAndSetProperty(valueDictionary, val => _urlName = val, "urlName"); ValidateAndSetProperty(valueDictionary, val => _documentTypeAlias = val, "nodeTypeAlias", ExamineFieldNames.ItemTypeFieldName); ValidateAndSetProperty(valueDictionary, val => _documentTypeId = Int32.Parse(val), "nodeType"); //ValidateAndSetProperty(valueDictionary, val => _writerId = int.Parse(val), "writerID"); ValidateAndSetProperty(valueDictionary, val => _creatorId = Int32.Parse(val), "creatorID", "writerID"); //this is a bit of a hack fix for: U4-1132 ValidateAndSetProperty(valueDictionary, val => _path = val, "path", "__Path"); ValidateAndSetProperty(valueDictionary, val => _createDate = ParseDateTimeValue(val), "createDate"); ValidateAndSetProperty(valueDictionary, val => _updateDate = ParseDateTimeValue(val), "updateDate"); ValidateAndSetProperty(valueDictionary, val => _level = Int32.Parse(val), "level"); ValidateAndSetProperty(valueDictionary, val => { int pId; ParentId = -1; if (Int32.TryParse(val, out pId)) { ParentId = pId; } }, "parentID"); _contentType = contentTypeCache.Get(PublishedItemType.Media, _documentTypeAlias); _properties = new Collection(); //handle content type properties //make sure we create them even if there's no value foreach (var propertyType in _contentType.PropertyTypes) { var alias = propertyType.Alias; _keysAdded.Add(alias); string value; const bool isPreviewing = false; // false :: never preview a media var property = valueDictionary.TryGetValue(alias, out value) == false || value == null ? new XmlPublishedProperty(propertyType, this, isPreviewing) : new XmlPublishedProperty(propertyType, this, isPreviewing, value); _properties.Add(property); } //loop through remaining values that haven't been applied foreach (var i in valueDictionary.Where(x => _keysAdded.Contains(x.Key) == false // not already processed && IgnoredKeys.Contains(x.Key) == false)) // not ignorable { if (i.Key.InvariantStartsWith("__")) { // no type for that one, dunno how to convert, drop it //IPublishedProperty property = new PropertyResult(i.Key, i.Value, PropertyResultType.CustomProperty); //_properties.Add(property); } else { // this is a property that does not correspond to anything, ignore and log Current.Logger.LogWarning("Dropping property '{PropertyKey}' because it does not belong to the content type.", i.Key); } } } private DateTime ParseDateTimeValue(string val) { if (LoadedFromExamine == false) return DateTime.Parse(val); //we need to parse the date time using Lucene converters var ticks = Int64.Parse(val); return new DateTime(ticks); } /// /// Flag to get/set if this was loaded from examine cache /// internal bool LoadedFromExamine { get; } //private readonly Func _getParent; private readonly Lazy _getParent; //private readonly Func> _getChildren; private readonly Lazy> _getChildren; private readonly Func _getProperty; private readonly IAppCache _appCache; /// /// Returns 'Media' as the item type /// public override PublishedItemType ItemType => PublishedItemType.Media; public override IPublishedContent Parent => _getParent.Value; public int ParentId { get; private set; } public override int Id => _id; public override Guid Key => _key; public override int? TemplateId => null; public override int SortOrder => _sortOrder; public override string Name => _name; private static readonly Lazy> NoCultures = new Lazy>(() => new Dictionary()); public override IReadOnlyDictionary Cultures => NoCultures.Value; public override string UrlSegment => _urlName; public override int WriterId => _creatorId; public override int CreatorId => _creatorId; public override string Path => _path; public override DateTime CreateDate => _createDate; public override DateTime UpdateDate => _updateDate; public override int Level => _level; public override bool IsDraft(string culture = null) => false; public override bool IsPublished(string culture = null) => true; public override IEnumerable Properties => _properties; public override IEnumerable Children => _getChildren.Value; public override IEnumerable ChildrenForAllCultures => Children; public override IPublishedProperty GetProperty(string alias) { return _getProperty(this, alias); } public override IPublishedContentType ContentType => _contentType; private readonly List _keysAdded = new List(); private int _id; private Guid _key; //private int _templateId; private int _sortOrder; private string _name; private string _urlName; private string _documentTypeAlias; private int _documentTypeId; //private int _writerId; private int _creatorId; private string _path; private DateTime _createDate; private DateTime _updateDate; //private Guid _version; private int _level; private readonly ICollection _properties; private readonly IPublishedContentType _contentType; private void ValidateAndSetProperty(IReadOnlyDictionary valueDictionary, Action setProperty, params string[] potentialKeys) { var key = potentialKeys.FirstOrDefault(x => valueDictionary.ContainsKey(x) && valueDictionary[x] != null); if (key == null) { throw new FormatException("The valueDictionary is not formatted correctly and is missing any of the '" + String.Join(",", potentialKeys) + "' elements"); } setProperty(valueDictionary[key]); _keysAdded.Add(key); } } }