public object GetPropertyValue(PropertyInfo property, JToken elementData, CodeFirstResolvingContext context)
        {
            if (!typeof(IRichTextContent).IsAssignableFrom(property.PropertyType))
            {
                throw new InvalidOperationException($"Type of property {property.Name} must implement {nameof(IRichTextContent)} in order to receive rich text content.");
            }

            var element = ((JObject)elementData);

            if (element == null)
            {
                return(null);
            }

            var links = element.Property("links")?.Value;
            var value = element.Property("value")?.Value?.ToObject <string>();

            // Handle rich_text link resolution
            if (links != null && elementData != null && context.Client.ContentLinkUrlResolver != null)
            {
                value = new ContentLinkResolver(context.Client.ContentLinkUrlResolver).ResolveContentLinks(value, links);
            }

            var blocks = new List <IRichTextBlock>();

            var htmlInput = new HtmlParser().Parse(value);

            foreach (var block in htmlInput.Body.Children)
            {
                if (block.TagName?.Equals("object", StringComparison.OrdinalIgnoreCase) == true && block.GetAttribute("type") == "application/kenticocloud" && block.GetAttribute("data-type") == "item")
                {
                    var codename = block.GetAttribute("data-codename");
                    blocks.Add(new InlineContentItem {
                        ContentItem = context.GetModularContentItem(codename)
                    });
                }
                else if (block.TagName?.Equals("figure", StringComparison.OrdinalIgnoreCase) == true)
                {
                    var img = block.Children.FirstOrDefault(child => child.TagName?.Equals("img", StringComparison.OrdinalIgnoreCase) == true);
                    if (img != null)
                    {
                        blocks.Add(new InlineImage {
                            Src = img.GetAttribute("src"), AltText = img.GetAttribute("alt")
                        });
                    }
                }
                else
                {
                    blocks.Add(new HtmlContent {
                        Html = block.OuterHtml
                    });
                }
            }

            return(new RichTextContent
            {
                Blocks = blocks
            });
        }
Ejemplo n.º 2
0
        internal object GetContentItemModel(Type t, JToken item, JToken modularContent, Dictionary <string, object> processedItems = null, HashSet <RichTextContentElements> currentlyResolvedRichStrings = null)
        {
            processedItems = processedItems ?? new Dictionary <string, object>();
            currentlyResolvedRichStrings = currentlyResolvedRichStrings ?? new HashSet <RichTextContentElements>();

            var richTextPropertiesToBeProcessed = new List <PropertyInfo>();
            var system = item["system"].ToObject <ContentItemSystemAttributes>();

            if (t == typeof(object))
            {
                // Try to find a specific type
                t = TypeProvider?.GetType(system.Type);
                if (t == null)
                {
                    throw new Exception($"No corresponding CLR type found for the '{system.Type}' content type. Provide a correct implementation of '{nameof(ICodeFirstTypeProvider)}' to the '{nameof(TypeProvider)}' property.");
                }
            }

            object instance = Activator.CreateInstance(t);

            if (!processedItems.ContainsKey(system.Codename))
            {
                processedItems.Add(system.Codename, instance);
            }

            var elementsData = (JObject)item["elements"];

            if (elementsData == null)
            {
                throw new InvalidOperationException("Missing elements node in the content item data.");
            }

            var context = new CodeFirstResolvingContext
            {
                GetModularContentItem = (codename) =>
                {
                    var modularContentNode     = (JObject)modularContent;
                    var modularContentItemNode = modularContentNode.Properties().FirstOrDefault(p => p.Name == codename)?.First;
                    if (modularContentItemNode != null)
                    {
                        if (processedItems.ContainsKey(codename))
                        {
                            return(processedItems[codename]);
                        }
                        else
                        {
                            return(GetContentItemModel(typeof(object), modularContentItemNode, modularContent, processedItems));
                        }
                    }
                    return(null);
                },
                Client = _client,
            };

            foreach (var property in instance.GetType().GetProperties())
            {
                var propertyType = property.PropertyType;
                if (property.SetMethod != null)
                {
                    if (propertyType == typeof(ContentItemSystemAttributes))
                    {
                        // Handle the system metadata
                        if (system != null)
                        {
                            property.SetValue(instance, system);
                        }
                    }
                    else
                    {
                        object value = null;

                        var elementData  = (JObject)elementsData.Properties()?.FirstOrDefault(p => PropertyMapper.IsMatch(property, p.Name, system?.Type))?.Value;
                        var elementValue = elementData?.Property("value")?.Value;

                        var valueConverter = GetValueConverter(property);
                        if (valueConverter != null)
                        {
                            value = valueConverter.GetPropertyValue(property, elementData, context);
                        }
                        else if (propertyType == typeof(string))
                        {
                            value = elementValue?.ToObject <string>();
                            var links = elementData?.Property("links")?.Value;
                            var modularContentInRichText = elementData?.Property("modular_content")?.Value;

                            // Handle rich_text link resolution
                            if (links != null && elementValue != null && ContentLinkResolver != null)
                            {
                                value = ContentLinkResolver.ResolveContentLinks((string)value, links);
                            }

                            if (modularContentInRichText != null && elementValue != null && _client.InlineContentItemsProcessor != null)
                            {
                                // At this point it's clear it's richtext because it contains modular content
                                richTextPropertiesToBeProcessed.Add(property);
                            }
                        }
                        else if (propertyType == typeof(IEnumerable <MultipleChoiceOption>) ||
                                 propertyType == typeof(IEnumerable <Asset>) ||
                                 propertyType == typeof(IEnumerable <TaxonomyTerm>) ||
                                 propertyType.GetTypeInfo().IsValueType)
                        {
                            // Handle non-hierarchical fields
                            value = elementValue?.ToObject(propertyType);
                        }
                        else if (propertyType.GetTypeInfo().IsGenericType &&
                                 ((propertyType.GetInterfaces().Any(gt => gt.GetTypeInfo().IsGenericType&& gt.GetTypeInfo().GetGenericTypeDefinition() == typeof(ICollection <>)) && propertyType.GetTypeInfo().IsClass) ||
                                  propertyType.GetGenericTypeDefinition() == typeof(IEnumerable <>)))
                        {
                            // Handle modular content
                            var contentItemCodenames = elementValue?.ToObject <IEnumerable <string> >();

                            var modularContentNode = (JObject)modularContent;
                            var genericArgs        = propertyType.GetGenericArguments();

                            // Create a List<T> based on the generic parameter of the input type (IEnumerable<T> or derived types)
                            Type collectionType = propertyType.GetTypeInfo().IsInterface ? typeof(List <>).MakeGenericType(genericArgs) : propertyType;

                            object contentItems = Activator.CreateInstance(collectionType);

                            if (contentItemCodenames != null && contentItemCodenames.Any())
                            {
                                foreach (string codename in contentItemCodenames)
                                {
                                    var modularContentItemNode = modularContentNode.Properties().FirstOrDefault(p => p.Name == codename)?.First;

                                    if (modularContentItemNode != null)
                                    {
                                        object contentItem = null;
                                        if (processedItems.ContainsKey(codename))
                                        {
                                            // Avoid infinite recursion by re-using already processed content items
                                            contentItem = processedItems[codename];
                                        }
                                        else
                                        {
                                            if (genericArgs.First() == typeof(ContentItem))
                                            {
                                                contentItem = new ContentItem(modularContentItemNode, modularContentNode, _client);
                                            }
                                            else
                                            {
                                                contentItem = GetContentItemModel(genericArgs.First(), modularContentItemNode, modularContentNode, processedItems);
                                            }
                                            if (!processedItems.ContainsKey(codename))
                                            {
                                                processedItems.Add(codename, contentItem);
                                            }
                                        }

                                        // It certain that the instance is of the ICollection<> type at this point, we can call "Add"
                                        contentItems.GetType().GetMethod("Add").Invoke(contentItems, new[] { contentItem });
                                    }
                                }
                            }

                            value = contentItems;
                        }
                        if (value != null)
                        {
                            property.SetValue(instance, value);
                        }
                    }
                }
            }

            // Richtext elements need to be processed last, so in case of circular dependency, content items resolved by
            // resolvers would have all elements already processed
            foreach (var property in richTextPropertiesToBeProcessed)
            {
                var value       = property.GetValue(instance)?.ToString();
                var elementData = (JObject)elementsData.Properties()?.FirstOrDefault(p => PropertyMapper.IsMatch(property, p.Name, system?.Type))?.Value;

                var modularContentInRichText = elementData?.Property("modular_content")?.Value;

                var currentlyProcessedString = new RichTextContentElements()
                {
                    ContentItemCodeName     = system.Codename,
                    RichTextElementCodeName = property.Name
                };
                if (currentlyResolvedRichStrings.Contains(currentlyProcessedString))
                {
                    // If this element is already being processed it's necessary to to use it as is (with removed inline content items)
                    // otherwise resolving would be stuck in an infinite loop
                    value = RemoveInlineContentItems(value);
                }
                else
                {
                    currentlyResolvedRichStrings.Add(currentlyProcessedString);
                    value = ProcessInlineContentItems(modularContent, processedItems, value, modularContentInRichText, currentlyResolvedRichStrings);
                    currentlyResolvedRichStrings.Remove(currentlyProcessedString);
                }
                if (value != null)
                {
                    property.SetValue(instance, value);
                }
            }

            return(instance);
        }