Merge branch 'dev-v7.8' into temp-U4-10707

# Conflicts:
#	src/Umbraco.Web/Editors/TourController.cs
#	src/Umbraco.Web/Umbraco.Web.csproj
This commit is contained in:
Claus
2018-01-10 10:10:19 +01:00
23 changed files with 277 additions and 75 deletions

View File

@@ -532,6 +532,7 @@ namespace Umbraco.Web.Editors
/// <returns></returns>
[FileUploadCleanupFilter]
[ContentPostValidate]
[OutgoingEditorModelEvent]
public ContentItemDisplay PostSave(
[ModelBinder(typeof(ContentItemBinder))]
ContentItemSave contentItem)
@@ -847,6 +848,7 @@ namespace Umbraco.Web.Editors
/// <param name="id"></param>
/// <returns></returns>
[EnsureUserPermissionForContent("id", 'U')]
[OutgoingEditorModelEvent]
public ContentItemDisplay PostUnPublish(int id)
{
var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));

View File

@@ -461,6 +461,7 @@ namespace Umbraco.Web.Editors
/// <returns></returns>
[FileUploadCleanupFilter]
[MediaPostValidate]
[OutgoingEditorModelEvent]
public MediaItemDisplay PostSave(
[ModelBinder(typeof(MediaItemBinder))]
MediaItemSave contentItem)

View File

@@ -256,6 +256,7 @@ namespace Umbraco.Web.Editors
/// </summary>
/// <returns></returns>
[FileUploadCleanupFilter]
[OutgoingEditorModelEvent]
public MemberDisplay PostSave(
[ModelBinder(typeof(MemberBinder))]
MemberSave contentItem)

View File

@@ -2,11 +2,11 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Umbraco.Core;
using Newtonsoft.Json.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi.Filters;
@@ -18,29 +18,35 @@ namespace Umbraco.Web.Editors
[UmbracoApplicationAuthorize(Constants.Applications.Content)]
public class TourController : UmbracoAuthorizedJsonController
{
public IEnumerable<Tour[]> GetTours()
public IEnumerable<BackOfficeTourFile> GetTours()
{
var tours = new List<Tour[]>();
var result = new List<BackOfficeTourFile>();
if (UmbracoConfig.For.UmbracoSettings().BackOffice.Tours.EnableTours == false)
return tours;
return result;
var toursPath = Path.Combine(IOHelper.MapPath(SystemDirectories.Config), "BackOfficeTours");
if (Directory.Exists(toursPath) == false)
return tours;
return result;
var tourFiles = Directory.GetFiles(toursPath, "*.json")
.OrderBy(x => x, StringComparer.InvariantCultureIgnoreCase);
var disabledTours = TourFilterResolver.Current.DisabledTours;
var coreTourFiles = Directory.GetFiles(
Path.Combine(IOHelper.MapPath(SystemDirectories.Config), "BackOfficeTours"), "*.json");
foreach (var tourFile in tourFiles)
foreach (var tourFile in coreTourFiles)
{
try
try
{
var contents = File.ReadAllText(tourFile);
result.Add(new BackOfficeTourFile
{
var contents = File.ReadAllText(tourFile);
var tourArray = JsonConvert.DeserializeObject<Tour[]>(contents);
tours.Add(tourArray.Where(x =>
disabledTours.Contains(x.Alias, StringComparer.InvariantCultureIgnoreCase) == false).ToArray());
FileName = Path.GetFileNameWithoutExtension(tourFile),
Tours = JsonConvert.DeserializeObject<BackOfficeTour[]>(contents)
});
}
catch (IOException e)
{
@@ -52,9 +58,33 @@ namespace Umbraco.Web.Editors
Logger.Error<TourController>("Error while trying to parse content as tour data: " + tourFile, e);
throw new JsonReaderException("Error while trying to parse content as tour data: " + tourFile, e);
}
}
return tours;
//collect all tour files in packges
foreach (var plugin in Directory.EnumerateDirectories(IOHelper.MapPath(SystemDirectories.AppPlugins)))
{
var pluginName = Path.GetFileName(plugin.TrimEnd('\\'));
foreach (var backofficeDir in Directory.EnumerateDirectories(plugin, "backoffice"))
{
foreach (var tourDir in Directory.EnumerateDirectories(backofficeDir, "tours"))
{
foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json"))
{
var contents = File.ReadAllText(tourFile);
result.Add(new BackOfficeTourFile
{
FileName = Path.GetFileNameWithoutExtension(tourFile),
PluginName = pluginName,
Tours = JsonConvert.DeserializeObject<BackOfficeTour[]>(contents)
});
}
}
}
}
return result.OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase);
}
}
}

View File

@@ -22,6 +22,7 @@ namespace Umbraco.Web
private readonly List<BaseIndexProvider> _indexesToRebuild = new List<BaseIndexProvider>();
private readonly ApplicationContext _appCtx;
private readonly ProfilingLogger _profilingLogger;
private static bool _isConfigured = false;
//this is used if we are not the MainDom, in which case we need to ensure that if indexes need rebuilding that this
//doesn't occur since that should only occur when we are MainDom
@@ -89,24 +90,7 @@ namespace Umbraco.Web
/// </summary>
public void Complete()
{
//We now need to disable waiting for indexing for Examine so that the appdomain is shutdown immediately and doesn't wait for pending
//indexing operations. We used to wait for indexing operations to complete but this can cause more problems than that is worth because
//that could end up halting shutdown for a very long time causing overlapping appdomains and many other problems.
foreach (var luceneIndexer in ExamineManager.Instance.IndexProviderCollection.OfType<LuceneIndexer>())
{
luceneIndexer.WaitForIndexQueueOnShutdown = false;
//we should check if the index is locked ... it shouldn't be! We are using simple fs lock now and we are also ensuring that
//the indexes are not operational unless MainDom is true so if _disableExamineIndexing is false then we should be in charge
if (_disableExamineIndexing == false)
{
var dir = luceneIndexer.GetLuceneDirectory();
if (IndexWriter.IsLocked(dir))
{
IndexWriter.Unlock(dir);
}
}
}
EnsureUnlockedAndConfigured();
//Ok, now that everything is complete we'll check if we've stored any references to index that need rebuilding and run them
// (see the initialize method for notes) - we'll ensure we remove the event handler too in case examine manager doesn't actually
@@ -131,6 +115,8 @@ namespace Umbraco.Web
//don't do anything if we have disabled this
if (_disableExamineIndexing) return;
EnsureUnlockedAndConfigured();
//If the developer has explicitly opted out of rebuilding indexes on startup then we
// should adhere to that and not do it, this means that if they are load balancing things will be
// out of sync if they are auto-scaling but there's not much we can do about that.
@@ -168,6 +154,40 @@ namespace Umbraco.Web
yield return index;
}
/// <summary>
/// Must be called to configure each index and ensure it's unlocked before any indexing occurs
/// </summary>
/// <remarks>
/// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before
/// either of these happens, we need to configure the indexes.
/// </remarks>
private void EnsureUnlockedAndConfigured()
{
if (_isConfigured) return;
_isConfigured = true;
foreach (var luceneIndexer in ExamineManager.Instance.IndexProviderCollection.OfType<LuceneIndexer>())
{
//We now need to disable waiting for indexing for Examine so that the appdomain is shutdown immediately and doesn't wait for pending
//indexing operations. We used to wait for indexing operations to complete but this can cause more problems than that is worth because
//that could end up halting shutdown for a very long time causing overlapping appdomains and many other problems.
luceneIndexer.WaitForIndexQueueOnShutdown = false;
//we should check if the index is locked ... it shouldn't be! We are using simple fs lock now and we are also ensuring that
//the indexes are not operational unless MainDom is true so if _disableExamineIndexing is false then we should be in charge
if (_disableExamineIndexing == false)
{
var dir = luceneIndexer.GetLuceneDirectory();
if (IndexWriter.IsLocked(dir))
{
_profilingLogger.Logger.Info<ExamineStartup>("Forcing index " + luceneIndexer.IndexSetName + " to be unlocked since it was left in a locked state");
IndexWriter.Unlock(dir);
}
}
}
}
private void OnInstanceOnBuildingEmptyIndexOnStartup(object sender, BuildingEmptyIndexOnStartupEventArgs args)
{
//store the indexer that needs rebuilding because it's empty for when the boot process

View File

@@ -0,0 +1,23 @@
using System;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace Umbraco.Web.Models
{
[DataContract(Name = "tour", Namespace = "")]
public class BackOfficeTour
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "alias")]
public string Alias { get; set; }
[DataMember(Name = "group")]
public string Group { get; set; }
[DataMember(Name = "allowDisable")]
public bool AllowDisable { get; set; }
[DataMember(Name = "steps")]
public BackOfficeTourStep[] Steps { get; set; }
}
}

View File

@@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Umbraco.Web.Models
{
[DataContract(Name = "tourFile", Namespace = "")]
public class BackOfficeTourFile
{
/// <summary>
/// The file name for the tour
/// </summary>
[DataMember(Name = "fileName")]
public string FileName { get; set; }
/// <summary>
/// The plugin folder that the tour comes from
/// </summary>
/// <remarks>
/// If this is null it means it's a Core tour
/// </remarks>
[DataMember(Name = "pluginName")]
public string PluginName { get; set; }
[DataMember(Name = "tours")]
public IEnumerable<BackOfficeTour> Tours { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
using System.Runtime.Serialization;
namespace Umbraco.Web.Models
{
[DataContract(Name = "step", Namespace = "")]
public class BackOfficeTourStep
{
[DataMember(Name = "title")]
public string Title { get; set; }
[DataMember(Name = "content")]
public string Content { get; set; }
[DataMember(Name = "type")]
public string Type { get; set; }
[DataMember(Name = "element")]
public string Element { get; set; }
[DataMember(Name = "elementPreventClick")]
public bool ElementPreventClick { get; set; }
[DataMember(Name = "backdropOpacity")]
public float BackdropOpacity { get; set; }
[DataMember(Name = "event")]
public string Event { get; set; }
}
}

View File

@@ -251,7 +251,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
// move to parent node
e = (XmlElement) e.ParentNode;
id = int.Parse(e.GetAttribute("id"));
id = int.Parse(e.GetAttribute("id"), CultureInfo.InvariantCulture);
hasDomains = id != -1 && domainHelper.NodeHasDomains(id);
}

View File

@@ -230,7 +230,7 @@ namespace Umbraco.Web
{
// todo: in v8, implement in a more efficient way
var legacyXml = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema;
var xpath = legacyXml ? "//node [@key=$guid]" : "//* [@isDoc and @key=$guid]";
var xpath = legacyXml ? "//node [@key=$guid]" : "//* [@key=$guid]";
var doc = cache.GetSingleByXPath(xpath, new XPathVariable("guid", id.ToString()));
return doc;
}

View File

@@ -372,6 +372,8 @@
<Compile Include="HealthCheck\Checks\DataIntegrity\XmlDataIntegrityHealthCheck.cs" />
<Compile Include="IHttpContextAccessor.cs" />
<Compile Include="Install\InstallSteps\ConfigureMachineKey.cs" />
<Compile Include="Models\BackOfficeTourFile.cs" />
<Compile Include="Models\BackOfficeTourStep.cs" />
<Compile Include="Models\ContentEditing\AssignedContentPermissions.cs" />
<Compile Include="Models\ContentEditing\AssignedUserGroupPermissions.cs" />
<Compile Include="Models\ContentEditing\ContentRedirectUrl.cs" />
@@ -430,6 +432,7 @@
<Compile Include="Models\RequestPasswordResetModel.cs" />
<Compile Include="Models\PublishedContentWithKeyBase.cs" />
<Compile Include="Models\Tour.cs" />
<Compile Include="Models\BackOfficeTour.cs" />
<Compile Include="Models\TourStep.cs" />
<Compile Include="Models\UserTourStatus.cs" />
<Compile Include="Models\ValidatePasswordResetCodeModel.cs" />