diff --git a/src/Umbraco.Core/Configuration/CoreDebug.cs b/src/Umbraco.Core/Configuration/CoreDebug.cs
index 7aa198f0e6..74a28e7637 100644
--- a/src/Umbraco.Core/Configuration/CoreDebug.cs
+++ b/src/Umbraco.Core/Configuration/CoreDebug.cs
@@ -18,10 +18,14 @@ namespace Umbraco.Core.Configuration
{
var appSettings = System.Configuration.ConfigurationManager.AppSettings;
LogUncompletedScopes = string.Equals("true", appSettings["Umbraco.CoreDebug.LogUncompletedScopes"], StringComparison.OrdinalIgnoreCase);
+ DumpOnTimeoutThreadAbort = string.Equals("true", appSettings["Umbraco.CoreDebug.DumpOnTimeoutThreadAbort"], StringComparison.OrdinalIgnoreCase);
}
// when true, Scope logs the stack trace for any scope that gets disposed without being completed.
// this helps troubleshooting rogue scopes that we forget to complete
public bool LogUncompletedScopes { get; private set; }
+ // when true, the Logger creates a minidump of w3wp in ~/App_Data/MiniDump whenever it logs
+ // an error due to a ThreadAbortException that is due to a timeout.
+ public bool DumpOnTimeoutThreadAbort { get; private set; }
}
}
diff --git a/src/Umbraco.Core/Diagnostics/MiniDump.cs b/src/Umbraco.Core/Diagnostics/MiniDump.cs
new file mode 100644
index 0000000000..e8c2e82f94
--- /dev/null
+++ b/src/Umbraco.Core/Diagnostics/MiniDump.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using Umbraco.Core.IO;
+
+namespace Umbraco.Core.Diagnostics
+{
+ // taken from https://blogs.msdn.microsoft.com/dondu/2010/10/24/writing-minidumps-in-c/
+ // and https://blogs.msdn.microsoft.com/dondu/2010/10/31/writing-minidumps-from-exceptions-in-c/
+ // which itself got it from http://blog.kalmbach-software.de/2008/12/13/writing-minidumps-in-c/
+
+ internal static class MiniDump
+ {
+ private static readonly object LockO = new object();
+
+ [Flags]
+ public enum Option : uint
+ {
+ // From dbghelp.h:
+ Normal = 0x00000000,
+ WithDataSegs = 0x00000001,
+ WithFullMemory = 0x00000002,
+ WithHandleData = 0x00000004,
+ FilterMemory = 0x00000008,
+ ScanMemory = 0x00000010,
+ WithUnloadedModules = 0x00000020,
+ WithIndirectlyReferencedMemory = 0x00000040,
+ FilterModulePaths = 0x00000080,
+ WithProcessThreadData = 0x00000100,
+ WithPrivateReadWriteMemory = 0x00000200,
+ WithoutOptionalData = 0x00000400,
+ WithFullMemoryInfo = 0x00000800,
+ WithThreadInfo = 0x00001000,
+ WithCodeSegs = 0x00002000,
+ WithoutAuxiliaryState = 0x00004000,
+ WithFullAuxiliaryState = 0x00008000,
+ WithPrivateWriteCopyMemory = 0x00010000,
+ IgnoreInaccessibleMemory = 0x00020000,
+ ValidTypeFlags = 0x0003ffff,
+ }
+
+ //typedef struct _MINIDUMP_EXCEPTION_INFORMATION {
+ // DWORD ThreadId;
+ // PEXCEPTION_POINTERS ExceptionPointers;
+ // BOOL ClientPointers;
+ //} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION;
+ [StructLayout(LayoutKind.Sequential, Pack = 4)] // Pack=4 is important! So it works also for x64!
+ public struct MiniDumpExceptionInformation
+ {
+ public uint ThreadId;
+ public IntPtr ExceptionPointers;
+ [MarshalAs(UnmanagedType.Bool)]
+ public bool ClientPointers;
+ }
+
+ //BOOL
+ //WINAPI
+ //MiniDumpWriteDump(
+ // __in HANDLE hProcess,
+ // __in DWORD ProcessId,
+ // __in HANDLE hFile,
+ // __in MINIDUMP_TYPE DumpType,
+ // __in_opt PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
+ // __in_opt PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
+ // __in_opt PMINIDUMP_CALLBACK_INFORMATION CallbackParam
+ // );
+
+ // Overload requiring MiniDumpExceptionInformation
+ [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
+ private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam);
+
+ // Overload supporting MiniDumpExceptionInformation == NULL
+ [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
+ private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam);
+
+ [DllImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
+ private static extern uint GetCurrentThreadId();
+
+ private static bool Write(SafeHandle fileHandle, Option options, bool withException = false)
+ {
+ var currentProcess = Process.GetCurrentProcess();
+ var currentProcessHandle = currentProcess.Handle;
+ var currentProcessId = (uint)currentProcess.Id;
+
+ MiniDumpExceptionInformation exp;
+
+ exp.ThreadId = GetCurrentThreadId();
+ exp.ClientPointers = false;
+ exp.ExceptionPointers = IntPtr.Zero;
+
+ if (withException)
+ exp.ExceptionPointers = Marshal.GetExceptionPointers();
+
+ var bRet = exp.ExceptionPointers == IntPtr.Zero
+ ? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)
+ : MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, ref exp, IntPtr.Zero, IntPtr.Zero);
+
+ return bRet;
+ }
+
+ public static bool Dump(Option options = Option.WithFullMemory, bool withException = false)
+ {
+ lock (LockO)
+ {
+ // work around "stack trace is not available while minidump debugging",
+ // by making sure a local var (that we can inspect) contains the stack trace.
+ // getting the call stack before it is unwound would require a special exception
+ // filter everywhere in our code = not!
+ var stacktrace = withException ? Environment.StackTrace : string.Empty;
+
+ var filepath = IOHelper.MapPath("~/App_Data/MiniDump");
+ if (Directory.Exists(filepath) == false)
+ Directory.CreateDirectory(filepath);
+
+ var filename = Path.Combine(filepath, string.Format("{0:yyyyMMddTHHmmss}.{1}.dmp", DateTime.UtcNow, Guid.NewGuid().ToString("N").Substring(0, 4)));
+ using (var stream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.Write))
+ {
+ return Write(stream.SafeFileHandle, options, withException);
+ }
+ }
+ }
+
+ public static bool OkToDump()
+ {
+ lock (LockO)
+ {
+ var filepath = IOHelper.MapPath("~/App_Data/MiniDump");
+ if (Directory.Exists(filepath) == false) return true;
+ var count = Directory.GetFiles(filepath, "*.dmp").Length;
+ return count < 8;
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Logging/Logger.cs b/src/Umbraco.Core/Logging/Logger.cs
index 66cad59733..d0bfcbfca0 100644
--- a/src/Umbraco.Core/Logging/Logger.cs
+++ b/src/Umbraco.Core/Logging/Logger.cs
@@ -7,6 +7,8 @@ using System.Threading;
using System.Web;
using log4net;
using log4net.Config;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Diagnostics;
namespace Umbraco.Core.Logging
{
@@ -57,23 +59,55 @@ namespace Umbraco.Core.Logging
internal ILog LoggerFor(object getTypeFromInstance)
{
if (getTypeFromInstance == null) throw new ArgumentNullException("getTypeFromInstance");
-
+
return LogManager.GetLogger(getTypeFromInstance.GetType());
}
-
+
public void Error(Type callingType, string message, Exception exception)
{
var logger = LogManager.GetLogger(callingType);
if (logger == null) return;
+ var dump = false;
+
if (IsTimeoutThreadAbortException(exception))
{
message += "\r\nThe thread has been aborted, because the request has timed out.";
+
+ // dump if configured, or if stacktrace contains Monitor.ReliableEnter
+ dump = UmbracoConfig.For.CoreDebug().DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception);
+
+ // dump if it is ok to dump (might have a cap on number of dump...)
+ dump &= MiniDump.OkToDump();
}
- logger.Error(message, exception);
+ if (dump)
+ {
+ try
+ {
+ var dumped = MiniDump.Dump(withException: true);
+ message += dumped
+ ? "\r\nA minidump was created in App_Data/MiniDump"
+ : "\r\nFailed to create a minidump";
+ }
+ catch (Exception e)
+ {
+ message += string.Format("\r\nFailed to create a minidump ({0}: {1})", e.GetType().FullName, e.Message);
+ }
+ }
+
+ logger.Error(message, exception);
}
+ private static bool IsMonitorEnterThreadAbortException(Exception exception)
+ {
+ var abort = exception as ThreadAbortException;
+ if (abort == null) return false;
+
+ var stacktrace = abort.StackTrace;
+ return stacktrace.Contains("System.Threading.Monitor.ReliableEnter");
+ }
+
private static bool IsTimeoutThreadAbortException(Exception exception)
{
var abort = exception as ThreadAbortException;
@@ -105,7 +139,7 @@ namespace Umbraco.Core.Logging
if (showHttpTrace && HttpContext.Current != null)
{
HttpContext.Current.Trace.Warn(callingType.Name, string.Format(message, formatItems.Select(x => x.Invoke()).ToArray()));
- }
+ }
var logger = LogManager.GetLogger(callingType);
if (logger == null || logger.IsWarnEnabled == false) return;
@@ -122,7 +156,7 @@ namespace Umbraco.Core.Logging
var logger = LogManager.GetLogger(callingType);
if (logger == null || logger.IsWarnEnabled == false) return;
var executedParams = formatItems.Select(x => x.Invoke()).ToArray();
- logger.WarnFormat((message) + ". Exception: " + e, executedParams);
+ logger.WarnFormat((message) + ". Exception: " + e, executedParams);
}
///
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
index d05960b08f..63164cf271 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
@@ -179,7 +179,7 @@ namespace Umbraco.Core.Models.PublishedContent
}
if (contentType == null)
- throw new Exception(string.Format("ContentTypeService failed to find a {0} type with alias \"{1}\".",
+ throw new InvalidOperationException(string.Format("ContentTypeService failed to find a {0} type with alias \"{1}\".",
itemType.ToString().ToLower(), alias));
return new PublishedContentType(contentType);
diff --git a/src/Umbraco.Core/Publishing/PublishStatus.cs b/src/Umbraco.Core/Publishing/PublishStatus.cs
index 3436e9070e..a2fdf1d6cd 100644
--- a/src/Umbraco.Core/Publishing/PublishStatus.cs
+++ b/src/Umbraco.Core/Publishing/PublishStatus.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Linq;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
@@ -12,7 +13,8 @@ namespace Umbraco.Core.Publishing
{
public PublishStatus(IContent content, PublishStatusType statusType, EventMessages eventMessages)
: base(content, statusType, eventMessages)
- {
+ {
+ InvalidProperties = Enumerable.Empty();
}
///
@@ -20,8 +22,7 @@ namespace Umbraco.Core.Publishing
///
public PublishStatus(IContent content, EventMessages eventMessages)
: this(content, PublishStatusType.Success, eventMessages)
- {
- }
+ { }
public IContent ContentItem
{
diff --git a/src/Umbraco.Core/Publishing/PublishingStrategy.cs b/src/Umbraco.Core/Publishing/PublishingStrategy.cs
index e09b26a74d..2c603a40ed 100644
--- a/src/Umbraco.Core/Publishing/PublishingStrategy.cs
+++ b/src/Umbraco.Core/Publishing/PublishingStrategy.cs
@@ -51,7 +51,7 @@ namespace Umbraco.Core.Publishing
///
///
/// to publish
- /// Id of the User issueing the publish operation
+ /// Id of the User issueing the publish operation
Attempt IPublishingStrategy2.Publish(IScopeUnitOfWork uow, IContent content, int userId)
{
var evtMsgs = _eventMessagesFactory.Get();
@@ -124,26 +124,26 @@ namespace Umbraco.Core.Publishing
/// By default this is set to true which means that it will publish any content item in the list that is completely unpublished and
/// not visible on the front-end. If set to false, this will only publish content that is live on the front-end but has new versions
/// that have yet to be published.
- ///
+ ///
///
///
- ///
+ ///
/// This method becomes complex once we start to be able to cancel events or stop publishing a content item in any way because if a
/// content item is not published then it's children shouldn't be published either. This rule will apply for the following conditions:
/// * If a document fails to be published, do not proceed to publish it's children if:
/// ** The document does not have a publish version
/// ** The document does have a published version but the includeUnpublishedDocuments = false
- ///
+ ///
/// In order to do this, we will order the content by level and begin by publishing each item at that level, then proceed to the next
- /// level and so on. If we detect that the above rule applies when the document publishing is cancelled we'll add it to the list of
+ /// level and so on. If we detect that the above rule applies when the document publishing is cancelled we'll add it to the list of
/// parentsIdsCancelled so that it's children don't get published.
- ///
+ ///
/// Its important to note that all 'root' documents included in the list *will* be published regardless of the rules mentioned
- /// above (unless it is invalid)!! By 'root' documents we are referring to documents in the list with the minimum value for their 'level'.
- /// In most cases the 'root' documents will only be one document since under normal circumstance we only publish one document and
+ /// above (unless it is invalid)!! By 'root' documents we are referring to documents in the list with the minimum value for their 'level'.
+ /// In most cases the 'root' documents will only be one document since under normal circumstance we only publish one document and
/// its children. The reason we have to do this is because if a user is publishing a document and it's children, it is implied that
/// the user definitely wants to publish it even if it has never been published before.
- ///
+ ///
///
IEnumerable> IPublishingStrategy2.PublishWithChildren(IScopeUnitOfWork uow,
IEnumerable content, int userId, bool includeUnpublishedDocuments)
@@ -157,7 +157,7 @@ namespace Umbraco.Core.Publishing
//group by levels and iterate over the sorted ascending level.
//TODO: This will cause all queries to execute, they will not be lazy but I'm not really sure being lazy actually made
- // much difference because we iterate over them all anyways?? Morten?
+ // much difference because we iterate over them all anyways?? Morten?
// Because we're grouping I think this will execute all the queries anyways so need to fetch it all first.
var fetchedContent = content.ToArray();
@@ -174,7 +174,7 @@ namespace Umbraco.Core.Publishing
var levelGroups = fetchedContent.GroupBy(x => x.Level);
foreach (var level in levelGroups.OrderBy(x => x.Key))
{
- //set the first level flag, used to ensure that all documents at the first level will
+ //set the first level flag, used to ensure that all documents at the first level will
//be published regardless of the rules mentioned in the remarks.
if (!firstLevel.HasValue)
{
@@ -224,7 +224,10 @@ namespace Umbraco.Core.Publishing
_logger.Info(
string.Format("Content '{0}' with Id '{1}' will not be published because some of it's content is not passing validation rules.",
item.Name, item.Id));
- statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedContentInvalid, evtMsgs)));
+ statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedContentInvalid, evtMsgs)
+ {
+ InvalidProperties = ((ContentBase)item).LastInvalidProperties
+ }));
//Does this document apply to our rule to cancel it's children being published?
CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments);
@@ -296,11 +299,11 @@ namespace Umbraco.Core.Publishing
///
///
/// See remarks on method: PublishWithChildrenInternal
- ///
+ ///
private void CheckCancellingOfChildPublishing(IContent content, List parentsIdsCancelled, bool includeUnpublishedDocuments)
{
//Does this document apply to our rule to cancel it's children being published?
- //TODO: We're going back to the service layer here... not sure how to avoid this? And this will add extra overhead to
+ //TODO: We're going back to the service layer here... not sure how to avoid this? And this will add extra overhead to
// any document that fails to publish...
var hasPublishedVersion = ApplicationContext.Current.Services.ContentService.HasPublishedVersion(content.Id);
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 90eba5b5b4..753812737b 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -199,6 +199,7 @@
+
@@ -219,7 +220,6 @@
-
@@ -323,6 +323,7 @@
+
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js
index 1b9dc090bb..6cf5f68461 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js
@@ -163,13 +163,13 @@ angular.module('umbraco.directives')
}
// ignore clicks on dialog from old dialog service
- var oldDialog = $(el).parents("#old-dialog-service");
+ var oldDialog = $(event.target).parents("#old-dialog-service");
if (oldDialog.length === 1) {
return;
}
// ignore clicks in tinyMCE dropdown(floatpanel)
- var floatpanel = $(el).parents(".mce-floatpanel");
+ var floatpanel = $(event.target).closest(".mce-floatpanel");
if (floatpanel.length === 1) {
return;
}
diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/publish.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/publish.aspx
index e86108616d..d6b6936144 100644
--- a/src/Umbraco.Web.UI/umbraco/dialogs/publish.aspx
+++ b/src/Umbraco.Web.UI/umbraco/dialogs/publish.aspx
@@ -80,7 +80,7 @@
-
+
diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs
index 202fe551c7..1b0a428610 100644
--- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs
+++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.XPath;
+using umbraco;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models;
@@ -315,9 +316,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
private void InitializeNode()
{
- InitializeNode(_xmlNode, UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema, _isPreviewing,
- out _id, out _key, out _template, out _sortOrder, out _name, out _writerName,
- out _urlName, out _creatorName, out _creatorId, out _writerId, out _docTypeAlias, out _docTypeId, out _path,
+ InitializeNode(_xmlNode, UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema, _isPreviewing,
+ out _id, out _key, out _template, out _sortOrder, out _name, out _writerName,
+ out _urlName, out _creatorName, out _creatorId, out _writerId, out _docTypeAlias, out _docTypeId, out _path,
out _version, out _createDate, out _updateDate, out _level, out _isDraft, out _contentType, out _properties,
PublishedContentType.Get);
@@ -345,7 +346,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
//return if this is null
if (xmlNode == null)
- {
+ {
return;
}
@@ -407,8 +408,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
}
//dictionary to store the property node data
- var propertyNodes = new Dictionary();
-
+ var propertyNodes = new Dictionary();
+
foreach (XmlNode n in xmlNode.ChildNodes)
{
var e = n as XmlElement;
@@ -432,7 +433,22 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
}
//lookup the content type and create the properties collection
- contentType = getPublishedContentType(PublishedItemType.Content, docTypeAlias);
+ try
+ {
+ contentType = getPublishedContentType(PublishedItemType.Content, docTypeAlias);
+
+ }
+ catch (InvalidOperationException e)
+ {
+ content.Instance.RefreshContentFromDatabase();
+
+
+ throw new InvalidOperationException(
+ string.Format("{0}. This usually indicates that the content cache is corrupt; the content cache has been rebuilt in an attempt to self-fix the issue.",
+ //keep the original message but don't use this as an inner exception because we want the above message to be displayed, if we use the inner exception
+ //we can keep the stack trace but the header message will be the original message and the one we really want to display will be hidden below the fold.
+ e.Message));
+ }
properties = new Dictionary(StringComparer.OrdinalIgnoreCase);
//fill in the property collection
diff --git a/src/Umbraco.Web/WebServices/BulkPublishController.cs b/src/Umbraco.Web/WebServices/BulkPublishController.cs
index cc9a153e07..e477db539a 100644
--- a/src/Umbraco.Web/WebServices/BulkPublishController.cs
+++ b/src/Umbraco.Web/WebServices/BulkPublishController.cs
@@ -46,16 +46,16 @@ namespace Umbraco.Web.WebServices
/*var result = ((ContentService) Services.ContentService)
.PublishWithChildrenInternal(content, UmbracoUser.Id, includeUnpublished)
.ToArray();*/
- var result = doc.PublishWithSubs(UmbracoUser.Id, includeUnpublished);
+ var result = doc.PublishWithSubs(UmbracoUser.Id, includeUnpublished).ToArray();
return Json(new
{
success = result.All(x => x.Success),
- message = GetMessageForStatuses(result.Select(x => x.Result), content)
+ message = GetMessageForStatuses(result.Select(x => x.Result).ToArray(), content)
});
}
}
- private string GetMessageForStatuses(IEnumerable statuses, IContent doc)
+ private string GetMessageForStatuses(PublishStatus[] statuses, IContent doc)
{
//if all are successful then just say it was successful
if (statuses.All(x => ((int) x.StatusType) < 10))
@@ -91,12 +91,12 @@ namespace Umbraco.Web.WebServices
return "Cannot publish document with a status of " + status.StatusType;
case PublishStatusType.FailedCancelledByEvent:
return ui.Text("publish", "contentPublishedFailedByEvent",
- string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), UmbracoUser);
+ string.Format("'{0}' ({1})", status.ContentItem.Name, status.ContentItem.Id), UmbracoUser);
case PublishStatusType.FailedContentInvalid:
return ui.Text("publish", "contentPublishedFailedInvalid",
new []{
- string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id),
- string.Join(",", status.InvalidProperties.Select(x => x.Alias))
+ string.Format("'{0}' ({1})", status.ContentItem.Name, status.ContentItem.Id),
+ string.Format("'{0}'", string.Join(", ", status.InvalidProperties.Select(x => x.Alias)))
},
UmbracoUser);
default:
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs
index 2499c1c817..9ba5f91bd3 100644
--- a/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs
+++ b/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs
@@ -99,7 +99,7 @@ namespace umbraco.presentation.preview
//Inject preview xml
parentId = document.Level == 1 ? -1 : document.ParentId;
var previewXml = document.ToPreviewXml(XmlContent);
- if (document.ContentEntity.Published == false
+ if (document.ContentEntity.Published == false
&& ApplicationContext.Current.Services.ContentService.HasPublishedVersion(document.Id))
previewXml.Attributes.Append(XmlContent.CreateAttribute("isDraft"));
XmlContent = content.GetAddOrUpdateXmlNode(XmlContent, document.Id, document.Level, parentId, previewXml);
@@ -187,13 +187,9 @@ namespace umbraco.presentation.preview
private static void CleanPreviewDirectory(int userId, DirectoryInfo dir)
{
- foreach (FileInfo file in dir.GetFiles(userId + "_*.config"))
- {
- DeletePreviewFile(userId, file);
- }
// also delete any files accessed more than 10 minutes ago
var now = DateTime.Now;
- foreach (FileInfo file in dir.GetFiles("*.config"))
+ foreach (var file in dir.GetFiles("*.config"))
{
if ((now - file.LastAccessTime).TotalMinutes > 10)
DeletePreviewFile(userId, file);
@@ -206,6 +202,12 @@ namespace umbraco.presentation.preview
{
file.Delete();
}
+ catch (IOException)
+ {
+ // for *some* reason deleting the file can fail,
+ // and it will work later on (long-lasting locks, etc),
+ // so just ignore the exception
+ }
catch (Exception ex)
{
LogHelper.Error(string.Format("Couldn't delete preview set: {0} - User {1}", file.Name, userId), ex);
diff --git a/src/umbraco.cms/businesslogic/Packager/data.cs b/src/umbraco.cms/businesslogic/Packager/data.cs
index 7781f1790f..536be6d435 100644
--- a/src/umbraco.cms/businesslogic/Packager/data.cs
+++ b/src/umbraco.cms/businesslogic/Packager/data.cs
@@ -302,7 +302,11 @@ namespace umbraco.cms.businesslogic.packager
XmlHelper.SetAttribute(Source, xmlDef, "enableSkins", package.EnableSkins.ToString());
XmlHelper.SetAttribute(Source, xmlDef, "skinRepoGuid", package.SkinRepoGuid.ToString());
XmlHelper.SetAttribute(Source, xmlDef, "iconUrl", package.IconUrl);
- XmlHelper.SetAttribute(Source, xmlDef, "umbVersion", package.UmbracoVersion.ToString(3));
+ if (package.UmbracoVersion != null)
+ {
+ XmlHelper.SetAttribute(Source, xmlDef, "umbVersion", package.UmbracoVersion.ToString(3));
+ }
+
var licenseNode = xmlDef.SelectSingleNode("license");
if (licenseNode == null)
diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs
index bd21517d80..3ed457d776 100644
--- a/src/umbraco.cms/businesslogic/web/Document.cs
+++ b/src/umbraco.cms/businesslogic/web/Document.cs
@@ -874,18 +874,15 @@ namespace umbraco.cms.businesslogic.web
[Obsolete("Don't use! Only used internally to support the legacy events", false)]
internal IEnumerable> PublishWithSubs(int userId, bool includeUnpublished)
{
- PublishEventArgs e = new PublishEventArgs();
+ var e = new PublishEventArgs();
FireBeforePublish(e);
- IEnumerable> publishedResults = Enumerable.Empty>();
+ if (e.Cancel) return Enumerable.Empty>();
- if (!e.Cancel)
- {
- publishedResults = ApplicationContext.Current.Services.ContentService
- .PublishWithChildrenWithStatus(ContentEntity, userId, includeUnpublished);
+ var publishedResults = ApplicationContext.Current.Services.ContentService
+ .PublishWithChildrenWithStatus(ContentEntity, userId, includeUnpublished);
- FireAfterPublish(e);
- }
+ FireAfterPublish(e);
return publishedResults;
}
@@ -893,7 +890,6 @@ namespace umbraco.cms.businesslogic.web
[Obsolete("Don't use! Only used internally to support the legacy events", false)]
internal Attempt SaveAndPublish(int userId)
{
- var result = Attempt.Fail(new PublishStatus(ContentEntity, PublishStatusType.FailedCancelledByEvent, new EventMessages()));
foreach (var property in GenericProperties)
{
ContentEntity.SetValue(property.PropertyType.Alias, property.Value);
@@ -901,32 +897,28 @@ namespace umbraco.cms.businesslogic.web
var saveArgs = new SaveEventArgs();
FireBeforeSave(saveArgs);
+ if (saveArgs.Cancel) return Attempt.Fail(new PublishStatus(ContentEntity, PublishStatusType.FailedCancelledByEvent, new EventMessages()));
- if (!saveArgs.Cancel)
- {
- var publishArgs = new PublishEventArgs();
- FireBeforePublish(publishArgs);
+ var publishArgs = new PublishEventArgs();
+ FireBeforePublish(publishArgs);
+ if (publishArgs.Cancel) return Attempt.Fail(new PublishStatus(ContentEntity, PublishStatusType.FailedCancelledByEvent, new EventMessages()));
- if (!publishArgs.Cancel)
- {
- //NOTE: The 'false' parameter will cause the PublishingStrategy events to fire which will ensure that the cache is refreshed.
- result = ApplicationContext.Current.Services.ContentService
- .SaveAndPublishWithStatus(ContentEntity, userId);
- base.VersionDate = ContentEntity.UpdateDate;
- this.UpdateDate = ContentEntity.UpdateDate;
+ //NOTE: The 'false' parameter will cause the PublishingStrategy events to fire which will ensure that the cache is refreshed.
+ var result = ApplicationContext.Current.Services.ContentService
+ .SaveAndPublishWithStatus(ContentEntity, userId);
+ VersionDate = ContentEntity.UpdateDate;
+ UpdateDate = ContentEntity.UpdateDate;
- //NOTE: This is just going to call the CMSNode Save which will launch into the CMSNode.BeforeSave and CMSNode.AfterSave evenths
- // which actually do dick all and there's no point in even having them there but just in case for some insane reason someone
- // has bound to those events, I suppose we'll need to keep this here.
- base.Save();
+ //NOTE: This is just going to call the CMSNode Save which will launch into the CMSNode.BeforeSave and CMSNode.AfterSave evenths
+ // which actually do dick all and there's no point in even having them there but just in case for some insane reason someone
+ // has bound to those events, I suppose we'll need to keep this here.
+ base.Save();
- //Launch the After Save event since we're doing 2 things in one operation: Saving and publishing.
- FireAfterSave(saveArgs);
+ //Launch the After Save event since we're doing 2 things in one operation: Saving and publishing.
+ FireAfterSave(saveArgs);
- //Now we need to fire the After publish event
- FireAfterPublish(publishArgs);
- }
- }
+ //Now we need to fire the After publish event
+ FireAfterPublish(publishArgs);
return result;
}