diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index 6a212a5a71..27ce6db3d8 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -47,7 +47,7 @@ namespace Umbraco.Core.Composing /// The application runtime cache. /// Files storage mode. /// A profiling logger. - public TypeLoader(IRuntimeCacheProvider runtimeCache, LocalTempStorage localTempStorage, ProfilingLogger logger) + public TypeLoader(IRuntimeCacheProvider runtimeCache, LocalTempStorage localTempStorage, IProfilingLogger logger) : this(runtimeCache, localTempStorage, logger, true) { } @@ -58,7 +58,7 @@ namespace Umbraco.Core.Composing /// Files storage mode. /// A profiling logger. /// Whether to detect changes using hashes. - internal TypeLoader(IRuntimeCacheProvider runtimeCache, LocalTempStorage localTempStorage, ProfilingLogger logger, bool detectChanges) + internal TypeLoader(IRuntimeCacheProvider runtimeCache, LocalTempStorage localTempStorage, IProfilingLogger logger, bool detectChanges) { _runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); _localTempStorage = localTempStorage == LocalTempStorage.Unknown ? LocalTempStorage.Default : localTempStorage; diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index d2236bab70..91627edb8b 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { internal class ContentElement : UmbracoConfigurationElement, IContentSection { - private const string DefaultPreviewBadge = @"In Preview Mode - click to end"; + private const string DefaultPreviewBadge = @"In Preview Mode - click to end"; [ConfigurationProperty("imaging")] internal ContentImagingElement Imaging => (ContentImagingElement) this["imaging"]; diff --git a/src/Umbraco.Core/Constants-Examine.cs b/src/Umbraco.Core/Constants-Examine.cs deleted file mode 100644 index ddc3500066..0000000000 --- a/src/Umbraco.Core/Constants-Examine.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Umbraco.Core -{ - public static partial class Constants - { - public static class Examine - { - /// - /// The alias of the internal member indexer - /// - public const string InternalMemberIndexer = "InternalMemberIndexer"; - - /// - /// The alias of the internal content indexer - /// - public const string InternalIndexer = "InternalIndexer"; - - /// - /// The alias of the external content indexer - /// - public const string ExternalIndexer = "ExternalIndexer"; - - } - } -} diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 4281ad7c42..4ea6efb860 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -225,14 +225,14 @@ namespace Umbraco.Core protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) { - var umbracoPlan = new UmbracoPlan(); - var stateValueKey = Upgrader.GetStateValueKey(umbracoPlan); + var upgrader = new UmbracoUpgrader(); + var stateValueKey = upgrader.StateValueKey; // no scope, no service - just directly accessing the database using (var database = databaseFactory.CreateDatabase()) { CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); - FinalMigrationState = umbracoPlan.FinalState; + FinalMigrationState = upgrader.Plan.FinalState; } logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", CurrentMigrationState, FinalMigrationState ?? ""); diff --git a/src/Umbraco.Core/Services/IMemberGroupService.cs b/src/Umbraco.Core/Services/IMemberGroupService.cs index 934bf1a480..6a554aad31 100644 --- a/src/Umbraco.Core/Services/IMemberGroupService.cs +++ b/src/Umbraco.Core/Services/IMemberGroupService.cs @@ -7,6 +7,7 @@ namespace Umbraco.Core.Services { IEnumerable GetAll(); IMemberGroup GetById(int id); + IEnumerable GetByIds(IEnumerable ids); IMemberGroup GetByName(string name); void Save(IMemberGroup memberGroup, bool raiseEvents = true); void Delete(IMemberGroup memberGroup); diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index cb2d90aa63..a926ce32aa 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -90,16 +90,6 @@ namespace Umbraco.Core.Services string[] userGroups = null, string filter = null); - /// - /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method - /// - /// - /// This method exists so that Umbraco developers can use one entry point to create/update users if they choose to. - /// - /// The user to save the password for - /// The password to save - void SavePassword(IUser user, string password); - /// /// Deletes or disables a User /// diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index c5ad233a7a..3464823856 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -2708,11 +2708,6 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.ContentTree); - if (string.IsNullOrWhiteSpace(content.Name)) - { - throw new ArgumentException("Cannot save content blueprint with empty name."); - } - if (content.HasIdentity == false) { content.CreatorId = userId; diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index b74abc03f7..be4f719bb1 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -739,7 +739,7 @@ namespace Umbraco.Core.Services.Implement try { - var container = new EntityContainer(Constants.ObjectTypes.DocumentType) + var container = new EntityContainer(ContainedObjectType) { Name = name, ParentId = parentId, diff --git a/src/Umbraco.Core/Services/Implement/MemberGroupService.cs b/src/Umbraco.Core/Services/Implement/MemberGroupService.cs index e38c1530db..15b3101744 100644 --- a/src/Umbraco.Core/Services/Implement/MemberGroupService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberGroupService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -60,6 +61,19 @@ namespace Umbraco.Core.Services.Implement } } + public IEnumerable GetByIds(IEnumerable ids) + { + if (ids == null || ids.Any() == false) + { + return new IMemberGroup[0]; + } + + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + return _memberGroupRepository.GetMany(ids.ToArray()); + } + } + public IMemberGroup GetById(int id) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) diff --git a/src/Umbraco.Core/Services/Implement/UserService.cs b/src/Umbraco.Core/Services/Implement/UserService.cs index 44358caa84..188c6feb04 100644 --- a/src/Umbraco.Core/Services/Implement/UserService.cs +++ b/src/Umbraco.Core/Services/Implement/UserService.cs @@ -227,30 +227,6 @@ namespace Umbraco.Core.Services.Implement Save(membershipUser); } - [Obsolete("ASP.NET Identity APIs like the BackOfficeUserManager should be used to manage passwords, this will not work with correct security practices because you would need the existing password")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void SavePassword(IUser user, string password) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - - var provider = MembershipProviderExtensions.GetUsersMembershipProvider(); - - if (provider.IsUmbracoMembershipProvider() == false) - throw new NotSupportedException("When using a non-Umbraco membership provider you must change the user password by using the MembershipProvider.ChangePassword method"); - - provider.ChangePassword(user.Username, "", password); - - //go re-fetch the member and update the properties that may have changed - var result = GetByUsername(user.Username); - if (result != null) - { - //should never be null but it could have been deleted by another thread. - user.RawPasswordValue = result.RawPasswordValue; - user.LastPasswordChangeDate = result.LastPasswordChangeDate; - user.UpdateDate = result.UpdateDate; - } - } - /// /// Deletes or disables a User /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c5319afb51..291242ff3f 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -311,7 +311,6 @@ - diff --git a/src/Umbraco.Examine/ExamineExtensions.cs b/src/Umbraco.Examine/ExamineExtensions.cs index 71e3e65c21..525f0deaa1 100644 --- a/src/Umbraco.Examine/ExamineExtensions.cs +++ b/src/Umbraco.Examine/ExamineExtensions.cs @@ -2,9 +2,12 @@ using System.Linq; using Examine; using Examine.LuceneEngine.Providers; +using Lucene.Net.Analysis; using Lucene.Net.Index; +using Lucene.Net.QueryParsers; using Lucene.Net.Search; using Lucene.Net.Store; +using Version = Lucene.Net.Util.Version; using Umbraco.Core.Logging; namespace Umbraco.Examine @@ -14,6 +17,29 @@ namespace Umbraco.Examine /// internal static class ExamineExtensions { + public static bool TryParseLuceneQuery(string query) + { + //TODO: I'd assume there would be a more strict way to parse the query but not that i can find yet, for now we'll + // also do this rudimentary check + if (!query.Contains(":")) + return false; + + try + { + //This will pass with a plain old string without any fields, need to figure out a way to have it properly parse + var parsed = new QueryParser(Version.LUCENE_30, "nodeName", new KeywordAnalyzer()).Parse(query); + return true; + } + catch (ParseException) + { + return false; + } + catch (Exception) + { + return false; + } + } + /// /// Forcibly unlocks all lucene based indexes /// diff --git a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs index 3647b57ae8..8d15613791 100644 --- a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs @@ -167,7 +167,7 @@ namespace Umbraco.Tests.Benchmarks [Benchmark] public void EmitCtor() { - var ctor = ReflectionUtilities.EmitConstuctor>(); + var ctor = ReflectionUtilities.EmitConstructor>(); var foo = ctor(_foo); } diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index ef50f0b48a..5237b92ab8 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -110,7 +110,6 @@ namespace Umbraco.Tests.Cache.PublishedCache } [TestCase("id")] - [TestCase("nodeId")] [TestCase("__NodeId")] public void DictionaryDocument_Id_Keys(string key) { @@ -127,7 +126,6 @@ namespace Umbraco.Tests.Cache.PublishedCache } [TestCase("nodeName")] - [TestCase("__nodeName")] public void DictionaryDocument_NodeName_Keys(string key) { var dicDoc = GetDictionaryDocument(nodeNameKey: key); diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 6165ecdac9..5148c7eb1b 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -59,18 +59,17 @@ namespace Umbraco.Tests.Composing // cleanup var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; - foreach (var d in Directory.GetDirectories(Path.Combine(assDir.FullName, "TypeLoader"))) - Directory.Delete(d, true); + var tlDir = Path.Combine(assDir.FullName, "TypeLoader"); + if (!Directory.Exists(tlDir)) + return; + Directory.Delete(tlDir, true); } private DirectoryInfo PrepareFolder() { var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; - var dir = Directory.CreateDirectory(Path.Combine(assDir.FullName, "TypeLoader", Guid.NewGuid().ToString("N"))); - foreach (var f in dir.GetFiles()) - { - f.Delete(); - } + var tlDir = Path.Combine(assDir.FullName, "TypeLoader"); + var dir = Directory.CreateDirectory(Path.Combine(tlDir, Guid.NewGuid().ToString("N"))); return dir; } diff --git a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs index d588e19f07..d1470756f0 100644 --- a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs +++ b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs @@ -53,8 +53,7 @@ namespace Umbraco.Tests.Configurations SystemDirectories.Root = rootPath; Assert.AreEqual(outcome, Current.Config.Global().GetUmbracoMvcArea()); } - - [TestCase("/umbraco/umbraco.aspx")] + [TestCase("/umbraco/editContent.aspx")] [TestCase("/install/default.aspx")] [TestCase("/install/")] diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs index f1ac463305..962d6d13a9 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs @@ -143,7 +143,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public void PreviewBadge() { - Assert.AreEqual(SettingsSection.Content.PreviewBadge, @"In Preview Mode - click to end"); + Assert.AreEqual(SettingsSection.Content.PreviewBadge, @"In Preview Mode - click to end"); } [Test] public void ResolveUrlsFromTextString() diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config index a436dad9f5..4c64485503 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config @@ -77,7 +77,7 @@ In Preview Mode - click to end + In Preview Mode - click to end ]]> diff --git a/src/Umbraco.Web.UI/Umbraco/ClientRedirect.aspx b/src/Umbraco.Web.UI/Umbraco/ClientRedirect.aspx deleted file mode 100644 index 2a10d4d344..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/ClientRedirect.aspx +++ /dev/null @@ -1,64 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" Inherits="Umbraco.Web.UI.Pages.UmbracoEnsuredPage" %> -<%-- - This page is required because we cannot reload the angular app with a changed Hash since it just detects the hash and doesn't reload. - So this is used purely for a full reload of an angular app with a changed hash. ---%> - - - - Redirecting... - - - - Redirecting... - - diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index d2366b46f6..537558867f 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -14,7 +14,7 @@ Vytvořit balíček Odstranit Deaktivovat - Vyprázdnit koš + Vyprázdnit koš Exportovat typ dokumentu Importovat typ dokumentu Importovat balíček @@ -210,7 +210,6 @@ Zavřít toto okno Jste si jistí. že chcete odstranit Jste si jistí, že chcete deaktivovat - Zatrhněte, prosím, tento box, abyste potvrdili odstranění %0% položek Jste si jistí? Jste si jistí? Vyjmout @@ -992,4 +991,4 @@ Vaše nedávná historie Relace vyprší za - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 7864d3baae..5efb8c5649 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -15,7 +15,7 @@ Opret gruppe Slet Deaktivér - Tøm papirkurv + Tøm papirkurv Aktivér Eksportér dokumenttype Importér dokumenttype @@ -333,7 +333,6 @@ Luk denne dialog Er du sikker på at du vil slette Er du sikker på du vil deaktivere - Afkryds venligst denne boks for at bekræfte sletningen af %0% enhed(er) Er du sikker på at du vil forlade Umbraco? Er du sikker? Klip diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index b2296b1ccf..53e87f8dfd 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -14,7 +14,7 @@ Paket erstellen Löschen Deaktivieren - Papierkorb leeren + Papierkorb leeren Dokumenttyp exportieren Dokumenttyp importieren Paket importieren @@ -222,7 +222,6 @@ Fenster schließen Sind Sie sicher? Deaktivieren bestätigen - Löschen von %0% Element(en) bestätigen Sind Sie sicher? Sind Sie sicher? Ausschneiden @@ -881,7 +880,7 @@ das Dokument '%1%' wurde von '%2%' zur Übersetzung in '%5%' freigegeben. Zum Bearbeiten verwenden Sie bitte diesen Link: http://%3%/translation/details.aspx?id=%4%. -Sie können sich auch alle anstehenden Übersetzungen gesammelt im Umbraco-Verwaltungsbereich anzeigen lassen: http://%3%/Umbraco.aspx +Sie können sich auch alle anstehenden Übersetzungen gesammelt im Umbraco-Verwaltungsbereich anzeigen lassen: http://%3%/umbraco Einen schönen Tag wünscht Ihr freundlicher Umbraco-Robot @@ -977,4 +976,4 @@ Ihr freundlicher Umbraco-Robot Ihr Verlauf Sitzung läuft ab in - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 1b11522081..a1ab9c6e6c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -16,7 +16,7 @@ Create group Delete Disable - Empty recycle bin + Empty recycle bin Enable Export Document Type Import Document Type @@ -350,7 +350,6 @@ Close this window Are you sure you want to delete Are you sure you want to disable - Please check this box to confirm deletion of %0% item(s) Are you sure? Are you sure? Cut @@ -1816,6 +1815,7 @@ To manage your website, simply open the Umbraco back office and start adding con Validation + No validation Validate as an email address Validate as a number Validate as a URL 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 775b40fd0c..028d8ef8ab 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -16,7 +16,7 @@ Create group Delete Disable - Empty recycle bin + Empty recycle bin Enable Export Document Type Import Document Type @@ -147,8 +147,9 @@ Viewing for Content deleted Content unpublished - Content saved and Published - Content saved and published for languages: %0% + Content unpublished for languages: %0% + Content published + Content published for languages: %0% Content saved Content saved for languages: %0% Content moved @@ -165,10 +166,12 @@ Save Delete Unpublish + Unpublish Rollback Send To Publish Send To Publish Sort + History (all variants) To change the document type for the selected content, first select from the list of valid types for this location. @@ -373,7 +376,6 @@ Close this window Are you sure you want to delete Are you sure you want to disable - Please check this box to confirm deletion of %0% item(s) Are you sure? Are you sure? Cut diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml index db94121405..7f35e90083 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml @@ -15,7 +15,7 @@ Crear grupo Borrar Deshabilitar - Vaciar Papelera + Vaciar Papelera Activar Exportar Documento (tipo) Importar Documento (tipo) @@ -296,7 +296,6 @@ Cerrar esta ventana Estás seguro que quieres borrar Estás seguro que quieres deshabilitar - Por favor selecciona esta casilla para confirmar la eliminación de %0% entrada(s) ¿Estás seguro? ¿Estás seguro? Cortar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index b44da9ba04..581ce754e0 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -16,7 +16,7 @@ Créer un package Supprimer Désactiver - Vider la corbeille + Vider la corbeille Activer Exporter le type de document Importer un type de document @@ -310,7 +310,6 @@ Fermer cette fenêtre Êtes-vous certain(e) de vouloir supprimer Êtes-vous certain(e) de vouloir désactiver - Cochez cette case pour confirmer la suppression de %0% élément(s) Êtes-vous certain(e)? Êtes-vous certain(e)? Couper diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml index 0fb00ef139..011745ac0f 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml @@ -13,7 +13,7 @@ צור חבילה מחק נטרל - רוקן סל מיחזור + רוקן סל מיחזור ייצא סוג קובץ ייבא סוג מסמך ייבא חבילה @@ -159,7 +159,6 @@ סגור חלון זה האם הינך בטוח שברצונך למחוק זאת? האם הינך בטוח שברצונך לכבות זאת? - סמן תיבה זו לאשר מחיקה סופית של %0% פריט/ים האם הינך בטוח? האם אתה בטוח? גזור @@ -888,4 +887,4 @@ To manage your website, simply open the Umbraco back office and start adding con סוגי משתמש כותב - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml index 7b8da34c7a..a55c626625 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml @@ -13,7 +13,7 @@ Crea pacchetto Cancella Disabilita - Svuota il cestino + Svuota il cestino Esporta il tipo di documento Importa il tipo di documento Importa il pacchetto @@ -164,7 +164,6 @@ Chiudi questa finestra - Taglia @@ -877,4 +876,4 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i Writer - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml index e53abc2b53..3b23ccf877 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml @@ -14,7 +14,7 @@ パッケージの作成 削除 無効 - ごみ箱を空にする + ごみ箱を空にする ドキュメントタイプの書出 ドキュメントタイプの読込 パッケージの読み込み @@ -224,7 +224,6 @@ このウィンドウを閉じる 削除しますか? 無効にしますか? - これらの %0% 個の項目を削除する場合は、チェックボックスにチェックを入れてください ログアウトしますか? 本当にいいですか? 切り取り @@ -1132,4 +1131,4 @@ Runwayをインストールして作られた新しいウェブサイトがど ... またはカスタム検証を入力 必須フィールドです - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml index f2d27094a8..7a46ea81c8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml @@ -13,7 +13,7 @@ 패키지 새로 만들기 삭제 비활성 - 휴지통 비우기 + 휴지통 비우기 추출 문서 유형 등록 문서 유형 패키지 등록 @@ -158,7 +158,6 @@ 이창 닫기 정말로 삭제하시겠습니까? 정말로 비활성화하시겠습니까? - %0% 항목(들)을 삭제하시려면 이 박스를 체크하세요 로그아웃 하시겠습니까? 확실합니까? TRANSLATE ME: 'Cut' @@ -868,4 +867,4 @@ 사용자 타입 작성자 - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index 0a20fa07d3..d51d41e558 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -14,7 +14,7 @@ Opprett pakke Slett Deaktiver - Tøm papirkurv + Tøm papirkurv Eksporter dokumenttype Importer dokumenttype Importer pakke @@ -218,7 +218,6 @@ Lukk dette vinduet Er du sikker på at du vil slette Er du sikker på at du vil deaktivere - Vennligst kryss av i denne boksen for å bekrefte sletting av %0% element(er) Er du sikker på at du vil forlate Umbraco? Er du sikker? Klipp ut @@ -961,4 +960,4 @@ Vennlig hilsen Umbraco roboten Din historikk Sesjonen utløper om - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml index 395fc1a869..4aa3126701 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml @@ -14,7 +14,7 @@ Nieuwe package Verwijderen Uitschakelen - Prullenbak leegmaken + Prullenbak leegmaken Documenttype exporteren Documenttype importeren Package importeren @@ -249,7 +249,6 @@ Sluit dit venster Weet je zeker dat je dit wilt verwijderen Weet je zeker dat je dit wilt uitschakelen - Vink aub dit keuzevak aan om het verwijderen van %0% item(s) te bevestigen Weet je het zeker? Weet je het zeker? Knippen diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml index d0f18119ef..e3e0fe8e7d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml @@ -15,7 +15,7 @@ Stwórz grupę Usuń Deaktywuj - Opróżnij kosz + Opróżnij kosz Aktywuj Eksportuj typ dokumentu Importuj typ dokumentu @@ -298,7 +298,6 @@ Zamknij to okno Jesteś pewny, że chcesz usunąć Jesteś pewny, że chcesz wyłączyć - Proszę zaznaczyć, aby potwierdzić usunięcie %0% elementów. Jesteś pewny? Jesteś pewny? Wytnij diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml index 98f557a78c..cff6009bb4 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml @@ -13,7 +13,7 @@ Criar Pacote Remover Desabilitar - Esvaziar Lixeira + Esvaziar Lixeira Exportar Tipo de Documento Importar Tipo de Documento Importar Pacote @@ -158,7 +158,6 @@ Fechar esta janela Certeza em remover Certeza em desabilitar - Favor selecionar esta caixa para confirmar a remoção de %0% item(s) Tem certeza Tem certeza? Cortar @@ -857,4 +856,4 @@ Você pode publicar esta página e todas suas sub-páginas ao selecionar pub Tipos de usuários Escrevente - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml index 61e4ea2d06..fc8504fb13 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml @@ -17,7 +17,7 @@ Значение по умолчанию Удалить Отключить - Очистить корзину + Очистить корзину Включить Экспорт Экспортировать @@ -346,7 +346,6 @@ Закрыть это окно Вы уверены, что хотите удалить Вы уверены, что хотите запретить - Пожалуйста, подтвердите удаление из корзины %0% элементов Вы уверены? Вы уверены? Вырезать diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml index 262df44eaf..77e213159b 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml @@ -15,7 +15,7 @@ Standardvärde Ta bort Avaktivera - Töm papperskorgen + Töm papperskorgen Exportera dokumenttyp Importera dokumenttyp Importera paket @@ -216,7 +216,6 @@ Stäng fönstret Är du säker på att du vill ta bort Är du säker på att du vill avaktivera - Kryssa i denna ruta för att bekräfta att %0% objekt tas bort Är du säker? Är du säker? Klipp ut @@ -904,4 +903,4 @@ Din nuvarande historik Din profil - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml index 79304d86c2..ddf9aafaae 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml @@ -14,7 +14,7 @@ Paket Oluştur Sil Devre Dışı Bırak - Geri Dönüşümü Boşat + Geri Dönüşümü Boşat Belge Türü Çıkart Belge Türü Al Paket Ekle @@ -191,7 +191,6 @@ Bu pencereyi kapatın Silmek istediğine emin misin Eğer devre dışı bırakmak istediğinizden emin misiniz - %0% öğe(lerin) silinmesi onaylamak için bu kutuyu kontrol edin Emin misiniz? Emin misiniz? Kes diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml index 0064653a69..05f8c3ecf7 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml @@ -14,7 +14,7 @@ 创建扩展包 删除 禁用 - 清空回收站 + 清空回收站 导出文档类型 导入文档类型 导入扩展包 @@ -237,7 +237,6 @@ 关闭窗口 您确定要删除吗 您确定要禁用吗 - 单击此框确定删除%0%项 您确定吗? 您确定吗? 剪切 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 2579592cfe..0a87631329 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml @@ -14,7 +14,7 @@ 創建擴展包 刪除 禁用 - 清空回收站 + 清空回收站 匯出文檔類型 導入文檔類型 導入擴展包 @@ -234,7 +234,6 @@ 關閉窗口 您確定要刪除嗎 您確定要禁用嗎 - 按一下此框確定刪除%0%項 您確定嗎? 您確定嗎? 剪切 diff --git a/src/Umbraco.Web.UI/Umbraco/endPreview.aspx b/src/Umbraco.Web.UI/Umbraco/endPreview.aspx deleted file mode 100644 index e55ca96618..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/endPreview.aspx +++ /dev/null @@ -1,3 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" Inherits="umbraco.presentation.endPreview" %> - - diff --git a/src/Umbraco.Web.UI/Umbraco/ping.aspx b/src/Umbraco.Web.UI/Umbraco/ping.aspx deleted file mode 100644 index ffb1631650..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/ping.aspx +++ /dev/null @@ -1,2 +0,0 @@ -<%@ Page language="c#" AutoEventWireup="True" %> -I'm alive! \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx b/src/Umbraco.Web.UI/Umbraco/umbraco.aspx deleted file mode 100644 index 6e70513afd..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx +++ /dev/null @@ -1,2 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="umbraco.aspx.cs" Inherits="Umbraco.Web.UI.Umbraco.umbraco" %> - diff --git a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs b/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs deleted file mode 100644 index c4d7992db4..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.UI; -using System.Web.UI.WebControls; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; - -namespace Umbraco.Web.UI.Umbraco -{ - public partial class umbraco : System.Web.UI.Page - { - protected void Page_Load(object sender, EventArgs e) - { - Response.Status = "301 Moved Permanently"; - Response.AddHeader("Location", Current.Config.Global().Path); - } - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.designer.cs b/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.designer.cs deleted file mode 100644 index c9a577fb34..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.designer.cs +++ /dev/null @@ -1,15 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Umbraco.Web.UI.Umbraco { - - - public partial class umbraco { - } -} diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index 42c8b01e24..df6fe953fe 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -41,7 +41,7 @@ In Preview Mode - click to end + In Preview Mode - click to end ]]> In Preview Mode - click to end + In Preview Mode - click to end ]]> diff --git a/src/Umbraco.Web.UI/default.aspx b/src/Umbraco.Web.UI/default.aspx index 75b0e5d28c..a41d1eccf7 100644 --- a/src/Umbraco.Web.UI/default.aspx +++ b/src/Umbraco.Web.UI/default.aspx @@ -1,2 +1,2 @@ -<%@ Page language="c#" Codebehind="default.aspx.cs" AutoEventWireup="True" Inherits="umbraco.UmbracoDefault" trace="true" validateRequest="false" %> +<%@ Page language="c#" AutoEventWireup="True" Inherits="umbraco.UmbracoDefault" trace="true" validateRequest="false" %> diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml index 0a20fa07d3..d51d41e558 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml @@ -14,7 +14,7 @@ Opprett pakke Slett Deaktiver - Tøm papirkurv + Tøm papirkurv Eksporter dokumenttype Importer dokumenttype Importer pakke @@ -218,7 +218,6 @@ Lukk dette vinduet Er du sikker på at du vil slette Er du sikker på at du vil deaktivere - Vennligst kryss av i denne boksen for å bekrefte sletting av %0% element(er) Er du sikker på at du vil forlate Umbraco? Er du sikker? Klipp ut @@ -961,4 +960,4 @@ Vennlig hilsen Umbraco roboten Din historikk Sesjonen utløper om - \ No newline at end of file + 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 2579592cfe..0a87631329 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml @@ -14,7 +14,7 @@ 創建擴展包 刪除 禁用 - 清空回收站 + 清空回收站 匯出文檔類型 導入文檔類型 導入擴展包 @@ -234,7 +234,6 @@ 關閉窗口 您確定要刪除嗎 您確定要禁用嗎 - 按一下此框確定刪除%0%項 您確定嗎? 您確定嗎? 剪切 diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 4d7a21ffef..3cecdbe8e5 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -486,7 +486,10 @@ namespace Umbraco.Web.Editors var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) { - Items = entities.Select(Mapper.Map) + Items = entities.Select(entity => Mapper.Map(entity, options => + options.AfterMap((src, dest) => { dest.AdditionalData["hasChildren"] = src.HasChildren; }) + ) + ) }; return pagedResult; diff --git a/src/Umbraco.Web/Editors/ExamineManagementController.cs b/src/Umbraco.Web/Editors/ExamineManagementController.cs index 67209c91bd..2f96ee3d45 100644 --- a/src/Umbraco.Web/Editors/ExamineManagementController.cs +++ b/src/Umbraco.Web/Editors/ExamineManagementController.cs @@ -73,7 +73,7 @@ namespace Umbraco.Web.Editors if (!msg.IsSuccessStatusCode) throw new HttpResponseException(msg); - var results = TryParseLuceneQuery(query) + var results = Examine.ExamineExtensions.TryParseLuceneQuery(query) ? searcher.Search(searcher.CreateCriteria().RawQuery(query), maxResults: pageSize * (pageIndex + 1)) : searcher.Search(query, true, maxResults: pageSize * (pageIndex + 1)); @@ -92,28 +92,7 @@ namespace Umbraco.Web.Editors }; } - private bool TryParseLuceneQuery(string query) - { - //TODO: I'd assume there would be a more strict way to parse the query but not that i can find yet, for now we'll - // also do this rudimentary check - if (!query.Contains(":")) - return false; - - try - { - //This will pass with a plain old string without any fields, need to figure out a way to have it properly parse - var parsed = new QueryParser(Version.LUCENE_30, "nodeName", new KeywordAnalyzer()).Parse(query); - return true; - } - catch (ParseException) - { - return false; - } - catch (Exception) - { - return false; - } - } + /// /// Check if the index has been rebuilt diff --git a/src/Umbraco.Web/Editors/KeepAliveController.cs b/src/Umbraco.Web/Editors/KeepAliveController.cs new file mode 100644 index 0000000000..b067a5b67e --- /dev/null +++ b/src/Umbraco.Web/Editors/KeepAliveController.cs @@ -0,0 +1,33 @@ +using System.Runtime.Serialization; +using System.Web.Http; +using Umbraco.Web.WebApi; + +namespace Umbraco.Web.Editors +{ + // fixme + // this is not authenticated, and therefore public, and therefore reveals we + // are running Umbraco - but, all requests should come from localhost really, + // so there should be a way to 404 when the request comes from the outside. + + public class KeepAliveController : UmbracoApiController + { + [HttpGet] + public KeepAlivePingResult Ping() + { + return new KeepAlivePingResult + { + Success = true, + Message = "I'm alive!" + }; + } + } + + public class KeepAlivePingResult + { + [DataMember(Name = "success")] + public bool Success { get; set; } + + [DataMember(Name = "message")] + public string Message { get; set; } + } +} diff --git a/src/Umbraco.Web/Editors/MemberGroupController.cs b/src/Umbraco.Web/Editors/MemberGroupController.cs index 4ea823b25a..6feaab540b 100644 --- a/src/Umbraco.Web/Editors/MemberGroupController.cs +++ b/src/Umbraco.Web/Editors/MemberGroupController.cs @@ -36,6 +36,17 @@ namespace Umbraco.Web.Editors return dto; } + public IEnumerable GetByIds([FromUri]int[] ids) + { + if (_provider.IsUmbracoMembershipProvider()) + { + return Services.MemberGroupService.GetByIds(ids) + .Select(Mapper.Map); + } + + return Enumerable.Empty(); + } + [HttpDelete] [HttpPost] public HttpResponseMessage DeleteById(int id) diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs index 6a91d20ae0..6e119d68d9 100644 --- a/src/Umbraco.Web/Editors/PreviewController.cs +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -88,5 +88,23 @@ namespace Umbraco.Web.Editors // if (string.IsNullOrEmpty(editor)) throw new ArgumentNullException(nameof(editor)); // return View(_globalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + editor.Replace(".html", string.Empty) + ".cshtml"); //} + + public ActionResult End(string redir = null) + { + var previewToken = Request.GetPreviewCookieValue(); + var service = Current.PublishedSnapshotService; + service.ExitPreview(previewToken); + + System.Web.HttpContext.Current.ExpireCookie(Constants.Web.PreviewCookieName); + + if (Uri.IsWellFormedUriString(redir, UriKind.Relative) + && redir.StartsWith("//") == false + && Uri.TryCreate(redir, UriKind.Relative, out Uri url)) + { + return Redirect(url.ToString()); + } + + return Redirect("/"); + } } } diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index 0d0438138a..00e124cb29 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -18,26 +18,26 @@ namespace Umbraco.Web.Editors [JsonCamelCaseFormatter] public class TemplateQueryController : UmbracoAuthorizedJsonController { - private IEnumerable Terms + private IEnumerable Terms { get { - return new List() + return new List() { - new OperathorTerm(Services.TextService.Localize("template/is"), Operathor.Equals, new [] {"string"}), - new OperathorTerm(Services.TextService.Localize("template/isNot"), Operathor.NotEquals, new [] {"string"}), - new OperathorTerm(Services.TextService.Localize("template/before"), Operathor.LessThan, new [] {"datetime"}), - new OperathorTerm(Services.TextService.Localize("template/beforeIncDate"), Operathor.LessThanEqualTo, new [] {"datetime"}), - new OperathorTerm(Services.TextService.Localize("template/after"), Operathor.GreaterThan, new [] {"datetime"}), - new OperathorTerm(Services.TextService.Localize("template/afterIncDate"), Operathor.GreaterThanEqualTo, new [] {"datetime"}), - new OperathorTerm(Services.TextService.Localize("template/equals"), Operathor.Equals, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/doesNotEqual"), Operathor.NotEquals, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/contains"), Operathor.Contains, new [] {"string"}), - new OperathorTerm(Services.TextService.Localize("template/doesNotContain"), Operathor.NotContains, new [] {"string"}), - new OperathorTerm(Services.TextService.Localize("template/greaterThan"), Operathor.GreaterThan, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/greaterThanEqual"), Operathor.GreaterThanEqualTo, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/lessThan"), Operathor.LessThan, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/lessThanEqual"), Operathor.LessThanEqualTo, new [] {"int"}) + new OperatorTerm(Services.TextService.Localize("template/is"), Operator.Equals, new [] {"string"}), + new OperatorTerm(Services.TextService.Localize("template/isNot"), Operator.NotEquals, new [] {"string"}), + new OperatorTerm(Services.TextService.Localize("template/before"), Operator.LessThan, new [] {"datetime"}), + new OperatorTerm(Services.TextService.Localize("template/beforeIncDate"), Operator.LessThanEqualTo, new [] {"datetime"}), + new OperatorTerm(Services.TextService.Localize("template/after"), Operator.GreaterThan, new [] {"datetime"}), + new OperatorTerm(Services.TextService.Localize("template/afterIncDate"), Operator.GreaterThanEqualTo, new [] {"datetime"}), + new OperatorTerm(Services.TextService.Localize("template/equals"), Operator.Equals, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/doesNotEqual"), Operator.NotEquals, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/contains"), Operator.Contains, new [] {"string"}), + new OperatorTerm(Services.TextService.Localize("template/doesNotContain"), Operator.NotContains, new [] {"string"}), + new OperatorTerm(Services.TextService.Localize("template/greaterThan"), Operator.GreaterThan, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/greaterThanEqual"), Operator.GreaterThanEqualTo, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/lessThan"), Operator.LessThan, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/lessThanEqual"), Operator.LessThanEqualTo, new [] {"int"}) }; } } @@ -67,6 +67,7 @@ namespace Umbraco.Web.Editors sb.Append("Model.Root()"); + //fixme: This timer thing is not correct, it's definitely not timing the resulting query, the timer really isn't important and might as well be removed var timer = new Stopwatch(); timer.Start(); diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs index ea76293df5..178027857c 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs @@ -122,6 +122,8 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) .AfterMap((src, dest) => { + //TODO: Properly map this (not aftermap) + //get the icon if there is one dest.Icon = src.Values.ContainsKey(UmbracoExamineIndex.IconFieldName) ? src.Values[UmbracoExamineIndex.IconFieldName] diff --git a/src/Umbraco.Web/Models/TemplateQuery/Operathor.cs b/src/Umbraco.Web/Models/TemplateQuery/Operator.cs similarity index 90% rename from src/Umbraco.Web/Models/TemplateQuery/Operathor.cs rename to src/Umbraco.Web/Models/TemplateQuery/Operator.cs index 561caec362..135c43507e 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/Operathor.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/Operator.cs @@ -1,6 +1,6 @@ namespace Umbraco.Web.Models.TemplateQuery { - public enum Operathor + public enum Operator { Equals = 1, NotEquals = 2, diff --git a/src/Umbraco.Web/Models/TemplateQuery/OperathorTerm.cs b/src/Umbraco.Web/Models/TemplateQuery/OperatorTerm.cs similarity index 56% rename from src/Umbraco.Web/Models/TemplateQuery/OperathorTerm.cs rename to src/Umbraco.Web/Models/TemplateQuery/OperatorTerm.cs index c14e1854aa..086f0ff818 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/OperathorTerm.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/OperatorTerm.cs @@ -2,24 +2,24 @@ namespace Umbraco.Web.Models.TemplateQuery { - public class OperathorTerm + public class OperatorTerm { - public OperathorTerm() + public OperatorTerm() { Name = "is"; - Operathor = Operathor.Equals; + Operator = Operator.Equals; AppliesTo = new [] { "string" }; } - public OperathorTerm(string name, Operathor operathor, IEnumerable appliesTo) + public OperatorTerm(string name, Operator @operator, IEnumerable appliesTo) { Name = name; - Operathor = operathor; + Operator = @operator; AppliesTo = appliesTo; } public string Name { get; set; } - public Operathor Operathor { get; set; } + public Operator Operator { get; set; } public IEnumerable AppliesTo { get; set; } } } diff --git a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs index 8ba943756f..9c5f2c80c0 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs @@ -4,7 +4,7 @@ { public PropertyModel Property { get; set; } - public OperathorTerm Term { get; set; } + public OperatorTerm Term { get; set; } public string ConstraintValue { get; set; } } @@ -53,30 +53,30 @@ } - switch (condition.Term.Operathor) + switch (condition.Term.Operator) { - case Operathor.Equals: + case Operator.Equals: operand = " == "; break; - case Operathor.NotEquals: + case Operator.NotEquals: operand = " != "; break; - case Operathor.GreaterThan: + case Operator.GreaterThan: operand = " > "; break; - case Operathor.GreaterThanEqualTo: + case Operator.GreaterThanEqualTo: operand = " >= "; break; - case Operathor.LessThan: + case Operator.LessThan: operand = " < "; break; - case Operathor.LessThanEqualTo: + case Operator.LessThanEqualTo: operand = " <= "; break; - case Operathor.Contains: + case Operator.Contains: value = string.Format("{0}{1}.Contains({2})", prefix, condition.Property.Alias, constraintValue); break; - case Operathor.NotContains: + case Operator.NotContains: value = string.Format("!{0}{1}.Contains({2})", prefix, condition.Property.Alias, constraintValue); break; default : diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 07c5aac5bb..74c8744ead 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -175,13 +175,17 @@ namespace Umbraco.Web.PropertyEditors { // create a temp property with the value var tempProp = new Property(propType); - tempProp.SetValue(propValues[propAlias] == null ? null : propValues[propAlias].ToString()); + // if the property varies by culture, make sure we save using the current culture + var propCulture = propType.VariesByCulture() || propType.VariesByCultureAndSegment() + ? culture + : null; + tempProp.SetValue(propValues[propAlias] == null ? null : propValues[propAlias].ToString(), propCulture); // convert that temp property, and store the converted value var propEditor = _propertyEditors[propType.PropertyEditorAlias]; var tempConfig = dataTypeService.GetDataType(propType.DataTypeId).Configuration; var valEditor = propEditor.GetValueEditor(tempConfig); - var convValue = valEditor.ToEditor(tempProp, dataTypeService); + var convValue = valEditor.ToEditor(tempProp, dataTypeService, propCulture); propValues[propAlias] = convValue == null ? null : JToken.FromObject(convValue); } catch (InvalidOperationException) diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs index 4453fe7321..7c311236c0 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs @@ -55,7 +55,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache ValidateAndSetProperty(valueDictionary, val => _key = Guid.Parse(val), "key"); //ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId"); ValidateAndSetProperty(valueDictionary, val => _sortOrder = Int32.Parse(val), "sortOrder"); - ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName", "__nodeName"); + ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName"); ValidateAndSetProperty(valueDictionary, val => _urlName = val, "urlName"); ValidateAndSetProperty(valueDictionary, val => _documentTypeAlias = val, "nodeTypeAlias", LuceneIndex.ItemTypeFieldName); ValidateAndSetProperty(valueDictionary, val => _documentTypeId = Int32.Parse(val), "nodeType"); diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index ac6b425e27..f203d5d2c9 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -240,9 +240,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache try { - if (eMgr.TryGetIndex(Constants.Examine.InternalIndexer, out var index)) + if (eMgr.TryGetIndex(Constants.UmbracoIndexes.InternalIndexName, out var index)) return index.GetSearcher(); - throw new InvalidOperationException($"No index found by name {Constants.Examine.InternalIndexer}"); + throw new InvalidOperationException($"No index found by name {Constants.UmbracoIndexes.InternalIndexName}"); } catch (FileNotFoundException) { diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 92edb88297..9a8f3ff7ac 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -270,7 +270,7 @@ namespace Umbraco.Web { //fixme: pass in the IExamineManager - indexName = string.IsNullOrEmpty(indexName) ? Constants.Examine.ExternalIndexer : indexName; + indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName : indexName; if (!ExamineManager.Instance.TryGetIndex(indexName, out var index)) throw new InvalidOperationException("No index found with name " + indexName); @@ -290,7 +290,7 @@ namespace Umbraco.Web { //fixme: pass in the IExamineManager - indexName = string.IsNullOrEmpty(indexName) ? Constants.Examine.ExternalIndexer : indexName; + indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName : indexName; if (!ExamineManager.Instance.TryGetIndex(indexName, out var index)) throw new InvalidOperationException("No index found with name " + indexName); @@ -312,8 +312,8 @@ namespace Umbraco.Web if (searchProvider == null) { - if (!ExamineManager.Instance.TryGetIndex(Constants.Examine.ExternalIndexer, out var index)) - throw new InvalidOperationException("No index found with name " + Constants.Examine.ExternalIndexer); + if (!ExamineManager.Instance.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out var index)) + throw new InvalidOperationException("No index found with name " + Constants.UmbracoIndexes.ExternalIndexName); searchProvider = index.GetSearcher(); } var results = searchProvider.Search(criteria); diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 04611000b9..774393f3de 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -192,7 +192,7 @@ namespace Umbraco.Web //fixme: inject IExamineManager indexName = string.IsNullOrEmpty(indexName) - ? Constants.Examine.ExternalIndexer + ? Constants.UmbracoIndexes.ExternalIndexName : indexName; if (!ExamineManager.Instance.TryGetIndex(indexName, out var index)) @@ -220,8 +220,8 @@ namespace Umbraco.Web //fixme: inject IExamineManager if (searcher == null) { - if (!ExamineManager.Instance.TryGetIndex(Constants.Examine.ExternalIndexer, out var index)) - throw new InvalidOperationException($"No index found by name {Constants.Examine.ExternalIndexer}"); + if (!ExamineManager.Instance.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out var index)) + throw new InvalidOperationException($"No index found by name {Constants.UmbracoIndexes.ExternalIndexName}"); searcher = index.GetSearcher(); } diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index 8c851d139f..4c879e931f 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -31,7 +31,7 @@ namespace Umbraco.Web.Routing #region GetUrl /// - public string GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) + public UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) { return null; // we have nothing to say } @@ -51,13 +51,14 @@ namespace Umbraco.Web.Routing /// Other urls are those that GetUrl would not return in the current context, but would be valid /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// - public IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) + public IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) { var node = umbracoContext.ContentCache.GetById(id); - if (node == null) return Enumerable.Empty(); + if (node == null) + yield break; if (!node.HasProperty(Constants.Conventions.Content.UrlAlias)) - return Enumerable.Empty(); + yield break; var domainHelper = umbracoContext.GetDomainHelper(_siteDomainHelper); @@ -82,20 +83,19 @@ namespace Umbraco.Web.Routing // the content finder may work, depending on the 'current' culture, // but there's no way we can return something meaningful here if (varies) - return Enumerable.Empty(); + yield break; var umbracoUrlName = node.Value(Constants.Conventions.Content.UrlAlias); if (string.IsNullOrWhiteSpace(umbracoUrlName)) - return Enumerable.Empty(); + yield break; var path = "/" + umbracoUrlName; var uri = new Uri(path, UriKind.Relative); - return new[] { UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString() }; + yield return UrlInfo.Url(UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString()); } else { // some domains: one url per domain, which is "/" - var result = new List(); foreach(var domainUri in domainUris) { // if the property is invariant, get the invariant value, url is "/" @@ -108,14 +108,13 @@ namespace Umbraco.Web.Routing ? node.Value(Constants.Conventions.Content.UrlAlias, culture: domainUri.Culture.Name) : node.Value(Constants.Conventions.Content.UrlAlias); - if (!string.IsNullOrWhiteSpace(umbracoUrlName)) - { - var path = "/" + umbracoUrlName; - var uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); - result.Add(UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString()); - } + if (string.IsNullOrWhiteSpace(umbracoUrlName)) + continue; + + var path = "/" + umbracoUrlName; + var uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); + yield return UrlInfo.Url(UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString(), domainUri.Culture.Name); } - return result; } } diff --git a/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs b/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs index ce1fc1ffab..ae17ff6258 100644 --- a/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs +++ b/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.Routing /// /// This will return the URL that is returned by the assigned custom if this is a custom route /// - public string GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) + public UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) { if (umbracoContext?.PublishedRequest?.PublishedContent == null) return null; if (umbracoContext.HttpContext?.Request?.RequestContext?.RouteData?.DataTokens == null) return null; @@ -26,13 +26,15 @@ namespace Umbraco.Web.Routing //NOTE: This looks like it might cause an infinite loop because PublishedContentBase.Url calls into UmbracoContext.Current.UrlProvider.GetUrl which calls back into the IUrlProvider pipeline // but the specific purpose of this is that a developer is using their own IPublishedContent that returns a specific Url and doesn't go back into the UrlProvider pipeline. //TODO: We could put a try/catch here just in case, else we could do some reflection checking to see if the implementation is PublishedContentBase and the Url property is not overridden. - return content.Id == umbracoContext.PublishedRequest.PublishedContent.Id - ? umbracoContext.PublishedRequest.PublishedContent.GetUrl(culture) - : null; + return UrlInfo.Url( + content.Id == umbracoContext.PublishedRequest.PublishedContent.Id + ? umbracoContext.PublishedRequest.PublishedContent.GetUrl(culture) + : null, + culture); } /// - /// This always returns null because this url provider is used purely to deal with Umbraco custom routes with + /// This always returns an empty result because this url provider is used purely to deal with Umbraco custom routes with /// UmbracoVirtualNodeRouteHandler, we really only care about the normal URL so that RedirectToCurrentUmbracoPage() works /// with SurfaceControllers /// @@ -40,9 +42,9 @@ namespace Umbraco.Web.Routing /// /// /// - public IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) + public IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) { - return null; + yield break; } } } diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index 61437b6640..5045b1af96 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Routing #region GetUrl /// - public virtual string GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) + public virtual UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) { if (!current.IsAbsoluteUri) throw new ArgumentException("Current url must be absolute.", nameof(current)); @@ -39,7 +39,7 @@ namespace Umbraco.Web.Routing return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture); } - internal string GetUrlFromRoute(string route, UmbracoContext umbracoContext, int id, Uri current, UrlProviderMode mode, string culture) + internal UrlInfo GetUrlFromRoute(string route, UmbracoContext umbracoContext, int id, Uri current, UrlProviderMode mode, string culture) { if (string.IsNullOrWhiteSpace(route)) { @@ -58,7 +58,9 @@ namespace Umbraco.Web.Routing : domainHelper.DomainForNode(int.Parse(route.Substring(0, pos)), current, culture); // assemble the url from domainUri (maybe null) and path - return AssembleUrl(domainUri, path, current, mode).ToString(); + var url = AssembleUrl(domainUri, path, current, mode).ToString(); + + return UrlInfo.Url(url, culture); } #endregion @@ -76,10 +78,11 @@ namespace Umbraco.Web.Routing /// Other urls are those that GetUrl would not return in the current context, but would be valid /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// - public virtual IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) + public virtual IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) { var node = umbracoContext.ContentCache.GetById(id); - if (node == null) return Enumerable.Empty(); + if (node == null) + yield break; var domainHelper = umbracoContext.GetDomainHelper(_siteDomainHelper); @@ -94,13 +97,14 @@ namespace Umbraco.Web.Routing // no domains = exit if (domainUris ==null) - return Enumerable.Empty(); + yield break; - var result = new List(); foreach (var d in domainUris) { + var culture = d?.Culture?.Name; + //although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok - var route = umbracoContext.ContentCache.GetRouteById(id, d?.Culture?.Name); + var route = umbracoContext.ContentCache.GetRouteById(id, culture); if (route == null) continue; //need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned) @@ -109,9 +113,8 @@ namespace Umbraco.Web.Routing var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path)); uri = UriUtility.UriFromUmbraco(uri, _globalSettings, _requestSettings); - result.Add(uri.ToString()); + yield return UrlInfo.Url(uri.ToString(), culture); } - return result; } #endregion @@ -146,7 +149,7 @@ namespace Umbraco.Web.Routing uri = new Uri(path, UriKind.Relative); break; default: - throw new ArgumentOutOfRangeException("mode"); + throw new ArgumentOutOfRangeException(nameof(mode)); } } else // a domain was found @@ -169,7 +172,7 @@ namespace Umbraco.Web.Routing uri = new Uri(CombinePaths(domainUri.Uri.AbsolutePath, path), UriKind.Relative); break; default: - throw new ArgumentOutOfRangeException("mode"); + throw new ArgumentOutOfRangeException(nameof(mode)); } } diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs index b6d79e788a..edfcb33aa5 100644 --- a/src/Umbraco.Web/Routing/DomainHelper.cs +++ b/src/Umbraco.Web/Routing/DomainHelper.cs @@ -30,10 +30,10 @@ namespace Umbraco.Web.Routing /// The culture, or null. /// The domain and its uri, if any, that best matches the specified uri and culture, else null. /// - /// f at least a domain is set on the node then the method returns the domain that + /// If at least a domain is set on the node then the method returns the domain that /// best matches the specified uri and culture, else it returns null. - /// If culture is null, uses the default culture for the installation instead. - /// fixme not exactly - if culture is !null, we MUST have a value for THAT culture, else we use default as hint + /// If culture is null, uses the default culture for the installation instead. Otherwise, + /// will try with the specified culture, else return null. /// internal DomainAndUri DomainForNode(int nodeId, Uri current, string culture = null) { @@ -49,13 +49,9 @@ namespace Umbraco.Web.Routing return null; // else filter - var domainAndUri = SelectDomain(domains, current, culture, _domainCache.DefaultCulture, + // it could be that none apply (due to culture) + return SelectDomain(domains, current, culture, _domainCache.DefaultCulture, (cdomainAndUris, ccurrent, cculture, cdefaultCulture) => _siteDomainHelper.MapDomain(cdomainAndUris, ccurrent, cculture, cdefaultCulture)); - - if (domainAndUri == null) - throw new Exception("DomainForUri returned null."); - - return domainAndUri; } /// @@ -202,13 +198,12 @@ namespace Umbraco.Web.Routing private static IReadOnlyCollection SelectByCulture(IReadOnlyCollection domainsAndUris, string culture, string defaultCulture) { + // we try our best to match cultures, but may end with a bogus domain + if (culture != null) // try the supplied culture { var cultureDomains = domainsAndUris.Where(x => x.Culture.Name.InvariantEquals(culture)).ToList(); if (cultureDomains.Count > 0) return cultureDomains; - - // if a culture is supplied, we *want* a url for that culture, else fail - throw new InvalidOperationException($"Could not find a domain for culture \"{culture}\"."); } if (defaultCulture != null) // try the defaultCulture culture @@ -224,13 +219,12 @@ namespace Umbraco.Web.Routing { DomainAndUri domainAndUri; + // we try our best to match cultures, but may end with a bogus domain + if (culture != null) // try the supplied culture { domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.Name.InvariantEquals(culture)); if (domainAndUri != null) return domainAndUri; - - // if a culture is supplied, we *want* a url for that culture, else fail - throw new InvalidOperationException($"Could not find a domain for culture \"{culture}\"."); } if (defaultCulture != null) // try the defaultCulture culture diff --git a/src/Umbraco.Web/Routing/IUrlProvider.cs b/src/Umbraco.Web/Routing/IUrlProvider.cs index 0f102dceb8..55d39880d6 100644 --- a/src/Umbraco.Web/Routing/IUrlProvider.cs +++ b/src/Umbraco.Web/Routing/IUrlProvider.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Routing /// when no culture is specified, the current culture. /// If the provider is unable to provide a url, it should return null. /// - string GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current); + UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current); /// /// Gets the other urls of a published content. @@ -37,6 +37,6 @@ namespace Umbraco.Web.Routing /// Other urls are those that GetUrl would not return in the current context, but would be valid /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// - IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current); + IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current); } } diff --git a/src/Umbraco.Web/Routing/UrlInfo.cs b/src/Umbraco.Web/Routing/UrlInfo.cs index 01dbe4a0e1..ae5c4b412c 100644 --- a/src/Umbraco.Web/Routing/UrlInfo.cs +++ b/src/Umbraco.Web/Routing/UrlInfo.cs @@ -1,4 +1,5 @@ -using System.Runtime.Serialization; +using System; +using System.Runtime.Serialization; namespace Umbraco.Web.Routing { @@ -6,13 +7,25 @@ namespace Umbraco.Web.Routing /// Represents infos for a url. /// [DataContract(Name = "urlInfo", Namespace = "")] - public class UrlInfo + public class UrlInfo : IEquatable { + + /// + /// Creates a instance representing a true url. + /// + public static UrlInfo Url(string text, string culture = null) => new UrlInfo(text, true, culture); + + /// + /// Creates a instance representing a message. + /// + public static UrlInfo Message(string text, string culture = null) => new UrlInfo(text, false, culture); + /// /// Initializes a new instance of the class. /// public UrlInfo(string text, bool isUrl, string culture) { + if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(text)); IsUrl = isUrl; Text = text; Culture = culture; @@ -38,13 +51,47 @@ namespace Umbraco.Web.Routing public string Text { get; } /// - /// Creates a instance representing a true url. + /// Checks equality /// - public static UrlInfo Url(string text, string culture = null) => new UrlInfo(text, true, culture); + /// + /// + /// + /// Compare both culture and Text as invariant strings since URLs are not case sensitive, nor are culture names within Umbraco + /// + public bool Equals(UrlInfo other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Culture, other.Culture, StringComparison.InvariantCultureIgnoreCase) && IsUrl == other.IsUrl && string.Equals(Text, other.Text, StringComparison.InvariantCultureIgnoreCase); + } - /// - /// Creates a instance representing a message. - /// - public static UrlInfo Message(string text, string culture = null) => new UrlInfo(text, false, culture); + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((UrlInfo) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (Culture != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Culture) : 0); + hashCode = (hashCode * 397) ^ IsUrl.GetHashCode(); + hashCode = (hashCode * 397) ^ (Text != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Text) : 0); + return hashCode; + } + } + + public static bool operator ==(UrlInfo left, UrlInfo right) + { + return Equals(left, right); + } + + public static bool operator !=(UrlInfo left, UrlInfo right) + { + return !Equals(left, right); + } } } diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index b265d48923..7ed530093c 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -202,7 +202,7 @@ namespace Umbraco.Web.Routing var url = _urlProviders.Select(provider => provider.GetUrl(_umbracoContext, content, mode, culture, current)) .FirstOrDefault(u => u != null); - return url ?? "#"; // legacy wants this + return url?.Text ?? "#"; // legacy wants this } internal string GetUrlFromRoute(int id, string route, string culture) @@ -210,7 +210,7 @@ namespace Umbraco.Web.Routing var provider = _urlProviders.OfType().FirstOrDefault(); var url = provider == null ? route // what else? - : provider.GetUrlFromRoute(route, UmbracoContext.Current, id, _umbracoContext.CleanedUmbracoUrl, Mode, culture); + : provider.GetUrlFromRoute(route, UmbracoContext.Current, id, _umbracoContext.CleanedUmbracoUrl, Mode, culture)?.Text; return url ?? "#"; } @@ -228,7 +228,7 @@ namespace Umbraco.Web.Routing /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// The results depend on the current url. /// - public IEnumerable GetOtherUrls(int id) + public IEnumerable GetOtherUrls(int id) { return GetOtherUrls(id, _umbracoContext.CleanedUmbracoUrl); } @@ -243,12 +243,9 @@ namespace Umbraco.Web.Routing /// Other urls are those that GetUrl would not return in the current context, but would be valid /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// - public IEnumerable GetOtherUrls(int id, Uri current) + public IEnumerable GetOtherUrls(int id, Uri current) { - // providers can return null or an empty list or a non-empty list, be prepared - var urls = _urlProviders.SelectMany(provider => provider.GetOtherUrls(_umbracoContext, id, current) ?? Enumerable.Empty()); - - return urls; + return _urlProviders.SelectMany(provider => provider.GetOtherUrls(_umbracoContext, id, current)); } #endregion diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 20adf2626d..8db657ed30 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Routing /// Use when displaying Urls. If errors occur when generating the Urls, they will show in the list. /// Contains all the Urls that we can figure out (based upon domains, etc). /// - public static IEnumerable GetContentUrls(this IContent content, + public static IEnumerable GetContentUrls(this IContent content, PublishedRouter publishedRouter, UmbracoContext umbracoContext, ILocalizationService localizationService, @@ -33,25 +33,71 @@ namespace Umbraco.Web.Routing if (contentService == null) throw new ArgumentNullException(nameof(contentService)); if (logger == null) throw new ArgumentNullException(nameof(logger)); - var urls = new List(); - if (content.Published == false) { - urls.Add(UrlInfo.Message(textService.Localize("content/itemNotPublished"))); - return urls; + yield return UrlInfo.Message(textService.Localize("content/itemNotPublished")); + yield break; } - + // build a list of urls, for the back-office // which will contain // - the 'main' urls, which is what .Url would return, for each culture // - the 'other' urls we know (based upon domains, etc) // - // need to work on each culture. - // on invariant trees, each culture return the same thing - // but, we don't know if the tree to this content is invariant + // need to work through each installed culture: + // on invariant nodes, each culture returns the same url segment but, + // we don't know if the branch to this content is invariant, so we need to ask + // for URLs for all cultures. + // and, not only for those assigned to domains in the branch, because we want + // to show what GetUrl() would return, for every culture. + var urls = new HashSet(); var cultures = localizationService.GetAllLanguages().Select(x => x.IsoCode).ToList(); + //get all URLs for all cultures + //in a HashSet, so de-duplicates too + foreach (var cultureUrl in GetContentUrlsByCulture(content, cultures, publishedRouter, umbracoContext, contentService, textService, logger)) + { + urls.Add(cultureUrl); + } + + //return the real urls first, then the messages + foreach (var urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key)) + { + //in some cases there will be the same URL for multiple cultures: + // * The entire branch is invariant + // * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed + + foreach (var dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant()).OrderBy(x => x.Text).ThenBy(x => x.Culture)) + yield return dUrl; + } + + // get the 'other' urls - ie not what you'd get with GetUrl() but urls that would route to the document, nevertheless. + // for these 'other' urls, we don't check whether they are routable, collide, anything - we just report them. + foreach (var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text).ThenBy(x => x.Culture)) + if (urls.Add(otherUrl)) //avoid duplicates + yield return otherUrl; + } + + /// + /// Tries to return a for each culture for the content while detecting collisions/errors + /// + /// + /// + /// + /// + /// + /// + /// + /// + private static IEnumerable GetContentUrlsByCulture(IContent content, + IEnumerable cultures, + PublishedRouter publishedRouter, + UmbracoContext umbracoContext, + IContentService contentService, + ILocalizedTextService textService, + ILogger logger) + { foreach (var culture in cultures) { // if content is variant, and culture is not published, skip @@ -75,53 +121,26 @@ namespace Umbraco.Web.Routing { // deal with 'could not get the url' case "#": - HandleCouldNotGetUrl(content, culture, urls, contentService, textService); + yield return HandleCouldNotGetUrl(content, culture, contentService, textService); break; // deal with exceptions case "#ex": - urls.Add(UrlInfo.Message(textService.Localize("content/getUrlException"), culture)); + yield return UrlInfo.Message(textService.Localize("content/getUrlException"), culture); break; // got a url, deal with collisions, add url default: - if (!DetectCollision(content, url, urls, culture, umbracoContext, publishedRouter, textService)) // detect collisions, etc - urls.Add(UrlInfo.Url(url, culture)); + if (DetectCollision(content, url, culture, umbracoContext, publishedRouter, textService, out var urlInfo)) // detect collisions, etc + yield return urlInfo; + else + yield return UrlInfo.Url(url, culture); break; } } - - // prepare for de-duplication - var durl = new Dictionary>(); - var dmsg = new Dictionary>(); - foreach (var url in urls) - { - var d = url.IsUrl ? durl : dmsg; - if (!d.TryGetValue(url.Text, out var l)) - d[url.Text] = l = new List(); - l.Add(url); - } - - // deduplicate, order urls first then messages, concatenate cultures (hide if 'all') - var ret = new List(); - foreach (var (text, infos) in durl) - ret.Add(UrlInfo.Url(text, infos.Count == cultures.Count ? null : string.Join(", ", infos.Select(x => x.Culture)))); - foreach (var (text, infos) in dmsg) - ret.Add(UrlInfo.Message(text, infos.Count == cultures.Count ? null : string.Join(", ", infos.Select(x => x.Culture)))); - - // get the 'other' urls - ie not what you'd get with GetUrl() but urls that would route to the document, nevertheless. - // for these 'other' urls, we don't check whether they are routable, collide, anything - we just report them. - // also, we are not dealing with cultures at all - that will have to wait - foreach(var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id)) - { - if (urls.Any(x => x.IsUrl && x.Text == otherUrl)) continue; - ret.Add(UrlInfo.Url(otherUrl)); - } - - return ret; } - private static void HandleCouldNotGetUrl(IContent content, string culture, List urls, IContentService contentService, ILocalizedTextService textService) + private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, ILocalizedTextService textService) { // document has a published version yet its url is "#" => a parent must be // unpublished, walk up the tree until we find it, and report. @@ -133,16 +152,16 @@ namespace Umbraco.Web.Routing while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture))); if (parent == null) // oops, internal error - urls.Add(UrlInfo.Message(textService.Localize("content/parentNotPublishedAnomaly"), culture)); + return UrlInfo.Message(textService.Localize("content/parentNotPublishedAnomaly"), culture); else if (!parent.Published) // totally not published - urls.Add(UrlInfo.Message(textService.Localize("content/parentNotPublished", new[] { parent.Name }), culture)); + return UrlInfo.Message(textService.Localize("content/parentNotPublished", new[] {parent.Name}), culture); else // culture not published - urls.Add(UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] { parent.Name }), culture)); + return UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] {parent.Name}), culture); } - private static bool DetectCollision(IContent content, string url, List urls, string culture, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService) + private static bool DetectCollision(IContent content, string url, string culture, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService, out UrlInfo urlInfo) { // test for collisions on the 'main' url var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute); @@ -151,9 +170,11 @@ namespace Umbraco.Web.Routing var pcr = publishedRouter.CreateRequest(umbracoContext, uri); publishedRouter.TryRouteRequest(pcr); + urlInfo = null; + if (pcr.HasPublishedContent == false) { - urls.Add(UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture)); + urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture); return true; } @@ -172,7 +193,7 @@ namespace Umbraco.Web.Routing l.Reverse(); var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")"; - urls.Add(UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture)); + urlInfo = UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture); return true; } diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs index 7ef1ec6453..572e4eb5a1 100644 --- a/src/Umbraco.Web/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Scheduling return true; // repeat } - var url = umbracoAppUrl + "/ping.aspx"; + var url = umbracoAppUrl.TrimEnd('/') + "/api/keepalive/ping"; var request = new HttpRequestMessage(HttpMethod.Get, url); var result = await _httpClient.SendAsync(request, token); diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 2faacf828d..b91b133ace 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -24,6 +24,8 @@ using System.Threading.Tasks; using Examine.LuceneEngine.Directories; using Umbraco.Core.Composing; using Umbraco.Core.Strings; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Trees; namespace Umbraco.Web.Search { @@ -51,6 +53,7 @@ namespace Umbraco.Web.Search // but greater that SafeXmlReaderWriter priority which is 60 private const int EnlistPriority = 80; + public override void Compose(Composition composition) { base.Compose(composition); diff --git a/src/Umbraco.Web/Search/SearchableTreeCollection.cs b/src/Umbraco.Web/Search/SearchableTreeCollection.cs index 86f4494353..38c329cafa 100644 --- a/src/Umbraco.Web/Search/SearchableTreeCollection.cs +++ b/src/Umbraco.Web/Search/SearchableTreeCollection.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Services; using Umbraco.Web.Trees; @@ -19,15 +21,17 @@ namespace Umbraco.Web.Search private Dictionary CreateDictionary(IApplicationTreeService treeService) { - var appTrees = treeService.GetAll().ToArray(); - var dictionary = new Dictionary(); + var appTrees = treeService.GetAll() + .OrderBy(x => x.SortOrder) + .ToArray(); + var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); var searchableTrees = this.ToArray(); - foreach (var searchableTree in searchableTrees) + foreach (var appTree in appTrees) { - var found = appTrees.FirstOrDefault(x => x.Alias == searchableTree.TreeAlias); + var found = searchableTrees.FirstOrDefault(x => x.TreeAlias.InvariantEquals(appTree.Alias)); if (found != null) { - dictionary[searchableTree.TreeAlias] = new SearchableApplicationTree(found.ApplicationAlias, found.Alias, searchableTree); + dictionary[found.TreeAlias] = new SearchableApplicationTree(appTree.ApplicationAlias, appTree.Alias, found); } } return dictionary; diff --git a/src/Umbraco.Web/Search/SearchableTreeCollectionBuilder.cs b/src/Umbraco.Web/Search/SearchableTreeCollectionBuilder.cs index 51f4b13f0a..2d668257ef 100644 --- a/src/Umbraco.Web/Search/SearchableTreeCollectionBuilder.cs +++ b/src/Umbraco.Web/Search/SearchableTreeCollectionBuilder.cs @@ -6,5 +6,8 @@ namespace Umbraco.Web.Search internal class SearchableTreeCollectionBuilder : LazyCollectionBuilderBase { protected override SearchableTreeCollectionBuilder This => this; + + //per request because generally an instance of ISearchableTree is a controller + protected override Lifetime CollectionLifetime => Lifetime.Request; } } diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 9aab30edae..c3ab7318a0 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -10,6 +10,7 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Examine; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Trees; using SearchResult = Examine.SearchResult; @@ -23,11 +24,18 @@ namespace Umbraco.Web.Search { private readonly IExamineManager _examineManager; private readonly UmbracoHelper _umbracoHelper; + private readonly ILocalizationService _languageService; + private readonly IEntityService _entityService; - public UmbracoTreeSearcher(IExamineManager examineManager, UmbracoHelper umbracoHelper) + public UmbracoTreeSearcher(IExamineManager examineManager, + UmbracoHelper umbracoHelper, + ILocalizationService languageService, + IEntityService entityService) { _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager)); _umbracoHelper = umbracoHelper ?? throw new ArgumentNullException(nameof(umbracoHelper)); + _languageService = languageService; + _entityService = entityService; } /// @@ -51,16 +59,22 @@ namespace Umbraco.Web.Search var sb = new StringBuilder(); string type; - var indexName = Constants.Examine.InternalIndexer; + var indexName = Constants.UmbracoIndexes.InternalIndexName; var fields = new[] { "id", "__NodeId" }; var umbracoContext = _umbracoHelper.UmbracoContext; - //TODO: WE should really just allow passing in a lucene raw query + //TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string + // manipulation for things like start paths, member types, etc... + //if (Examine.ExamineExtensions.TryParseLuceneQuery(query)) + //{ + + //} + switch (entityType) { case UmbracoEntityTypes.Member: - indexName = Constants.Examine.InternalMemberIndexer; + indexName = Constants.UmbracoIndexes.MembersIndexName; type = "member"; fields = new[] { "id", "__NodeId", "email", "loginName" }; if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && searchFrom.Trim() != "-1") @@ -72,13 +86,13 @@ namespace Umbraco.Web.Search break; case UmbracoEntityTypes.Media: type = "media"; - var allMediaStartNodes = umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(Current.Services.EntityService); - AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, Current.Services.EntityService); + var allMediaStartNodes = umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService); + AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, _entityService); break; case UmbracoEntityTypes.Document: type = "content"; - var allContentStartNodes = umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(Current.Services.EntityService); - AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, Current.Services.EntityService); + var allContentStartNodes = umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(_entityService); + AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, _entityService); break; default: throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); @@ -89,11 +103,43 @@ namespace Umbraco.Web.Search var internalSearcher = index.GetSearcher(); + if (!BuildQuery(sb, query, searchFrom, fields, type)) + { + totalFound = 0; + return Enumerable.Empty(); + } + + var raw = internalSearcher.CreateCriteria().RawQuery(sb.ToString()); + + var result = internalSearcher + //only return the number of items specified to read up to the amount of records to fill from 0 -> the number of items on the page requested + .Search(raw, Convert.ToInt32(pageSize * (pageIndex + 1))); + + totalFound = result.TotalItemCount; + + var pagedResult = result.Skip(Convert.ToInt32(pageIndex)); + + switch (entityType) + { + case UmbracoEntityTypes.Member: + return MemberFromSearchResults(pagedResult.ToArray()); + case UmbracoEntityTypes.Media: + return MediaFromSearchResults(pagedResult); + case UmbracoEntityTypes.Document: + return ContentFromSearchResults(pagedResult); + default: + throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); + } + } + + private bool BuildQuery(StringBuilder sb, string query, string searchFrom, string[] fields, string type) + { //build a lucene query: - // the __nodeName will be boosted 10x without wildcards - // then __nodeName will be matched normally with wildcards + // the nodeName will be boosted 10x without wildcards + // then nodeName will be matched normally with wildcards // the rest will be normal without wildcards + var allLangs = _languageService.GetAllLanguages().Select(x => x.IsoCode.ToLowerInvariant()).ToList(); //check if text is surrounded by single or double quotes, if so, then exact match var surroundedByQuotes = Regex.IsMatch(query, "^\".*?\"$") @@ -102,15 +148,14 @@ namespace Umbraco.Web.Search if (surroundedByQuotes) { //strip quotes, escape string, the replace again - query = query.Trim(new[] { '\"', '\'' }); + query = query.Trim('\"', '\''); query = Lucene.Net.QueryParsers.QueryParser.Escape(query); //nothing to search if (searchFrom.IsNullOrWhiteSpace() && query.IsNullOrWhiteSpace()) { - totalFound = 0; - return new List(); + return false; } //update the query with the query term @@ -119,10 +164,9 @@ namespace Umbraco.Web.Search //add back the surrounding quotes query = string.Format("{0}{1}{0}", "\"", query); - //node name exactly boost x 10 - sb.Append("+(__nodeName: ("); - sb.Append(query.ToLower()); - sb.Append(")^10.0 "); + sb.Append("+("); + + AppendNodeNamePhraseWithBoost(sb, query, allLangs); foreach (var f in fields) { @@ -143,8 +187,7 @@ namespace Umbraco.Web.Search //nothing to search if (searchFrom.IsNullOrWhiteSpace() && trimmed.IsNullOrWhiteSpace()) { - totalFound = 0; - return new List(); + return false; } //update the query with the query term @@ -154,24 +197,12 @@ namespace Umbraco.Web.Search var querywords = query.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - //node name exactly boost x 10 - sb.Append("+(__nodeName:"); - sb.Append("\""); - sb.Append(query.ToLower()); - sb.Append("\""); - sb.Append("^10.0 "); - - //node name normally with wildcards - sb.Append(" __nodeName:"); - sb.Append("("); - foreach (var w in querywords) - { - sb.Append(w.ToLower()); - sb.Append("* "); - } - sb.Append(") "); + sb.Append("+("); + AppendNodeNameExactWithBoost(sb, query, allLangs); + AppendNodeNameWithWildcards(sb, querywords, allLangs); + foreach (var f in fields) { //additional fields normally @@ -195,26 +226,69 @@ namespace Umbraco.Web.Search sb.Append("+__IndexType:"); sb.Append(type); - var raw = internalSearcher.CreateCriteria().RawQuery(sb.ToString()); + return true; + } - var result = internalSearcher - //only return the number of items specified to read up to the amount of records to fill from 0 -> the number of items on the page requested - .Search(raw, Convert.ToInt32(pageSize * (pageIndex + 1))); + private void AppendNodeNamePhraseWithBoost(StringBuilder sb, string query, IEnumerable allLangs) + { + //node name exactly boost x 10 + sb.Append("nodeName: ("); + sb.Append(query.ToLower()); + sb.Append(")^10.0 "); - totalFound = result.TotalItemCount; - - var pagedResult = result.Skip(Convert.ToInt32(pageIndex)); - - switch (entityType) + //also search on all variant node names + foreach (var lang in allLangs) { - case UmbracoEntityTypes.Member: - return MemberFromSearchResults(pagedResult.ToArray()); - case UmbracoEntityTypes.Media: - return MediaFromSearchResults(pagedResult); - case UmbracoEntityTypes.Document: - return ContentFromSearchResults(pagedResult); - default: - throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); + //node name exactly boost x 10 + sb.Append($"nodeName_{lang}: ("); + sb.Append(query.ToLower()); + sb.Append(")^10.0 "); + } + } + + private void AppendNodeNameExactWithBoost(StringBuilder sb, string query, IEnumerable allLangs) + { + //node name exactly boost x 10 + sb.Append("nodeName:"); + sb.Append("\""); + sb.Append(query.ToLower()); + sb.Append("\""); + sb.Append("^10.0 "); + //also search on all variant node names + foreach (var lang in allLangs) + { + //node name exactly boost x 10 + sb.Append($"nodeName_{lang}:"); + sb.Append("\""); + sb.Append(query.ToLower()); + sb.Append("\""); + sb.Append("^10.0 "); + } + } + + private void AppendNodeNameWithWildcards(StringBuilder sb, string[] querywords, IEnumerable allLangs) + { + //node name normally with wildcards + sb.Append("nodeName:"); + sb.Append("("); + foreach (var w in querywords) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + sb.Append(") "); + //also search on all variant node names + foreach (var lang in allLangs) + { + //node name normally with wildcards + sb.Append($"nodeName_{lang}:"); + sb.Append("("); + foreach (var w in querywords) + { + sb.Append(w.ToLower()); + sb.Append("* "); + } + sb.Append(") "); } } @@ -278,32 +352,33 @@ namespace Umbraco.Web.Search /// /// /// - private IEnumerable MemberFromSearchResults(ISearchResult[] results) + private IEnumerable MemberFromSearchResults(IEnumerable results) { - var mapped = Mapper.Map>(results).ToArray(); //add additional data - foreach (var m in mapped) + foreach (var result in results) { + var m = Mapper.Map(result); + //if no icon could be mapped, it will be set to document, so change it to picture if (m.Icon == "icon-document") { m.Icon = "icon-user"; } - - var searchResult = results.First(x => x.Id == m.Id.ToString()); - if (searchResult.Values.ContainsKey("email") && searchResult.Values["email"] != null) + + if (result.Values.ContainsKey("email") && result.Values["email"] != null) { - m.AdditionalData["Email"] = results.First(x => x.Id == m.Id.ToString()).Values["email"]; + m.AdditionalData["Email"] = result.Values["email"]; } - if (searchResult.Values.ContainsKey("__key") && searchResult.Values["__key"] != null) + if (result.Values.ContainsKey("__key") && result.Values["__key"] != null) { - if (Guid.TryParse(searchResult.Values["__key"], out var key)) + if (Guid.TryParse(result.Values["__key"], out var key)) { m.Key = key; } } + + yield return m; } - return mapped; } /// @@ -313,17 +388,17 @@ namespace Umbraco.Web.Search /// private IEnumerable MediaFromSearchResults(IEnumerable results) { - var mapped = Mapper.Map>(results).ToArray(); //add additional data - foreach (var m in mapped) + foreach (var result in results) { + var m = Mapper.Map(result); //if no icon could be mapped, it will be set to document, so change it to picture if (m.Icon == "icon-document") { m.Icon = "icon-picture"; } + yield return m; } - return mapped; } /// @@ -333,17 +408,28 @@ namespace Umbraco.Web.Search /// private IEnumerable ContentFromSearchResults(IEnumerable results) { - var mapped = Mapper.Map>(results).ToArray(); - //add additional data - foreach (var m in mapped) + var defaultLang = _languageService.GetDefaultLanguageIsoCode(); + + foreach (var result in results) { - var intId = m.Id.TryConvertTo(); + var entity = Mapper.Map(result); + + var intId = entity.Id.TryConvertTo(); if (intId.Success) { - m.AdditionalData["Url"] = _umbracoHelper.Url(intId.Result); + //if it varies by culture, return the default language URL + if (result.Values.TryGetValue(UmbracoContentIndex.VariesByCultureFieldName, out var varies) && varies == "1") + { + entity.AdditionalData["Url"] = _umbracoHelper.Url(intId.Result, defaultLang); + } + else + { + entity.AdditionalData["Url"] = _umbracoHelper.Url(intId.Result); + } } + + yield return entity; } - return mapped; } } diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index d2b94c815b..a09c6fc039 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -36,10 +36,12 @@ namespace Umbraco.Web.Trees public class ContentTreeController : ContentTreeControllerBase, ISearchableTree { private readonly UmbracoTreeSearcher _treeSearcher; + private readonly ActionCollection _actions; - public ContentTreeController(UmbracoTreeSearcher treeSearcher) + public ContentTreeController(UmbracoTreeSearcher treeSearcher, ActionCollection actions) { _treeSearcher = treeSearcher; + _actions = actions; } protected override int RecycleBinId => Constants.System.RecycleBinContent; @@ -61,14 +63,12 @@ namespace Umbraco.Web.Trees //Special check to see if it ia a container, if so then we'll hide children. var isContainer = entity.IsContainer; // && (queryStrings.Get("isDialog") != "true"); - var hasChildren = ShouldRenderChildrenOfContainer(entity); - var node = CreateTreeNode( entity, Constants.ObjectTypes.Document, parentId, queryStrings, - hasChildren); + entity.HasChildren); // set container style if it is one if (isContainer) @@ -127,7 +127,7 @@ namespace Umbraco.Web.Trees // we need to get the default permissions as you can't set permissions on the very root node var permission = Services.UserService.GetPermissions(Security.CurrentUser, Constants.System.Root).First(); - var nodeActions = Current.Actions.FromEntityPermission(permission) + var nodeActions = _actions.FromEntityPermission(permission) .Select(x => new MenuItem(x)); //these two are the standard items @@ -313,8 +313,7 @@ namespace Umbraco.Web.Trees private void AddActionNode(IUmbracoEntity item, MenuItemCollection menu, bool hasSeparator = false, bool convert = false, bool opensDialog = false) where TAction : IAction { - //fixme: Inject - var menuItem = menu.Items.Add(Services.TextService.Localize("actions", Current.Actions.GetAction().Alias), hasSeparator); + var menuItem = menu.Items.Add(Services.TextService.Localize("actions", _actions.GetAction().Alias), hasSeparator); if (convert) menuItem.ConvertLegacyMenuItem(item, "content", "content"); menuItem.OpensDialog = opensDialog; } diff --git a/src/Umbraco.Web/Trees/ISearchableTree.cs b/src/Umbraco.Web/Trees/ISearchableTree.cs index 4146bfaf45..3d82d548c8 100644 --- a/src/Umbraco.Web/Trees/ISearchableTree.cs +++ b/src/Umbraco.Web/Trees/ISearchableTree.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; +using Umbraco.Core.Composing; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Trees { - public interface ISearchableTree + public interface ISearchableTree : IDiscoverable { /// /// The alias of the tree that the belongs to diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 8533081dde..47cd1cb693 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -59,18 +59,15 @@ namespace Umbraco.Web.Trees /// protected override TreeNode GetSingleTreeNode(IEntitySlim entity, string parentId, FormDataCollection queryStrings) { - //Special check to see if it ia a container, if so then we'll hide children. - var isContainer = entity.IsContainer; // && (queryStrings.Get("isDialog") != "true"); - var node = CreateTreeNode( entity, Constants.ObjectTypes.Media, parentId, queryStrings, - entity.HasChildren && !isContainer); + entity.HasChildren); // entity is either a container, or a media - if (isContainer) + if (entity.IsContainer) { node.SetContainerStyle(); node.AdditionalData.Add("isContainer", true); diff --git a/src/Umbraco.Web/Trees/MemberTreeController.cs b/src/Umbraco.Web/Trees/MemberTreeController.cs index ae2ad1824d..39568e2efa 100644 --- a/src/Umbraco.Web/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTreeController.cs @@ -127,14 +127,14 @@ namespace Umbraco.Web.Trees if (id == Constants.System.Root.ToInvariantString()) { nodes.Add( - CreateTreeNode(Constants.Conventions.MemberTypes.AllMembersListId, id, queryStrings, Services.TextService.Localize("member/allMembers"), "icon-users", false, + CreateTreeNode(Constants.Conventions.MemberTypes.AllMembersListId, id, queryStrings, Services.TextService.Localize("member/allMembers"), "icon-users", true, queryStrings.GetValue("application") + TreeAlias.EnsureStartsWith('/') + "/list/" + Constants.Conventions.MemberTypes.AllMembersListId)); if (_isUmbracoProvider) { nodes.AddRange(Services.MemberTypeService.GetAll() .Select(memberType => - CreateTreeNode(memberType.Alias, id, queryStrings, memberType.Name, "icon-users", false, + CreateTreeNode(memberType.Alias, id, queryStrings, memberType.Name, "icon-users", true, queryStrings.GetValue("application") + TreeAlias.EnsureStartsWith('/') + "/list/" + memberType.Alias))); } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ac6e40623d..d00ebaaeb6 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -36,6 +36,7 @@ false latest + @@ -115,6 +116,7 @@ + @@ -529,9 +531,6 @@ - - ASPXCodeBehind - @@ -690,8 +689,8 @@ - - + + @@ -1171,9 +1170,6 @@ ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Default.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Default.aspx.cs deleted file mode 100644 index f1a05c1185..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Default.aspx.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Web; -using System.Web.Mvc; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; - -namespace umbraco -{ - /// - /// Summary description for _Default. - /// - public partial class _Default : System.Web.UI.Page - { - protected void Page_Load(object sender, System.EventArgs e) - { - //var mvcHandler = new MvcHandler() - //Server.TransferRequest(); - //Server.Transfer("~/Umbraco/Default"); - //Server.Transfer("umbraco.aspx"); - // Put user code to initialize the page here - } - - /// - /// Form1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlForm Form1; - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/endPreview.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/endPreview.aspx.cs deleted file mode 100644 index 822f346705..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/endPreview.aspx.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Web; -using System.Web.UI; -using Umbraco.Core; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web.PublishedCache; - -namespace umbraco.presentation -{ - public class endPreview : Page - { - protected void Page_Load(object sender, EventArgs e) - { - var request = (new HttpRequestWrapper(Request)); - - var previewToken = request.GetPreviewCookieValue(); - var service = Current.PublishedSnapshotService; - service.ExitPreview(previewToken); - - HttpContext.Current.ExpireCookie(Constants.Web.PreviewCookieName); - - var redir = Request.QueryString["redir"]; - Uri url = null; - - if (Uri.IsWellFormedUriString(redir, UriKind.Relative) == false - || redir.StartsWith("//") - || Uri.TryCreate(redir, UriKind.Relative, out url) == false) - { - Response.Redirect("/", true); - } - - Response.Redirect(url.ToString(), true); - } - } -} diff --git a/src/umbraco.sln b/src/umbraco.sln index 41bd8359c3..0bdcb53d99 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -14,13 +14,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{2849E9D4 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{FD962632-184C-4005-A5F3-E705D92FC645}" ProjectSection(SolutionItems) = preProject - ..\docs\BUILD.md = ..\docs\BUILD.md - ..\docs\CODE_OF_CONDUCT.md = ..\docs\CODE_OF_CONDUCT.md - ..\docs\CONTRIBUTING.md = ..\docs\CONTRIBUTING.md - ..\LICENSE.md = ..\LICENSE.md - ..\docs\PULL_REQUEST_TEMPLATE.md = ..\docs\PULL_REQUEST_TEMPLATE.md - ..\docs\README.md = ..\docs\README.md - ..\docs\V8_GETTING_STARTED.md = ..\docs\V8_GETTING_STARTED.md + ..\.github\BUILD.md = ..\.github\BUILD.md + ..\.github\CLEAR.md = ..\.github\CLEAR.md + ..\.github\CODE_OF_CONDUCT.md = ..\.github\CODE_OF_CONDUCT.md + ..\.github\CONTRIBUTING.md = ..\.github\CONTRIBUTING.md + ..\.github\CONTRIBUTING_DETAILED.md = ..\.github\CONTRIBUTING_DETAILED.md + ..\.github\CONTRIBUTION_GUIDELINES.md = ..\.github\CONTRIBUTION_GUIDELINES.md + ..\.github\PULL_REQUEST_TEMPLATE.md = ..\.github\PULL_REQUEST_TEMPLATE.md + ..\.github\README.md = ..\.github\README.md + ..\.github\REVIEW_PROCESS.md = ..\.github\REVIEW_PROCESS.md + ..\.github\V8_GETTING_STARTED.md = ..\.github\V8_GETTING_STARTED.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B5BD12C1-A454-435E-8A46-FF4A364C0382}" @@ -92,6 +95,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DocTools", "DocTools", "{53 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Tests.Benchmarks", "Umbraco.Tests.Benchmarks\Umbraco.Tests.Benchmarks.csproj", "{3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemplates", "{C7311C00-2184-409B-B506-52A5FAEA8736}" + ProjectSection(SolutionItems) = preProject + ..\.github\ISSUE_TEMPLATE\1_Bug.md = ..\.github\ISSUE_TEMPLATE\1_Bug.md + ..\.github\ISSUE_TEMPLATE\2_Feature_request.md = ..\.github\ISSUE_TEMPLATE\2_Feature_request.md + ..\.github\ISSUE_TEMPLATE\3_Support_question.md = ..\.github\ISSUE_TEMPLATE\3_Support_question.md + ..\.github\ISSUE_TEMPLATE\4_Documentation_issue.md = ..\.github\ISSUE_TEMPLATE\4_Documentation_issue.md + ..\.github\ISSUE_TEMPLATE\5_Security_issue.md = ..\.github\ISSUE_TEMPLATE\5_Security_issue.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -135,6 +147,7 @@ Global {5B03EF4E-E0AC-4905-861B-8C3EC1A0D458} = {227C3B55-80E5-4E7E-A802-BE16C5128B9D} {53594E5B-64A2-4545-8367-E3627D266AE8} = {FD962632-184C-4005-A5F3-E705D92FC645} {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} + {C7311C00-2184-409B-B506-52A5FAEA8736} = {FD962632-184C-4005-A5F3-E705D92FC645} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7A0F2E34-D2AF-4DAB-86A0-7D7764B3D0EC}