Merge remote-tracking branch 'origin/6.2.0' into 6.2.0
This commit is contained in:
@@ -25,6 +25,20 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
#region IContent
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this entity was just published as part of a recent save operation (i.e. it wasn't previously published)
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This is helpful for determining if the published event will execute during the saved event for a content item.
|
||||
/// </remarks>
|
||||
internal static bool JustPublished(this IContent entity)
|
||||
{
|
||||
var dirty = (IRememberBeingDirty)entity;
|
||||
return dirty.WasPropertyDirty("Published") && entity.Published;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a new version should be created
|
||||
/// </summary>
|
||||
|
||||
@@ -23,5 +23,5 @@ namespace Umbraco.Core.Models
|
||||
var dirty = (IRememberBeingDirty)entity;
|
||||
return dirty.WasPropertyDirty("Id");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,12 +176,20 @@ namespace Umbraco.Core.Services
|
||||
var contentType = FindContentTypeByAlias(contentTypeAlias);
|
||||
var content = new Content(name, parentId, contentType);
|
||||
|
||||
//NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found
|
||||
// out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now.
|
||||
if (Creating.IsRaisedEventCancelled(new NewEventArgs<IContent>(content, contentTypeAlias, parentId), this))
|
||||
{
|
||||
content.WasCancelled = true;
|
||||
return content;
|
||||
}
|
||||
|
||||
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IContent>(content), this))
|
||||
{
|
||||
content.WasCancelled = true;
|
||||
return content;
|
||||
}
|
||||
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var repository = _repositoryFactory.CreateContentRepository(uow))
|
||||
{
|
||||
@@ -191,6 +199,8 @@ namespace Umbraco.Core.Services
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
Saved.RaiseEvent(new SaveEventArgs<IContent>(content, false), this);
|
||||
|
||||
Created.RaiseEvent(new NewEventArgs<IContent>(content, false, contentTypeAlias, parentId), this);
|
||||
|
||||
Audit.Add(AuditTypes.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id);
|
||||
@@ -216,12 +226,20 @@ namespace Umbraco.Core.Services
|
||||
var contentType = FindContentTypeByAlias(contentTypeAlias);
|
||||
var content = new Content(name, parent, contentType);
|
||||
|
||||
//NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found
|
||||
// out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now.
|
||||
if (Creating.IsRaisedEventCancelled(new NewEventArgs<IContent>(content, contentTypeAlias, parent), this))
|
||||
{
|
||||
content.WasCancelled = true;
|
||||
return content;
|
||||
}
|
||||
|
||||
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IContent>(content), this))
|
||||
{
|
||||
content.WasCancelled = true;
|
||||
return content;
|
||||
}
|
||||
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var repository = _repositoryFactory.CreateContentRepository(uow))
|
||||
{
|
||||
@@ -231,6 +249,8 @@ namespace Umbraco.Core.Services
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
Saved.RaiseEvent(new SaveEventArgs<IContent>(content, false), this);
|
||||
|
||||
Created.RaiseEvent(new NewEventArgs<IContent>(content, false, contentTypeAlias, parent), this);
|
||||
|
||||
Audit.Add(AuditTypes.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id);
|
||||
|
||||
@@ -122,12 +122,23 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
var mediaType = FindMediaTypeByAlias(mediaTypeAlias);
|
||||
var media = new Models.Media(name, parentId, mediaType);
|
||||
|
||||
//NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found
|
||||
// out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now.
|
||||
if (Creating.IsRaisedEventCancelled(new NewEventArgs<IMedia>(media, mediaTypeAlias, parentId), this))
|
||||
{
|
||||
media.WasCancelled = true;
|
||||
return media;
|
||||
}
|
||||
|
||||
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IMedia>(media), this))
|
||||
{
|
||||
media.WasCancelled = true;
|
||||
return media;
|
||||
}
|
||||
|
||||
//TODO: Once we fix up the transaction logic, these write locks should be replaced with
|
||||
// an outter transaction instead.
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
@@ -142,6 +153,8 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
Saved.RaiseEvent(new SaveEventArgs<IMedia>(media, false), this);
|
||||
|
||||
Created.RaiseEvent(new NewEventArgs<IMedia>(media, false, mediaTypeAlias, parentId), this);
|
||||
|
||||
Audit.Add(AuditTypes.New, string.Format("Media '{0}' was created with Id {1}", name, media.Id), media.CreatorId, media.Id);
|
||||
@@ -166,12 +179,21 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
var mediaType = FindMediaTypeByAlias(mediaTypeAlias);
|
||||
var media = new Models.Media(name, parent, mediaType);
|
||||
|
||||
//NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found
|
||||
// out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now.
|
||||
if (Creating.IsRaisedEventCancelled(new NewEventArgs<IMedia>(media, mediaTypeAlias, parent), this))
|
||||
{
|
||||
media.WasCancelled = true;
|
||||
return media;
|
||||
}
|
||||
|
||||
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IMedia>(media), this))
|
||||
{
|
||||
media.WasCancelled = true;
|
||||
return media;
|
||||
}
|
||||
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
@@ -186,6 +208,8 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
Saved.RaiseEvent(new SaveEventArgs<IMedia>(media, false), this);
|
||||
|
||||
Created.RaiseEvent(new NewEventArgs<IMedia>(media, false, mediaTypeAlias, parent), this);
|
||||
|
||||
Audit.Add(AuditTypes.New, string.Format("Media '{0}' was created with Id {1}", name, media.Id), media.CreatorId, media.Id);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
<cdf:CssInclude ID="CssInclude1" runat="server" FilePath="propertypane/style.css" PathNameAlias="UmbracoClient" />
|
||||
<cdf:CssInclude ID="CssInclude2" runat="server" FilePath="plugins/uGoLive/Dashboard.css" PathNameAlias="UmbracoRoot" />
|
||||
<cdf:JsInclude ID="JsInclude2" runat="server" FilePath="UI/knockout.js" Priority="3" PathNameAlias="UmbracoClient" />
|
||||
<cdf:JsInclude ID="JsInclude3" runat="server" FilePath="plugins/uGoLive/jquery.tmpl.js" Priority="1" PathNameAlias="UmbracoRoot" />
|
||||
<cdf:JsInclude ID="JsInclude1" runat="server" FilePath="plugins/uGoLive/Dashboard.js" PathNameAlias="UmbracoRoot" />
|
||||
|
||||
|
||||
@@ -131,19 +131,30 @@ namespace Umbraco.Web.Cache
|
||||
MediaService.Moving += MediaServiceMoving;
|
||||
MediaService.Trashing += MediaServiceTrashing;
|
||||
|
||||
ContentService.Created += ContentServiceCreated;
|
||||
//Bind to content events - this is for unpublished content syncing across servers (primarily for examine)
|
||||
|
||||
ContentService.Saved += ContentServiceSaved;
|
||||
ContentService.Deleted += ContentServiceDeleted;
|
||||
ContentService.Copied += ContentServiceCopied;
|
||||
//NOTE: we do not listen for the trashed event because there is no cache to update for content in that case since
|
||||
// the unpublishing event handles that, and for examine with unpublished content indexes, we want to keep that data
|
||||
// in the index, it's not until it's complete deleted that we want to remove it.
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region Content service event handlers
|
||||
|
||||
/// <summary>
|
||||
/// When an entity is copied new permissions may be assigned to it based on it's parent, if that is the
|
||||
/// case then we need to clear all user permissions cache.
|
||||
/// Handles cache refreshgi for when content is copied
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
static void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs<IContent> e)
|
||||
/// <remarks>
|
||||
/// When an entity is copied new permissions may be assigned to it based on it's parent, if that is the
|
||||
/// case then we need to clear all user permissions cache.
|
||||
/// </remarks>
|
||||
static void ContentServiceCopied(IContentService sender, CopyEventArgs<IContent> e)
|
||||
{
|
||||
//check if permissions have changed
|
||||
var permissionsChanged = ((Content)e.Copy).WasPropertyDirty("PermissionsChanged");
|
||||
@@ -151,23 +162,63 @@ namespace Umbraco.Web.Cache
|
||||
{
|
||||
DistributedCache.Instance.RefreshAllUserPermissionsCache();
|
||||
}
|
||||
|
||||
//run the un-published cache refresher
|
||||
DistributedCache.Instance.RefreshUnpublishedPageCache(e.Copy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When an entity is created new permissions may be assigned to it based on it's parent, if that is the
|
||||
/// case then we need to clear all user permissions cache.
|
||||
/// Handles cache refreshing for when content is deleted (not unpublished)
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
static void ContentServiceCreated(IContentService sender, Core.Events.NewEventArgs<IContent> e)
|
||||
static void ContentServiceDeleted(IContentService sender, DeleteEventArgs<IContent> e)
|
||||
{
|
||||
//check if permissions have changed
|
||||
var permissionsChanged = ((Content)e.Entity).WasPropertyDirty("PermissionsChanged");
|
||||
if (permissionsChanged)
|
||||
DistributedCache.Instance.RemoveUnpublishedPageCache(e.DeletedEntities.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles cache refreshing for when content is saved (not published)
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks>
|
||||
/// When an entity is saved we need to notify other servers about the change in order for the Examine indexes to
|
||||
/// stay up-to-date for unpublished content.
|
||||
///
|
||||
/// When an entity is created new permissions may be assigned to it based on it's parent, if that is the
|
||||
/// case then we need to clear all user permissions cache.
|
||||
/// </remarks>
|
||||
static void ContentServiceSaved(IContentService sender, SaveEventArgs<IContent> e)
|
||||
{
|
||||
var clearUserPermissions = false;
|
||||
e.SavedEntities.ForEach(x =>
|
||||
{
|
||||
//check if it is new
|
||||
if (x.IsNewEntity())
|
||||
{
|
||||
//check if permissions have changed
|
||||
var permissionsChanged = ((Content)x).WasPropertyDirty("PermissionsChanged");
|
||||
if (permissionsChanged)
|
||||
{
|
||||
clearUserPermissions = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (clearUserPermissions)
|
||||
{
|
||||
DistributedCache.Instance.RefreshAllUserPermissionsCache();
|
||||
}
|
||||
}
|
||||
|
||||
//filter out the entities that have only been saved (not newly published) since
|
||||
// newly published ones will be synced with the published page cache refresher
|
||||
var unpublished = e.SavedEntities.Where(x => x.JustPublished() == false);
|
||||
//run the un-published cache refresher
|
||||
DistributedCache.Instance.RefreshUnpublishedPageCache(unpublished.ToArray());
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region ApplicationTree event handlers
|
||||
@@ -451,12 +502,12 @@ namespace Umbraco.Web.Cache
|
||||
InvalidateCacheForPermissionsChange(sender);
|
||||
}
|
||||
|
||||
void UserServiceSavedUser(IUserService sender, Core.Events.SaveEventArgs<Core.Models.Membership.IUser> e)
|
||||
static void UserServiceSavedUser(IUserService sender, SaveEventArgs<IUser> e)
|
||||
{
|
||||
e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserCache(x.Id));
|
||||
}
|
||||
|
||||
void UserServiceDeletedUser(IUserService sender, Core.Events.DeleteEventArgs<Core.Models.Membership.IUser> e)
|
||||
static void UserServiceDeletedUser(IUserService sender, DeleteEventArgs<IUser> e)
|
||||
{
|
||||
e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserCache(x.Id));
|
||||
}
|
||||
@@ -547,7 +598,7 @@ namespace Umbraco.Web.Cache
|
||||
#region Media event handlers
|
||||
static void MediaServiceTrashing(IMediaService sender, Core.Events.MoveEventArgs<IMedia> e)
|
||||
{
|
||||
DistributedCache.Instance.RemoveMediaCache(e.Entity);
|
||||
DistributedCache.Instance.RemoveMediaCache(false, e.Entity);
|
||||
}
|
||||
|
||||
static void MediaServiceMoving(IMediaService sender, Core.Events.MoveEventArgs<IMedia> e)
|
||||
@@ -557,7 +608,7 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
static void MediaServiceDeleting(IMediaService sender, Core.Events.DeleteEventArgs<IMedia> e)
|
||||
{
|
||||
DistributedCache.Instance.RemoveMediaCache(e.DeletedEntities.ToArray());
|
||||
DistributedCache.Instance.RemoveMediaCache(true, e.DeletedEntities.ToArray());
|
||||
}
|
||||
|
||||
static void MediaServiceSaved(IMediaService sender, Core.Events.SaveEventArgs<IMedia> e)
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Umbraco.Web.Cache
|
||||
public const string ApplicationCacheRefresherId = "B15F34A1-BC1D-4F8B-8369-3222728AB4C8";
|
||||
public const string TemplateRefresherId = "DD12B6A0-14B9-46e8-8800-C154F74047C8";
|
||||
public const string PageCacheRefresherId = "27AB3022-3DFA-47b6-9119-5945BC88FD66";
|
||||
public const string UnpublishedPageCacheRefresherId = "55698352-DFC5-4DBE-96BD-A4A0F6F77145";
|
||||
public const string MemberCacheRefresherId = "E285DF34-ACDC-4226-AE32-C0CB5CF388DA";
|
||||
public const string MemberGroupCacheRefresherId = "187F236B-BD21-4C85-8A7C-29FBA3D6C00C";
|
||||
public const string MediaCacheRefresherId = "B29286DD-2D40-4DDB-B325-681226589FEC";
|
||||
|
||||
@@ -123,8 +123,7 @@ namespace Umbraco.Web.Cache
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region Data type cache
|
||||
/// <summary>
|
||||
/// Refreshes the cache amongst servers for a data type
|
||||
@@ -232,7 +231,28 @@ namespace Umbraco.Web.Cache
|
||||
public static void RemovePageCache(this DistributedCache dc, int documentId)
|
||||
{
|
||||
dc.Remove(new Guid(DistributedCache.PageCacheRefresherId), documentId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// invokes the unpublished page cache refresher
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="content"></param>
|
||||
public static void RefreshUnpublishedPageCache(this DistributedCache dc, params IContent[] content)
|
||||
{
|
||||
dc.Refresh(new Guid(DistributedCache.UnpublishedPageCacheRefresherId), x => x.Id, content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// invokes the unpublished page cache refresher
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="content"></param>
|
||||
public static void RemoveUnpublishedPageCache(this DistributedCache dc, params IContent[] content)
|
||||
{
|
||||
dc.Remove(new Guid(DistributedCache.UnpublishedPageCacheRefresherId), x => x.Id, content);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Member cache
|
||||
@@ -291,7 +311,7 @@ namespace Umbraco.Web.Cache
|
||||
public static void RefreshMediaCache(this DistributedCache dc, params IMedia[] media)
|
||||
{
|
||||
dc.RefreshByJson(new Guid(DistributedCache.MediaCacheRefresherId),
|
||||
MediaCacheRefresher.SerializeToJsonPayload(media));
|
||||
MediaCacheRefresher.SerializeToJsonPayload(MediaCacheRefresher.OperationType.Saved, media));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -304,6 +324,7 @@ namespace Umbraco.Web.Cache
|
||||
/// to clear all of the cache but the media item will be removed before the other servers can
|
||||
/// look it up. Only here for legacy purposes.
|
||||
/// </remarks>
|
||||
[Obsolete("Ensure to clear with other RemoveMediaCache overload")]
|
||||
public static void RemoveMediaCache(this DistributedCache dc, int mediaId)
|
||||
{
|
||||
dc.Remove(new Guid(DistributedCache.MediaCacheRefresherId), mediaId);
|
||||
@@ -313,11 +334,14 @@ namespace Umbraco.Web.Cache
|
||||
/// Removes the cache amongst servers for media items
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="isPermanentlyDeleted"></param>
|
||||
/// <param name="media"></param>
|
||||
public static void RemoveMediaCache(this DistributedCache dc, params IMedia[] media)
|
||||
public static void RemoveMediaCache(this DistributedCache dc, bool isPermanentlyDeleted, params IMedia[] media)
|
||||
{
|
||||
dc.RefreshByJson(new Guid(DistributedCache.MediaCacheRefresherId),
|
||||
MediaCacheRefresher.SerializeToJsonPayload(media));
|
||||
dc.RefreshByJson(new Guid(DistributedCache.MediaCacheRefresherId),
|
||||
MediaCacheRefresher.SerializeToJsonPayload(
|
||||
isPermanentlyDeleted ? MediaCacheRefresher.OperationType.Deleted : MediaCacheRefresher.OperationType.Trashed,
|
||||
media));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Web.Script.Serialization;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
@@ -19,13 +20,13 @@ namespace Umbraco.Web.Cache
|
||||
public class MediaCacheRefresher : JsonCacheRefresherBase<MediaCacheRefresher>
|
||||
{
|
||||
#region Static helpers
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts the json to a JsonPayload object
|
||||
/// </summary>
|
||||
/// <param name="json"></param>
|
||||
/// <returns></returns>
|
||||
private static JsonPayload[] DeserializeFromJsonPayload(string json)
|
||||
internal static JsonPayload[] DeserializeFromJsonPayload(string json)
|
||||
{
|
||||
var serializer = new JavaScriptSerializer();
|
||||
var jsonObject = serializer.Deserialize<JsonPayload[]>(json);
|
||||
@@ -35,12 +36,13 @@ namespace Umbraco.Web.Cache
|
||||
/// <summary>
|
||||
/// Creates the custom Json payload used to refresh cache amongst the servers
|
||||
/// </summary>
|
||||
/// <param name="operation"></param>
|
||||
/// <param name="media"></param>
|
||||
/// <returns></returns>
|
||||
internal static string SerializeToJsonPayload(params IMedia[] media)
|
||||
internal static string SerializeToJsonPayload(OperationType operation, params IMedia[] media)
|
||||
{
|
||||
var serializer = new JavaScriptSerializer();
|
||||
var items = media.Select(FromMedia).ToArray();
|
||||
var items = media.Select(x => FromMedia(x, operation)).ToArray();
|
||||
var json = serializer.Serialize(items);
|
||||
return json;
|
||||
}
|
||||
@@ -49,15 +51,17 @@ namespace Umbraco.Web.Cache
|
||||
/// Converts a macro to a jsonPayload object
|
||||
/// </summary>
|
||||
/// <param name="media"></param>
|
||||
/// <param name="operation"></param>
|
||||
/// <returns></returns>
|
||||
private static JsonPayload FromMedia(IMedia media)
|
||||
internal static JsonPayload FromMedia(IMedia media, OperationType operation)
|
||||
{
|
||||
if (media == null) return null;
|
||||
|
||||
var payload = new JsonPayload
|
||||
{
|
||||
Id = media.Id,
|
||||
Path = media.Path
|
||||
Path = media.Path,
|
||||
Operation = operation
|
||||
};
|
||||
return payload;
|
||||
}
|
||||
@@ -66,10 +70,18 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
#region Sub classes
|
||||
|
||||
private class JsonPayload
|
||||
internal enum OperationType
|
||||
{
|
||||
Saved,
|
||||
Trashed,
|
||||
Deleted
|
||||
}
|
||||
|
||||
internal class JsonPayload
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public int Id { get; set; }
|
||||
public OperationType Operation { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -97,13 +109,15 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
public override void Refresh(int id)
|
||||
{
|
||||
ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id)));
|
||||
ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id), OperationType.Saved));
|
||||
base.Refresh(id);
|
||||
}
|
||||
|
||||
public override void Remove(int id)
|
||||
{
|
||||
ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id)));
|
||||
{
|
||||
ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id),
|
||||
//NOTE: we'll just default to trashed for this one.
|
||||
OperationType.Trashed));
|
||||
base.Remove(id);
|
||||
}
|
||||
|
||||
@@ -121,7 +135,7 @@ namespace Umbraco.Web.Cache
|
||||
string.Format("{0}_{1}_True", CacheKeys.MediaCacheKey, idPart));
|
||||
|
||||
// Also clear calls that only query this specific item!
|
||||
if (idPart == payload.Id.ToString())
|
||||
if (idPart == payload.Id.ToString(CultureInfo.InvariantCulture))
|
||||
ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(
|
||||
string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id));
|
||||
|
||||
|
||||
33
src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs
Normal file
33
src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Web.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// A cache refresher used for non-published content, this is primarily to notify Examine indexes to update
|
||||
/// </summary>
|
||||
public sealed class UnpublishedPageCacheRefresher : TypedCacheRefresherBase<UnpublishedPageCacheRefresher, IContent>
|
||||
{
|
||||
protected override UnpublishedPageCacheRefresher Instance
|
||||
{
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
public override Guid UniqueIdentifier
|
||||
{
|
||||
get { return new Guid(DistributedCache.UnpublishedPageCacheRefresherId); }
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get { return "Unpublished Page Refresher"; }
|
||||
}
|
||||
|
||||
//NOTE: There is no functionality for this cache refresher, it is here simply to emit events on each server for which examine
|
||||
// binds to. We could put the Examine index functionality in here but we've kept it all in the ExamineEvents class so that all of
|
||||
// the logic is in one place. In the future we may put the examine logic in a cache refresher instead (that would make sense) but we'd
|
||||
// want to get this done before making more cache refreshers:
|
||||
// http://issues.umbraco.org/issue/U4-2633
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using System.Xml;
|
||||
@@ -7,9 +8,13 @@ using Examine;
|
||||
using Examine.LuceneEngine;
|
||||
using Lucene.Net.Documents;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Web.Cache;
|
||||
using UmbracoExamine;
|
||||
using umbraco;
|
||||
using umbraco.BusinessLogic;
|
||||
@@ -50,20 +55,12 @@ namespace Umbraco.Web.Search
|
||||
if (registeredProviders == 0)
|
||||
return;
|
||||
|
||||
MediaService.Saved += MediaServiceSaved;
|
||||
MediaService.Deleted += MediaServiceDeleted;
|
||||
MediaService.Moved += MediaServiceMoved;
|
||||
MediaService.Trashed += MediaServiceTrashed;
|
||||
|
||||
ContentService.Saved += ContentServiceSaved;
|
||||
ContentService.Deleted += ContentServiceDeleted;
|
||||
ContentService.Moved += ContentServiceMoved;
|
||||
ContentService.Trashed += ContentServiceTrashed;
|
||||
|
||||
//These should only fire for providers that DONT have SupportUnpublishedContent set to true
|
||||
content.AfterUpdateDocumentCache += ContentAfterUpdateDocumentCache;
|
||||
content.AfterClearDocumentCache += ContentAfterClearDocumentCache;
|
||||
|
||||
//Bind to distributed cache events - this ensures that this logic occurs on ALL servers that are taking part
|
||||
// in a load balanced environment.
|
||||
CacheRefresherBase<UnpublishedPageCacheRefresher>.CacheUpdated += UnpublishedPageCacheRefresherCacheUpdated;
|
||||
CacheRefresherBase<PageCacheRefresher>.CacheUpdated += PublishedPageCacheRefresherCacheUpdated;
|
||||
CacheRefresherBase<MediaCacheRefresher>.CacheUpdated += MediaCacheRefresherCacheUpdated;
|
||||
|
||||
Member.AfterSave += MemberAfterSave;
|
||||
Member.AfterDelete += MemberAfterDelete;
|
||||
|
||||
@@ -79,60 +76,192 @@ namespace Umbraco.Web.Search
|
||||
}
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
static void ContentServiceTrashed(IContentService sender, Core.Events.MoveEventArgs<IContent> e)
|
||||
/// <summary>
|
||||
/// Handles index management for all media events - basically handling saving/copying/trashing/deleting
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
static void MediaCacheRefresherCacheUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs e)
|
||||
{
|
||||
IndexConent(e.Entity);
|
||||
switch (e.MessageType)
|
||||
{
|
||||
case MessageType.RefreshById:
|
||||
var c1 = ApplicationContext.Current.Services.MediaService.GetById((int)e.MessageObject);
|
||||
if (c1 != null)
|
||||
{
|
||||
ReIndexForMedia(c1, c1.Trashed == false);
|
||||
}
|
||||
break;
|
||||
case MessageType.RemoveById:
|
||||
var c2 = ApplicationContext.Current.Services.MediaService.GetById((int)e.MessageObject);
|
||||
if (c2 != null)
|
||||
{
|
||||
//This is triggered when the item has trashed.
|
||||
// So we need to delete the index from all indexes not supporting unpublished content.
|
||||
|
||||
DeleteIndexForEntity(c2.Id, true);
|
||||
|
||||
//We then need to re-index this item for all indexes supporting unpublished content
|
||||
|
||||
ReIndexForMedia(c2, false);
|
||||
}
|
||||
break;
|
||||
case MessageType.RefreshByJson:
|
||||
|
||||
var jsonPayloads = MediaCacheRefresher.DeserializeFromJsonPayload((string)e.MessageObject);
|
||||
if (jsonPayloads.Any())
|
||||
{
|
||||
foreach (var payload in jsonPayloads)
|
||||
{
|
||||
switch (payload.Operation)
|
||||
{
|
||||
case MediaCacheRefresher.OperationType.Saved:
|
||||
var media = ApplicationContext.Current.Services.MediaService.GetById(payload.Id);
|
||||
if (media != null)
|
||||
{
|
||||
ReIndexForMedia(media, media.Trashed == false);
|
||||
}
|
||||
break;
|
||||
case MediaCacheRefresher.OperationType.Trashed:
|
||||
//keep if trashed for indexes supporting unpublished
|
||||
DeleteIndexForEntity(payload.Id, true);
|
||||
break;
|
||||
case MediaCacheRefresher.OperationType.Deleted:
|
||||
//permanently remove from all indexes
|
||||
DeleteIndexForEntity(payload.Id, false);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case MessageType.RefreshByInstance:
|
||||
case MessageType.RemoveByInstance:
|
||||
case MessageType.RefreshAll:
|
||||
default:
|
||||
//We don't support these, these message types will not fire for media
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles index management for all published content events - basically handling published/unpublished
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks>
|
||||
/// This will execute on all servers taking part in load balancing
|
||||
/// </remarks>
|
||||
[SecuritySafeCritical]
|
||||
static void MediaServiceTrashed(IMediaService sender, Core.Events.MoveEventArgs<IMedia> e)
|
||||
static void PublishedPageCacheRefresherCacheUpdated(PageCacheRefresher sender, CacheRefresherEventArgs e)
|
||||
{
|
||||
IndexMedia(e.Entity);
|
||||
switch (e.MessageType)
|
||||
{
|
||||
case MessageType.RefreshById:
|
||||
var c1 = ApplicationContext.Current.Services.ContentService.GetById((int)e.MessageObject);
|
||||
if (c1 != null)
|
||||
{
|
||||
ReIndexForContent(c1, true);
|
||||
}
|
||||
break;
|
||||
case MessageType.RemoveById:
|
||||
|
||||
//This is triggered when the item has been unpublished or trashed (which also performs an unpublish).
|
||||
|
||||
var c2 = ApplicationContext.Current.Services.ContentService.GetById((int)e.MessageObject);
|
||||
if (c2 != null)
|
||||
{
|
||||
// So we need to delete the index from all indexes not supporting unpublished content.
|
||||
|
||||
DeleteIndexForEntity(c2.Id, true);
|
||||
|
||||
// We then need to re-index this item for all indexes supporting unpublished content
|
||||
|
||||
ReIndexForContent(c2, false);
|
||||
}
|
||||
break;
|
||||
case MessageType.RefreshByInstance:
|
||||
var c3 = e.MessageObject as IContent;
|
||||
if (c3 != null)
|
||||
{
|
||||
ReIndexForContent(c3, true);
|
||||
}
|
||||
break;
|
||||
case MessageType.RemoveByInstance:
|
||||
|
||||
//This is triggered when the item has been unpublished or trashed (which also performs an unpublish).
|
||||
|
||||
var c4 = e.MessageObject as IContent;
|
||||
if (c4 != null)
|
||||
{
|
||||
// So we need to delete the index from all indexes not supporting unpublished content.
|
||||
|
||||
DeleteIndexForEntity(c4.Id, true);
|
||||
|
||||
// We then need to re-index this item for all indexes supporting unpublished content
|
||||
|
||||
ReIndexForContent(c4, false);
|
||||
}
|
||||
break;
|
||||
case MessageType.RefreshAll:
|
||||
case MessageType.RefreshByJson:
|
||||
default:
|
||||
//We don't support these for examine indexing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles index management for all unpublished content events - basically handling saving/copying/deleting
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks>
|
||||
/// This will execute on all servers taking part in load balancing
|
||||
/// </remarks>
|
||||
[SecuritySafeCritical]
|
||||
static void ContentServiceMoved(IContentService sender, Umbraco.Core.Events.MoveEventArgs<IContent> e)
|
||||
static void UnpublishedPageCacheRefresherCacheUpdated(UnpublishedPageCacheRefresher sender, CacheRefresherEventArgs e)
|
||||
{
|
||||
IndexConent(e.Entity);
|
||||
}
|
||||
switch (e.MessageType)
|
||||
{
|
||||
case MessageType.RefreshById:
|
||||
var c1 = ApplicationContext.Current.Services.ContentService.GetById((int) e.MessageObject);
|
||||
if (c1 != null)
|
||||
{
|
||||
ReIndexForContent(c1, false);
|
||||
}
|
||||
break;
|
||||
case MessageType.RemoveById:
|
||||
|
||||
// This is triggered when the item is permanently deleted
|
||||
|
||||
DeleteIndexForEntity((int)e.MessageObject, false);
|
||||
break;
|
||||
case MessageType.RefreshByInstance:
|
||||
var c3 = e.MessageObject as IContent;
|
||||
if (c3 != null)
|
||||
{
|
||||
ReIndexForContent(c3, false);
|
||||
}
|
||||
break;
|
||||
case MessageType.RemoveByInstance:
|
||||
|
||||
[SecuritySafeCritical]
|
||||
static void ContentServiceDeleted(IContentService sender, Umbraco.Core.Events.DeleteEventArgs<IContent> e)
|
||||
{
|
||||
e.DeletedEntities.ForEach(
|
||||
content =>
|
||||
ExamineManager.Instance.DeleteFromIndex(
|
||||
content.Id.ToString(),
|
||||
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>().Where(x => x.EnableDefaultEventHandler)));
|
||||
}
|
||||
// This is triggered when the item is permanently deleted
|
||||
|
||||
[SecuritySafeCritical]
|
||||
static void ContentServiceSaved(IContentService sender, Umbraco.Core.Events.SaveEventArgs<IContent> e)
|
||||
{
|
||||
e.SavedEntities.ForEach(IndexConent);
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
static void MediaServiceMoved(IMediaService sender, Umbraco.Core.Events.MoveEventArgs<IMedia> e)
|
||||
{
|
||||
IndexMedia(e.Entity);
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
static void MediaServiceDeleted(IMediaService sender, Umbraco.Core.Events.DeleteEventArgs<IMedia> e)
|
||||
{
|
||||
e.DeletedEntities.ForEach(
|
||||
media =>
|
||||
ExamineManager.Instance.DeleteFromIndex(
|
||||
media.Id.ToString(),
|
||||
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>().Where(x => x.EnableDefaultEventHandler)));
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
static void MediaServiceSaved(IMediaService sender, Umbraco.Core.Events.SaveEventArgs<IMedia> e)
|
||||
{
|
||||
e.SavedEntities.ForEach(IndexMedia);
|
||||
var c4 = e.MessageObject as IContent;
|
||||
if (c4 != null)
|
||||
{
|
||||
DeleteIndexForEntity(c4.Id, false);
|
||||
}
|
||||
break;
|
||||
case MessageType.RefreshAll:
|
||||
case MessageType.RefreshByJson:
|
||||
default:
|
||||
//We don't support these, these message types will not fire for unpublished content
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
@@ -156,39 +285,6 @@ namespace Umbraco.Web.Search
|
||||
.Where(x => x.EnableDefaultEventHandler));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only Update indexes for providers that dont SupportUnpublishedContent
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
[SecuritySafeCritical]
|
||||
private static void ContentAfterUpdateDocumentCache(Document sender, DocumentCacheEventArgs e)
|
||||
{
|
||||
//ensure that only the providers that have DONT unpublishing support enabled
|
||||
//that are also flagged to listen
|
||||
ExamineManager.Instance.ReIndexNode(ToXDocument(sender, true).Root, IndexTypes.Content,
|
||||
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
|
||||
.Where(x => !x.SupportUnpublishedContent
|
||||
&& x.EnableDefaultEventHandler));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only update indexes for providers that don't SupportUnpublishedContnet
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
[SecuritySafeCritical]
|
||||
private static void ContentAfterClearDocumentCache(Document sender, DocumentCacheEventArgs e)
|
||||
{
|
||||
var nodeId = sender.Id.ToString();
|
||||
//ensure that only the providers that DONT have unpublishing support enabled
|
||||
//that are also flagged to listen
|
||||
ExamineManager.Instance.DeleteFromIndex(nodeId,
|
||||
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
|
||||
.Where(x => !x.SupportUnpublishedContent
|
||||
&& x.EnableDefaultEventHandler));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler to create a lower cased version of the node name, this is so we can support case-insensitive searching and still
|
||||
/// use the Whitespace Analyzer
|
||||
@@ -210,27 +306,61 @@ namespace Umbraco.Web.Search
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void IndexMedia(IMedia sender)
|
||||
[SecuritySafeCritical]
|
||||
private static void ReIndexForMedia(IMedia sender, bool isMediaPublished)
|
||||
{
|
||||
ExamineManager.Instance.ReIndexNode(
|
||||
sender.ToXml(), "media",
|
||||
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>().Where(x => x.EnableDefaultEventHandler));
|
||||
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
|
||||
|
||||
//Index this item for all indexers if the media is not trashed, otherwise if the item is trashed
|
||||
// then only index this for indexers supporting unpublished media
|
||||
|
||||
.Where(x => isMediaPublished || (x.SupportUnpublishedContent))
|
||||
.Where(x => x.EnableDefaultEventHandler));
|
||||
}
|
||||
|
||||
private static void IndexConent(IContent sender)
|
||||
/// <summary>
|
||||
/// Remove items from any index that doesn't support unpublished content
|
||||
/// </summary>
|
||||
/// <param name="entityId"></param>
|
||||
/// <param name="keepIfUnpublished">
|
||||
/// If true, indicates that we will only delete this item from indexes that don't support unpublished content.
|
||||
/// If false it will delete this from all indexes regardless.
|
||||
/// </param>
|
||||
[SecuritySafeCritical]
|
||||
private static void DeleteIndexForEntity(int entityId, bool keepIfUnpublished)
|
||||
{
|
||||
ExamineManager.Instance.DeleteFromIndex(
|
||||
entityId.ToString(CultureInfo.InvariantCulture),
|
||||
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
|
||||
|
||||
//if keepIfUnpublished == true then only delete this item from indexes not supporting unpublished content,
|
||||
// otherwise if keepIfUnpublished == false then remove from all indexes
|
||||
|
||||
.Where(x => keepIfUnpublished == false || x.SupportUnpublishedContent == false)
|
||||
.Where(x => x.EnableDefaultEventHandler));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-indexes a content item whether published or not but only indexes them for indexes supporting unpublished content
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="isContentPublished">
|
||||
/// Value indicating whether the item is published or not
|
||||
/// </param>
|
||||
[SecuritySafeCritical]
|
||||
private static void ReIndexForContent(IContent sender, bool isContentPublished)
|
||||
{
|
||||
//only index this content if the indexer supports unpublished content. that is because the
|
||||
// content.AfterUpdateDocumentCache will handle anything being published and will only index against indexers
|
||||
// that only support published content.
|
||||
// NOTE: The events for publishing have changed slightly from 6.0 to 6.1 and are streamlined in 6.1. Before
|
||||
// this event would fire before publishing, then again after publishing. Now the save event fires once before
|
||||
// publishing and that is all.
|
||||
|
||||
ExamineManager.Instance.ReIndexNode(
|
||||
sender.ToXml(), "content",
|
||||
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
|
||||
.Where(x => x.SupportUnpublishedContent && x.EnableDefaultEventHandler));
|
||||
|
||||
//Index this item for all indexers if the content is published, otherwise if the item is not published
|
||||
// then only index this for indexers supporting unpublished content
|
||||
|
||||
.Where(x => isContentPublished || (x.SupportUnpublishedContent))
|
||||
.Where(x => x.EnableDefaultEventHandler));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -288,6 +288,7 @@
|
||||
<Compile Include="Cache\StylesheetCacheRefresher.cs" />
|
||||
<Compile Include="Cache\StylesheetPropertyCacheRefresher.cs" />
|
||||
<Compile Include="Cache\TemplateCacheRefresher.cs" />
|
||||
<Compile Include="Cache\UnpublishedPageCacheRefresher.cs" />
|
||||
<Compile Include="Cache\UserCacheRefresher.cs" />
|
||||
<Compile Include="Cache\UserPermissionsCacheRefresher.cs" />
|
||||
<Compile Include="Cache\UserTypeCacheRefresher.cs" />
|
||||
|
||||
Reference in New Issue
Block a user