Merge remote-tracking branch 'origin/netcore/netcore' into netcore/feature/identity
# Conflicts: # src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs # src/Umbraco.Web/Editors/BackOfficeController.cs
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
using Umbraco.Web.Security;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
@@ -9,8 +12,30 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
//[ValidationFilter] // TODO: I don't actually think this is required with our custom Application Model conventions applied
|
||||
[TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] // TODO: This could be applied with our Application Model conventions
|
||||
[IsBackOffice] // TODO: This could be applied with our Application Model conventions
|
||||
public class AuthenticationController : ControllerBase
|
||||
public class AuthenticationController : UmbracoApiController
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
// TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController and it should not be an auto-routed api controller
|
||||
|
||||
public AuthenticationController(IUmbracoContextAccessor umbracoContextAccessor)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current user's cookie is valid and if so returns OK or a 400 (BadRequest)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public bool IsAuthenticated()
|
||||
{
|
||||
var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
var attempt = umbracoContext.Security.AuthorizeRequest();
|
||||
if (attempt == ValidateRequestAttempt.Success)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
public class BackOfficeAssetsController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IFileSystem _jsLibFileSystem;
|
||||
|
||||
public BackOfficeAssetsController(IIOHelper ioHelper, ILogger logger, IGlobalSettings globalSettings)
|
||||
{
|
||||
_jsLibFileSystem = new PhysicalFileSystem(ioHelper, logger, globalSettings.UmbracoPath + Path.DirectorySeparatorChar + "lib");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public object GetSupportedLocales()
|
||||
{
|
||||
const string momentLocaleFolder = "moment";
|
||||
const string flatpickrLocaleFolder = "flatpickr/l10n";
|
||||
|
||||
return new
|
||||
{
|
||||
moment = GetLocales(momentLocaleFolder),
|
||||
flatpickr = GetLocales(flatpickrLocaleFolder)
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetLocales(string path)
|
||||
{
|
||||
var cultures = _jsLibFileSystem.GetFiles(path, "*.js").ToList();
|
||||
for (var i = 0; i < cultures.Count; i++)
|
||||
{
|
||||
cultures[i] = cultures[i]
|
||||
.Substring(cultures[i].IndexOf(path, StringComparison.Ordinal) + path.Length + 1);
|
||||
}
|
||||
return cultures;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
private readonly IRuntimeMinifier _runtimeMinifier;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly ILocalizedTextService _textService;
|
||||
private readonly IGridConfig _gridConfig;
|
||||
@@ -46,7 +45,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
IRuntimeMinifier runtimeMinifier,
|
||||
IGlobalSettings globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IUmbracoApplicationLifetime umbracoApplicationLifetime,
|
||||
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
ILocalizedTextService textService,
|
||||
IGridConfig gridConfig,
|
||||
@@ -59,7 +58,6 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
_runtimeMinifier = runtimeMinifier;
|
||||
_globalSettings = globalSettings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_umbracoApplicationLifetime = umbracoApplicationLifetime;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_textService = textService;
|
||||
_gridConfig = gridConfig ?? throw new ArgumentNullException(nameof(gridConfig));
|
||||
@@ -130,7 +128,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
return new JsonNetResult { Data = nestedDictionary, Formatting = Formatting.None };
|
||||
}
|
||||
|
||||
[UmbracoAuthorize(Order = 0)] // TODO: Re-implement UmbracoAuthorizeAttribute
|
||||
[UmbracoAuthorize(Order = 0)]
|
||||
[HttpGet]
|
||||
public JsonNetResult GetGridConfig()
|
||||
{
|
||||
@@ -223,5 +221,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
}
|
||||
return Redirect("/");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
234
src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs
Normal file
234
src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Core.Dashboards;
|
||||
using Umbraco.Web.Services;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
//we need to fire up the controller like this to enable loading of remote css directly from this controller
|
||||
[PluginController("UmbracoApi")]
|
||||
[ValidationFilter]
|
||||
[TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] // TODO: This could be applied with our Application Model conventions
|
||||
[IsBackOffice]
|
||||
[UmbracoAuthorize]
|
||||
public class DashboardController : UmbracoApiController
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly AppCaches _appCaches;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IDashboardService _dashboardService;
|
||||
private readonly IUmbracoVersion _umbracoVersion;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DashboardController"/> with all its dependencies.
|
||||
/// </summary>
|
||||
public DashboardController(
|
||||
IGlobalSettings globalSettings,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
ISqlContext sqlContext,
|
||||
ServiceContext services,
|
||||
AppCaches appCaches,
|
||||
ILogger logger,
|
||||
IRuntimeState runtimeState,
|
||||
IDashboardService dashboardService,
|
||||
IUmbracoVersion umbracoVersion,
|
||||
IShortStringHelper shortStringHelper)
|
||||
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_appCaches = appCaches;
|
||||
_logger = logger;
|
||||
_dashboardService = dashboardService;
|
||||
_umbracoVersion = umbracoVersion;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
}
|
||||
|
||||
//we have just one instance of HttpClient shared for the entire application
|
||||
private static readonly HttpClient HttpClient = new HttpClient();
|
||||
|
||||
//we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side
|
||||
[TypeFilter(typeof(ValidateAngularAntiForgeryTokenAttribute))]
|
||||
public async Task<JObject> GetRemoteDashboardContent(string section, string baseUrl = "https://dashboard.umbraco.org/")
|
||||
{
|
||||
var user = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser;
|
||||
var allowedSections = string.Join(",", user.AllowedSections);
|
||||
var language = user.Language;
|
||||
var version = _umbracoVersion.SemanticVersion.ToSemanticString();
|
||||
|
||||
var url = string.Format(baseUrl + "{0}?section={0}&allowed={1}&lang={2}&version={3}", section, allowedSections, language, version);
|
||||
var key = "umbraco-dynamic-dashboard-" + language + allowedSections.Replace(",", "-") + section;
|
||||
|
||||
var content = _appCaches.RuntimeCache.GetCacheItem<JObject>(key);
|
||||
var result = new JObject();
|
||||
if (content != null)
|
||||
{
|
||||
result = content;
|
||||
}
|
||||
else
|
||||
{
|
||||
//content is null, go get it
|
||||
try
|
||||
{
|
||||
//fetch dashboard json and parse to JObject
|
||||
var json = await HttpClient.GetStringAsync(url);
|
||||
content = JObject.Parse(json);
|
||||
result = content;
|
||||
|
||||
_appCaches.RuntimeCache.InsertCacheItem<JObject>(key, () => result, new TimeSpan(0, 30, 0));
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.Error<DashboardController>(ex.InnerException ?? ex, "Error getting dashboard content from {Url}", url);
|
||||
|
||||
//it's still new JObject() - we return it like this to avoid error codes which triggers UI warnings
|
||||
_appCaches.RuntimeCache.InsertCacheItem<JObject>(key, () => result, new TimeSpan(0, 5, 0));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> GetRemoteDashboardCss(string section, string baseUrl = "https://dashboard.umbraco.org/")
|
||||
{
|
||||
var url = string.Format(baseUrl + "css/dashboard.css?section={0}", section);
|
||||
var key = "umbraco-dynamic-dashboard-css-" + section;
|
||||
|
||||
var content = _appCaches.RuntimeCache.GetCacheItem<string>(key);
|
||||
var result = string.Empty;
|
||||
|
||||
if (content != null)
|
||||
{
|
||||
result = content;
|
||||
}
|
||||
else
|
||||
{
|
||||
//content is null, go get it
|
||||
try
|
||||
{
|
||||
//fetch remote css
|
||||
content = await HttpClient.GetStringAsync(url);
|
||||
|
||||
//can't use content directly, modified closure problem
|
||||
result = content;
|
||||
|
||||
//save server content for 30 mins
|
||||
_appCaches.RuntimeCache.InsertCacheItem<string>(key, () => result, new TimeSpan(0, 30, 0));
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.Error<DashboardController>(ex.InnerException ?? ex, "Error getting dashboard CSS from {Url}", url);
|
||||
|
||||
//it's still string.Empty - we return it like this to avoid error codes which triggers UI warnings
|
||||
_appCaches.RuntimeCache.InsertCacheItem<string>(key, () => result, new TimeSpan(0, 5, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return Content(result,"text/css", Encoding.UTF8);
|
||||
|
||||
}
|
||||
|
||||
public async Task<IActionResult> GetRemoteXml(string site, string url)
|
||||
{
|
||||
// This is used in place of the old feedproxy.config
|
||||
// Which was used to grab data from our.umbraco.com, umbraco.com or umbraco.tv
|
||||
// for certain dashboards or the help drawer
|
||||
var urlPrefix = string.Empty;
|
||||
switch (site.ToUpper())
|
||||
{
|
||||
case "TV":
|
||||
urlPrefix = "https://umbraco.tv/";
|
||||
break;
|
||||
|
||||
case "OUR":
|
||||
urlPrefix = "https://our.umbraco.com/";
|
||||
break;
|
||||
|
||||
case "COM":
|
||||
urlPrefix = "https://umbraco.com/";
|
||||
break;
|
||||
|
||||
default:
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
|
||||
//Make remote call to fetch videos or remote dashboard feed data
|
||||
var key = $"umbraco-XML-feed-{site}-{url.ToCleanString(_shortStringHelper, CleanStringType.UrlSegment)}";
|
||||
|
||||
var content = _appCaches.RuntimeCache.GetCacheItem<string>(key);
|
||||
var result = string.Empty;
|
||||
|
||||
if (content != null)
|
||||
{
|
||||
result = content;
|
||||
}
|
||||
else
|
||||
{
|
||||
//content is null, go get it
|
||||
try
|
||||
{
|
||||
//fetch remote css
|
||||
content = await HttpClient.GetStringAsync($"{urlPrefix}{url}");
|
||||
|
||||
//can't use content directly, modified closure problem
|
||||
result = content;
|
||||
|
||||
//save server content for 30 mins
|
||||
_appCaches.RuntimeCache.InsertCacheItem<string>(key, () => result, new TimeSpan(0, 30, 0));
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.Error<DashboardController>(ex.InnerException ?? ex, "Error getting remote dashboard data from {UrlPrefix}{Url}", urlPrefix, url);
|
||||
|
||||
//it's still string.Empty - we return it like this to avoid error codes which triggers UI warnings
|
||||
_appCaches.RuntimeCache.InsertCacheItem<string>(key, () => result, new TimeSpan(0, 5, 0));
|
||||
}
|
||||
}
|
||||
|
||||
return Content(result,"text/xml", Encoding.UTF8);
|
||||
|
||||
}
|
||||
|
||||
// return IDashboardSlim - we don't need sections nor access rules
|
||||
[TypeFilter(typeof(ValidateAngularAntiForgeryTokenAttribute))]
|
||||
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
|
||||
public IEnumerable<Tab<IDashboardSlim>> GetDashboard(string section)
|
||||
{
|
||||
var currentUser = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser;
|
||||
return _dashboardService.GetDashboards(section, currentUser).Select(x => new Tab<IDashboardSlim>
|
||||
{
|
||||
Id = x.Id,
|
||||
Alias = x.Alias,
|
||||
Label = x.Label,
|
||||
Expanded = x.Expanded,
|
||||
IsActive = x.IsActive,
|
||||
Properties = x.Properties.Select(y => new DashboardSlim
|
||||
{
|
||||
Alias = y.Alias,
|
||||
View = y.View
|
||||
})
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using Examine;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Examine;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Exceptions;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Search;
|
||||
using SearchResult = Umbraco.Web.Models.ContentEditing.SearchResult;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
public class ExamineManagementController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IExamineManager _examineManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private readonly IIndexDiagnosticsFactory _indexDiagnosticsFactory;
|
||||
private readonly IAppPolicyCache _runtimeCache;
|
||||
private readonly IndexRebuilder _indexRebuilder;
|
||||
|
||||
|
||||
public ExamineManagementController(IExamineManager examineManager, ILogger logger, IIOHelper ioHelper, IIndexDiagnosticsFactory indexDiagnosticsFactory,
|
||||
AppCaches appCaches,
|
||||
IndexRebuilder indexRebuilder)
|
||||
{
|
||||
_examineManager = examineManager;
|
||||
_logger = logger;
|
||||
_ioHelper = ioHelper;
|
||||
_indexDiagnosticsFactory = indexDiagnosticsFactory;
|
||||
_runtimeCache = appCaches.RuntimeCache;
|
||||
_indexRebuilder = indexRebuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the details for indexers
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<ExamineIndexModel> GetIndexerDetails()
|
||||
{
|
||||
return _examineManager.Indexes.Select(CreateModel).OrderBy(x => x.Name.TrimEnd("Indexer"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the details for searchers
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<ExamineSearcherModel> GetSearcherDetails()
|
||||
{
|
||||
var model = new List<ExamineSearcherModel>(
|
||||
_examineManager.RegisteredSearchers.Select(searcher => new ExamineSearcherModel { Name = searcher.Name })
|
||||
.OrderBy(x => x.Name.TrimEnd("Searcher"))); //order by name , but strip the "Searcher" from the end if it exists
|
||||
return model;
|
||||
}
|
||||
|
||||
public SearchResults GetSearchResults(string searcherName, string query, int pageIndex = 0, int pageSize = 20)
|
||||
{
|
||||
if (query.IsNullOrWhiteSpace())
|
||||
return SearchResults.Empty();
|
||||
|
||||
var msg = ValidateSearcher(searcherName, out var searcher);
|
||||
if (!msg.IsSuccessStatusCode())
|
||||
throw new HttpResponseException(msg);
|
||||
|
||||
// NativeQuery will work for a single word/phrase too (but depends on the implementation) the lucene one will work.
|
||||
var results = searcher.CreateQuery().NativeQuery(query).Execute(maxResults: pageSize * (pageIndex + 1));
|
||||
|
||||
var pagedResults = results.Skip(pageIndex * pageSize);
|
||||
|
||||
return new SearchResults
|
||||
{
|
||||
TotalRecords = results.TotalItemCount,
|
||||
Results = pagedResults.Select(x => new SearchResult
|
||||
{
|
||||
Id = x.Id,
|
||||
Score = x.Score,
|
||||
//order the values by key
|
||||
Values = new Dictionary<string, string>(x.Values.OrderBy(y => y.Key).ToDictionary(y => y.Key, y => y.Value))
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check if the index has been rebuilt
|
||||
/// </summary>
|
||||
/// <param name="indexName"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This is kind of rudimentary since there's no way we can know that the index has rebuilt, we
|
||||
/// have a listener for the index op complete so we'll just check if that key is no longer there in the runtime cache
|
||||
/// </remarks>
|
||||
public ActionResult<ExamineIndexModel> PostCheckRebuildIndex(string indexName)
|
||||
{
|
||||
var validate = ValidateIndex(indexName, out var index);
|
||||
|
||||
if (!validate.IsSuccessStatusCode())
|
||||
throw new HttpResponseException(validate);
|
||||
|
||||
validate = ValidatePopulator(index);
|
||||
if (!validate.IsSuccessStatusCode())
|
||||
throw new HttpResponseException(validate);
|
||||
|
||||
var cacheKey = "temp_indexing_op_" + indexName;
|
||||
var found = _runtimeCache.Get(cacheKey);
|
||||
|
||||
//if its still there then it's not done
|
||||
return found != null
|
||||
? null
|
||||
: CreateModel(index);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds the index
|
||||
/// </summary>
|
||||
/// <param name="indexName"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult PostRebuildIndex(string indexName)
|
||||
{
|
||||
var validate = ValidateIndex(indexName, out var index);
|
||||
if (!validate.IsSuccessStatusCode())
|
||||
throw new HttpResponseException(validate);
|
||||
|
||||
validate = ValidatePopulator(index);
|
||||
if (!validate.IsSuccessStatusCode())
|
||||
throw new HttpResponseException(validate);
|
||||
|
||||
_logger.Info<ExamineManagementController>("Rebuilding index '{IndexName}'", indexName);
|
||||
|
||||
//remove it in case there's a handler there already
|
||||
index.IndexOperationComplete -= Indexer_IndexOperationComplete;
|
||||
|
||||
//now add a single handler
|
||||
index.IndexOperationComplete += Indexer_IndexOperationComplete;
|
||||
|
||||
try
|
||||
{
|
||||
var cacheKey = "temp_indexing_op_" + index.Name;
|
||||
//put temp val in cache which is used as a rudimentary way to know when the indexing is done
|
||||
_runtimeCache.Insert(cacheKey, () => "tempValue", TimeSpan.FromMinutes(5));
|
||||
|
||||
_indexRebuilder.RebuildIndex(indexName);
|
||||
|
||||
////populate it
|
||||
//foreach (var populator in _populators.Where(x => x.IsRegistered(indexName)))
|
||||
// populator.Populate(index);
|
||||
|
||||
return new OkResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//ensure it's not listening
|
||||
index.IndexOperationComplete -= Indexer_IndexOperationComplete;
|
||||
_logger.Error<ExamineManagementController>(ex, "An error occurred rebuilding index");
|
||||
var response = new ConflictObjectResult("The index could not be rebuilt at this time, most likely there is another thread currently writing to the index. Error: {ex}");
|
||||
|
||||
SetReasonPhrase(response, "Could Not Rebuild");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
private ExamineIndexModel CreateModel(IIndex index)
|
||||
{
|
||||
var indexName = index.Name;
|
||||
|
||||
var indexDiag = _indexDiagnosticsFactory.Create(index);
|
||||
|
||||
var isHealth = indexDiag.IsHealthy();
|
||||
var properties = new Dictionary<string, object>
|
||||
{
|
||||
[nameof(IIndexDiagnostics.DocumentCount)] = indexDiag.DocumentCount,
|
||||
[nameof(IIndexDiagnostics.FieldCount)] = indexDiag.FieldCount,
|
||||
};
|
||||
foreach (var p in indexDiag.Metadata)
|
||||
properties[p.Key] = p.Value;
|
||||
|
||||
var indexerModel = new ExamineIndexModel
|
||||
{
|
||||
Name = indexName,
|
||||
HealthStatus = isHealth.Success ? (isHealth.Result ?? "Healthy") : (isHealth.Result ?? "Unhealthy"),
|
||||
ProviderProperties = properties,
|
||||
CanRebuild = _indexRebuilder.CanRebuild(index)
|
||||
};
|
||||
|
||||
|
||||
return indexerModel;
|
||||
}
|
||||
|
||||
private ActionResult ValidateSearcher(string searcherName, out ISearcher searcher)
|
||||
{
|
||||
//try to get the searcher from the indexes
|
||||
if (_examineManager.TryGetIndex(searcherName, out var index))
|
||||
{
|
||||
searcher = index.GetSearcher();
|
||||
return new OkResult();
|
||||
}
|
||||
|
||||
|
||||
//if we didn't find anything try to find it by an explicitly declared searcher
|
||||
if (_examineManager.TryGetSearcher(searcherName, out searcher))
|
||||
return new OkResult();
|
||||
|
||||
var response1 = new BadRequestObjectResult($"No searcher found with name = {searcherName}");
|
||||
SetReasonPhrase(response1, "Searcher Not Found");
|
||||
return response1;
|
||||
}
|
||||
|
||||
private ActionResult ValidatePopulator(IIndex index)
|
||||
{
|
||||
if (_indexRebuilder.CanRebuild(index))
|
||||
return new OkResult();
|
||||
|
||||
var response = new BadRequestObjectResult($"The index {index.Name} cannot be rebuilt because it does not have an associated {typeof(IIndexPopulator)}");
|
||||
SetReasonPhrase(response, "Index cannot be rebuilt");
|
||||
return response;
|
||||
}
|
||||
|
||||
private ActionResult ValidateIndex(string indexName, out IIndex index)
|
||||
{
|
||||
index = null;
|
||||
|
||||
if (_examineManager.TryGetIndex(indexName, out index))
|
||||
{
|
||||
//return Ok!
|
||||
return new OkResult();
|
||||
}
|
||||
|
||||
var response = new BadRequestObjectResult($"No index found with name = {indexName}");
|
||||
SetReasonPhrase(response, "Index Not Found");
|
||||
return response;
|
||||
}
|
||||
|
||||
private void SetReasonPhrase(IActionResult response, string reasonPhrase)
|
||||
{
|
||||
//TODO we should update this behavior, as HTTP2 do not have ReasonPhrase. Could as well be returned in body
|
||||
// https://github.com/aspnet/HttpAbstractions/issues/395
|
||||
var httpResponseFeature = HttpContext.Features.Get<IHttpResponseFeature>();
|
||||
if (!(httpResponseFeature is null))
|
||||
{
|
||||
httpResponseFeature.ReasonPhrase = reasonPhrase;
|
||||
}
|
||||
}
|
||||
|
||||
private void Indexer_IndexOperationComplete(object sender, EventArgs e)
|
||||
{
|
||||
var indexer = (IIndex)sender;
|
||||
|
||||
_logger.Debug<ExamineManagementController>("Logging operation completed for index {IndexName}", indexer.Name);
|
||||
|
||||
//ensure it's not listening anymore
|
||||
indexer.IndexOperationComplete -= Indexer_IndexOperationComplete;
|
||||
|
||||
_logger
|
||||
.Info<ExamineManagementController
|
||||
>($"Rebuilding index '{indexer.Name}' done.");
|
||||
|
||||
var cacheKey = "temp_indexing_op_" + indexer.Name;
|
||||
_runtimeCache.Clear(cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/Umbraco.Web.BackOffice/Controllers/HelpController.cs
Normal file
63
src/Umbraco.Web.BackOffice/Controllers/HelpController.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Web.Editors;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
public class HelpController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public HelpController(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private static HttpClient _httpClient;
|
||||
public async Task<List<HelpPage>> GetContextHelpForPage(string section, string tree, string baseUrl = "https://our.umbraco.com")
|
||||
{
|
||||
var url = string.Format(baseUrl + "/Umbraco/Documentation/Lessons/GetContextHelpDocs?sectionAlias={0}&treeAlias={1}", section, tree);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if (_httpClient == null)
|
||||
_httpClient = new HttpClient();
|
||||
|
||||
//fetch dashboard json and parse to JObject
|
||||
var json = await _httpClient.GetStringAsync(url);
|
||||
var result = JsonConvert.DeserializeObject<List<HelpPage>>(json);
|
||||
if (result != null)
|
||||
return result;
|
||||
|
||||
}
|
||||
catch (HttpRequestException rex)
|
||||
{
|
||||
_logger.Info(GetType(), $"Check your network connection, exception: {rex.Message}");
|
||||
}
|
||||
|
||||
return new List<HelpPage>();
|
||||
}
|
||||
}
|
||||
|
||||
[DataContract(Name = "HelpPage")]
|
||||
public class HelpPage
|
||||
{
|
||||
[DataMember(Name = "name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember(Name = "description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[DataMember(Name = "url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
[DataMember(Name = "type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Media;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Mvc;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// The API controller used for getting URLs for images with parameters
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This controller allows for retrieving URLs for processed images, such as resized, cropped,
|
||||
/// or otherwise altered. These can be different based on the IImageUrlGenerator
|
||||
/// implementation in use, and so the BackOffice could should not rely on hard-coded string
|
||||
/// building to generate correct URLs
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[PluginController("UmbracoApi")]
|
||||
public class ImageUrlGeneratorController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IImageUrlGenerator _imageUrlGenerator;
|
||||
|
||||
public ImageUrlGeneratorController(IImageUrlGenerator imageUrlGenerator)
|
||||
{
|
||||
_imageUrlGenerator = imageUrlGenerator;
|
||||
}
|
||||
|
||||
public string GetCropUrl(string mediaPath, int? width = null, int? height = null, ImageCropMode? imageCropMode = null, string animationProcessMode = null)
|
||||
{
|
||||
return _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(mediaPath)
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
ImageCropMode = imageCropMode,
|
||||
AnimationProcessMode = animationProcessMode
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
130
src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs
Normal file
130
src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Media;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// A controller used to return images for media
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
public class ImagesController : UmbracoAuthorizedApiController
|
||||
{
|
||||
private readonly IMediaFileSystem _mediaFileSystem;
|
||||
private readonly IContentSettings _contentSettings;
|
||||
private readonly IImageUrlGenerator _imageUrlGenerator;
|
||||
|
||||
public ImagesController(IMediaFileSystem mediaFileSystem, IContentSettings contentSettings, IImageUrlGenerator imageUrlGenerator)
|
||||
{
|
||||
_mediaFileSystem = mediaFileSystem;
|
||||
_contentSettings = contentSettings;
|
||||
_imageUrlGenerator = imageUrlGenerator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the big thumbnail image for the original image path
|
||||
/// </summary>
|
||||
/// <param name="originalImagePath"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// If there is no original image is found then this will return not found.
|
||||
/// </remarks>
|
||||
public IActionResult GetBigThumbnail(string originalImagePath)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(originalImagePath)
|
||||
? Ok()
|
||||
: GetResized(originalImagePath, 500);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a resized image for the image at the given path
|
||||
/// </summary>
|
||||
/// <param name="imagePath"></param>
|
||||
/// <param name="width"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// If there is no media, image property or image file is found then this will return not found.
|
||||
/// </remarks>
|
||||
public IActionResult GetResized(string imagePath, int width)
|
||||
{
|
||||
var ext = Path.GetExtension(imagePath);
|
||||
|
||||
// we need to check if it is an image by extension
|
||||
if (_contentSettings.IsImageFile(ext) == false)
|
||||
return NotFound();
|
||||
|
||||
//redirect to ImageProcessor thumbnail with rnd generated from last modified time of original media file
|
||||
DateTimeOffset? imageLastModified = null;
|
||||
try
|
||||
{
|
||||
imageLastModified = _mediaFileSystem.GetLastModified(imagePath);
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// if we get an exception here it's probably because the image path being requested is an image that doesn't exist
|
||||
// in the local media file system. This can happen if someone is storing an absolute path to an image online, which
|
||||
// is perfectly legal but in that case the media file system isn't going to resolve it.
|
||||
// so ignore and we won't set a last modified date.
|
||||
}
|
||||
|
||||
var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : null;
|
||||
var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(imagePath) { UpScale = false, Width = width, AnimationProcessMode = "first", ImageCropMode = ImageCropMode.Max, CacheBusterValue = rnd });
|
||||
|
||||
return new RedirectResult(imageUrl, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a processed image for the image at the given path
|
||||
/// </summary>
|
||||
/// <param name="imagePath"></param>
|
||||
/// <param name="width"></param>
|
||||
/// <param name="height"></param>
|
||||
/// <param name="focalPointLeft"></param>
|
||||
/// <param name="focalPointTop"></param>
|
||||
/// <param name="animationProcessMode"></param>
|
||||
/// <param name="mode"></param>
|
||||
/// <param name="upscale"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// If there is no media, image property or image file is found then this will return not found.
|
||||
/// </remarks>
|
||||
public string GetProcessedImageUrl(string imagePath,
|
||||
int? width = null,
|
||||
int? height = null,
|
||||
int? focalPointLeft = null,
|
||||
int? focalPointTop = null,
|
||||
string animationProcessMode = "first",
|
||||
ImageCropMode mode = ImageCropMode.Max,
|
||||
bool upscale = false,
|
||||
string cacheBusterValue = "")
|
||||
{
|
||||
var options = new ImageUrlGenerationOptions(imagePath)
|
||||
{
|
||||
AnimationProcessMode = animationProcessMode,
|
||||
CacheBusterValue = cacheBusterValue,
|
||||
Height = height,
|
||||
ImageCropMode = mode,
|
||||
UpScale = upscale,
|
||||
Width = width,
|
||||
};
|
||||
if (focalPointLeft.HasValue && focalPointTop.HasValue)
|
||||
{
|
||||
options.FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(focalPointTop.Value, focalPointLeft.Value);
|
||||
}
|
||||
|
||||
return _imageUrlGenerator.GetImageUrl(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
216
src/Umbraco.Web.BackOffice/Controllers/TourController.cs
Normal file
216
src/Umbraco.Web.BackOffice/Controllers/TourController.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Tour;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
public class TourController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly TourFilterCollection _filters;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly ITourSettings _tourSettings;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
public TourController(
|
||||
TourFilterCollection filters,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
ITourSettings tourSettings,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IContentTypeService contentTypeService)
|
||||
{
|
||||
_filters = filters;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
|
||||
_tourSettings = tourSettings;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_contentTypeService = contentTypeService;
|
||||
}
|
||||
|
||||
public IEnumerable<BackOfficeTourFile> GetTours()
|
||||
{
|
||||
var result = new List<BackOfficeTourFile>();
|
||||
|
||||
if (_tourSettings.EnableTours == false)
|
||||
return result;
|
||||
|
||||
var user = _umbracoContextAccessor.UmbracoContext.Security.CurrentUser;
|
||||
if (user == null)
|
||||
return result;
|
||||
|
||||
//get all filters that will be applied to all tour aliases
|
||||
var aliasOnlyFilters = _filters.Where(x => x.PluginName == null && x.TourFileName == null).ToList();
|
||||
|
||||
//don't pass in any filters for core tours that have a plugin name assigned
|
||||
var nonPluginFilters = _filters.Where(x => x.PluginName == null).ToList();
|
||||
|
||||
//add core tour files
|
||||
var coreToursPath = Path.Combine(_hostingEnvironment.MapPathContentRoot(Core.Constants.SystemDirectories.Config), "BackOfficeTours");
|
||||
if (Directory.Exists(coreToursPath))
|
||||
{
|
||||
foreach (var tourFile in Directory.EnumerateFiles(coreToursPath, "*.json"))
|
||||
{
|
||||
TryParseTourFile(tourFile, result, nonPluginFilters, aliasOnlyFilters);
|
||||
}
|
||||
}
|
||||
|
||||
//collect all tour files in packages
|
||||
var appPlugins = _hostingEnvironment.MapPathContentRoot(Core.Constants.SystemDirectories.AppPlugins);
|
||||
if (Directory.Exists(appPlugins))
|
||||
{
|
||||
foreach (var plugin in Directory.EnumerateDirectories(appPlugins))
|
||||
{
|
||||
var pluginName = Path.GetFileName(plugin.TrimEnd('\\'));
|
||||
var pluginFilters = _filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginName))
|
||||
.ToList();
|
||||
|
||||
//If there is any filter applied to match the plugin only (no file or tour alias) then ignore the plugin entirely
|
||||
var isPluginFiltered = pluginFilters.Any(x => x.TourFileName == null && x.TourAlias == null);
|
||||
if (isPluginFiltered) continue;
|
||||
|
||||
//combine matched package filters with filters not specific to a package
|
||||
var combinedFilters = nonPluginFilters.Concat(pluginFilters).ToList();
|
||||
|
||||
foreach (var backofficeDir in Directory.EnumerateDirectories(plugin, "backoffice"))
|
||||
{
|
||||
foreach (var tourDir in Directory.EnumerateDirectories(backofficeDir, "tours"))
|
||||
{
|
||||
foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json"))
|
||||
{
|
||||
TryParseTourFile(tourFile, result, combinedFilters, aliasOnlyFilters, pluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Get all allowed sections for the current user
|
||||
var allowedSections = user.AllowedSections.ToList();
|
||||
|
||||
var toursToBeRemoved = new List<BackOfficeTourFile>();
|
||||
|
||||
//Checking to see if the user has access to the required tour sections, else we remove the tour
|
||||
foreach (var backOfficeTourFile in result)
|
||||
{
|
||||
if (backOfficeTourFile.Tours != null)
|
||||
{
|
||||
foreach (var tour in backOfficeTourFile.Tours)
|
||||
{
|
||||
if (tour.RequiredSections != null)
|
||||
{
|
||||
foreach (var toursRequiredSection in tour.RequiredSections)
|
||||
{
|
||||
if (allowedSections.Contains(toursRequiredSection) == false)
|
||||
{
|
||||
toursToBeRemoved.Add(backOfficeTourFile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.Except(toursToBeRemoved).OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a tours for a specific doctype
|
||||
/// </summary>
|
||||
/// <param name="doctypeAlias">The documenttype alias</param>
|
||||
/// <returns>A <see cref="BackOfficeTour"/></returns>
|
||||
public IEnumerable<BackOfficeTour> GetToursForDoctype(string doctypeAlias)
|
||||
{
|
||||
var tourFiles = this.GetTours();
|
||||
|
||||
var doctypeAliasWithCompositions = new List<string>
|
||||
{
|
||||
doctypeAlias
|
||||
};
|
||||
|
||||
var contentType = _contentTypeService.Get(doctypeAlias);
|
||||
|
||||
if (contentType != null)
|
||||
{
|
||||
doctypeAliasWithCompositions.AddRange(contentType.CompositionAliases());
|
||||
}
|
||||
|
||||
return tourFiles.SelectMany(x => x.Tours)
|
||||
.Where(x =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(x.ContentType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var contentTypes = x.ContentType.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(ct => ct.Trim());
|
||||
return contentTypes.Intersect(doctypeAliasWithCompositions).Any();
|
||||
});
|
||||
}
|
||||
|
||||
private void TryParseTourFile(string tourFile,
|
||||
ICollection<BackOfficeTourFile> result,
|
||||
List<BackOfficeTourFilter> filters,
|
||||
List<BackOfficeTourFilter> aliasOnlyFilters,
|
||||
string pluginName = null)
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(tourFile);
|
||||
if (fileName == null) return;
|
||||
|
||||
//get the filters specific to this file
|
||||
var fileFilters = filters.Where(x => x.TourFileName != null && x.TourFileName.IsMatch(fileName)).ToList();
|
||||
|
||||
//If there is any filter applied to match the file only (no tour alias) then ignore the file entirely
|
||||
var isFileFiltered = fileFilters.Any(x => x.TourAlias == null);
|
||||
if (isFileFiltered) return;
|
||||
|
||||
//now combine all aliases to filter below
|
||||
var aliasFilters = aliasOnlyFilters.Concat(filters.Where(x => x.TourAlias != null))
|
||||
.Select(x => x.TourAlias)
|
||||
.ToList();
|
||||
|
||||
try
|
||||
{
|
||||
var contents = System.IO.File.ReadAllText(tourFile);
|
||||
var tours = JsonConvert.DeserializeObject<BackOfficeTour[]>(contents);
|
||||
|
||||
var backOfficeTours = tours.Where(x =>
|
||||
aliasFilters.Count == 0 || aliasFilters.All(filter => filter.IsMatch(x.Alias)) == false);
|
||||
|
||||
var user = _umbracoContextAccessor.UmbracoContext.Security.CurrentUser;
|
||||
|
||||
var localizedTours = backOfficeTours.Where(x =>
|
||||
string.IsNullOrWhiteSpace(x.Culture) || x.Culture.Equals(user.Language,
|
||||
StringComparison.InvariantCultureIgnoreCase)).ToList();
|
||||
|
||||
var tour = new BackOfficeTourFile
|
||||
{
|
||||
FileName = Path.GetFileNameWithoutExtension(tourFile),
|
||||
PluginName = pluginName,
|
||||
Tours = localizedTours
|
||||
};
|
||||
|
||||
//don't add if all of the tours are filtered
|
||||
if (tour.Tours.Any())
|
||||
result.Add(tour);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new IOException("Error while trying to read file: " + tourFile, e);
|
||||
}
|
||||
catch (JsonReaderException e)
|
||||
{
|
||||
throw new JsonReaderException("Error while trying to parse content as tour data: " + tourFile, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for authorized auto-routed Umbraco API controllers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This controller will also append a custom header to the response if the user
|
||||
/// is logged in using forms authentication which indicates the seconds remaining
|
||||
/// before their timeout expires.
|
||||
/// </remarks>
|
||||
[IsBackOffice]
|
||||
//[UmbracoUserTimeoutFilter] //TODO reintroduce
|
||||
[UmbracoAuthorize]
|
||||
[DisableBrowserCache]
|
||||
[UmbracoWebApiRequireHttps]
|
||||
//[CheckIfUserTicketDataIsStale] //TODO reintroduce
|
||||
//[UnhandedExceptionLoggerConfiguration] //TODO reintroduce
|
||||
//[EnableDetailedErrors] //TODO reintroduce
|
||||
public abstract class UmbracoAuthorizedApiController : UmbracoApiController
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// An abstract API controller that only supports JSON and all requests must contain the correct csrf header
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Inheriting from this controller means that ALL of your methods are JSON methods that are called by Angular,
|
||||
/// methods that are not called by Angular or don't contain a valid csrf header will NOT work.
|
||||
/// </remarks>
|
||||
[TypeFilter(typeof(ValidateAngularAntiForgeryTokenAttribute))]
|
||||
[TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] // TODO: This could be applied with our Application Model conventions
|
||||
public abstract class UmbracoAuthorizedJsonController : UmbracoAuthorizedApiController
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user