diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml
index 42fe9f920b..a229898673 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml
@@ -259,13 +259,6 @@
Pište pro filtrování...
-
Povolené podřízené typy uzlů
-
Vytvořit
-
Odstranit záložku
-
Popis
-
Nová záložka
-
Záložka
-
Miniatura
Přidat předlohu
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml
index e75ff6d83d..7cc7c7823a 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml
@@ -503,13 +503,6 @@
Genererer alias...
-
Tilladte typer
-
Opret
-
Slet fane
-
Beskrivelse
-
Ny fane
-
Fane
-
Thumbnail
Opret brugerdefineret listevisning
Fjern brugerdefineret listevisning
@@ -609,6 +602,7 @@
Første
Generelt
Grupper
+
Gruppe
Højde
Hjælp
Skjul
@@ -1338,6 +1332,7 @@ Mange hilsner fra Umbraco robotten
Indtast makronavn
Parametre
Definer de parametre, der skal være tilgængelige, når du bruger denne makro.
+
Vælg partial view makrofil
Alternativt felt
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml
index 7e1cb96228..3c8d24ec9c 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml
@@ -273,13 +273,6 @@
Der Benutzername ist normalerweise Ihre E-Mail-Adresse
-
Dokumenttypen, die unterhalb dieses Typs erlaubt sind
-
Erstellen
-
Registerkarte löschen
-
Beschreibung
-
Neue Registerkarte
-
Registerkarte
-
Illustration
Angepasste Listenansicht erstellen
Angepasste Listenansicht entfernen
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml
index 372bc3158d..78fe096bd1 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml
@@ -521,13 +521,6 @@
Generating alias...
-
Allowed child node types
-
Create
-
Delete tab
-
Description
-
New tab
-
Tab
-
Thumbnail
Create custom list view
Remove custom list view
A content type, media type or member type with this alias already exists
@@ -632,6 +625,7 @@
First
General
Groups
+
Group
Height
Help
Hide
@@ -1165,7 +1159,7 @@ To manage your website, simply open the Umbraco back office and start adding con
Downloads
Likes
Compatibility
-
This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be gauranteed for versions reported below 100%
+
This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be guaranteed for versions reported below 100%
External sources
Author
Documentation
@@ -1615,6 +1609,7 @@ To manage your website, simply open the Umbraco back office and start adding con
Enter macro name
Parameters
Define the parameters that should be available when using this macro.
+
Select partial view macro file
Building models
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml
index fb5a6bd359..4e13407a4c 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml
@@ -523,13 +523,6 @@
Generating alias...
-
Allowed child node types
-
Create
-
Delete tab
-
Description
-
New tab
-
Tab
-
Thumbnail
Create custom list view
Remove custom list view
A content type, media type or member type with this alias already exists
@@ -635,6 +628,7 @@
First
General
Groups
+
Group
Height
Help
Hide
@@ -1625,6 +1619,7 @@ To manage your website, simply open the Umbraco back office and start adding con
Enter macro name
Parameters
Define the parameters that should be available when using this macro.
+
Select partial view macro file
Building models
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml
index aef92af6e1..66cad5fd26 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml
@@ -391,13 +391,6 @@
Tu nombre de usuario normalmente es tu e-mail
-
Tipos de nodos hijos permitidos
-
Crear
-
Borrar pestaña
-
Descripción
-
Nueva pestaña
-
Pestaña
-
Miniatura
Crear un tipo de listado personalizado
Quitar el tipo de listado personalizado
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml
index e992767f4f..8e378f0bc9 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml
@@ -406,13 +406,6 @@
Votre nom d'utilisateur est généralement votre adresse email
-
Types de noeuds enfants autorisés
-
Créer
-
Supprimer l'onglet
-
Description
-
Nouvel onglet
-
Onglet
-
Miniature
Créer une liste personnalisée
Supprimer la liste personnalisée
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml
index a0a6c2cefc..ece1810ef5 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml
@@ -202,13 +202,6 @@
שם התצוגה לשפה
-
תת פריטי תוכן מאושרים:
-
צור
-
מחק לשונית
-
תיאור
-
לשונית חדשה
-
לשונית
-
תמונה ממוזערת
הוסף ערך מקדים
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml
index 8d8105c4d1..3ae078252a 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml
@@ -205,13 +205,6 @@
-
Possibili nodetypes figli
-
Crea
-
Cancella tab
-
Descrizione
-
Nuova tab
-
Tab
-
Miniatura
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml
index f6d957743e..303d0f0427 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml
@@ -300,13 +300,6 @@
入力してタグを追加 (各タグの後に Enter を押してください)...
-
子ノードとして許可するタイプ
-
新規作成
-
タブの削除
-
説明
-
新しいタブ
-
タブ
-
サムネイル
カスタムリストビューを作成する
カスタムリストビューを削除する
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml
index 2336dccb16..512d192237 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml
@@ -201,13 +201,6 @@
국가명
-
자식노드 타입 허용
-
생성
-
색인 삭제
-
설명
-
새 색인
-
색인
-
썸네일
이전값 더하기
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml
index b38c11a6e0..cd6878a196 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml
@@ -268,13 +268,6 @@
Skriv inn nøkkelord (trykk på Enter etter hvert nøkkelord)...
-
Tillatte underordnede noder
-
Opprett
-
Slett arkfane
-
Beskrivelse
-
Ny arkfane
-
Arkfane
-
Miniatyrbilde
Opprett brukerdefinert listevisning
Fjern brukerdefinert listevisning
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml
index 28d71ed003..556a8c4d48 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml
@@ -330,13 +330,6 @@
Jouw gebruikersnaam is meestal jouw email
-
Toegelaten subnodetypes
-
Nieuw
-
Tab verwijderen
-
Omschrijving
-
Nieuwe tab
-
Tab
-
Miniatuur
Maak een aangepaste lijstweergave
Verwijder aangepaste lijstweergave
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml
index 96e8e980a6..055d9eddcf 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml
@@ -393,13 +393,6 @@
Twoja nazwa użytkownika to przeważnie Twój adres e-mail
-
Dozwolone węzły pochodne
-
Stwórz
-
Usuń zakładkę
-
Opis
-
Nowa zakładka
-
Zakładka
-
Miniatura
Stwórz niestandardowy widok listy
Usuń niestandardowy widok listy
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml
index a275fc8cd9..767617a2d5 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml
@@ -199,13 +199,6 @@
Nome da Cultura
-
Tipos de nós filhos permitidos
-
Criar
-
Remover guia
-
Descrição
-
Nova guia
-
Guia
-
Miniatura
Adicionar valor prévio
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml
index a0fc2c3ffc..7698453b4a 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml
@@ -427,15 +427,8 @@
Обзор словаря
-
Допустимые типы дочерних узлов
-
Создать
Создать пользовательский список
-
Удалить вкладку
-
Описание
-
Новая вкладка
Удалить пользовательский список
-
Вкладка
-
Миниатюра
Добавить предустановленное значение
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml
index 9e2e312c38..bd847a605a 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml
@@ -257,15 +257,8 @@
Språknamn
-
Tillåtna typer för underliggande noder
-
Skapa
Skapa en anpassad listvy
-
Ta bort flik
-
Beskrivning
-
Ny flik
Radera anpassad listvy
-
Flik
-
Miniatyrbild
Lägg till värde
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml
index 1a298c0a35..347f205a1f 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml
@@ -239,13 +239,6 @@
(Basın, her etiketinden sonra girin) etiket eklemek için yazın ...
-
İzin alt düğüm çeşitleri
-
Oluştur
-
Sekmesini sil
-
Tanım
-
Yeni sekme
-
Sekme
-
Küçük resim
Özel liste görünüm oluşturun
Özel liste görünümü kaldır
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml
index c8c3f75e17..40a81413e5 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml
@@ -318,13 +318,6 @@
输入您的电子邮件
-
允许子项节点类型
-
创建
-
删除选项卡
-
描述
-
新建选项卡
-
选项卡
-
缩略图
创建自定义列表视图
删除自定义列表视图
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml
index 56639aa308..a212826bbc 100644
--- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml
+++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml
@@ -311,13 +311,6 @@
輸入您的電子郵件
-
允許子項節點類型
-
創建
-
刪除選項卡
-
描述
-
新建選項卡
-
選項卡
-
縮略圖
新增自訂清單檢視
移除自訂清單檢視
diff --git a/src/Umbraco.Web.UI/favicon.ico b/src/Umbraco.Web.UI/favicon.ico
new file mode 100644
index 0000000000..c0749ddf7f
Binary files /dev/null and b/src/Umbraco.Web.UI/favicon.ico differ
diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs
index 818e8ecf77..6596fee245 100644
--- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs
+++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs
@@ -13,6 +13,7 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Scoping;
using Umbraco.Web.Composing;
+using System.ComponentModel;
namespace Umbraco.Web
{
@@ -26,6 +27,13 @@ namespace Umbraco.Web
{
private readonly IUmbracoDatabaseFactory _databaseFactory;
+ [Obsolete("This overload should not be used, enableDistCalls has no effect")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public BatchedDatabaseServerMessenger(
+ IRuntimeState runtime, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IGlobalSettings globalSettings, bool enableDistCalls, DatabaseServerMessengerOptions options)
+ : this(runtime, databaseFactory, scopeProvider, sqlContext, proflog, globalSettings, options)
+ { }
+
public BatchedDatabaseServerMessenger(
IRuntimeState runtime, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IGlobalSettings globalSettings, DatabaseServerMessengerOptions options)
: base(runtime, scopeProvider, sqlContext, proflog, globalSettings, true, options)
diff --git a/src/Umbraco.Web/Compose/NotificationsComponent.cs b/src/Umbraco.Web/Compose/NotificationsComponent.cs
index 08bcdf345c..db602b3db9 100644
--- a/src/Umbraco.Web/Compose/NotificationsComponent.cs
+++ b/src/Umbraco.Web/Compose/NotificationsComponent.cs
@@ -20,11 +20,13 @@ namespace Umbraco.Web.Compose
{
private readonly Notifier _notifier;
private readonly ActionCollection _actions;
+ private readonly IContentService _contentService;
- public NotificationsComponent(Notifier notifier, ActionCollection actions)
+ public NotificationsComponent(Notifier notifier, ActionCollection actions, IContentService contentService)
{
_notifier = notifier;
_actions = actions;
+ _contentService = contentService;
}
public void Initialize()
@@ -41,11 +43,25 @@ namespace Umbraco.Web.Compose
//Send notifications for the update and created actions
ContentService.Saved += (sender, args) => ContentServiceSaved(_notifier, sender, args, _actions);
- //Send notifications for the delete action
- ContentService.Deleted += (sender, args) => _notifier.Notify(_actions.GetAction
(), args.DeletedEntities.ToArray());
-
//Send notifications for the unpublish action
ContentService.Unpublished += (sender, args) => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray());
+
+ //Send notifications for the move/move to recycle bin and restore actions
+ ContentService.Moved += (sender, args) => ContentServiceMoved(_notifier, sender, args, _actions);
+
+ //Send notifications for the delete action when content is moved to the recycle bin
+ ContentService.Trashed += (sender, args) => _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray());
+
+ //Send notifications for the copy action
+ ContentService.Copied += (sender, args) => _notifier.Notify(_actions.GetAction(), args.Original);
+
+ //Send notifications for the rollback action
+ ContentService.RolledBack += (sender, args) => _notifier.Notify(_actions.GetAction(), args.Entity);
+
+ //Send notifications for the public access changed action
+ PublicAccessService.Saved += (sender, args) => PublicAccessServiceSaved(_notifier, sender, args, _contentService, _actions);
+
+ UserService.UserGroupPermissionsAssigned += (sender, args) => UserServiceUserGroupPermissionsAssigned(_notifier, sender, args, _contentService, _actions);
}
public void Terminate()
@@ -90,6 +106,42 @@ namespace Umbraco.Web.Compose
notifier.Notify(actions.GetAction(), updatedEntities.ToArray());
}
+ private void UserServiceUserGroupPermissionsAssigned(Notifier notifier, IUserService sender, Core.Events.SaveEventArgs args, IContentService contentService, ActionCollection actions)
+ {
+ var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.EntityId)).ToArray();
+ if(entities.Any() == false)
+ {
+ return;
+ }
+ notifier.Notify(actions.GetAction(), entities);
+ }
+
+ private void ContentServiceMoved(Notifier notifier, IContentService sender, Core.Events.MoveEventArgs args, ActionCollection actions)
+ {
+ // notify about the move for all moved items
+ _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray());
+
+ // for any items being moved from the recycle bin (restored), explicitly notify about that too
+ var restoredEntities = args.MoveInfoCollection
+ .Where(m => m.OriginalPath.Contains(Constants.System.RecycleBinContentString))
+ .Select(m => m.Entity)
+ .ToArray();
+ if(restoredEntities.Any())
+ {
+ _notifier.Notify(_actions.GetAction(), restoredEntities);
+ }
+ }
+
+ private void PublicAccessServiceSaved(Notifier notifier, IPublicAccessService sender, Core.Events.SaveEventArgs args, IContentService contentService, ActionCollection actions)
+ {
+ var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray();
+ if(entities.Any() == false)
+ {
+ return;
+ }
+ notifier.Notify(actions.GetAction(), entities);
+ }
+
///
/// This class is used to send the notifications
///
diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs
index 420c0b8a4c..5be5e45ecd 100644
--- a/src/Umbraco.Web/Composing/Current.cs
+++ b/src/Umbraco.Web/Composing/Current.cs
@@ -101,6 +101,9 @@ namespace Umbraco.Web.Composing
public static UrlProviderCollection UrlProviders
=> Factory.GetInstance();
+ public static MediaUrlProviderCollection MediaUrlProviders
+ => Factory.GetInstance();
+
public static HealthCheckCollectionBuilder HealthCheckCollectionBuilder
=> Factory.GetInstance();
diff --git a/src/Umbraco.Web/CompositionExtensions.cs b/src/Umbraco.Web/CompositionExtensions.cs
index 9052b20934..27a56afc1e 100644
--- a/src/Umbraco.Web/CompositionExtensions.cs
+++ b/src/Umbraco.Web/CompositionExtensions.cs
@@ -90,6 +90,13 @@ namespace Umbraco.Web
public static UrlProviderCollectionBuilder UrlProviders(this Composition composition)
=> composition.WithCollectionBuilder();
+ ///
+ /// Gets the media url providers collection builder.
+ ///
+ /// The composition.
+ public static MediaUrlProviderCollectionBuilder MediaUrlProviders(this Composition composition)
+ => composition.WithCollectionBuilder();
+
///
/// Gets the backoffice sections/applications collection builder.
///
diff --git a/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs b/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs
index 9d186b02f8..da7dbee364 100644
--- a/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs
+++ b/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs
@@ -9,7 +9,7 @@ namespace Umbraco.Web.ContentApps
internal class ContentEditorContentAppFactory : IContentAppFactory
{
// see note on ContentApp
- private const int Weight = -100;
+ internal const int Weight = -100;
private ContentApp _contentApp;
private ContentApp _mediaApp;
diff --git a/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs b/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs
index 7421a55907..ab605c7014 100644
--- a/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs
+++ b/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.ContentEditing;
using Umbraco.Core.Models.Membership;
@@ -99,6 +100,12 @@ namespace Umbraco.Web.ContentApps
if (configTabName != null && String.IsNullOrWhiteSpace(configTabName.ToString()) == false)
contentApp.Name = configTabName.ToString();
}
+ // if the list view is configured to show umbContent first, update the list view content app weight accordingly
+ if(listViewConfig.ContainsKey("showContentFirst") &&
+ listViewConfig["showContentFirst"]?.ToString().TryConvertTo().Result == true)
+ {
+ contentApp.Weight = ContentEditorContentAppFactory.Weight + 1;
+ }
//This is the view model used for the list view app
contentApp.ViewModel = new List
diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs
index 4b23e46a6c..de65689353 100644
--- a/src/Umbraco.Web/Editors/ContentController.cs
+++ b/src/Umbraco.Web/Editors/ContentController.cs
@@ -1483,7 +1483,7 @@ namespace Umbraco.Web.Editors
[EnsureUserPermissionForContent(Constants.System.RecycleBinContent, ActionDelete.ActionLetter)]
public HttpResponseMessage EmptyRecycleBin()
{
- Services.ContentService.EmptyRecycleBin();
+ Services.ContentService.EmptyRecycleBin(Security.GetUserId().ResultOr(Constants.Security.SuperUserId));
return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty"));
}
diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs
index e648a4d140..05f7b6525c 100644
--- a/src/Umbraco.Web/Editors/EntityController.cs
+++ b/src/Umbraco.Web/Editors/EntityController.cs
@@ -99,8 +99,25 @@ namespace Umbraco.Web.Editors
/// A starting point for the search, generally a node id, but for members this is a member type alias
///
///
+ [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)]
[HttpGet]
public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null)
+ {
+ return Search(query, type, ignoreUserStartNodes: false, searchFrom);
+ }
+
+ ///
+ /// Searches for results based on the entity type
+ ///
+ ///
+ ///
+ ///
+ /// A starting point for the search, generally a node id, but for members this is a member type alias
+ ///
+ /// If set to true, user and group start node permissions will be ignored.
+ ///
+ [HttpGet]
+ public IEnumerable Search(string query, UmbracoEntityTypes type, bool? ignoreUserStartNodes, string searchFrom = null)
{
// TODO: Should we restrict search results based on what app the user has access to?
// - Theoretically you shouldn't be able to see member data if you don't have access to members right?
@@ -110,7 +127,7 @@ namespace Umbraco.Web.Editors
//TODO: This uses the internal UmbracoTreeSearcher, this instead should delgate to the ISearchableTree implementation for the type
- return ExamineSearch(query, type, searchFrom);
+ return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes != null && ignoreUserStartNodes.Value);
}
///
@@ -534,6 +551,7 @@ namespace Umbraco.Web.Editors
}
}
+ [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)]
public PagedResult GetPagedDescendants(
int id,
UmbracoEntityTypes type,
@@ -542,6 +560,20 @@ namespace Umbraco.Web.Editors
string orderBy = "SortOrder",
Direction orderDirection = Direction.Ascending,
string filter = "")
+ {
+ return GetPagedDescendants(id, type, pageNumber, pageSize,
+ ignoreUserStartNodes: false, orderBy, orderDirection, filter);
+ }
+
+ public PagedResult GetPagedDescendants(
+ int id,
+ UmbracoEntityTypes type,
+ int pageNumber,
+ int pageSize,
+ bool ignoreUserStartNodes,
+ string orderBy = "SortOrder",
+ Direction orderDirection = Direction.Ascending,
+ string filter = "")
{
if (pageNumber <= 0)
throw new HttpResponseException(HttpStatusCode.NotFound);
@@ -569,7 +601,7 @@ namespace Umbraco.Web.Editors
break;
}
- entities = aids == null || aids.Contains(Constants.System.Root)
+ entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes
? Services.EntityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords,
SqlContext.Query().Where(x => x.Name.Contains(filter)),
Ordering.By(orderBy, orderDirection), includeTrashed: false)
@@ -611,9 +643,15 @@ namespace Umbraco.Web.Editors
}
}
- public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings)
+ [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)]
+ public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormDataCollection queryStrings)
{
- return GetResultForAncestors(id, type, queryStrings);
+ return GetResultForAncestors(id, type, queryStrings, ignoreUserStartNodes: false);
+ }
+
+ public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings, bool ignoreUserStartNodes)
+ {
+ return GetResultForAncestors(id, type, queryStrings, ignoreUserStartNodes);
}
///
@@ -622,10 +660,11 @@ namespace Umbraco.Web.Editors
///
///
///
+ /// If set to true, user and group start node permissions will be ignored.
///
- private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null)
+ private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false)
{
- return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, searchFrom);
+ return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, ignoreUserStartNodes, searchFrom);
}
private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType)
@@ -651,7 +690,7 @@ namespace Umbraco.Web.Editors
}
}
- private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, FormDataCollection queryStrings = null)
+ private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, FormDataCollection queryStrings = null, bool ignoreUserStartNodes = false)
{
var objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
@@ -660,35 +699,38 @@ namespace Umbraco.Web.Editors
var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray();
- int[] aids = null;
- switch (entityType)
+ if (ignoreUserStartNodes == false)
{
- case UmbracoEntityTypes.Document:
- aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService);
- break;
- case UmbracoEntityTypes.Media:
- aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService);
- break;
- }
-
- if (aids != null)
- {
- var lids = new List();
- var ok = false;
- foreach (var i in ids)
+ int[] aids = null;
+ switch (entityType)
{
- if (ok)
- {
- lids.Add(i);
- continue;
- }
- if (aids.Contains(i))
- {
- lids.Add(i);
- ok = true;
- }
+ case UmbracoEntityTypes.Document:
+ aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService);
+ break;
+ case UmbracoEntityTypes.Media:
+ aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService);
+ break;
+ }
+
+ if (aids != null)
+ {
+ var lids = new List();
+ var ok = false;
+ foreach (var i in ids)
+ {
+ if (ok)
+ {
+ lids.Add(i);
+ continue;
+ }
+ if (aids.Contains(i))
+ {
+ lids.Add(i);
+ ok = true;
+ }
+ }
+ ids = lids.ToArray();
}
- ids = lids.ToArray();
}
var culture = queryStrings?.GetValue("culture");
diff --git a/src/Umbraco.Web/Editors/LogController.cs b/src/Umbraco.Web/Editors/LogController.cs
index 4f750920d3..c3e3790378 100644
--- a/src/Umbraco.Web/Editors/LogController.cs
+++ b/src/Umbraco.Web/Editors/LogController.cs
@@ -18,10 +18,15 @@ namespace Umbraco.Web.Editors
[UmbracoApplicationAuthorize(Core.Constants.Applications.Content, Core.Constants.Applications.Media)]
public PagedResult GetPagedEntityLog(int id,
int pageNumber = 1,
- int pageSize = 0,
+ int pageSize = 10,
Direction orderDirection = Direction.Descending,
DateTime? sinceDate = null)
{
+ if (pageSize <= 0 || pageNumber <= 0)
+ {
+ return new PagedResult(0, pageNumber, pageSize);
+ }
+
long totalRecords;
var dateQuery = sinceDate.HasValue ? SqlContext.Query().Where(x => x.CreateDate >= sinceDate) : null;
var result = Services.AuditService.GetPagedItemsByEntity(id, pageNumber - 1, pageSize, out totalRecords, orderDirection, customFilter: dateQuery);
@@ -37,10 +42,15 @@ namespace Umbraco.Web.Editors
public PagedResult GetPagedCurrentUserLog(
int pageNumber = 1,
- int pageSize = 0,
+ int pageSize = 10,
Direction orderDirection = Direction.Descending,
DateTime? sinceDate = null)
{
+ if (pageSize <= 0 || pageNumber <= 0)
+ {
+ return new PagedResult(0, pageNumber, pageSize);
+ }
+
long totalRecords;
var dateQuery = sinceDate.HasValue ? SqlContext.Query().Where(x => x.CreateDate >= sinceDate) : null;
var userId = Security.GetUserId().ResultOr(0);
diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs
index 3b7142397b..1097646830 100644
--- a/src/Umbraco.Web/Editors/MediaController.cs
+++ b/src/Umbraco.Web/Editors/MediaController.cs
@@ -82,7 +82,7 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(HttpStatusCode.NotFound);
}
- var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, Security.GetUserId().ResultOr(0));
+ var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, Security.GetUserId().ResultOr(Constants.Security.SuperUserId));
var mapped = Mapper.Map(emptyContent);
//remove the listview app if it exists
@@ -246,6 +246,7 @@ namespace Umbraco.Web.Editors
///
[FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")]
public PagedResult> GetChildren(int id,
+ bool ignoreUserStartNodes,
int pageNumber = 0,
int pageSize = 0,
string orderBy = "SortOrder",
@@ -255,7 +256,7 @@ namespace Umbraco.Web.Editors
{
//if a request is made for the root node data but the user's start node is not the default, then
// we need to return their start nodes
- if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false)
+ if (id == Constants.System.Root && UserStartNodes.Length > 0 && (UserStartNodes.Contains(Constants.System.Root) == false && ignoreUserStartNodes == false))
{
if (pageNumber > 0)
return new PagedResult>(0, 0, 0);
@@ -311,6 +312,7 @@ namespace Umbraco.Web.Editors
}
///
+ /// This method is obsolete, use the overload with ignoreUserStartNodes instead
/// Returns the child media objects - using the entity GUID id
///
///
@@ -321,8 +323,34 @@ namespace Umbraco.Web.Editors
///
///
///
+ [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)]
[FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")]
public PagedResult> GetChildren(Guid id,
+ int pageNumber = 0,
+ int pageSize = 0,
+ string orderBy = "SortOrder",
+ Direction orderDirection = Direction.Ascending,
+ bool orderBySystemField = true,
+ string filter = "")
+ {
+ return GetChildren(id, ignoreUserStartNodes: false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
+ }
+
+ ///
+ /// Returns the child media objects - using the entity GUID id
+ ///
+ ///
+ /// /// If set to true, user and group start node permissions will be ignored.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")]
+ public PagedResult> GetChildren(Guid id,
+ bool ignoreUserStartNodes,
int pageNumber = 0,
int pageSize = 0,
string orderBy = "SortOrder",
@@ -333,12 +361,13 @@ namespace Umbraco.Web.Editors
var entity = Services.EntityService.Get(id);
if (entity != null)
{
- return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
+ return GetChildren(entity.Id, ignoreUserStartNodes, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
///
+ /// This method is obsolete, use the overload with ignoreUserStartNodes instead
/// Returns the child media objects - using the entity UDI id
///
///
@@ -349,6 +378,7 @@ namespace Umbraco.Web.Editors
///
///
///
+ [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)]
[FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")]
public PagedResult> GetChildren(Udi id,
int pageNumber = 0,
@@ -357,6 +387,31 @@ namespace Umbraco.Web.Editors
Direction orderDirection = Direction.Ascending,
bool orderBySystemField = true,
string filter = "")
+ {
+ return GetChildren(id, ignoreUserStartNodes: false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
+ }
+
+ ///
+ /// Returns the child media objects - using the entity UDI id
+ ///
+ ///
+ /// If set to true, user and group start node permissions will be ignored.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")]
+ public PagedResult> GetChildren(Udi id,
+ bool ignoreUserStartNodes,
+ int pageNumber = 0,
+ int pageSize = 0,
+ string orderBy = "SortOrder",
+ Direction orderDirection = Direction.Ascending,
+ bool orderBySystemField = true,
+ string filter = "")
{
var guidUdi = id as GuidUdi;
if (guidUdi != null)
@@ -364,7 +419,7 @@ namespace Umbraco.Web.Editors
var entity = Services.EntityService.Get(guidUdi.Guid);
if (entity != null)
{
- return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
+ return GetChildren(entity.Id, ignoreUserStartNodes, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
}
}
@@ -392,7 +447,7 @@ namespace Umbraco.Web.Editors
//if the current item is in the recycle bin
if (foundMedia.Trashed == false)
{
- var moveResult = Services.MediaService.MoveToRecycleBin(foundMedia, (int)Security.CurrentUser.Id);
+ var moveResult = Services.MediaService.MoveToRecycleBin(foundMedia, Security.GetUserId().ResultOr(Constants.Security.SuperUserId));
if (moveResult == false)
{
//returning an object of INotificationModel will ensure that any pending
@@ -402,7 +457,7 @@ namespace Umbraco.Web.Editors
}
else
{
- var deleteResult = Services.MediaService.Delete(foundMedia, (int)Security.CurrentUser.Id);
+ var deleteResult = Services.MediaService.Delete(foundMedia, Security.GetUserId().ResultOr(Constants.Security.SuperUserId));
if (deleteResult == false)
{
//returning an object of INotificationModel will ensure that any pending
@@ -426,7 +481,7 @@ namespace Umbraco.Web.Editors
var destinationParentID = move.ParentId;
var sourceParentID = toMove.ParentId;
- var moveResult = Services.MediaService.Move(toMove, move.ParentId);
+ var moveResult = Services.MediaService.Move(toMove, move.ParentId, Security.GetUserId().ResultOr(Constants.Security.SuperUserId));
if (sourceParentID == destinationParentID)
{
@@ -502,7 +557,7 @@ namespace Umbraco.Web.Editors
}
//save the item
- var saveStatus = Services.MediaService.Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id);
+ var saveStatus = Services.MediaService.Save(contentItem.PersistedContent, Security.GetUserId().ResultOr(Constants.Security.SuperUserId));
//return the updated model
var display = Mapper.Map(contentItem.PersistedContent);
@@ -548,7 +603,7 @@ namespace Umbraco.Web.Editors
[HttpPost]
public HttpResponseMessage EmptyRecycleBin()
{
- Services.MediaService.EmptyRecycleBin();
+ Services.MediaService.EmptyRecycleBin(Security.GetUserId().ResultOr(Constants.Security.SuperUserId));
return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty"));
}
diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs
index ed9903e07a..d7f457287a 100644
--- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs
+++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs
@@ -130,15 +130,15 @@ namespace Umbraco.Web
if (mediaItem.HasProperty(propertyAlias) == false || mediaItem.HasValue(propertyAlias) == false)
return string.Empty;
+ var mediaItemUrl = mediaItem.MediaUrl(propertyAlias);
+
//get the default obj from the value converter
var cropperValue = mediaItem.Value(propertyAlias);
//is it strongly typed?
var stronglyTyped = cropperValue as ImageCropperValue;
- string mediaItemUrl;
if (stronglyTyped != null)
{
- mediaItemUrl = stronglyTyped.Src;
return GetCropUrl(
mediaItemUrl, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions,
cacheBusterValue, furtherOptions, ratioMode, upScale);
@@ -149,14 +149,12 @@ namespace Umbraco.Web
if (jobj != null)
{
stronglyTyped = jobj.ToObject();
- mediaItemUrl = stronglyTyped.Src;
return GetCropUrl(
mediaItemUrl, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions,
cacheBusterValue, furtherOptions, ratioMode, upScale);
}
//it's a single string
- mediaItemUrl = cropperValue.ToString();
return GetCropUrl(
mediaItemUrl, width, height, mediaItemUrl, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions,
cacheBusterValue, furtherOptions, ratioMode, upScale);
@@ -322,7 +320,7 @@ namespace Umbraco.Web
if (crop == null && !string.IsNullOrWhiteSpace(cropAlias))
return null;
- imageProcessorUrl.Append(cropDataSet.Src);
+ imageProcessorUrl.Append(imageUrl);
cropDataSet.AppendCropBaseUrl(imageProcessorUrl, crop, string.IsNullOrWhiteSpace(cropAlias), preferFocalPoint);
if (crop != null & useCropDimensions)
diff --git a/src/Umbraco.Web/Media/ImageHelper.cs b/src/Umbraco.Web/Media/ImageHelper.cs
index 5a5724dc7d..899f817d5a 100644
--- a/src/Umbraco.Web/Media/ImageHelper.cs
+++ b/src/Umbraco.Web/Media/ImageHelper.cs
@@ -1,17 +1,7 @@
using System;
-using System.Collections.Generic;
using System.Drawing;
using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Umbraco.Core;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.Configuration.UmbracoSettings;
-using Umbraco.Core.IO;
-using Umbraco.Core.Media;
-using Umbraco.Core.Models;
-using Umbraco.Web.Composing;
using Umbraco.Web.Media.Exif;
namespace Umbraco.Web.Media
@@ -28,9 +18,9 @@ namespace Umbraco.Web.Media
/// use potentially large amounts of memory.
public static Size GetDimensions(Stream stream)
{
+ //Try to load with exif
try
{
- //Try to load with exif
var jpgInfo = ImageFile.FromStream(stream);
if (jpgInfo != null
@@ -45,11 +35,17 @@ namespace Umbraco.Web.Media
return new Size(width, height);
}
}
+ }
+ catch
+ {
+ //We will just swallow, just means we can't read exif data, we don't want to log an error either
+ }
- //we have no choice but to try to read in via GDI
+ //we have no choice but to try to read in via GDI
+ try
+ {
using (var image = Image.FromStream(stream))
{
-
var fileWidth = image.Width;
var fileHeight = image.Height;
return new Size(fileWidth, fileHeight);
@@ -57,13 +53,10 @@ namespace Umbraco.Web.Media
}
catch (Exception)
{
- //We will just swallow, just means we can't read exif data, we don't want to log an error either
- return new Size(Constants.Conventions.Media.DefaultSize, Constants.Conventions.Media.DefaultSize);
+ //We will just swallow, just means we can't read via GDI, we don't want to log an error either
}
-
+
+ return new Size(Constants.Conventions.Media.DefaultSize, Constants.Conventions.Media.DefaultSize);
}
-
-
-
}
}
diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs
index 78c25ced33..94698f84bd 100644
--- a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs
+++ b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs
@@ -24,7 +24,7 @@ namespace Umbraco.Web.Models.Mapping
mapper.Define((source, context) => new EntityBasic(), Map);
mapper.Define((source, context) => new ContentTypeSort(), Map);
mapper.Define((source, context) => new EntityBasic(), Map);
- mapper.Define((source, context) => new SearchResultEntity(), Map);
+ mapper.Define((source, context) => new SearchResultEntity(), Map);
mapper.Define((source, context) => new SearchResultEntity(), Map);
mapper.Define>((source, context) => context.MapEnumerable(source));
mapper.Define, IEnumerable>((source, context) => context.MapEnumerable(source));
diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs
index 39933b49be..cdcfd8a0cd 100644
--- a/src/Umbraco.Web/Models/PublishedContentBase.cs
+++ b/src/Umbraco.Web/Models/PublishedContentBase.cs
@@ -81,54 +81,23 @@ namespace Umbraco.Web.Models
///
///
- /// The url of documents are computed by the document url providers. The url of medias are, at the moment,
- /// computed here from the 'umbracoFile' property -- but we should move to media url providers at some point.
+ /// The url of documents are computed by the document url providers. The url of medias are computed by the media url providers
///
public virtual string GetUrl(string culture = null) // TODO: consider .GetCulture("fr-FR").Url
{
+ var umbracoContext = UmbracoContextAccessor.UmbracoContext;
+
+ if (umbracoContext == null)
+ throw new InvalidOperationException("Cannot compute Url for a content item when UmbracoContext is null.");
+ if (umbracoContext.UrlProvider == null)
+ throw new InvalidOperationException("Cannot compute Url for a content item when UmbracoContext.UrlProvider is null.");
+
switch (ItemType)
{
case PublishedItemType.Content:
- var umbracoContext = UmbracoContextAccessor.UmbracoContext;
-
- if (umbracoContext == null)
- throw new InvalidOperationException("Cannot compute Url for a content item when UmbracoContext is null.");
- if (umbracoContext.UrlProvider == null)
- throw new InvalidOperationException("Cannot compute Url for a content item when UmbracoContext.UrlProvider is null.");
-
return umbracoContext.UrlProvider.GetUrl(this, culture);
-
case PublishedItemType.Media:
- var prop = GetProperty(Constants.Conventions.Media.File);
- if (prop?.GetValue() == null)
- {
- return string.Empty;
- }
-
- var propType = ContentType.GetPropertyType(Constants.Conventions.Media.File);
-
- // TODO: consider implementing media url providers
- // note: that one does not support variations
- //This is a hack - since we now have 2 properties that support a URL: upload and cropper, we need to detect this since we always
- // want to return the normal URL and the cropper stores data as json
- switch (propType.EditorAlias)
- {
- case Constants.PropertyEditors.Aliases.UploadField:
-
- return prop.GetValue().ToString();
- case Constants.PropertyEditors.Aliases.ImageCropper:
- //get the url from the json format
-
- var stronglyTyped = prop.GetValue() as ImageCropperValue;
- if (stronglyTyped != null)
- {
- return stronglyTyped.Src;
- }
- return prop.GetValue()?.ToString();
- }
-
- return string.Empty;
-
+ return umbracoContext.UrlProvider.GetMediaUrl(this, Constants.Conventions.Media.File, culture);
default:
throw new NotSupportedException();
}
diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs
index 7879e2b42b..5653e3fe03 100644
--- a/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs
+++ b/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs
@@ -10,5 +10,8 @@ namespace Umbraco.Web.PropertyEditors
[ConfigurationField("startNodeId", "Start node", "treepicker")] // + config in configuration editor ctor
public Udi StartNodeId { get; set; }
+
+ [ConfigurationField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]
+ public bool IgnoreUserStartNodes { get; set; }
}
}
diff --git a/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs b/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs
index e2b46b360d..136ab88204 100644
--- a/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs
+++ b/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs
@@ -15,5 +15,8 @@ namespace Umbraco.Web.PropertyEditors
// TODO: Make these strongly typed, for now this works though
[ConfigurationField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")]
public JObject Rte { get; set; }
+
+ [ConfigurationField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.
Note: this applies to all editors in this grid editor except for the rich text editor, which has it's own option for that.")]
+ public bool IgnoreUserStartNodes { get; set; }
}
}
diff --git a/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs b/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs
index bcddc18369..4f854a7556 100644
--- a/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs
+++ b/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs
@@ -63,6 +63,9 @@ namespace Umbraco.Web.PropertyEditors
[ConfigurationField("tabName", "Content app name", "textstring", Description = "The name of the listview content app (default if empty: 'Child Items')")]
public string TabName { get; set; }
+ [ConfigurationField("showContentFirst", "Show Content App First", "boolean", Description = "Enable this to show the content app by default instead of the list view app")]
+ public bool ShowContentFirst { get; set; }
+
public class Property
{
[JsonProperty("alias")]
diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs
index 4844e2f822..fa430e103b 100644
--- a/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs
+++ b/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs
@@ -19,5 +19,8 @@ namespace Umbraco.Web.PropertyEditors
[ConfigurationField("startNodeId", "Start node", "mediapicker")]
public Udi StartNodeId { get; set; }
+
+ [ConfigurationField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]
+ public bool IgnoreUserStartNodes { get; set; }
}
}
diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs
index b6333c3140..a0a2467b1c 100644
--- a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs
+++ b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs
@@ -22,5 +22,8 @@ namespace Umbraco.Web.PropertyEditors
[ConfigurationField("showOpenButton", "Show open button (this feature is in preview!)", "boolean", Description = "Opens the node in a dialog")]
public bool ShowOpen { get; set; }
+
+ [ConfigurationField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]
+ public bool IgnoreUserStartNodes { get; set; }
}
}
diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs
index 515512eff8..ec9439ceea 100644
--- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs
+++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs
@@ -9,5 +9,8 @@ namespace Umbraco.Web.PropertyEditors
[ConfigurationField("maxNumber", "Maximum number of items", "number")]
public int MaxNumber { get; set; }
+
+ [ConfigurationField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]
+ public bool IgnoreUserStartNodes { get; set; }
}
}
diff --git a/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs b/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs
index 13bf269bcd..d99c2b17e0 100644
--- a/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs
+++ b/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs
@@ -14,5 +14,8 @@ namespace Umbraco.Web.PropertyEditors
[ConfigurationField("hideLabel", "Hide Label", "boolean")]
public bool HideLabel { get; set; }
+
+ [ConfigurationField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]
+ public bool IgnoreUserStartNodes { get; set; }
}
}
diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs
index 93e4afc7e5..2181c4fd49 100644
--- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs
+++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs
@@ -35,9 +35,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
public override Type GetPropertyValueType(PublishedPropertyType propertyType)
{
var contentTypes = propertyType.DataType.ConfigurationAs().ContentTypes;
- return contentTypes.Length > 1
- ? typeof (IEnumerable)
- : typeof (IEnumerable<>).MakeGenericType(ModelType.For(contentTypes[0].Alias));
+ return contentTypes.Length == 1
+ ? typeof (IEnumerable<>).MakeGenericType(ModelType.For(contentTypes[0].Alias))
+ : typeof (IEnumerable);
}
///
@@ -57,9 +57,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
{
var configuration = propertyType.DataType.ConfigurationAs();
var contentTypes = configuration.ContentTypes;
- var elements = contentTypes.Length > 1
- ? new List()
- : PublishedModelFactory.CreateModelList(contentTypes[0].Alias);
+ var elements = contentTypes.Length == 1
+ ? PublishedModelFactory.CreateModelList(contentTypes[0].Alias)
+ : new List();
var value = (string)inter;
if (string.IsNullOrWhiteSpace(value)) return elements;
diff --git a/src/Umbraco.Web/PublishedCache/IPublishedCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedCache.cs
index ff459a2d9b..3cd7b924fb 100644
--- a/src/Umbraco.Web/PublishedCache/IPublishedCache.cs
+++ b/src/Umbraco.Web/PublishedCache/IPublishedCache.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Xml.XPath;
+using Umbraco.Core;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Xml;
@@ -29,6 +30,15 @@ namespace Umbraco.Web.PublishedCache
/// The value of overrides defaults.
IPublishedContent GetById(bool preview, Guid contentId);
+ ///
+ /// Gets a content identified by its Udi identifier.
+ ///
+ /// A value indicating whether to consider unpublished content.
+ /// The content Udi identifier.
+ /// The content, or null.
+ /// The value of overrides defaults.
+ IPublishedContent GetById(bool preview, Udi contentId);
+
///
/// Gets a content identified by its unique identifier.
///
@@ -45,6 +55,14 @@ namespace Umbraco.Web.PublishedCache
/// Considers published or unpublished content depending on defaults.
IPublishedContent GetById(Guid contentId);
+ ///
+ /// Gets a content identified by its unique identifier.
+ ///
+ /// The content unique identifier.
+ /// The content, or null.
+ /// Considers published or unpublished content depending on defaults.
+ IPublishedContent GetById(Udi contentId);
+
///
/// Gets a value indicating whether the cache contains a specified content.
///
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs
index 0e74ea919f..4bd3fcf247 100644
--- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs
@@ -238,6 +238,18 @@ namespace Umbraco.Web.PublishedCache.NuCache
return GetNodePublishedContent(node, preview);
}
+ public override IPublishedContent GetById(bool preview, Udi contentId)
+ {
+ var guidUdi = contentId as GuidUdi;
+ if (guidUdi == null)
+ throw new ArgumentException($"Udi must be of type {typeof(GuidUdi).Name}.", nameof(contentId));
+
+ if (guidUdi.EntityType != Constants.UdiEntityType.Document)
+ throw new ArgumentException($"Udi entity type must be \"{Constants.UdiEntityType.Document}\".", nameof(contentId));
+
+ return GetById(preview, guidUdi.Guid);
+ }
+
public override bool HasById(bool preview, int contentId)
{
var n = _snapshot.Get(contentId);
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs
index f7bdb4400f..112ccd9931 100644
--- a/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.XPath;
+using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Xml;
@@ -44,6 +45,20 @@ namespace Umbraco.Web.PublishedCache.NuCache
return n?.PublishedModel;
}
+ public override IPublishedContent GetById(bool preview, Udi contentId)
+ {
+ var guidUdi = contentId as GuidUdi;
+ if (guidUdi == null)
+ throw new ArgumentException($"Udi must be of type {typeof(GuidUdi).Name}.", nameof(contentId));
+
+ if (guidUdi.EntityType != Constants.UdiEntityType.Media)
+ throw new ArgumentException($"Udi entity type must be \"{Constants.UdiEntityType.Media}\".", nameof(contentId));
+
+ // ignore preview, there's only draft for media
+ var n = _snapshot.Get(guidUdi.Guid);
+ return n?.PublishedModel;
+ }
+
public override bool HasById(bool preview, int contentId)
{
var n = _snapshot.Get(contentId);
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs
index bf16074040..073428336d 100755
--- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs
@@ -1381,19 +1381,24 @@ WHERE cmsContentNu.nodeId IN (
long total;
do
{
+ // the tree is locked, counting and comparing to total is safe
var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path"));
var items = new List();
+ var count = 0;
foreach (var c in descendants)
{
// always the edited version
items.Add(GetDto(c, false));
+
// and also the published version if it makes any sense
if (c.Published)
items.Add(GetDto(c, true));
+
+ count++;
}
db.BulkInsertRecords(items);
- processed += items.Count;
+ processed += count;
} while (processed < total);
}
@@ -1448,10 +1453,11 @@ WHERE cmsContentNu.nodeId IN (
long total;
do
{
+ // the tree is locked, counting and comparing to total is safe
var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path"));
- var items = descendants.Select(m => GetDto(m, false)).ToArray();
+ var items = descendants.Select(m => GetDto(m, false)).ToList();
db.BulkInsertRecords(items);
- processed += items.Length;
+ processed += items.Count;
} while (processed < total);
}
diff --git a/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs b/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs
index b0fe1a4240..b88ae26704 100644
--- a/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs
+++ b/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.XPath;
+using Umbraco.Core;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Xml;
@@ -19,23 +20,22 @@ namespace Umbraco.Web.PublishedCache
public abstract IPublishedContent GetById(bool preview, int contentId);
public IPublishedContent GetById(int contentId)
- {
- return GetById(PreviewDefault, contentId);
- }
+ => GetById(PreviewDefault, contentId);
public abstract IPublishedContent GetById(bool preview, Guid contentId);
public IPublishedContent GetById(Guid contentId)
- {
- return GetById(PreviewDefault, contentId);
- }
+ => GetById(PreviewDefault, contentId);
+
+ public abstract IPublishedContent GetById(bool preview, Udi contentId);
+
+ public IPublishedContent GetById(Udi contentId)
+ => GetById(PreviewDefault, contentId);
public abstract bool HasById(bool preview, int contentId);
public bool HasById(int contentId)
- {
- return HasById(PreviewDefault, contentId);
- }
+ => HasById(PreviewDefault, contentId);
public abstract IEnumerable GetAtRoot(bool preview);
diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs
index 54afb7abbd..6ac21e047b 100644
--- a/src/Umbraco.Web/PublishedContentExtensions.cs
+++ b/src/Umbraco.Web/PublishedContentExtensions.cs
@@ -42,24 +42,69 @@ namespace Umbraco.Web
///
/// The content.
/// The absolute url for the content.
- public static string UrlAbsolute(this IPublishedContent content)
+ public static string UrlAbsolute(this IPublishedContent content) => content.UrlAbsolute(null);
+
+ ///
+ /// Gets the absolute url for the content.
+ ///
+ /// The content.
+ /// The culture to get the url for
+ /// The absolute url for the content.
+ public static string UrlAbsolute(this IPublishedContent content, string culture)
{
// adapted from PublishedContentBase.Url
+
+ if (Current.UmbracoContext == null)
+ throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext is null.");
+ if (Current.UmbracoContext.UrlProvider == null)
+ throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext.UrlProvider is null.");
+
switch (content.ItemType)
{
case PublishedItemType.Content:
- if (Current.UmbracoContext == null)
- throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext is null.");
- if (Current.UmbracoContext.UrlProvider == null)
- throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext.UrlProvider is null.");
- return Current.UmbracoContext.UrlProvider.GetUrl(content.Id, true);
+ return Current.UmbracoContext.UrlProvider.GetUrl(content.Id, true, culture);
case PublishedItemType.Media:
- throw new NotSupportedException("AbsoluteUrl is not supported for media types.");
+ return Current.UmbracoContext.UrlProvider.GetMediaUrl(content, Constants.Conventions.Media.File, true);
default:
throw new ArgumentOutOfRangeException();
}
}
+ ///
+ /// Gets the url for the media.
+ ///
+ /// The content.
+ /// The property alias to resolve the url from.
+ /// The variation language.
+ /// The url for the content.
+ /// Better use the GetMediaUrl method but that method is here to complement MediaUrlAbsolute().
+ public static string MediaUrl(this IPublishedContent content, string propertyAlias, string culture = null)
+ {
+ if (Current.UmbracoContext == null)
+ throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext is null.");
+ if (Current.UmbracoContext.UrlProvider == null)
+ throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext.UrlProvider is null.");
+
+ return Current.UmbracoContext.UrlProvider.GetMediaUrl(content, propertyAlias, culture);
+ }
+
+ ///
+ /// Gets the absolute url for the media.
+ ///
+ /// The content.
+ /// The property alias to resolve the url from.
+ /// The variation language.
+ /// The absolute url for the media.
+ public static string MediaUrlAbsolute(this IPublishedContent content, string propertyAlias, string culture = null)
+ {
+ if (Current.UmbracoContext == null)
+ throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext is null.");
+ if (Current.UmbracoContext.UrlProvider == null)
+ throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext.UrlProvider is null.");
+
+ return Current.UmbracoContext.UrlProvider.GetMediaUrl(content, propertyAlias, true, culture);
+ }
+
///
/// Gets the Url segment.
///
diff --git a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs
new file mode 100644
index 0000000000..a9c961c280
--- /dev/null
+++ b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs
@@ -0,0 +1,69 @@
+using System;
+using Umbraco.Core;
+using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Core.PropertyEditors.ValueConverters;
+
+namespace Umbraco.Web.Routing
+{
+ ///
+ /// Default media url provider.
+ ///
+ public class DefaultMediaUrlProvider : IMediaUrlProvider
+ {
+ ///
+ public virtual UrlInfo GetMediaUrl(UmbracoContext umbracoContext, IPublishedContent content,
+ string propertyAlias,
+ UrlProviderMode mode, string culture, Uri current)
+ {
+ var prop = content.GetProperty(propertyAlias);
+ var value = prop?.GetValue(culture);
+ if (value == null)
+ {
+ return null;
+ }
+
+ var propType = prop.PropertyType;
+ string path = null;
+
+ switch (propType.EditorAlias)
+ {
+ case Constants.PropertyEditors.Aliases.UploadField:
+ path = value.ToString();
+ break;
+ case Constants.PropertyEditors.Aliases.ImageCropper:
+ //get the url from the json format
+ path = value is ImageCropperValue stronglyTyped ? stronglyTyped.Src : value.ToString();
+ break;
+ }
+
+ var url = AssembleUrl(path, current, mode);
+ return url == null ? null : UrlInfo.Url(url.ToString(), culture);
+ }
+
+ private Uri AssembleUrl(string path, Uri current, UrlProviderMode mode)
+ {
+ if (string.IsNullOrEmpty(path))
+ return null;
+
+ Uri uri;
+
+ if (current == null)
+ mode = UrlProviderMode.Relative; // best we can do
+
+ switch (mode)
+ {
+ case UrlProviderMode.Absolute:
+ uri = new Uri(current?.GetLeftPart(UriPartial.Authority) + path);
+ break;
+ case UrlProviderMode.Relative:
+ case UrlProviderMode.Auto:
+ uri = new Uri(path, UriKind.Relative);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(mode));
+ }
+
+ return UriUtility.MediaUriFromUmbraco(uri);
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Routing/IMediaUrlProvider.cs b/src/Umbraco.Web/Routing/IMediaUrlProvider.cs
new file mode 100644
index 0000000000..419e4d78df
--- /dev/null
+++ b/src/Umbraco.Web/Routing/IMediaUrlProvider.cs
@@ -0,0 +1,31 @@
+using System;
+using Umbraco.Core.Models.PublishedContent;
+
+namespace Umbraco.Web.Routing
+{
+ ///
+ /// Provides media urls.
+ ///
+ public interface IMediaUrlProvider
+ {
+ ///
+ /// Gets the url of a media item.
+ ///
+ /// The Umbraco context.
+ /// The published content.
+ /// The property alias to resolve the url from.
+ /// The url mode.
+ /// The variation language.
+ /// The current absolute url.
+ /// The url for the media.
+ ///
+ /// The url is absolute or relative depending on mode and on current.
+ /// If the media is multi-lingual, gets the url for the specified culture or,
+ /// when no culture is specified, the current culture.
+ /// The url provider can ignore the mode and always return an absolute url,
+ /// e.g. a cdn url provider will most likely always return an absolute url.
+ /// If the provider is unable to provide a url, it returns null.
+ ///
+ UrlInfo GetMediaUrl(UmbracoContext umbracoContext, IPublishedContent content, string propertyAlias, UrlProviderMode mode, string culture, Uri current);
+ }
+}
diff --git a/src/Umbraco.Web/Routing/IUrlProvider.cs b/src/Umbraco.Web/Routing/IUrlProvider.cs
index 55d39880d6..9459c2552d 100644
--- a/src/Umbraco.Web/Routing/IUrlProvider.cs
+++ b/src/Umbraco.Web/Routing/IUrlProvider.cs
@@ -10,7 +10,7 @@ namespace Umbraco.Web.Routing
public interface IUrlProvider
{
///
- /// Gets the nice url of a published content.
+ /// Gets the url of a published content.
///
/// The Umbraco context.
/// The published content.
diff --git a/src/Umbraco.Web/Routing/MediaUrlProviderCollection.cs b/src/Umbraco.Web/Routing/MediaUrlProviderCollection.cs
new file mode 100644
index 0000000000..eef0cbd3bc
--- /dev/null
+++ b/src/Umbraco.Web/Routing/MediaUrlProviderCollection.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Web.Routing
+{
+ public class MediaUrlProviderCollection : BuilderCollectionBase
+ {
+ public MediaUrlProviderCollection(IEnumerable items)
+ : base(items)
+ { }
+ }
+}
diff --git a/src/Umbraco.Web/Routing/MediaUrlProviderCollectionBuilder.cs b/src/Umbraco.Web/Routing/MediaUrlProviderCollectionBuilder.cs
new file mode 100644
index 0000000000..7bfc56ed0d
--- /dev/null
+++ b/src/Umbraco.Web/Routing/MediaUrlProviderCollectionBuilder.cs
@@ -0,0 +1,9 @@
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Web.Routing
+{
+ public class MediaUrlProviderCollectionBuilder : OrderedCollectionBuilderBase
+ {
+ protected override MediaUrlProviderCollectionBuilder This => this;
+ }
+}
diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs
index 0662d46f49..525695c274 100644
--- a/src/Umbraco.Web/Routing/UrlProvider.cs
+++ b/src/Umbraco.Web/Routing/UrlProvider.cs
@@ -7,6 +7,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
+using Umbraco.Web.Models;
namespace Umbraco.Web.Routing
{
@@ -23,13 +24,15 @@ namespace Umbraco.Web.Routing
/// The Umbraco context.
/// Routing settings.
/// The list of url providers.
+ /// The list of media url providers.
/// The current variation accessor.
- public UrlProvider(UmbracoContext umbracoContext, IWebRoutingSection routingSettings, IEnumerable urlProviders, IVariationContextAccessor variationContextAccessor)
+ public UrlProvider(UmbracoContext umbracoContext, IWebRoutingSection routingSettings, IEnumerable urlProviders, IEnumerable mediaUrlProviders, IVariationContextAccessor variationContextAccessor)
{
if (routingSettings == null) throw new ArgumentNullException(nameof(routingSettings));
_umbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext));
_urlProviders = urlProviders;
+ _mediaUrlProviders = mediaUrlProviders;
_variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
var provider = UrlProviderMode.Auto;
Mode = provider;
@@ -45,12 +48,14 @@ namespace Umbraco.Web.Routing
///
/// The Umbraco context.
/// The list of url providers.
+ /// The list of media url providers
/// The current variation accessor.
/// An optional provider mode.
- public UrlProvider(UmbracoContext umbracoContext, IEnumerable urlProviders, IVariationContextAccessor variationContextAccessor, UrlProviderMode mode = UrlProviderMode.Auto)
+ public UrlProvider(UmbracoContext umbracoContext, IEnumerable urlProviders, IEnumerable mediaUrlProviders, IVariationContextAccessor variationContextAccessor, UrlProviderMode mode = UrlProviderMode.Auto)
{
_umbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext));
_urlProviders = urlProviders;
+ _mediaUrlProviders = mediaUrlProviders;
_variationContextAccessor = variationContextAccessor;
Mode = mode;
@@ -58,6 +63,7 @@ namespace Umbraco.Web.Routing
private readonly UmbracoContext _umbracoContext;
private readonly IEnumerable _urlProviders;
+ private readonly IEnumerable _mediaUrlProviders;
private readonly IVariationContextAccessor _variationContextAccessor;
///
@@ -84,7 +90,7 @@ namespace Umbraco.Web.Routing
=> GetUrl(content, Mode, culture, current);
///
- /// Gets the nice url of a published content.
+ /// Gets the url of a published content.
///
/// The published content.
/// A value indicating whether the url should be absolute in any case.
@@ -109,7 +115,7 @@ namespace Umbraco.Web.Routing
=> GetUrl(GetDocument(id), Mode, culture, current);
///
- /// Gets the nice url of a published content.
+ /// Gets the url of a published content.
///
/// The published content identifier.
/// A value indicating whether the url should be absolute in any case.
@@ -124,7 +130,7 @@ namespace Umbraco.Web.Routing
=> GetUrl(GetDocument(id), GetMode(absolute), culture, current);
///
- /// Gets the nice url of a published content.
+ /// Gets the url of a published content.
///
/// The published content identifier.
/// The url mode.
@@ -145,7 +151,7 @@ namespace Umbraco.Web.Routing
=> GetUrl(GetDocument(id), Mode, culture, current);
///
- /// Gets the nice url of a published content.
+ /// Gets the url of a published content.
///
/// The published content identifier.
/// A value indicating whether the url should be absolute in any case.
@@ -160,7 +166,7 @@ namespace Umbraco.Web.Routing
=> GetUrl(GetDocument(id), GetMode(absolute), culture, current);
///
- /// Gets the nice url of a published content.
+ /// Gets the url of a published content.
///
/// The published content identifier.
/// The url mode.
@@ -171,7 +177,7 @@ namespace Umbraco.Web.Routing
=> GetUrl(GetDocument(id), mode, culture, current);
///
- /// Gets the nice url of a published content.
+ /// Gets the url of a published content.
///
/// The published content.
/// The url mode.
@@ -250,5 +256,86 @@ namespace Umbraco.Web.Routing
}
#endregion
+
+ #region GetMediaUrl
+
+ ///
+ /// Gets the url of a media item.
+ ///
+ /// The published content.
+ /// The property alias to resolve the url from.
+ /// The variation language.
+ /// The current absolute url.
+ /// The url for the media.
+ ///
+ /// The url is absolute or relative depending on mode and on current.
+ /// If the media is multi-lingual, gets the url for the specified culture or,
+ /// when no culture is specified, the current culture.
+ /// If the provider is unable to provide a url, it returns .
+ ///
+ public string GetMediaUrl(IPublishedContent content, string propertyAlias, string culture = null, Uri current = null)
+ => GetMediaUrl(content, propertyAlias, Mode, culture, current);
+
+ ///
+ /// Gets the url of a media item.
+ ///
+ /// The published content.
+ /// The property alias to resolve the url from.
+ /// A value indicating whether the url should be absolute in any case.
+ /// The variation language.
+ /// The current absolute url.
+ /// The url for the media.
+ ///
+ /// The url is absolute or relative depending on mode and on current.
+ /// If the media is multi-lingual, gets the url for the specified culture or,
+ /// when no culture is specified, the current culture.
+ /// If the provider is unable to provide a url, it returns .
+ ///
+ public string GetMediaUrl(IPublishedContent content, string propertyAlias, bool absolute, string culture = null, Uri current = null)
+ => GetMediaUrl(content, propertyAlias, GetMode(absolute), culture, current);
+ ///
+ /// Gets the url of a media item.
+ ///
+ /// The published content.
+ /// The property alias to resolve the url from.
+ /// The url mode.
+ /// The variation language.
+ /// The current absolute url.
+ /// The url for the media.
+ ///
+ /// The url is absolute or relative depending on mode and on current.
+ /// If the media is multi-lingual, gets the url for the specified culture or,
+ /// when no culture is specified, the current culture.
+ /// If the provider is unable to provide a url, it returns .
+ ///
+ public string GetMediaUrl(IPublishedContent content,
+ string propertyAlias, UrlProviderMode mode,
+ string culture = null, Uri current = null)
+ {
+ if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias));
+
+ if (content == null)
+ return "";
+
+ // this the ONLY place where we deal with default culture - IMediaUrlProvider always receive a culture
+ // be nice with tests, assume things can be null, ultimately fall back to invariant
+ // (but only for variant content of course)
+ if (content.ContentType.VariesByCulture())
+ {
+ if (culture == null)
+ culture = _variationContextAccessor?.VariationContext?.Culture ?? "";
+ }
+
+ if (current == null)
+ current = _umbracoContext.CleanedUmbracoUrl;
+
+ var url = _mediaUrlProviders.Select(provider =>
+ provider.GetMediaUrl(_umbracoContext, content, propertyAlias, mode, culture, current))
+ .FirstOrDefault(u => u != null);
+
+ return url?.Text ?? "";
+ }
+
+ #endregion
}
}
diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs
index f1c8fcc12f..5a464701e0 100644
--- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs
+++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs
@@ -183,6 +183,9 @@ namespace Umbraco.Web.Runtime
.Append()
.Append();
+ composition.WithCollectionBuilder()
+ .Append();
+
composition.RegisterUnique();
composition.WithCollectionBuilder()
diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs
index 43db9ff0ba..7fb51a61eb 100644
--- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs
+++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs
@@ -39,6 +39,7 @@ namespace Umbraco.Web.Search
}
///
+ /// This method is obsolete, use the overload with ignoreUserStartNodes instead
/// Searches for results based on the entity type
///
///
@@ -50,11 +51,39 @@ namespace Umbraco.Web.Search
///
///
///
+ [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)]
public IEnumerable ExamineSearch(
string query,
UmbracoEntityTypes entityType,
int pageSize,
- long pageIndex, out long totalFound, string searchFrom = null)
+ long pageIndex,
+ out long totalFound,
+ string searchFrom = null)
+ {
+ return ExamineSearch(query, entityType, pageSize, pageIndex, out totalFound, ignoreUserStartNodes: false, searchFrom);
+ }
+
+ ///
+ /// Searches for results based on the entity type
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// A starting point for the search, generally a node id, but for members this is a member type alias
+ ///
+ ///
+ ///
+ /// If set to true, user and group start node permissions will be ignored.
+ ///
+ public IEnumerable ExamineSearch(
+ string query,
+ UmbracoEntityTypes entityType,
+ int pageSize,
+ long pageIndex,
+ out long totalFound,
+ bool ignoreUserStartNodes,
+ string searchFrom = null)
{
var sb = new StringBuilder();
@@ -85,12 +114,12 @@ namespace Umbraco.Web.Search
case UmbracoEntityTypes.Media:
type = "media";
var allMediaStartNodes = _umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService);
- AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, _entityService);
+ AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService);
break;
case UmbracoEntityTypes.Document:
type = "content";
var allContentStartNodes = _umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(_entityService);
- AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, _entityService);
+ AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService);
break;
default:
throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType);
@@ -288,7 +317,7 @@ namespace Umbraco.Web.Search
}
}
- private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, IEntityService entityService)
+ private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, bool ignoreUserStartNodes, IEntityService entityService)
{
if (sb == null) throw new ArgumentNullException(nameof(sb));
if (entityService == null) throw new ArgumentNullException(nameof(entityService));
@@ -311,7 +340,7 @@ namespace Umbraco.Web.Search
// make sure we don't find anything
sb.Append("+__Path:none ");
}
- else if (startNodeIds.Contains(-1) == false) // -1 = no restriction
+ else if (startNodeIds.Contains(-1) == false && ignoreUserStartNodes == false) // -1 = no restriction
{
var entityPaths = entityService.GetAllPaths(objectType, startNodeIds);
diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
index 0c02dd6e46..d01e9fffb4 100644
--- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
+++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
@@ -69,7 +69,7 @@ namespace Umbraco.Web.Trees
{
var node = base.CreateRootNode(queryStrings);
- if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false)
+ if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false && IgnoreUserStartNodes(queryStrings) == false)
{
node.AdditionalData["noAccess"] = true;
}
@@ -90,11 +90,11 @@ namespace Umbraco.Web.Trees
internal TreeNode GetSingleTreeNodeWithAccessCheck(IEntitySlim e, string parentId, FormDataCollection queryStrings)
{
var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out var hasPathAccess);
- if (entityIsAncestorOfStartNodes == false)
+ if (IgnoreUserStartNodes(queryStrings) == false && entityIsAncestorOfStartNodes == false)
return null;
var treeNode = GetSingleTreeNode(e, parentId, queryStrings);
- if (hasPathAccess == false)
+ if (IgnoreUserStartNodes(queryStrings) == false && hasPathAccess == false)
{
treeNode.AdditionalData["noAccess"] = true;
}
@@ -134,7 +134,7 @@ namespace Umbraco.Web.Trees
// ensure that the user has access to that node, otherwise return the empty tree nodes collection
// TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access
- if (HasPathAccess(id, queryStrings) == false)
+ if (IgnoreUserStartNodes(queryStrings) == false && HasPathAccess(id, queryStrings) == false)
{
Logger.Warn("User {Username} does not have access to node with id {Id}", Security.CurrentUser.Username, id);
return nodes;
@@ -255,8 +255,9 @@ namespace Umbraco.Web.Trees
///
protected sealed override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
{
+ var ignoreUserStartNodes = queryStrings.GetValue(TreeQueryStringParameters.IgnoreUserStartNodes);
//check if we're rendering the root
- if (id == Constants.System.RootString && UserStartNodes.Contains(Constants.System.Root))
+ if (id == Constants.System.RootString && UserStartNodes.Contains(Constants.System.Root) || ignoreUserStartNodes)
{
var altStartId = string.Empty;
diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs
index 4acf807b77..2e409c2820 100644
--- a/src/Umbraco.Web/Trees/TreeControllerBase.cs
+++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs
@@ -369,6 +369,16 @@ namespace Umbraco.Web.Trees
return queryStrings.GetValue(TreeQueryStringParameters.Use) == "dialog";
}
+ ///
+ /// If the request should allows a user to choose nodes that they normally don't have access to
+ ///
+ ///
+ ///
+ protected bool IgnoreUserStartNodes(FormDataCollection queryStrings)
+ {
+ return queryStrings.GetValue(TreeQueryStringParameters.IgnoreUserStartNodes);
+ }
+
///
/// An event that allows developers to modify the tree node collection that is being rendered
///
diff --git a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs
index 466aff5a1f..0fcf5321e4 100644
--- a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs
+++ b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs
@@ -8,6 +8,7 @@
public const string Use = "use";
public const string Application = "application";
public const string StartNodeId = "startNodeId";
+ public const string IgnoreUserStartNodes = "ignoreUserStartNodes";
//public const string OnNodeClick = "OnNodeClick";
//public const string RenderParent = "RenderParent";
}
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index b3cab3b2cb..e565f354c8 100755
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -214,7 +214,11 @@
+
+
+
+
diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs
index 8ab6f8c946..9ae9a5cb16 100644
--- a/src/Umbraco.Web/UmbracoContext.cs
+++ b/src/Umbraco.Web/UmbracoContext.cs
@@ -33,6 +33,7 @@ namespace Umbraco.Web
WebSecurity webSecurity,
IUmbracoSettingsSection umbracoSettings,
IEnumerable urlProviders,
+ IEnumerable mediaUrlProviders,
IGlobalSettings globalSettings,
IVariationContextAccessor variationContextAccessor)
{
@@ -41,6 +42,7 @@ namespace Umbraco.Web
if (webSecurity == null) throw new ArgumentNullException(nameof(webSecurity));
if (umbracoSettings == null) throw new ArgumentNullException(nameof(umbracoSettings));
if (urlProviders == null) throw new ArgumentNullException(nameof(urlProviders));
+ if (mediaUrlProviders == null) throw new ArgumentNullException(nameof(mediaUrlProviders));
VariationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
_globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings));
@@ -71,7 +73,7 @@ namespace Umbraco.Web
//
OriginalRequestUrl = GetRequestFromContext()?.Url ?? new Uri("http://localhost");
CleanedUmbracoUrl = UriUtility.UriToUmbraco(OriginalRequestUrl);
- UrlProvider = new UrlProvider(this, umbracoSettings.WebRouting, urlProviders, variationContextAccessor);
+ UrlProvider = new UrlProvider(this, umbracoSettings.WebRouting, urlProviders, mediaUrlProviders, variationContextAccessor);
}
///
diff --git a/src/Umbraco.Web/UmbracoContextFactory.cs b/src/Umbraco.Web/UmbracoContextFactory.cs
index 9bb2a79411..2a812036bf 100644
--- a/src/Umbraco.Web/UmbracoContextFactory.cs
+++ b/src/Umbraco.Web/UmbracoContextFactory.cs
@@ -29,12 +29,13 @@ namespace Umbraco.Web
private readonly IUmbracoSettingsSection _umbracoSettings;
private readonly IGlobalSettings _globalSettings;
private readonly UrlProviderCollection _urlProviders;
+ private readonly MediaUrlProviderCollection _mediaUrlProviders;
private readonly IUserService _userService;
///
/// Initializes a new instance of the class.
///
- public UmbracoContextFactory(IUmbracoContextAccessor umbracoContextAccessor, IPublishedSnapshotService publishedSnapshotService, IVariationContextAccessor variationContextAccessor, IDefaultCultureAccessor defaultCultureAccessor, IUmbracoSettingsSection umbracoSettings, IGlobalSettings globalSettings, UrlProviderCollection urlProviders, IUserService userService)
+ public UmbracoContextFactory(IUmbracoContextAccessor umbracoContextAccessor, IPublishedSnapshotService publishedSnapshotService, IVariationContextAccessor variationContextAccessor, IDefaultCultureAccessor defaultCultureAccessor, IUmbracoSettingsSection umbracoSettings, IGlobalSettings globalSettings, UrlProviderCollection urlProviders, MediaUrlProviderCollection mediaUrlProviders, IUserService userService)
{
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
_publishedSnapshotService = publishedSnapshotService ?? throw new ArgumentNullException(nameof(publishedSnapshotService));
@@ -44,6 +45,7 @@ namespace Umbraco.Web
_umbracoSettings = umbracoSettings ?? throw new ArgumentNullException(nameof(umbracoSettings));
_globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings));
_urlProviders = urlProviders ?? throw new ArgumentNullException(nameof(urlProviders));
+ _mediaUrlProviders = mediaUrlProviders ?? throw new ArgumentNullException(nameof(mediaUrlProviders));
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
}
@@ -55,7 +57,7 @@ namespace Umbraco.Web
var webSecurity = new WebSecurity(httpContext, _userService, _globalSettings);
- return new UmbracoContext(httpContext, _publishedSnapshotService, webSecurity, _umbracoSettings, _urlProviders, _globalSettings, _variationContextAccessor);
+ return new UmbracoContext(httpContext, _publishedSnapshotService, webSecurity, _umbracoSettings, _urlProviders, _mediaUrlProviders, _globalSettings, _variationContextAccessor);
}
///
diff --git a/src/Umbraco.Web/UriUtility.cs b/src/Umbraco.Web/UriUtility.cs
index 04357a3a5a..9e94a4a20a 100644
--- a/src/Umbraco.Web/UriUtility.cs
+++ b/src/Umbraco.Web/UriUtility.cs
@@ -72,6 +72,15 @@ namespace Umbraco.Web
return uri.Rewrite(path);
}
+ // maps a media umbraco uri to a public uri
+ // ie with virtual directory - that is all for media
+ public static Uri MediaUriFromUmbraco(Uri uri)
+ {
+ var path = uri.GetSafeAbsolutePath();
+ path = ToAbsolute(path);
+ return uri.Rewrite(path);
+ }
+
// maps a public uri to an internal umbraco uri
// ie no virtual directory, no .aspx, lowercase...
public static Uri UriToUmbraco(Uri uri)
diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs
index efee045890..0c53838592 100644
--- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs
+++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs
@@ -11,6 +11,7 @@ using Umbraco.Core.Models;
using Umbraco.Web.Actions;
using Umbraco.Core.Security;
using System.Net;
+using System.Web;
namespace Umbraco.Web.WebApi.Filters
{
@@ -66,7 +67,7 @@ namespace Umbraco.Web.WebApi.Filters
//not logged in
throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized);
}
-
+
int nodeId;
if (_nodeId.HasValue == false)
{
@@ -116,24 +117,29 @@ namespace Umbraco.Web.WebApi.Filters
nodeId = _nodeId.Value;
}
- var permissionResult = ContentPermissionsHelper.CheckPermissions(nodeId,
- Current.UmbracoContext.Security.CurrentUser,
- Current.Services.UserService,
- Current.Services.ContentService,
- Current.Services.EntityService,
- out var contentItem,
- _permissionToCheck.HasValue ? new[] { _permissionToCheck.Value } : null);
-
- if (permissionResult == ContentPermissionsHelper.ContentAccess.NotFound)
- throw new HttpResponseException(HttpStatusCode.NotFound);
-
- if (permissionResult == ContentPermissionsHelper.ContentAccess.Denied)
- throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse());
-
- if (contentItem != null)
+ var queryStringCollection = HttpUtility.ParseQueryString(actionContext.Request.RequestUri.Query);
+ bool.TryParse(queryStringCollection["ignoreUserStartNodes"], out var ignoreUserStartNodes);
+ if (ignoreUserStartNodes == false)
{
- //store the content item in request cache so it can be resolved in the controller without re-looking it up
- actionContext.Request.Properties[typeof(IContent).ToString()] = contentItem;
+ var permissionResult = ContentPermissionsHelper.CheckPermissions(nodeId,
+ Current.UmbracoContext.Security.CurrentUser,
+ Current.Services.UserService,
+ Current.Services.ContentService,
+ Current.Services.EntityService,
+ out var contentItem,
+ _permissionToCheck.HasValue ? new[] {_permissionToCheck.Value} : null);
+
+ if (permissionResult == ContentPermissionsHelper.ContentAccess.NotFound)
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+
+ if (permissionResult == ContentPermissionsHelper.ContentAccess.Denied)
+ throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse());
+
+ if (contentItem != null)
+ {
+ //store the content item in request cache so it can be resolved in the controller without re-looking it up
+ actionContext.Request.Properties[typeof(IContent).ToString()] = contentItem;
+ }
}
base.OnActionExecuting(actionContext);
diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs
index 21dc60e6cc..f8b02f08ca 100644
--- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs
+++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs
@@ -3,12 +3,14 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
+using System.Web;
using System.Web.Http.Filters;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Composing;
using Umbraco.Core.Security;
+using Umbraco.Web.Trees;
namespace Umbraco.Web.WebApi.Filters
{
@@ -72,7 +74,12 @@ namespace Umbraco.Web.WebApi.Filters
protected virtual void FilterItems(IUser user, IList items)
{
- FilterBasedOnStartNode(items, user);
+ bool.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.IgnoreUserStartNodes), out var ignoreUserStartNodes);
+
+ if (ignoreUserStartNodes == false)
+ {
+ FilterBasedOnStartNode(items, user);
+ }
}
internal void FilterBasedOnStartNode(IList items, IUser user)