private async Task <ContentValidateResult> UpdateContentItemVersionAsync(ContentItem updatingVersion, ContentItem updatedVersion, IEnumerable <ContentItem> evictionVersions = null)
        {
            // Replaces the id to force the current item to be updated
            updatingVersion.Id = updatedVersion.Id;

            var modifiedUtc  = updatedVersion.ModifiedUtc;
            var publishedUtc = updatedVersion.PublishedUtc;

            // Remove previous published or draft items if necesary or they will continue to be listed as published or draft.
            var discardLatest   = false;
            var removePublished = false;

            var importingLatest = updatedVersion.Latest;
            var existingLatest  = updatingVersion.Latest;

            // If latest values do not match and importing latest is true then we must find and evict the previous latest.
            if (importingLatest != existingLatest && importingLatest == true)
            {
                discardLatest = true;
            }

            var importingPublished = updatedVersion.Published;
            var existingPublished  = updatingVersion.Published;

            // If published values do not match and importing published is true then we must find and evict the previous published
            // This is when the existing content item version is not published, but the importing version is set to published.
            // For this to occur there must have been a draft made, and the mutation to published is being made on the draft.
            if (importingPublished != existingPublished && importingPublished == true)
            {
                removePublished = true;
            }

            if (discardLatest && removePublished)
            {
                await RemoveVersionsAsync(updatingVersion, evictionVersions);
            }
            else if (discardLatest)
            {
                await RemoveLatestVersionAsync(updatingVersion, evictionVersions);
            }
            else if (removePublished)
            {
                await RemovePublishedVersionAsync(updatingVersion, evictionVersions);
            }

            updatingVersion.Merge(updatedVersion, UpdateJsonMergeSettings);
            updatingVersion.Latest    = importingLatest;
            updatingVersion.Published = importingPublished;

            await UpdateAsync(updatingVersion);

            var result = await ValidateAsync(updatingVersion);

            // Session is cancelled now so previous updates to versions are cancelled also.
            if (!result.Succeeded)
            {
                return(result);
            }

            if (importingPublished)
            {
                // Invoke published handlers to add information to persistent stores
                var publishContext = new PublishContentContext(updatingVersion, null);

                await Handlers.InvokeAsync((handler, context) => handler.PublishingAsync(context), publishContext, _logger);

                await ReversedHandlers.InvokeAsync((handler, context) => handler.PublishedAsync(context), publishContext, _logger);
            }
            else
            {
                await SaveDraftAsync(updatingVersion);
            }

            // Restore values that may have been altered by handlers.
            if (modifiedUtc.HasValue)
            {
                updatingVersion.ModifiedUtc = modifiedUtc;
            }
            if (publishedUtc.HasValue)
            {
                updatingVersion.PublishedUtc = publishedUtc;
            }

            return(result);
        }
        public async Task ImportAsync(IEnumerable <ContentItem> contentItems)
        {
            var skip = 0;
            var take = ImportBatchSize;

            var batchedContentItems = contentItems.Take(take);

            while (batchedContentItems.Any())
            {
                // Preload all the versions for this batch from the database.
                var versionIds = batchedContentItems
                                 .Where(x => !String.IsNullOrEmpty(x.ContentItemVersionId))
                                 .Select(x => x.ContentItemVersionId);

                var itemIds = batchedContentItems
                              .Where(x => !String.IsNullOrEmpty(x.ContentItemId))
                              .Select(x => x.ContentItemId);

                var existingContentItems = await _session
                                           .Query <ContentItem, ContentItemIndex>(x =>
                                                                                  x.ContentItemId.IsIn(itemIds) &&
                                                                                  (x.Latest || x.Published || x.ContentItemVersionId.IsIn(versionIds)))
                                           .ListAsync();

                var versionsToUpdate         = existingContentItems.Where(c => versionIds.Any(v => String.Equals(v, c.ContentItemVersionId, StringComparison.OrdinalIgnoreCase)));
                var versionsThatMaybeEvicted = existingContentItems.Except(versionsToUpdate);

                foreach (var version in existingContentItems)
                {
                    await LoadAsync(version);
                }

                foreach (var importingItem in batchedContentItems)
                {
                    ContentItem originalVersion = null;
                    if (!String.IsNullOrEmpty(importingItem.ContentItemVersionId))
                    {
                        originalVersion = versionsToUpdate.FirstOrDefault(x => String.Equals(x.ContentItemVersionId, importingItem.ContentItemVersionId, StringComparison.OrdinalIgnoreCase));
                    }

                    if (originalVersion == null)
                    {
                        // The version does not exist in the current database.
                        var context = new ImportContentContext(importingItem);

                        await Handlers.InvokeAsync((handler, context) => handler.ImportingAsync(context), context, _logger);

                        var evictionVersions = versionsThatMaybeEvicted.Where(x => String.Equals(x.ContentItemId, importingItem.ContentItemId, StringComparison.OrdinalIgnoreCase));
                        var result           = await CreateContentItemVersionAsync(importingItem, evictionVersions);

                        if (!result.Succeeded)
                        {
                            if (_logger.IsEnabled(LogLevel.Error))
                            {
                                _logger.LogError("Error importing content item version id '{ContentItemVersionId}' : '{Errors}'", importingItem?.ContentItemVersionId, string.Join(", ", result.Errors));
                            }

                            throw new ValidationException(string.Join(", ", result.Errors));
                        }

                        // Imported handlers will only be fired if the validation has been successful.
                        // Consumers should implement validated handlers to alter the success of that operation.
                        await ReversedHandlers.InvokeAsync((handler, context) => handler.ImportedAsync(context), context, _logger);
                    }
                    else
                    {
                        // The version exists in the database.
                        // It is important to only import changed items.
                        // We compare the two versions and skip importing it if they are the same.
                        // We do this to prevent unnecessary sql updates, and because UpdateContentItemVersionAsync
                        // may remove drafts of updated items.
                        // This is necesary because an imported item maybe set to latest, and published.
                        // In this case, the draft item in the system, must be removed, or there will be two drafts.
                        // The draft item should be removed, because it would now be orphaned, as the imported published item
                        // would be further ahead, on a timeline, between the two.

                        var jImporting = JObject.FromObject(importingItem);

                        // Removed Published and Latest from consideration when evaluating.
                        // Otherwise an import of an unchanged (but published) version would overwrite a newer published version.
                        jImporting.Remove(nameof(ContentItem.Published));
                        jImporting.Remove(nameof(ContentItem.Latest));

                        var jOriginal = JObject.FromObject(originalVersion);

                        jOriginal.Remove(nameof(ContentItem.Published));
                        jOriginal.Remove(nameof(ContentItem.Latest));

                        if (JToken.DeepEquals(jImporting, jOriginal))
                        {
                            _logger.LogInformation("Importing '{ContentItemVersionId}' skipped as it is unchanged", importingItem.ContentItemVersionId);
                            continue;
                        }

                        // Handlers are only fired if the import is going ahead.
                        var context = new ImportContentContext(importingItem, originalVersion);

                        await Handlers.InvokeAsync((handler, context) => handler.ImportingAsync(context), context, _logger);

                        var evictionVersions = versionsThatMaybeEvicted.Where(x => String.Equals(x.ContentItemId, importingItem.ContentItemId, StringComparison.OrdinalIgnoreCase));
                        var result           = await UpdateContentItemVersionAsync(originalVersion, importingItem, evictionVersions);

                        if (!result.Succeeded)
                        {
                            if (_logger.IsEnabled(LogLevel.Error))
                            {
                                _logger.LogError("Error importing content item version id '{ContentItemVersionId}' : '{Errors}'", importingItem.ContentItemVersionId, string.Join(", ", result.Errors));
                            }

                            throw new ValidationException(string.Join(", ", result.Errors));
                        }

                        // Imported handlers will only be fired if the validation has been successful.
                        // Consumers should implement validated handlers to alter the success of that operation.
                        await ReversedHandlers.InvokeAsync((handler, context) => handler.ImportedAsync(context), context, _logger);
                    }
                }

                skip += ImportBatchSize;
                take += ImportBatchSize;
                batchedContentItems = contentItems.Skip(skip).Take(take);
            }
        }
        private async Task <ContentValidateResult> CreateContentItemVersionAsync(ContentItem contentItem, IEnumerable <ContentItem> evictionVersions = null)
        {
            if (String.IsNullOrEmpty(contentItem.ContentItemId))
            {
                // NewAsync should be used to create new content items.
                throw new ArgumentNullException(nameof(ContentItem.ContentItemId));
            }

            // Initializes the Id as it could be interpreted as an updated object when added back to YesSql
            contentItem.Id = 0;

            // Maintain modified and published dates as these will be reset by the Create Handlers
            var modifiedUtc  = contentItem.ModifiedUtc;
            var publishedUtc = contentItem.PublishedUtc;
            var owner        = contentItem.Owner;
            var author       = contentItem.Author;

            if (String.IsNullOrEmpty(contentItem.ContentItemVersionId))
            {
                contentItem.ContentItemVersionId = _idGenerator.GenerateUniqueId(contentItem);
            }

            // Remove previous latest item or they will continue to be listed as latest.
            // When importing a new draft the existing latest must be set to false. The creating version wins.
            if (contentItem.Latest && !contentItem.Published)
            {
                await RemoveLatestVersionAsync(contentItem, evictionVersions);
            }
            else if (contentItem.Published)
            {
                // When importing a published item existing drafts and existing published must be removed.
                // Otherwise an existing draft would become an orphan and if published would overwrite
                // the imported (which we assume is the version that wins) content.
                await RemoveVersionsAsync(contentItem, evictionVersions);
            }
            // When neither published or latest the operation will create a database record
            // which will be part of the content item archive.

            // Invoked create handlers.
            var context = new CreateContentContext(contentItem);
            await Handlers.InvokeAsync((handler, context) => handler.CreatingAsync(context), context, _logger);

            // The content item should be placed in the session store so that further calls
            // to ContentManager.Get by a scoped index provider will resolve the imported item correctly.
            _session.Save(contentItem);
            _contentManagerSession.Store(contentItem);

            await ReversedHandlers.InvokeAsync((handler, context) => handler.CreatedAsync(context), context, _logger);

            await UpdateAsync(contentItem);

            var result = await ValidateAsync(contentItem);

            if (!result.Succeeded)
            {
                return(result);
            }

            if (contentItem.Published)
            {
                // Invoke published handlers to add information to persistent stores
                var publishContext = new PublishContentContext(contentItem, null);

                await Handlers.InvokeAsync((handler, context) => handler.PublishingAsync(context), publishContext, _logger);

                await ReversedHandlers.InvokeAsync((handler, context) => handler.PublishedAsync(context), publishContext, _logger);
            }
            else
            {
                await SaveDraftAsync(contentItem);
            }

            // Restore values that may have been altered by handlers.
            if (modifiedUtc.HasValue)
            {
                contentItem.ModifiedUtc = modifiedUtc;
            }
            if (publishedUtc.HasValue)
            {
                contentItem.PublishedUtc = publishedUtc;
            }

            // There is a risk here that the owner or author does not exist in the importing system.
            // We check that at least a value has been supplied, if not the owner property and author
            // property would be left as the user who has run this import.
            if (!String.IsNullOrEmpty(owner))
            {
                contentItem.Owner = owner;
            }
            if (!String.IsNullOrEmpty(author))
            {
                contentItem.Author = author;
            }

            return(result);
        }
 public Task <ContentValidateResult> CreateContentItemVersionAsync(ContentItem contentItem)
 {
     return(CreateContentItemVersionAsync(contentItem, null));
 }
 public Task <ContentValidateResult> UpdateContentItemVersionAsync(ContentItem updatingVersion, ContentItem updatedVersion)
 {
     return(UpdateContentItemVersionAsync(updatingVersion, updatedVersion, null));
 }
        public static async Task <IHtmlContent> DisplayAsync(this OrchardRazorHelper razorHelper, ContentItem content, string displayType = "", string groupId = "", IUpdateModel updater = null)
        {
            var displayManager = razorHelper.HttpContext.RequestServices.GetService <IContentItemDisplayManager>();
            var shape          = await displayManager.BuildDisplayAsync(content, updater, displayType, groupId);

            return(await razorHelper.DisplayHelper(shape));
        }
        public async Task <ContentItem> GetAsync(string contentItemId, VersionOptions options)
        {
            if (String.IsNullOrEmpty(contentItemId))
            {
                return(null);
            }

            ContentItem contentItem = null;

            if (options.IsLatest)
            {
                contentItem = await _session
                              .Query <ContentItem, ContentItemIndex>()
                              .Where(x => x.ContentItemId == contentItemId && x.Latest == true)
                              .FirstOrDefaultAsync();
            }
            else if (options.IsDraft && !options.IsDraftRequired)
            {
                contentItem = await _session
                              .Query <ContentItem, ContentItemIndex>()
                              .Where(x =>
                                     x.ContentItemId == contentItemId &&
                                     x.Published == false &&
                                     x.Latest == true)
                              .FirstOrDefaultAsync();
            }
            else if (options.IsDraft || options.IsDraftRequired)
            {
                // Loaded whatever is the latest as it will be cloned
                contentItem = await _session
                              .Query <ContentItem, ContentItemIndex>()
                              .Where(x =>
                                     x.ContentItemId == contentItemId &&
                                     x.Latest == true)
                              .FirstOrDefaultAsync();
            }
            else if (options.IsPublished)
            {
                // If the published version is requested and is already loaded, we can
                // return it right away
                if (_contentManagerSession.RecallPublishedItemId(contentItemId, out contentItem))
                {
                    return(contentItem);
                }

                contentItem = await _session.ExecuteQuery(new PublishedContentItemById(contentItemId)).FirstOrDefaultAsync();
            }

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

            contentItem = await LoadAsync(contentItem);

            if (options.IsDraftRequired)
            {
                // When draft is required and latest is published a new version is added
                if (contentItem.Published)
                {
                    // We save the previous version further because this call might do a session query.
                    var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(contentItem.ContentType);

                    // Check if not versionable, meaning we use only one version
                    if (!(contentTypeDefinition?.GetSettings <ContentTypeSettings>().Versionable ?? true))
                    {
                        contentItem.Published = false;
                    }
                    else
                    {
                        // Save the previous version
                        _session.Save(contentItem);

                        contentItem = await BuildNewVersionAsync(contentItem);
                    }
                }

                // Save the new version
                _session.Save(contentItem);
            }

            return(contentItem);
        }
Ejemplo n.º 8
0
 /// <summary>
 /// Updates the content part with the specified type.
 /// </summary>
 /// <param name="contentItem">The <see cref="ContentItem"/>.</param>
 /// <param name="part">The content part to update.</param>
 /// <typeparam name="TPart">The type of the content part.</typeparam>
 /// <returns>The current <see cref="ContentItem"/> instance.</returns>
 public static ContentItem Apply <TPart>(this ContentItem contentItem, TPart part) where TPart : ContentPart
 {
     contentItem.Apply(typeof(TPart).Name, part);
     return(contentItem);
 }
Ejemplo n.º 9
0
        /// <summary>
        /// Creates (persists) a new Published content item
        /// </summary>
        /// <param name="contentItem">The content instance filled with all necessary data</param>

        public static Task CreateAsync(this IContentManager contentManager, ContentItem contentItem)
        {
            return(contentManager.CreateAsync(contentItem, VersionOptions.Published));
        }
Ejemplo n.º 10
0
 /// <summary>
 /// Removes a content part by its type.
 /// </summary>
 /// <param name="contentItem">The <see cref="ContentItem"/>.</param>
 /// <typeparam name="TPart">The type of the content part.</typeparam>
 public static void Remove <TPart>(this ContentItem contentItem) where TPart : ContentPart, new()
 {
     contentItem.Remove(typeof(TPart).Name);
 }
Ejemplo n.º 11
0
 /// <summary>
 /// Gets a content part by its type or create a new one.
 /// </summary>
 /// <param name="contentItem">The <see cref="ContentItem"/>.</param>
 /// <typeparam name="TPart">The type of the content part.</typeparam>
 /// <returns>The content part instance or a new one if it doesn't exist.</returns>
 public static TPart GetOrCreate <TPart>(this ContentItem contentItem) where TPart : ContentPart, new()
 {
     return(contentItem.GetOrCreate <TPart>(typeof(TPart).Name));
 }
Ejemplo n.º 12
0
 /// <summary>
 /// Gets a content part by its type.
 /// </summary>
 /// <param name="contentItem">The <see cref="ContentItem"/>.</param>
 /// <typeparam name="TPart">The type of the content part.</typeparam>
 /// <returns>The content part or <code>null</code> if it doesn't exist.</returns>
 public static TPart As <TPart>(this ContentItem contentItem) where TPart : ContentPart
 {
     return(contentItem.Get <TPart>(typeof(TPart).Name));
 }
Ejemplo n.º 13
0
        /// <summary>
        /// Merges properties to the contents of a content item.
        /// </summary>
        /// <param name="contentItem">The <see cref="ContentItem"/>.</param>
        /// <param name="properties">The object to merge.</param>
        /// <param name="jsonMergeSettings">The <see cref="JsonMergeSettings"/> to use.</param>
        /// <returns>The current <see cref="ContentItem"/> instance.</returns>
        public static ContentItem Merge(this ContentItem contentItem, object properties, JsonMergeSettings jsonMergeSettings = null)
        {
            var props = JObject.FromObject(properties);

            var originalDocumentId = contentItem.Id;

            contentItem.Data.Merge(props, jsonMergeSettings);
            contentItem.Elements.Clear();

            // Return to original value or it will be interpreated as a different object by YesSql.
            contentItem.Id = originalDocumentId;

            // After merging content here we need to remove all the well known properties from the Data jObject
            // or these properties will take precedence over the properties on the C# object when and if they are mutated.
            if (props.ContainsKey(nameof(contentItem.DisplayText)))
            {
                contentItem.DisplayText = props[nameof(contentItem.DisplayText)].ToString();
                contentItem.Data.Remove(nameof(contentItem.DisplayText));
            }

            if (props.ContainsKey(nameof(contentItem.Owner)))
            {
                contentItem.Owner = props[nameof(contentItem.Owner)].ToString();
                contentItem.Data.Remove(nameof(contentItem.Owner));
            }

            if (props.ContainsKey(nameof(contentItem.Author)))
            {
                contentItem.Author = props[nameof(contentItem.Author)].ToString();
                contentItem.Data.Remove(nameof(contentItem.Author));
            }

            // Do not set these properties on the content item as they are the responsibility of the content manager.
            if (props.ContainsKey(nameof(contentItem.Published)))
            {
                contentItem.Data.Remove(nameof(contentItem.Published));
            }

            if (props.ContainsKey(nameof(contentItem.Latest)))
            {
                contentItem.Data.Remove(nameof(contentItem.Latest));
            }

            if (props.ContainsKey(nameof(contentItem.CreatedUtc)))
            {
                contentItem.Data.Remove(nameof(contentItem.CreatedUtc));
            }

            if (props.ContainsKey(nameof(contentItem.ModifiedUtc)))
            {
                contentItem.Data.Remove(nameof(contentItem.ModifiedUtc));
            }

            if (props.ContainsKey(nameof(contentItem.PublishedUtc)))
            {
                contentItem.Data.Remove(nameof(contentItem.PublishedUtc));
            }

            if (props.ContainsKey(nameof(contentItem.ContentItemId)))
            {
                contentItem.Data.Remove(nameof(contentItem.ContentItemId));
            }

            if (props.ContainsKey(nameof(contentItem.ContentItemVersionId)))
            {
                contentItem.Data.Remove(nameof(contentItem.ContentItemVersionId));
            }

            if (props.ContainsKey(nameof(contentItem.ContentType)))
            {
                contentItem.Data.Remove(nameof(contentItem.ContentType));
            }

            return(contentItem);
        }
Ejemplo n.º 14
0
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var contentItem = new ContentItem();
            var skip        = false;

            while (skip || reader.Read())
            {
                skip = false;

                if (reader.TokenType == JsonToken.EndObject)
                {
                    break;
                }

                if (reader.TokenType != JsonToken.PropertyName)
                {
                    continue;
                }

                var propertyName = (string)reader.Value;

                switch (propertyName)
                {
                case nameof(ContentItem.Id):
                    contentItem.Id = reader.ReadAsInt32() ?? 0;
                    break;

                case nameof(ContentItem.ContentItemId):
                    contentItem.ContentItemId = reader.ReadAsString();
                    break;

                case nameof(ContentItem.ContentItemVersionId):
                    contentItem.ContentItemVersionId = reader.ReadAsString();
                    break;

                case nameof(ContentItem.ContentType):
                    contentItem.ContentType = reader.ReadAsString();
                    break;

                case nameof(ContentItem.DisplayText):
                    contentItem.DisplayText = reader.ReadAsString();
                    break;

                case nameof(ContentItem.Latest):
                    contentItem.Latest = reader.ReadAsBoolean() ?? false;
                    break;

                case nameof(ContentItem.Published):
                    contentItem.Published = reader.ReadAsBoolean() ?? false;
                    break;

                case nameof(ContentItem.PublishedUtc):
                    contentItem.PublishedUtc = reader.ReadAsDateTime();
                    break;

                case nameof(ContentItem.ModifiedUtc):
                    contentItem.ModifiedUtc = reader.ReadAsDateTime();
                    break;

                case nameof(ContentItem.CreatedUtc):
                    contentItem.CreatedUtc = reader.ReadAsDateTime();
                    break;

                case nameof(ContentItem.Author):
                    contentItem.Author = reader.ReadAsString();
                    break;

                case nameof(ContentItem.Owner):
                    contentItem.Owner = reader.ReadAsString();
                    break;

                default:
                    var customProperty = JProperty.Load(reader);
                    contentItem.Data.Add(customProperty);

                    // Skip reading a token as JPproperty.Load already did the next one
                    skip = true;
                    break;
                }
            }

            return(contentItem);
        }