Merge branch 'v8/dev' into temp8-4427-3

This commit is contained in:
Stephan
2019-04-01 12:00:35 +02:00
26 changed files with 594 additions and 145 deletions

View File

@@ -203,6 +203,28 @@ namespace Umbraco.Core
composition.RegisterUnique(_ => registrar);
}
/// <summary>
/// Sets the database server messenger options.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="factory">A function creating the options.</param>
/// <remarks>Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.</remarks>
public static void SetDatabaseServerMessengerOptions(this Composition composition, Func<IFactory, DatabaseServerMessengerOptions> factory)
{
composition.RegisterUnique(factory);
}
/// <summary>
/// Sets the database server messenger options.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="options">Options.</param>
/// <remarks>Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.</remarks>
public static void SetDatabaseServerMessengerOptions(this Composition composition, DatabaseServerMessengerOptions options)
{
composition.RegisterUnique(_ => options);
}
/// <summary>
/// Sets the short string helper.
/// </summary>

View File

@@ -7,6 +7,7 @@ using System.Web;
using System.Xml.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NPoco.Expressions;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
@@ -52,8 +53,8 @@ namespace Umbraco.Core
return ContentStatus.Unpublished;
}
#endregion
/// <summary>
@@ -134,9 +135,14 @@ namespace Umbraco.Core
/// <summary>
/// Sets the posted file value of a property.
/// </summary>
/// <remarks>This really is for FileUpload fields only, and should be obsoleted. For anything else,
/// you need to store the file by yourself using Store and then figure out
/// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself.</remarks>
public static void SetValue(this IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, HttpPostedFileBase postedFile, string culture = null, string segment = null)
{
content.SetValue(contentTypeBaseServiceProvider, propertyTypeAlias, postedFile.FileName, postedFile.InputStream, culture, segment);
}
/// <summary>
/// Sets the posted file value of a property.
/// </summary>
public static void SetValue(this IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null)
{
if (filename == null || filestream == null) return;

View File

@@ -23,21 +23,21 @@ namespace Umbraco.Core
/// <returns></returns>
/// <remarks>
/// There are some special routes we need to check to properly determine this:
///
///
/// If any route has an extension in the path like .aspx = back office
///
///
/// These are def back office:
/// /Umbraco/BackOffice = back office
/// /Umbraco/Preview = back office
/// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end
/// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice
/// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute.
///
///
/// These are def front-end:
/// /Umbraco/Surface = front-end
/// /Umbraco/Api = front-end
/// But if we've got this far we'll just have to assume it's front-end anyways.
///
///
/// </remarks>
internal static bool IsBackOfficeRequest(this Uri url, string applicationPath, IGlobalSettings globalSettings)
{
@@ -152,9 +152,9 @@ namespace Umbraco.Core
var toInclude = new[] {".aspx", ".ashx", ".asmx", ".axd", ".svc"};
return toInclude.Any(ext.InvariantEquals) == false;
}
catch (ArgumentException ex)
catch (ArgumentException)
{
Current.Logger.Error(typeof(UriExtensions), ex, "Failed to determine if request was client side");
Current.Logger.Debug(typeof(UriExtensions), "Failed to determine if request was client side (invalid chars in path \"{Path}\"?)", url.LocalPath);
return false;
}
}

View File

@@ -10,6 +10,7 @@ using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Core.Composing;
using Moq;
using Newtonsoft.Json;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
@@ -32,6 +33,8 @@ namespace Umbraco.Tests.PublishedContent
protected override void Compose()
{
base.Compose();
_publishedSnapshotAccessorMock = new Mock<IPublishedSnapshotAccessor>();
Composition.RegisterUnique<IPublishedSnapshotAccessor>(_publishedSnapshotAccessorMock.Object);
Composition.RegisterUnique<IPublishedModelFactory>(f => new PublishedModelFactory(f.GetInstance<TypeLoader>().GetTypes<PublishedContentModel>()));
Composition.RegisterUnique<IPublishedContentTypeFactory, PublishedContentTypeFactory>();
@@ -87,6 +90,7 @@ namespace Umbraco.Tests.PublishedContent
}
private readonly Guid _node1173Guid = Guid.NewGuid();
private Mock<IPublishedSnapshotAccessor> _publishedSnapshotAccessorMock;
protected override string GetXmlContent(int templateId)
{
@@ -792,6 +796,91 @@ namespace Umbraco.Tests.PublishedContent
Assert.IsTrue(customDoc3.IsDescendantOrSelf(customDoc3));
}
[Test]
public void SiblingsAndSelf()
{
// Structure:
// - Root : 1046 (no parent)
// -- Level1.1: 1173 (parent 1046)
// --- Level1.1.1: 1174 (parent 1173)
// --- Level1.1.2: 117 (parent 1173)
// --- Level1.1.3: 1177 (parent 1173)
// --- Level1.1.4: 1178 (parent 1173)
// --- Level1.1.5: 1176 (parent 1173)
// -- Level1.2: 1175 (parent 1046)
// -- Level1.3: 4444 (parent 1046)
var root = GetNode(1046);
var level1_1 = GetNode(1173);
var level1_1_1 = GetNode(1174);
var level1_1_2 = GetNode(117);
var level1_1_3 = GetNode(1177);
var level1_1_4 = GetNode(1178);
var level1_1_5 = GetNode(1176);
var level1_2 = GetNode(1175);
var level1_3 = GetNode(4444);
_publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot()).Returns(new []{root});
CollectionAssertAreEqual(new []{root}, root.SiblingsAndSelf());
CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_1.SiblingsAndSelf());
CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_2.SiblingsAndSelf());
CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_3.SiblingsAndSelf());
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_1.SiblingsAndSelf());
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_2.SiblingsAndSelf());
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_3.SiblingsAndSelf());
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_4.SiblingsAndSelf());
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_5.SiblingsAndSelf());
}
[Test]
public void Siblings()
{
// Structure:
// - Root : 1046 (no parent)
// -- Level1.1: 1173 (parent 1046)
// --- Level1.1.1: 1174 (parent 1173)
// --- Level1.1.2: 117 (parent 1173)
// --- Level1.1.3: 1177 (parent 1173)
// --- Level1.1.4: 1178 (parent 1173)
// --- Level1.1.5: 1176 (parent 1173)
// -- Level1.2: 1175 (parent 1046)
// -- Level1.3: 4444 (parent 1046)
var root = GetNode(1046);
var level1_1 = GetNode(1173);
var level1_1_1 = GetNode(1174);
var level1_1_2 = GetNode(117);
var level1_1_3 = GetNode(1177);
var level1_1_4 = GetNode(1178);
var level1_1_5 = GetNode(1176);
var level1_2 = GetNode(1175);
var level1_3 = GetNode(4444);
_publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot()).Returns(new []{root});
CollectionAssertAreEqual(new IPublishedContent[0], root.Siblings());
CollectionAssertAreEqual( new []{level1_2, level1_3}, level1_1.Siblings());
CollectionAssertAreEqual( new []{level1_1, level1_3}, level1_2.Siblings());
CollectionAssertAreEqual( new []{level1_1, level1_2}, level1_3.Siblings());
CollectionAssertAreEqual( new []{ level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_1.Siblings());
CollectionAssertAreEqual( new []{level1_1_1, level1_1_3, level1_1_4, level1_1_5}, level1_1_2.Siblings());
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_4, level1_1_5}, level1_1_3.Siblings());
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_5}, level1_1_4.Siblings());
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4}, level1_1_5.Siblings());
}
private void CollectionAssertAreEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual)
where T: IPublishedContent
{
var e = expected.Select(x => x.Id);
var a = actual.Select(x => x.Id);
CollectionAssert.AreEquivalent(e, a, $"\nExpected:\n{string.Join(", ", e)}\n\nActual:\n{string.Join(", ", a)}");
}
[Test]
public void FragmentProperty()

View File

@@ -62,8 +62,8 @@ function valPropertyMsg(serverValidationManager) {
if (!watcher) {
watcher = scope.$watch("currentProperty.value",
function (newValue, oldValue) {
if (!newValue || angular.equals(newValue, oldValue)) {
if (angular.equals(newValue, oldValue)) {
return;
}
@@ -78,10 +78,12 @@ function valPropertyMsg(serverValidationManager) {
// based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg
// is the only one, then we'll clear.
if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) {
if (errCount === 0 || (errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) {
scope.errorMsg = "";
formCtrl.$setValidity('valPropertyMsg', true);
stopWatch();
} else if (showValidation && scope.errorMsg === "") {
formCtrl.$setValidity('valPropertyMsg', false);
scope.errorMsg = getErrorMsg();
}
}, true);
}
@@ -152,6 +154,7 @@ function valPropertyMsg(serverValidationManager) {
showValidation = true;
if (hasError && scope.errorMsg === "") {
scope.errorMsg = getErrorMsg();
startWatch();
}
else if (!hasError) {
scope.errorMsg = "";

View File

@@ -85,17 +85,17 @@ angular.module('umbraco.services')
nArray.push(item);
if(!item.sticky) {
$timeout(function() {
var found = _.find(nArray, function(i) {
return i.id === item.id;
});
if (found) {
var index = nArray.indexOf(found);
nArray.splice(index, 1);
}
}, 7000);
$timeout(
function() {
var found = _.find(nArray, function(i) {
return i.id === item.id;
});
if (found) {
var index = nArray.indexOf(found);
nArray.splice(index, 1);
}
}
, 10000);
}
return item;

View File

@@ -137,6 +137,7 @@
@import "components/umb-iconpicker.less";
@import "components/umb-insert-code-box.less";
@import "components/umb-packages.less";
@import "components/umb-logviewer.less";
@import "components/umb-package-local-install.less";
@import "components/umb-panel-group.less";
@import "components/umb-lightbox.less";

View File

@@ -22,7 +22,14 @@
font-size: 14px;
border: none;
position: relative;
margin-bottom: 0;
border-radius: 10px;
margin: 10px;
.close {
top: 0;
right: -6px;
opacity: 0.4;
}
}
.umb-notifications__notification.-extra-padding {

View File

@@ -0,0 +1,42 @@
/* PACKAGE DETAILS */
.umb-logviewer {
display: flex;
flex-flow: row wrap;
}
@sidebarwidth: 350px; // Width of sidebar. Ugly hack because of old version of Less
.umb-logviewer__main-content {
flex: 1 1 auto;
margin-right: 20px;
width: calc(~'100%' - ~'@{sidebarwidth}' - ~'20px'); // Make sure that the main content area doesn't gets affected by inline styling
min-width: 500px;
.btn-link {
text-align: left;
}
}
.umb-logviewer__sidebar {
flex: 0 0 @sidebarwidth;
}
@media (max-width: 768px) {
.umb-logviewer {
flex-direction: column;
}
.umb-logviewer__main-content {
flex: 1 1 auto;
width: 100%;
margin-bottom: 30px;
margin-right: 0;
}
.umb-logviewer__sidebar {
flex: 1 1 auto;
width: 100%;
}
}

View File

@@ -52,7 +52,17 @@
bottom: 0px;
left: 0px;
right: 0px;
position: absolute;;
position: absolute;
}
.--notInFront .umb-modalcolumn::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
background: rgba(0,0,0,.4);
}
/* re-align loader */

View File

@@ -16,6 +16,75 @@
border-left: 1px solid @gray-10;
}
.date-wrapper__date .flatpickr-input > a {
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
padding: 4px 15px;
box-sizing: border-box;
min-width: 200px;
color: @ui-action-discreet-type;
border: 1px dashed @ui-action-discreet-border;
border-radius: 3px;
&:hover, &:focus {
text-decoration: none;
color: @ui-action-discreet-type-hover;
border-color: @ui-action-discreet-border-hover;
localize {
text-decoration: none;
}
}
}
//----- VARIANTS SCHEDULED PUBLISH ------
.date-wrapper-mini {
display: flex;
flex-direction: row;
}
.date-wrapper-mini__date {
display: flex;
margin-left: 5px;
margin-top: 5px;
margin-bottom: 10px;
&:first-of-type {
margin-left: 0;
}
}
.date-wrapper-mini__date .flatpickr-input > a {
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
padding: 1px 15px;
box-sizing: border-box;
min-width: 180px;
color: @ui-action-discreet-type;
border: 1px dashed @ui-action-discreet-border;
border-radius: 3px;
&:hover, &:focus {
text-decoration: none;
color: @ui-action-discreet-type-hover;
border-color: @ui-action-discreet-border-hover;
localize {
text-decoration: none;
}
}
}
//------------------- HISTORY ------------------
.history {
@@ -71,4 +140,4 @@
.history-line {
display: none;
}
}
}

View File

@@ -27,7 +27,7 @@
{{vm.variants[0].releaseDateFormatted}}
</button>
<a ng-hide="vm.variants[0].releaseDate" href="" class="bold" style="color: #00aea2; text-decoration: underline;">
<a ng-hide="vm.variants[0].releaseDate" href="">
<localize key="content_setDate">Set date</localize>
</a>
</div>
@@ -59,7 +59,7 @@
{{vm.variants[0].expireDateFormatted}}
</button>
<a ng-hide="vm.variants[0].expireDate" href="" class="bold" style="color: #00aea2; text-decoration: underline;">
<a ng-hide="vm.variants[0].expireDate" href="">
<localize key="content_setDate">Set date</localize>
</a>
</div>
@@ -84,8 +84,8 @@
<div class="umb-list umb-list--condensed">
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.dirtyVariantFilter">
<ng-form name="scheduleSelectorForm">
<div class="umb-list-item" ng-repeat="variant in vm.variants">
<ng-form name="scheduleSelectorForm" style="width:100%;">
<div class="flex">
<umb-checkbox
@@ -106,9 +106,9 @@
</span>
</label>
<div class="flex items-center" style="margin-top: 10px; margin-bottom: 10px;">
<div class="date-wrapper-mini">
<div class="date-wrapper-mini__date" ng-if="vm.dirtyVariantFilter(variant) && (variant.releaseDate || variant.save)">
<div class="flex items-center" ng-if="variant.releaseDate || variant.save">
<div style="font-size: 13px; margin-right: 5px;">Publish:<em ng-show="!variant.save">&nbsp;&nbsp;{{variant.releaseDateFormatted}}</em></div>
<div class="btn-group flex" style="font-size: 14px; margin-right: 10px;" ng-if="variant.save">
@@ -123,7 +123,7 @@
{{variant.releaseDateFormatted}}
</button>
<a ng-hide="variant.releaseDate" href="" class="bold" style="color: #00aea2; text-decoration: underline;">
<a ng-hide="variant.releaseDate" href="">
<localize key="content_setDate">Set date</localize>
</a>
</div>
@@ -134,7 +134,7 @@
</div>
</div>
<div class="flex items-center" ng-if="variant.expireDate || variant.save">
<div class="date-wrapper-mini__date" ng-if="variant.expireDate || variant.save">
<div style="font-size: 13px; margin-right: 5px;">Unpublish:<em ng-show="!variant.save">&nbsp;&nbsp;{{variant.expireDateFormatted}}</em></div>
<div class="btn-group flex" style="font-size: 14px;" ng-if="variant.save">
@@ -149,7 +149,7 @@
{{variant.expireDateFormatted}}
</button>
<a ng-hide="variant.expireDate" href="" class="bold" style="color: #00aea2; text-decoration: underline;">
<a ng-hide="variant.expireDate" href="">
<localize key="content_setDate">Set date</localize>
</a>
</div>
@@ -182,24 +182,6 @@
<br />
</div>
<div class="umb-list umb-list--condensed" ng-if="vm.hasPristineVariants">
<div style="margin-bottom: 15px; font-weight: bold;">
<p><localize key="content_publishedLanguages"></localize></p>
</div>
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.pristineVariantFilter track by variant.language.culture">
<div>
<div style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
</div>
<div class="umb-permission__description">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-show="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -13,7 +13,8 @@
type="button"
size="s"
action="vm.disableUrlTracker($event)"
label-key="redirectUrls_disableUrlTracker">
label-key="redirectUrls_disableUrlTracker"
button-style="white">
</umb-button>
<umb-button
@@ -22,7 +23,8 @@
size="s"
button-style="success"
action="vm.enableUrlTracker()"
label-key="redirectUrls_enableUrlTracker">
label-key="redirectUrls_enableUrlTracker"
button-style="success">
</umb-button>
</umb-editor-sub-header-section>

View File

@@ -1,4 +1,4 @@
<div data-element="editor-logs" ng-controller="Umbraco.Editors.LogViewer.OverviewController as vm" class="clearfix">
<div data-element="editor-logs" ng-controller="Umbraco.Editors.LogViewer.OverviewController as vm" class="clearfix" id="logview">
<umb-editor-view footer="false">
@@ -24,8 +24,8 @@
</umb-box>
</div>
<div class="umb-package-details" ng-show="!vm.loading && vm.canLoadLogs">
<div class="umb-package-details__main-content">
<div class="umb-logviewer" ng-show="!vm.loading && vm.canLoadLogs">
<div class="umb-logviewer__main-content">
<!-- Saved Searches -->
<umb-box>
<umb-box-header title="Saved Searches"></umb-box-header>
@@ -68,7 +68,7 @@
</umb-box>
</div>
<div class="umb-package-details__sidebar">
<div class="umb-logviewer__sidebar">
<!-- No of Errors -->
<umb-box ng-click="vm.searchLogQuery('Has(@Exception)')" style="cursor:pointer;">
<umb-box-header title="Number of Errors"></umb-box-header>
@@ -95,4 +95,4 @@
</div>
</umb-editor-container>
</umb-editor-view>
</div>
</div>

View File

@@ -7,7 +7,7 @@
<!doctype html>
<html lang="en">
<head>
<base href="@ViewBag.UmbracoBaseFolder/" />
<base href="@ViewData.GetUmbracoBaseFolder()/" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -66,8 +66,8 @@
var Umbraco = {};
Umbraco.Sys = {};
Umbraco.Sys.ServerVariables = {
"installApiBaseUrl": "@ViewBag.InstallApiBaseUrl",
"umbracoBaseUrl": "@ViewBag.UmbracoBaseFolder"
"installApiBaseUrl": "@ViewData.GetInstallApiBaseUrl()",
"umbracoBaseUrl": "@ViewData.GetUmbracoBaseFolder()"
};
</script>
<script src="lib/lazyload-js/lazyload.min.js"></script>

View File

@@ -51,7 +51,7 @@
@{
var externalLoginUrl = Url.Action("ExternalLogin", "BackOffice", new
{
area = ViewBag.UmbracoPath,
area = ViewData.GetUmbracoPath(),
//Custom redirect URL since we don't want to just redirect to the back office since this is for authing upgrades
redirectUrl = Url.Action("AuthorizeUpgrade", "BackOffice")
});
@@ -61,7 +61,7 @@
<script type="text/javascript">
document.angularReady = function (app) {
@Html.AngularValueExternalLoginInfoScript((IEnumerable<string>)ViewBag.ExternalSignInError)
@Html.AngularValueExternalLoginInfoScript(ViewData.GetExternalSignInError())
@Html.AngularValueResetPasswordCodeInfoScript(ViewData["PasswordResetCode"])
}

View File

@@ -112,12 +112,12 @@
on-login="hideLoginScreen()">
</umb-login>
@Html.BareMinimumServerVariablesScript(Url, Url.Action("ExternalLogin", "BackOffice", new { area = ViewBag.UmbracoPath }), Model.Features, Current.Configs.Global())
@Html.BareMinimumServerVariablesScript(Url, Url.Action("ExternalLogin", "BackOffice", new { area = ViewData.GetUmbracoPath() }), Model.Features, Current.Configs.Global())
<script>
document.angularReady = function(app) {
@Html.AngularValueExternalLoginInfoScript((IEnumerable<string>)ViewBag.ExternalSignInError)
@Html.AngularValueExternalLoginInfoScript(ViewData.GetExternalSignInError())
@Html.AngularValueResetPasswordCodeInfoScript(ViewData["PasswordResetCode"])
//required for the noscript trick
document.getElementById("mainwrapper").style.display = "inherit";

View File

@@ -27,9 +27,8 @@ namespace Umbraco.Web
private readonly IUmbracoDatabaseFactory _databaseFactory;
public BatchedDatabaseServerMessenger(
IRuntimeState runtime, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IGlobalSettings globalSettings,
bool enableDistCalls, DatabaseServerMessengerOptions options)
: base(runtime, scopeProvider, sqlContext, proflog, globalSettings, enableDistCalls, options)
IRuntimeState runtime, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IGlobalSettings globalSettings, DatabaseServerMessengerOptions options)
: base(runtime, scopeProvider, sqlContext, proflog, globalSettings, true, options)
{
_databaseFactory = databaseFactory;
}

View File

@@ -38,54 +38,44 @@ namespace Umbraco.Web.Compose
public sealed class DatabaseServerRegistrarAndMessengerComposer : ComponentComposer<DatabaseServerRegistrarAndMessengerComponent>, ICoreComposer
{
public static DatabaseServerMessengerOptions GetDefaultOptions(IFactory factory)
{
var logger = factory.GetInstance<ILogger>();
var indexRebuilder = factory.GetInstance<IndexRebuilder>();
return new DatabaseServerMessengerOptions
{
//These callbacks will be executed if the server has not been synced
// (i.e. it is a new server or the lastsynced.txt file has been removed)
InitializingCallbacks = new Action[]
{
//rebuild the xml cache file if the server is not synced
() =>
{
// rebuild the published snapshot caches entirely, if the server is not synced
// this is equivalent to DistributedCache RefreshAll... but local only
// (we really should have a way to reuse RefreshAll... locally)
// note: refresh all content & media caches does refresh content types too
var svc = Current.PublishedSnapshotService;
svc.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) });
svc.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _, out _);
svc.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _);
},
//rebuild indexes if the server is not synced
// NOTE: This will rebuild ALL indexes including the members, if developers want to target specific
// indexes then they can adjust this logic themselves.
() => { ExamineComponent.RebuildIndexes(indexRebuilder, logger, false, 5000); }
}
};
}
public override void Compose(Composition composition)
{
base.Compose(composition);
composition.SetServerMessenger(factory =>
{
var runtime = factory.GetInstance<IRuntimeState>();
var databaseFactory = factory.GetInstance<IUmbracoDatabaseFactory>();
var globalSettings = factory.GetInstance<IGlobalSettings>();
var proflog = factory.GetInstance<IProfilingLogger>();
var scopeProvider = factory.GetInstance<IScopeProvider>();
var sqlContext = factory.GetInstance<ISqlContext>();
var logger = factory.GetInstance<ILogger>();
var indexRebuilder = factory.GetInstance<IndexRebuilder>();
return new BatchedDatabaseServerMessenger(
runtime, databaseFactory, scopeProvider, sqlContext, proflog, globalSettings,
true,
//Default options for web including the required callbacks to build caches
new DatabaseServerMessengerOptions
{
//These callbacks will be executed if the server has not been synced
// (i.e. it is a new server or the lastsynced.txt file has been removed)
InitializingCallbacks = new Action[]
{
//rebuild the xml cache file if the server is not synced
() =>
{
// rebuild the published snapshot caches entirely, if the server is not synced
// this is equivalent to DistributedCache RefreshAll... but local only
// (we really should have a way to reuse RefreshAll... locally)
// note: refresh all content & media caches does refresh content types too
var svc = Current.PublishedSnapshotService;
svc.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) });
svc.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _, out _);
svc.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _);
},
//rebuild indexes if the server is not synced
// NOTE: This will rebuild ALL indexes including the members, if developers want to target specific
// indexes then they can adjust this logic themselves.
() =>
{
ExamineComponent.RebuildIndexes(indexRebuilder, logger, false, 5000);
}
}
});
});
composition.SetDatabaseServerMessengerOptions(GetDefaultOptions);
composition.SetServerMessenger<BatchedDatabaseServerMessenger>();
}
}
@@ -128,7 +118,7 @@ namespace Umbraco.Web.Compose
}
public void Initialize()
{
{
//We will start the whole process when a successful request is made
if (_registrar != null || _messenger != null)
UmbracoModule.RouteAttempt += RegisterBackgroundTasksOnce;

View File

@@ -45,10 +45,6 @@ namespace Umbraco.Web.Editors
private BackOfficeUserManager<BackOfficeIdentityUser> _userManager;
private BackOfficeSignInManager _signInManager;
private const string TokenExternalSignInError = "ExternalSignInError";
private const string TokenPasswordResetCode = "PasswordResetCode";
private static readonly string[] TempDataTokenNames = { TokenExternalSignInError, TokenPasswordResetCode };
public BackOfficeController(ManifestParser manifestParser, UmbracoFeatures features, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper)
: base(globalSettings, umbracoContextAccessor, services, appCaches, profilingLogger, umbracoHelper)
{
@@ -294,13 +290,13 @@ namespace Umbraco.Web.Editors
if (result)
{
//Add a flag and redirect for it to be displayed
TempData[TokenPasswordResetCode] = new ValidatePasswordResetCodeModel { UserId = userId, ResetCode = resetCode };
TempData[ViewDataExtensions.TokenPasswordResetCode] = new ValidatePasswordResetCodeModel { UserId = userId, ResetCode = resetCode };
return RedirectToLocal(Url.Action("Default", "BackOffice"));
}
}
//Add error and redirect for it to be displayed
TempData[TokenPasswordResetCode] = new[] { Services.TextService.Localize("login/resetCodeExpired") };
TempData[ViewDataExtensions.TokenPasswordResetCode] = new[] { Services.TextService.Localize("login/resetCodeExpired") };
return RedirectToLocal(Url.Action("Default", "BackOffice"));
}
@@ -314,7 +310,7 @@ namespace Umbraco.Web.Editors
if (loginInfo == null)
{
//Add error and redirect for it to be displayed
TempData[TokenExternalSignInError] = new[] { "An error occurred, could not get external login info" };
TempData[ViewDataExtensions.TokenExternalSignInError] = new[] { "An error occurred, could not get external login info" };
return RedirectToLocal(Url.Action("Default", "BackOffice"));
}
@@ -325,7 +321,7 @@ namespace Umbraco.Web.Editors
}
//Add errors and redirect for it to be displayed
TempData[TokenExternalSignInError] = result.Errors;
TempData[ViewDataExtensions.TokenExternalSignInError] = result.Errors;
return RedirectToLocal(Url.Action("Default", "BackOffice"));
}
@@ -341,17 +337,12 @@ namespace Umbraco.Web.Editors
if (defaultResponse == null) throw new ArgumentNullException("defaultResponse");
if (externalSignInResponse == null) throw new ArgumentNullException("externalSignInResponse");
ViewBag.UmbracoPath = GlobalSettings.GetUmbracoMvcArea();
ViewData.SetUmbracoPath(GlobalSettings.GetUmbracoMvcArea());
//check if there is the TempData with the any token name specified, if so, assign to view bag and render the view
foreach (var tempDataTokenName in TempDataTokenNames)
{
if (TempData[tempDataTokenName] != null)
{
ViewData[tempDataTokenName] = TempData[tempDataTokenName];
return defaultResponse();
}
}
if (ViewData.FromTempData(TempData, ViewDataExtensions.TokenExternalSignInError) ||
ViewData.FromTempData(TempData, ViewDataExtensions.TokenPasswordResetCode))
return defaultResponse();
//First check if there's external login info, if there's not proceed as normal
var loginInfo = await OwinContext.Authentication.GetExternalLoginInfoAsync(
@@ -416,7 +407,7 @@ namespace Umbraco.Web.Editors
{
if (await AutoLinkAndSignInExternalAccount(loginInfo, autoLinkOptions) == false)
{
ViewData[TokenExternalSignInError] = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to an account" };
ViewData.SetExternalSignInError(new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to an account" });
}
//Remove the cookie otherwise this message will keep appearing
@@ -440,7 +431,7 @@ namespace Umbraco.Web.Editors
//we are allowing auto-linking/creating of local accounts
if (loginInfo.Email.IsNullOrWhiteSpace())
{
ViewData[TokenExternalSignInError] = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not provided an email address, the account cannot be linked." };
ViewData.SetExternalSignInError(new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not provided an email address, the account cannot be linked." });
}
else
{
@@ -448,7 +439,7 @@ namespace Umbraco.Web.Editors
var foundByEmail = Services.UserService.GetByEmail(loginInfo.Email);
if (foundByEmail != null)
{
ViewData[TokenExternalSignInError] = new[] { "A user with this email address already exists locally. You will need to login locally to Umbraco and link this external provider: " + loginInfo.Login.LoginProvider };
ViewData.SetExternalSignInError(new[] { "A user with this email address already exists locally. You will need to login locally to Umbraco and link this external provider: " + loginInfo.Login.LoginProvider });
}
else
{
@@ -477,21 +468,21 @@ namespace Umbraco.Web.Editors
if (userCreationResult.Succeeded == false)
{
ViewData[TokenExternalSignInError] = userCreationResult.Errors;
ViewData.SetExternalSignInError(userCreationResult.Errors);
}
else
{
var linkResult = await UserManager.AddLoginAsync(autoLinkUser.Id, loginInfo.Login);
if (linkResult.Succeeded == false)
{
ViewData[TokenExternalSignInError] = linkResult.Errors;
ViewData.SetExternalSignInError(linkResult.Errors);
//If this fails, we should really delete the user since it will be in an inconsistent state!
var deleteResult = await UserManager.DeleteAsync(autoLinkUser);
if (deleteResult.Succeeded == false)
{
//DOH! ... this isn't good, combine all errors to be shown
ViewData[TokenExternalSignInError] = linkResult.Errors.Concat(deleteResult.Errors);
ViewData.SetExternalSignInError(linkResult.Errors.Concat(deleteResult.Errors));
}
}
else

View File

@@ -60,10 +60,10 @@ namespace Umbraco.Web.Install.Controllers
}
// gen the install base url
ViewBag.InstallApiBaseUrl = Url.GetUmbracoApiService("GetSetup", "InstallApi", "UmbracoInstall").TrimEnd("GetSetup");
ViewData.SetInstallApiBaseUrl(Url.GetUmbracoApiService("GetSetup", "InstallApi", "UmbracoInstall").TrimEnd("GetSetup"));
// get the base umbraco folder
ViewBag.UmbracoBaseFolder = IOHelper.ResolveUrl(SystemDirectories.Umbraco);
ViewData.SetUmbracoBaseFolder(IOHelper.ResolveUrl(SystemDirectories.Umbraco));
_installHelper.InstallStatus(false, "");

View File

@@ -10,6 +10,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Examine;
using Umbraco.Web.Composing;
using Umbraco.Web.PublishedCache;
namespace Umbraco.Web
{
@@ -21,6 +22,7 @@ namespace Umbraco.Web
// see notes in PublishedElementExtensions
//
private static IPublishedValueFallback PublishedValueFallback => Current.PublishedValueFallback;
private static IPublishedSnapshot PublishedSnapshot => Current.PublishedSnapshot;
#region Urls
@@ -235,7 +237,8 @@ namespace Umbraco.Web
/// </summary>
/// <param name="contents">The content items.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null).</param>
internal static IEnumerable<IPublishedContent> WhereIsInvariantOrHasCulture(this IEnumerable<IPublishedContent> contents, string culture = null)
internal static IEnumerable<T> WhereIsInvariantOrHasCulture<T>(this IEnumerable<T> contents, string culture = null)
where T : class, IPublishedContent
{
if (contents == null) throw new ArgumentNullException(nameof(contents));
@@ -1117,6 +1120,97 @@ namespace Umbraco.Web
#endregion
#region Axes: Siblings
/// <summary>
/// Gets the siblings of the content.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <returns>The siblings of the content.</returns>
/// <remarks>
/// <para>Note that in V7 this method also return the content node self.</para>
/// </remarks>
public static IEnumerable<IPublishedContent> Siblings(this IPublishedContent content, string culture = null)
{
return SiblingsAndSelf(content, culture).Where(x => x.Id != content.Id);
}
/// <summary>
/// Gets the siblings of the content, of a given content type.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <param name="contentTypeAlias">The content type alias.</param>
/// <returns>The siblings of the content, of the given content type.</returns>
/// <remarks>
/// <para>Note that in V7 this method also return the content node self.</para>
/// </remarks>
public static IEnumerable<IPublishedContent> SiblingsOfType(this IPublishedContent content, string contentTypeAlias, string culture = null)
{
return SiblingsAndSelfOfType(content, contentTypeAlias, culture).Where(x => x.Id != content.Id);
}
/// <summary>
/// Gets the siblings of the content, of a given content type.
/// </summary>
/// <typeparam name="T">The content type.</typeparam>
/// <param name="content">The content.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <returns>The siblings of the content, of the given content type.</returns>
/// <remarks>
/// <para>Note that in V7 this method also return the content node self.</para>
/// </remarks>
public static IEnumerable<IPublishedContent> Siblings<T>(this IPublishedContent content, string culture = null)
where T : class, IPublishedContent
{
return SiblingsAndSelf<T>(content, culture).Where(x => x.Id != content.Id);
}
/// <summary>
/// Gets the siblings of the content including the node itself to indicate the position.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <returns>The siblings of the content including the node itself.</returns>
public static IEnumerable<IPublishedContent> SiblingsAndSelf(this IPublishedContent content, string culture = null)
{
return content.Parent != null
? content.Parent.Children(culture)
: PublishedSnapshot.Content.GetAtRoot().WhereIsInvariantOrHasCulture(culture);
}
/// <summary>
/// Gets the siblings of the content including the node itself to indicate the position, of a given content type.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <param name="contentTypeAlias">The content type alias.</param>
/// <returns>The siblings of the content including the node itself, of the given content type.</returns>
public static IEnumerable<IPublishedContent> SiblingsAndSelfOfType(this IPublishedContent content, string contentTypeAlias, string culture = null)
{
return content.Parent != null
? content.Parent.ChildrenOfType(contentTypeAlias, culture)
: PublishedSnapshot.Content.GetAtRoot().OfTypes(contentTypeAlias).WhereIsInvariantOrHasCulture(culture);
}
/// <summary>
/// Gets the siblings of the content including the node itself to indicate the position, of a given content type.
/// </summary>
/// <typeparam name="T">The content type.</typeparam>
/// <param name="content">The content.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <returns>The siblings of the content including the node itself, of the given content type.</returns>
public static IEnumerable<T> SiblingsAndSelf<T>(this IPublishedContent content, string culture = null)
where T : class, IPublishedContent
{
return content.Parent != null
? content.Parent.Children<T>(culture)
: PublishedSnapshot.Content.GetAtRoot().OfType<T>().WhereIsInvariantOrHasCulture(culture);
}
#endregion
#region Axes: custom
/// <summary>

View File

@@ -289,11 +289,31 @@ namespace Umbraco.Web.Security
return MemberCache.GetByProviderKey(key);
}
public virtual IEnumerable<IPublishedContent> GetByProviderKeys(IEnumerable<object> keys)
{
return keys?.Select(GetByProviderKey).WhereNotNull() ?? Enumerable.Empty<IPublishedContent>();
}
public virtual IPublishedContent GetById(int memberId)
{
return MemberCache.GetById(memberId);
}
public virtual IEnumerable<IPublishedContent> GetByIds(IEnumerable<int> memberIds)
{
return memberIds?.Select(GetById).WhereNotNull() ?? Enumerable.Empty<IPublishedContent>();
}
public virtual IPublishedContent GetById(Guid memberId)
{
return GetByProviderKey(memberId);
}
public virtual IEnumerable<IPublishedContent> GetByIds(IEnumerable<Guid> memberIds)
{
return GetByProviderKeys(memberIds.OfType<object>());
}
public virtual IPublishedContent GetByUsername(string username)
{
return MemberCache.GetByUsername(username);

View File

@@ -216,6 +216,7 @@
<Compile Include="Models\TemplateQuery\OperatorFactory.cs" />
<Compile Include="UmbracoContextFactory.cs" />
<Compile Include="UmbracoContextReference.cs" />
<Compile Include="ViewDataExtensions.cs" />
<Compile Include="WebApi\Filters\AdminUsersAuthorizeAttribute.cs" />
<Compile Include="WebApi\Filters\OnlyLocalRequestsAttribute.cs" />
<Compile Include="PropertyEditors\MultiUrlPickerConfiguration.cs" />

View File

@@ -264,7 +264,7 @@ namespace Umbraco.Web
public IPublishedContent Member(Guid id)
{
return MembershipHelper.GetByProviderKey(id);
return MembershipHelper.GetById(id);
}
public IPublishedContent Member(object id)
@@ -289,6 +289,56 @@ namespace Umbraco.Web
return asInt ? MembershipHelper.GetById(asInt.Result) : MembershipHelper.GetByProviderKey(id);
}
public IEnumerable<IPublishedContent> Members(IEnumerable<int> ids)
{
return MembershipHelper.GetByIds(ids);
}
public IEnumerable<IPublishedContent> Members(IEnumerable<string> ids)
{
return ids.Select(Member).WhereNotNull();
}
public IEnumerable<IPublishedContent> Members(IEnumerable<Guid> ids)
{
return MembershipHelper.GetByIds(ids);
}
public IEnumerable<IPublishedContent> Members(IEnumerable<Udi> ids)
{
return ids.Select(Member).WhereNotNull();
}
public IEnumerable<IPublishedContent> Members(IEnumerable<object> ids)
{
return ids.Select(Member).WhereNotNull();
}
public IEnumerable<IPublishedContent> Members(params int[] ids)
{
return ids.Select(Member).WhereNotNull();
}
public IEnumerable<IPublishedContent> Members(params string[] ids)
{
return ids.Select(Member).WhereNotNull();
}
public IEnumerable<IPublishedContent> Members(params Guid[] ids)
{
return MembershipHelper.GetByIds(ids);
}
public IEnumerable<IPublishedContent> Members(params Udi[] ids)
{
return ids.Select(Member).WhereNotNull();
}
public IEnumerable<IPublishedContent> Members(params object[] ids)
{
return ids.Select(Member).WhereNotNull();
}
#endregion
#region Content

View File

@@ -0,0 +1,71 @@
using System.Collections.Generic;
using System.Web.Mvc;
namespace Umbraco.Web
{
public static class ViewDataExtensions
{
public const string TokenUmbracoPath = "UmbracoPath";
public const string TokenInstallApiBaseUrl = "InstallApiBaseUrl";
public const string TokenUmbracoBaseFolder = "UmbracoBaseFolder";
public const string TokenExternalSignInError = "ExternalSignInError";
public const string TokenPasswordResetCode = "PasswordResetCode";
public static bool FromTempData(this ViewDataDictionary viewData, TempDataDictionary tempData, string token)
{
if (tempData[token] == null) return false;
viewData[token] = tempData[token];
return true;
}
public static string GetUmbracoPath(this ViewDataDictionary viewData)
{
return (string)viewData[TokenUmbracoPath];
}
public static void SetUmbracoPath(this ViewDataDictionary viewData, string value)
{
viewData[TokenUmbracoPath] = value;
}
public static string GetInstallApiBaseUrl(this ViewDataDictionary viewData)
{
return (string)viewData[TokenInstallApiBaseUrl];
}
public static void SetInstallApiBaseUrl(this ViewDataDictionary viewData, string value)
{
viewData[TokenInstallApiBaseUrl] = value;
}
public static string GetUmbracoBaseFolder(this ViewDataDictionary viewData)
{
return (string)viewData[TokenUmbracoBaseFolder];
}
public static void SetUmbracoBaseFolder(this ViewDataDictionary viewData, string value)
{
viewData[TokenUmbracoBaseFolder] = value;
}
public static IEnumerable<string> GetExternalSignInError(this ViewDataDictionary viewData)
{
return (IEnumerable<string>)viewData[TokenExternalSignInError];
}
public static void SetExternalSignInError(this ViewDataDictionary viewData, IEnumerable<string> value)
{
viewData[TokenExternalSignInError] = value;
}
public static string GetPasswordResetCode(this ViewDataDictionary viewData)
{
return (string)viewData[TokenPasswordResetCode];
}
public static void SetPasswordResetCode(this ViewDataDictionary viewData, string value)
{
viewData[TokenPasswordResetCode] = value;
}
}
}