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); }
/// <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); }
/// <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)); }
/// <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); }
/// <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)); }
/// <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)); }
/// <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); }
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); }