From fe1080dd64b09cd569a885962ab934a1428339ec Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Tue, 27 Jun 2023 10:33:46 +0200 Subject: [PATCH 01/34] Check the cases where there might be a custom image type, instead of an the default Umbraco image type (#14463) --- .../Controllers/MediaController.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index 50c54f420f..5648f3046b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -760,11 +760,25 @@ public class MediaController : ContentControllerBase break; } - // If media type is still File then let's check if it's an image. + // If media type is still File then let's check if it's an image or a custom image type. if (mediaTypeAlias == Constants.Conventions.MediaTypes.File && _imageUrlGenerator.IsSupportedImageFormat(ext)) { - mediaTypeAlias = Constants.Conventions.MediaTypes.Image; + if (allowedContentTypes.Any(mt => mt.Alias == Constants.Conventions.MediaTypes.Image)) + { + mediaTypeAlias = Constants.Conventions.MediaTypes.Image; + } + else + { + IMediaType? customType = allowedContentTypes.FirstOrDefault(mt => + mt.CompositionPropertyTypes.Any(pt => + pt.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper)); + + if (customType is not null) + { + mediaTypeAlias = customType.Alias; + } + } } } else From e86fb0eeb6e8c4c969683d5303373a83905ac91a Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Tue, 27 Jun 2023 13:35:01 +0200 Subject: [PATCH 02/34] Adding null check and descriptive error (#14469) --- src/Umbraco.Web.BackOffice/Controllers/MediaController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index 5648f3046b..a26934b3c1 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -579,9 +579,9 @@ public class MediaController : ContentControllerBase Directory.CreateDirectory(root); //must have a file - if (file.Count == 0) + if (file is null || file.Count == 0) { - return NotFound(); + return NotFound("No file was uploaded"); } //get the string json from the request From b716e98780074d20f693f6dc7507b332f985356b Mon Sep 17 00:00:00 2001 From: Russell Date: Wed, 31 May 2023 22:45:02 +1200 Subject: [PATCH 03/34] Change "Media Picker" in RTE to "Image Picker" (#14321) (cherry picked from commit 02206213373b77644d348bbaa52893b834d3dfc5) # Conflicts: # src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js --- .../src/common/services/tinymce.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index afb3fa1e84..584870e6e5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -664,7 +664,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s createMediaPicker: function (editor, callback) { editor.addButton('umbmediapicker', { icon: 'custom icon-picture', - tooltip: 'Media Picker', + tooltip: 'Image Picker', stateSelector: 'img[data-udi]', onclick: function () { From aa4a1b3498ac2b3a571e2b4f19007914956f0e9d Mon Sep 17 00:00:00 2001 From: Russell Date: Wed, 31 May 2023 22:46:02 +1200 Subject: [PATCH 04/34] Fix for User null from IP address "::1" has logged out (#14322) (cherry picked from commit dd59ef17f521aceed57219d5bb6537ce66b08789) --- .../Controllers/AuthenticationController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 7d49adaf39..1a55e164a6 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -624,7 +624,7 @@ public class AuthenticationController : UmbracoApiControllerBase await _signInManager.SignOutAsync(); _logger.LogInformation("User {UserName} from IP address {RemoteIpAddress} has logged out", - User.Identity == null ? "UNKNOWN" : User.Identity.Name, HttpContext.Connection.RemoteIpAddress); + result.Principal.Identity == null ? "UNKNOWN" : result.Principal.Identity.Name, HttpContext.Connection.RemoteIpAddress); var userId = result.Principal.Identity?.GetUserId(); SignOutSuccessResult args = _userManager.NotifyLogoutSuccess(User, userId); From 8f6370a4808e6a9fbd81a9187c65c2d92b8ac500 Mon Sep 17 00:00:00 2001 From: Miguel Pinto Date: Wed, 31 May 2023 16:39:37 +0200 Subject: [PATCH 05/34] Swapped the sortCreateDateAscending and sortCreateDateDescending translation values to match the intended behavior (#14315) (cherry picked from commit 76cb85224e058f0f50fbbf61e522d48325d5089f) --- src/Umbraco.Core/EmbeddedResources/Lang/bs.xml | 4 ++-- src/Umbraco.Core/EmbeddedResources/Lang/cs.xml | 4 ++-- src/Umbraco.Core/EmbeddedResources/Lang/da.xml | 4 ++-- src/Umbraco.Core/EmbeddedResources/Lang/de.xml | 4 ++-- src/Umbraco.Core/EmbeddedResources/Lang/en.xml | 4 ++-- src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml | 4 ++-- src/Umbraco.Core/EmbeddedResources/Lang/fr.xml | 4 ++-- src/Umbraco.Core/EmbeddedResources/Lang/it.xml | 4 ++-- src/Umbraco.Core/EmbeddedResources/Lang/nl.xml | 4 ++-- src/Umbraco.Core/EmbeddedResources/Lang/pt.xml | 2 ++ src/Umbraco.Core/EmbeddedResources/Lang/sv.xml | 4 ++-- src/Umbraco.Core/EmbeddedResources/Lang/tr.xml | 4 ++-- 12 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml b/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml index f7b763bdfa..c22086a81a 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml @@ -2238,8 +2238,8 @@ Da upravljate svojom web lokacijom, jednostavno otvorite Umbraco backoffice i po Neaktivan Ime (A-Z) Ime (Z-A) - Najnovije - Najstarije + Najstarije + Najnovije Zadnja prijava Nijedna korisnička grupa nije dodana Ako želite da onemogućite ovog dvofaktorskog provajdera, onda morate uneti kod prikazan na vašem uređaju za autentifikaciju: diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml b/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml index 09c679475d..26c7087c80 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml @@ -1869,8 +1869,8 @@ Neaktivní Jméno (A-Z) Jméno (Z-A) - Nejnovější - Nejstarší + Nejstarší + Nejnovější Poslední přihlášení Nebyly přidány žádné skupiny uživatelů diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index 6a496a9b8f..dd68711783 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -1927,8 +1927,8 @@ Mange hilsner fra Umbraco robotten Inaktiv Navn (A-Å) Navn (Å-A) - Nyeste - Ældste + Ældste + Nyeste Sidst logget ind Ingen brugere er blevet tilføjet Hvis du ønsker at slå denne totrinsbekræftelse fra, så skal du nu indtaste koden fra din enhed: diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/de.xml b/src/Umbraco.Core/EmbeddedResources/Lang/de.xml index 310516a6bb..4bcf80994e 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/de.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/de.xml @@ -2179,8 +2179,8 @@ Nicht aktiv Name (A-Z) Name (Z-A) - Newest - Oldest + Oldest + Newest Last login diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index bda3ffbfec..1d3f563c9c 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -2245,8 +2245,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Inactive Name (A-Z) Name (Z-A) - Newest - Oldest + Oldest + Newest Last login No user groups have been added If you wish to disable this two-factor provider, then you must enter the code shown on your authentication device: diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 46c7dad6cd..d38a8f0c06 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -2340,8 +2340,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Inactive Name (A-Z) Name (Z-A) - Newest - Oldest + Oldest + Newest Last login No user groups have been added If you wish to disable this two-factor provider, then you must enter the code shown on your authentication device: diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml index 1576cedbe4..55dad6e986 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml @@ -1910,8 +1910,8 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Inactif Nom (A-Z) Nom (Z-A) - Plus récent - Plus ancien + Plus ancien + Plus récent Dernière connexion diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/it.xml b/src/Umbraco.Core/EmbeddedResources/Lang/it.xml index 5e45248d48..b1ac0385e1 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/it.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/it.xml @@ -2319,8 +2319,8 @@ Per gestire il tuo sito web, è sufficiente aprire il backoffice di Umbraco e in Inattivi Nome (A-Z) Nome (Z-A) - - + + Ultimo login Non sono stati aggiunti gruppi di utenti diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml index 9c17715e50..0a39cf05a7 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml @@ -2056,8 +2056,8 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Inactief Naam (A-Z) Naam (Z-A) - Nieuwste - Oudste + Oudste + Nieuwste Laatste login Er zijn geen gebruikersgroepen toegevoegd diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/pt.xml b/src/Umbraco.Core/EmbeddedResources/Lang/pt.xml index f6cdb21208..29ae77a2d8 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/pt.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/pt.xml @@ -822,6 +822,8 @@ Você pode publicar esta página e todas suas sub-páginas ao selecionar pub Tipo de usuário Tipos de usuários Escrevente + Mais antigo + Mais recente Selecionar tudo diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml index ec52c0343c..d219464321 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml @@ -1001,8 +1001,8 @@ Skribent Din nuvarande historik Din profil - Nyast - Äldst + Äldst + Nyast Senaste login diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml index 1195378516..ebd284465f 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml @@ -1996,8 +1996,8 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey Etkin Değil Ad (AZ) Ad (ZA) - En yeni - En eski + En eski + En yeni Son giriş Hiçbir kullanıcı grubu eklenmedi From b8415ed7f07e76939bf020d71f561fa398fd2847 Mon Sep 17 00:00:00 2001 From: Paul Seal Date: Fri, 16 Jun 2023 09:31:09 +0100 Subject: [PATCH 06/34] Update the tour text to the correct text and to save confusion (cherry picked from commit 5073a26e7f58ef986a599940f82df5c1e1fefdb3) --- .../EmbeddedResources/Tours/getting-started.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json b/src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json index eba7e94d1e..502b1fe47e 100644 --- a/src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json +++ b/src/Umbraco.Web.BackOffice/EmbeddedResources/Tours/getting-started.json @@ -146,7 +146,7 @@ { "element": "#dialog [data-element='action-documentType']", "title": "Create Document Type", - "content": "

Click Document Type to create a new document type with a template. The template will be automatically created and set as the default template for this Document Type.

You will use the template in a later tour to render content.

", + "content": "

Click Document Type with Template to create a new document type with a template. The template will be automatically created and set as the default template for this Document Type.

You will use the template in a later tour to render content.

", "event": "click" }, { @@ -197,8 +197,8 @@ }, { "element": "[data-element~='editor-property-settings'] [data-element='editor-add']", - "title": "Add editor", - "content": "When you add an editor you choose what the input method for this property will be. Click Add editor to open the editor picker dialog.", + "title": "Select editor", + "content": "When you select an editor you choose what the input method for this property will be. Click Select editor to open the editor picker dialog.", "event": "click" }, { From b99e14e2880968b829b2597c7122cc4805f32092 Mon Sep 17 00:00:00 2001 From: alevak09 <73195407+alevak09@users.noreply.github.com> Date: Fri, 16 Jun 2023 10:49:22 +0200 Subject: [PATCH 07/34] Add Croatian Language (#14379) Croatian language translation of Umbraco backoffice and culture settings (cherry picked from commit 9f4d3e55257fa865c5b474d691243b8d5cbd0f5c) --- .../EmbeddedResources/Lang/hr.xml | 2829 +++++++++++++++++ 1 file changed, 2829 insertions(+) create mode 100644 src/Umbraco.Core/EmbeddedResources/Lang/hr.xml diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml new file mode 100644 index 0000000000..492eb3155a --- /dev/null +++ b/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml @@ -0,0 +1,2829 @@ + + + + alevak09 + https://github.com/alevak09 + + + Kultura i imena hostova + Audit zapis + Pregledaj čvor + Promijeni Tip Dokumenta + Kopiraj + Kreiraj + Izvezi + Kreiraj Paket + Kreiraj grupu + Obriši + Onemogući + Uredi postavke + Isprazni koš za smeće + Omogući + Izvezi Tip Dokumenta + Uvezi Tip Dokumenta + Uvezi Paket + Uređivanje na stranici + Izlaz + Premjesti + Obavijesti + Javni pristup + Objavi + Poništi objavu + Osvježi + Ponovna objava cijele stranice + Ukloni + Preimenuj + Vrati + Odaberite gdje ćete kopirati + Odaberite gdje ćete premjestiti + Odaberite gdje ćete uvesti + do u strukturi stabla ispod + Odaberite gdje želite kopirati odabrane stavke + Odaberite gdje želite premjestiti odabrane stavke + je premješteno u + je kopirana u + je obrisana + Dozvole + Vraćanje unazad + Pošalji na objavljivanje + Pošalji na prijevod + Postavi grupu + Sortiraj + Prevedi + Ažuriraj + Postavi dozvole + Otključaj + Kreirajte Predložak Sadržaja + Ponovo pošaljite pozivnicu + + + Sadržaj + Administracija + Struktura + Ostalo + + + Dopustite pristup za dodjelu kulture i imena hostova + Dopustite pristup za pregled dnevnika povijesti čvora + Dopustite pristup za pregled čvora + Dopustite pristup za promjenu Tipa Dokumenta za čvor + Dopustite pristup za kopiranje čvora + Dopustite pristup za kreiranje čvora + Dopustite pristup za brisanje čvora + Dopustite pristup za premještaj čvora + Dopustite pristup za postavljanje i promjenu javnog pristupa za čvor + Dopustite pristup za objavljivanje čvora + Dopustite pristup da poništavanje objave čvora + Dopustite pristup za promjenu dozvola za čvor + Dopustite pristup za vraćanje čvora na prethodno stanje + Dopustite pristup za slanje čvora na odobrenje prije objavljivanja + Dopustite pristup za slanje čvora na prijevod + Dopustite pristup za promjenu sortiranja čvorova + Dopustite pristup za prevođenje čvora + Dopustite pristup za spremanje čvora + Dopustite pristup za kreiranje Predloška Sadržaja + Dopustite pristup za podešavanje obavijesti za čvorove + + + Sadržaj + Info + + + Dozvola odbijena. + Dodaj novu domenu + Dodaj postojeću domenu + Ukloni + Nevažeći čvor. + Jedna ili više domena imaju nevažeći format. + Domena je već dodijeljena. + Jezik + Domena + Nova domena '%0%' je kreirana + Domena '%0%' je obrisana + Domena '%0%' je već dodijeljena + Domena '%0%' je ažurirana + Uredi trenutne domene + + + Naslijedi + Kultura + + ili naslijedite kulturu od roditeljskih čvorova. Također će se primijeniti
+ na trenutni čvor, osim ako se domena u nastavku ne primjenjuje.]]> +
+ Domene + + + Obriši odabir + Odaberi + Uradi nešto drugo + Podebljano + Odustani od uvlačenje odlomka + Umetni polje obrasca + Umetni grafički naslov + Uredi Html + Uvuci odlomak + Nakošeno + Centrirano + Poravnanje lijevo + Poravnanje desno + Umetni link + Umetni lokalni link (sidro) + Obična lista + Numerička lista + Umetni makro + Umetni sliku + Objavi i zatvori + Objavi sa potomcima + Uredite odnose + Povratak na listu + Spremi + Spremi i zatvori + Spremi i objavi + Spremi i pošalji na odobrenje + Spremi prikaz liste + Zakazivanje objave + Spremi i pregledaj + Pregled je onemogućen jer nije dodijeljen predložak + Odaberi stil + Prikaži stilove + Umetni tablicu + Spremi i generiraj modele + Poništi + Vrati + Obriši tag + Odustani + Potvrdi + Više opcija za objavljivanje + Pošalji + + + Pregled za + Sadržaj je obrisan + Poništena objava Sadržaja + Poništena je objava sadržaja za jezike: %0% + Sadržaj je spremljen i objavljen + Sadržaj spremljen i objavljen za jezike: %0% + Sadržaj spremljen + Sadržaj spremljen za jezike: %0% + Sadržaj premješten + Sadržaj kopiran + Sadržaj vraćen + Sadržaj poslan na objavljivanje + Sadržaj poslan na objavljivanje za jezike: %0% + Sortiranje podređenih stavki je izvršio korisnik + %0% + Čišćenje je onemogućeno za verziju: %0% + Čišćenje je omogućeno za verziju: %0% + Kopiraj + Objavljeno + Objavi + Premjesti + Spremljeno + Spremi + Obriši + Poništi objavu + Vrati na stariju verziju + Pošalji na objavljivanje + Pošalji na objavljivanje + Sortiraj + Prilagođeno + Spremi + Spremi + Povijest (sve varijante) + + + Naziv mape ne može sadržavati nedozvoljene znakove. + Nije uspjelo brisanje stavke: %0% + + + Da li je objavljeno + Više o ovoj stranici + Alias + (kako bi opisali sliku preko telefona) + Alternativni linkovi + Kliknite za uređivanje ove stavke + Kreirao + Originalni autor + Ažurirao + Kreirano + Datum i vrijeme kreiranja ovog dokumenta + Tip dokumenta + Uređivanje + Ukloni na + Ova stavka je promijenjena nakon objavljivanja + Ova stavka nije objavljena + Posljednje objavljeno + Nema stavki za prikaz + Nema stavki za prikaz na listi. + Nije dodan sadržaj + Nijedan član nije dodan + Tip medija + Link do medijske stavke + Grupa članova + Uloga + Tip člana + Nisu napravljene nikakve promjene + Nije odabran datum + Naslov stranice + Ova medijska stavka nema vezu + Svojstva + Ovaj dokument je objavljen, ali nije vidljiv jer nadređeni '%0%' nije objavljen + Ova kultura je objavljena, ali nije vidljiva jer nije objavljena na nadređenom '%0%' + Ovaj dokument je objavljen, ali nije u predmemoriji + Nije moguće dohvatiti URL + Ovaj dokument je objavljen, ali njegov URL je u sukobu sa sadržajem %0% + Ovaj dokument je objavljen, ali njegov URL se ne može preusmjeriti + Objavi + Objavljeno + Objavljeno (promjene na čekanju) + Status objave + + %0% i sve stavke sadržaja ispod i time čineći njihov sadržaj javno dostupnim.]]> + + + Objavi na + Poništi objavu na + Obriši datum + Postavi datum + Sortiranje je ažurirano + Da biste sortirali čvorove, jednostavno pomaknite čvorove ili kliknite na jedno od zaglavlja kolona. Možete odabrati + više čvorova držeći tipku "shift" ili "control" dok birate + + Statistika + Naslov (opcionalno) + Alternativni tekst (opcionalno) + Natpis (opcionalno) + Tip + Poništi objavu + Neobjavljeno + Nije kreirano + Zadnje uređivano + Datum/vrijeme uređivanja ovog dokumenta + Ukloni datoteke + Kliknite ovdje kako bi uklonili sliku iz medijske stavke + Kliknite ovdje kako bi uklonili datoteku iz medijske stavke + Link na dokument + Član grupe + Nije član grupe + Dječje stavke + Meta + Ovo se označava kao sljedeće vrijeme na serveru: + + Što ovo znači?]]> + Jeste li sigurni da želite obrisati ovu stavku? + Svojstvo %0% koristi uređivač %1% koji nije podržan za Ugniježđeni + Sadržaj. + + Jeste li sigurni da želite obrisati sve stavke? + Za ovo svojstvo nisu konfigurirani tipovi sadržaja. + Dodajte tip elementa + Odaberi tip elementa + Odaberite grupu čija svojstva trebaju biti prikazana. Ako je ostavljeno prazno, + koristit će se prva grupa na tipu elementa. + + Unesite angular izraz za procjenu svake stavke za njeno + ime. Koristi + + za prikaz indeksa stavke + Odabrani tip elementa ne sadrži nijednu podržanu grupu (ovaj uređivač ne podržava kartice, promijenite ih u grupe ili koristite uređivač liste blokova). + Dodajte još jedan okvir za tekst + Uklonite ovaj okvir za tekst + Korijen Sadržaja + Uključite neobjavljeni sadržaj. + Ova vrijednost je skrivena. Ako vam je potreban pristup da vidite ovu vrijednost, obratite se + administratoru web stranice. + + Ova vrijednost je skrivena. + Koje jezike želite objaviti? + Koje jezike želite poslati na odobrenje? + Koje jezike želite zakazano objaviti? + Odaberite jezike za poništavanje objavljivanja. Poništavanje objavljivanja obaveznog jezika će + poništiti objavljivanje svih jezika. + + Sve nove varijante će biti spremljene. + Koje varijante želite objaviti? + Odaberite koje varijante želite spremiti. + Za objavljivanje su potrebne sljedeće varijante: + Nije spremno za objavljivanje + Spremno za objavljivanje? + Spremno za spremanje? + Poništi fokusnu točku + Pošalji na odobrenje + Odaberite datum i vrijeme za objavljivanje i/ili poništavanje objave stavke sadržaja. + Kreiraj novo + Zalijepi iz međuspremnika + Ova stavka je u košu za smeće + + + %0%
]]> + Prazno + Odaberite predložak sadržaja + Predložak sadržaja kreiran + Predložak sadržaja je kreiran od '%0%' + Drugi predložak sadržaja sa istim nazivom već postoji + Predložak sadržaja je unaprijed definiran sadržaj koji uređivač može odabrati da bi se koristio + kao osnova za kreiranje novog sadržaja + + + + Kliknite za prijenos + ili kliknite ovdje kako bi odaberali datoteke + Nije moguće učitati ovu datoteku, jer nema odobreni tip datoteke + Nije moguće učitati ovu datoteku, format medija sa nastavkom '%0%' nije dozvoljen + Nije moguće učitati ovu datoteku, jer nema važeći naziv datoteke + Maksimalna veličina datoteke je + Korijen medija + Kreiranje mape pod ID-om roditelja nije uspjelo %0% + Preimenovanje mape sa ID-om %0% nije uspjelo + Povucite i ispustite svoje datoteke u područje + + + Kreirajte novog člana + Svi članovi + Grupe članova nemaju dodatnih svojstva za uređivanje. + Dvostruka provjera autentičnosti + + + Kopiranje tipa sadržaja nije uspjelo + Premještanje tipa sadržaja nije uspjelo + + + Kopiranje tipa medija nije uspjelo + Premještanje tipa medija nije uspjelo + Automatski odabir + + + Kopiranje tipa člana nije uspjelo + + + Gdje želite kreirati novi %0% + Kreirajte stavku pod + Odaberite vrstu dokumenta za koju želite napraviti predložak sadržaja + Unesite naziv mape + Odaberite vrstu i naslov + + Dokument Tip unutar sekcije Postavke, uređivanjem Dozvoljeni tipovi podređenih čvorova unutar Dozvole.]]> + + Dokument Tip unutar sekcije Postavke.]]> + Odabrana stranica u stablu sadržaja ne dozvoljava kreiranje nijedne stranice ispod. + + Uredi dozvole za ovaj tip dokumenta + Kreiraj novi tip dokumenta + + Dokument Tip unutar sekcije Postavke, izmjenom Dozvoli kao root opcije unutar Dozvole.]]> + + Media Tip unutar sekcije Postavke, uređivanjem Dozvoljeni tipovi podređenih čvorova unutar Dozvole.]]> + Odabrani medij u stablu ne dopušta bilo koji drugi medij + kreiran ispod njega. + + Uredi dozvole za ovaj tip medija + Tip dokumenta bez predloška + Tip dokumenta sa predloškom + Definiranje podataka za stranicu sadržaja koja se može kreirati u stablu sadržaja i direktno je dostupana preko URL-a. + + Tip dokumenta + Definiranje podataka za komponentu sadržaja koju mogu kreirati urednici u + stablu sadržaja i može biti izabrana na drugim stranicama, ali nema direktan URL. + + Tip elementa + Definiranje sheme za ponavljajući skup svojstava, na primjer, u 'Bloku + Uređivaču svojstava Lista' ili 'Ugniježđeni sadržaj'. + + Kompozicija + Definiranje višenamjenski skup svojstava koja se mogu uključiti u definiciju + više drugih vrsta dokumenata. Na primjer, skup 'Common Page Settings'. + + Mapa + Koristi se za organiziranje tipova dokumenata, sastava i tipova elemenata kreiranih u ovome + Stablo vrste dokumenta. + + Nova mapa + Novi tip podatka + Nova JavaScript datoteka + Novi prazan djelomični prikaz + Novi djelomični prikaz za makro + Novi djelomični prikaz iz isječka + Novi djelomični prikaz za makro iz isječka + Novi djelomični prikaz za makro (bez makroa) + Nova CSS datoteka + Nova Rich Text Editor CSS datoteka + + + Pregledajte svoju web stranicu + - Sakrij + Ako se Umbraco ne otvara, možda ćete morati dozvoliti skočne prozore sa ove stranice + je otvoren u novom prozoru + Ponovno pokreni + Posjetite + Dobrodošli + + + Ostani + Poništite promjene + Imate ne spremljene promjene + Jeste li sigurni da želite izaći s ove stranice? - imate ne spremljene promjene + Objavljivanjem će odabrane stavke biti vidljive na stranici. + Poništavanje objavljivanja će ukloniti odabrane stavke i sve njihove potomke sa + stranice. + + Poništavanje objavljivanja će ukloniti ovu stranicu i sve njene potomke sa stranice. + Imate ne spremljene promjene. Promjenom vrste dokumenta odbacit će se promjene. + + + Završeno + Obrisana %0% stavka + Obrisano %0% stavki + Obrisana %0% od %1% stavka + Obrisano %0% od %1% stavki + Objavljeno %0% stavka + Objavljeno %0% stavki + Objavljeno %0% od %1% stavka + Objavljeno %0% od %1% stavki + Neobjavljeno za %0% stavku + Neobjavljeno za %0% stavki + Neobjavljeno za %0% od %1% stavku + Neobjavljeno za %0% od %1% stavki + Premještena %0% stavka + Premješteno %0% stavki + Premješteno %0% od %1% stavku + Premješteno %0% od %1% stavki + Kopirana %0% stavka + Kopirano %0% stavki + Kopirano %0% od %1% stavku + Kopirano %0% od %1% stavki + + + Naslov linka + Link + Sidro / querystring + Naziv + Upravljanje nazivima domena + Zatvorite ovaj prozor + Jeste li sigurni da želite obrisati + %0% od %1% stavki]]> + Jeste li sigurni da želite onemogućiti + Jeste li sigurni da želite ukloniti + %0%]]> + Jeste li sigurni? + Jeste li sigurni? + Izreži + Uredi stavku iz rječnika + Uredi jezik + Uredite odabrane medije + Umetni lokalnu vezu + Umetni znak + Umetni grafički naslov + Umetni sliku + Umetni link + Klikni za dodavanje makro + Umetni tablicu + Ovo će obrisati jezik + Promjena kulture jezika može biti skupa operacija i rezultirat će promjenama u predmemoriji sadržaja i indeksima koji se rekonstruiraju + + Zadnje uređivano + Link + Interni link: + Kada koristite lokalni linkovi, umetnite "#" ispred linka + Otvoriti u novom prozoru? + Ovaj makro ne sadrži svojstva koja možete uređivati + Zalijepi + Uredi dozvole za + Postavi dozvole za + Postavi dozvole za %0% za grupu korisnika %1% + Odaberi grupe korisnika za koje želite postaviti dozvole + Stavke u košu za smeće se upravo brišu. Molimo vas da ne zatvarate ovaj prozor + dok se ova operacija odvija + + Koš za smeće je sada prazna + Kada se stavke obrišu iz koša za smeće, nestat će zauvijek + + regexlib.com web servis trenutno ima probleme u komunikaciji na koje ne možemo utjecati. Žao nam je zbog nastalih smetnji.]]> + Traži regular expression kako bi njime validirali polje unosa. Primjer: 'e-pošta, + 'poštanski broj', 'URL'. + + Ukloni makro + Obavezno polje + Stranica je ponovo indeksirana + Predmemorija web stranice je osvježena. Sav objavljeni sadržaj je sada ažuriran. Dok će sav + neobjavljen sadržaj ostati neobjavljen + + Predmemorija web stranice će biti osvježena. Svi objavljeni sadržaji bit će ažurirani, dok će sav + neobjavljeni sadržaj ostati neobjavljen. + + Broj kolona + Broj redova + Klikni na sliku za prikaz pune veličine + Odaberi stavku + Prikaži stavku predmemorije + Odnosi se na original + Uključiti potomke + Prijateljska zajednica + Link na stranicu + Otvara povezani dokument u novom prozoru ili kartici + Link do medija + Odaberi početni čvor sadržaja + Odaberi medije + Odaberi tip medija + Odaberi ikonu + Odaberi stavku + Odaberi vezu + Odaberi makro + Odaberi sadržaj + Odaberi tip sadržaja + Odaberi početni čvor medija + Odaberi člana + Odaberi grupu članova + Odaberi tip članova + Odaberi čvor + Odaberi jezike + Odaberi sekcije + Odaberi korisnika + Odaberi korisnike + Nema pronađenih ikona + Nema parametara za ovaj makro + Nema dostupnih makroa za umetanje + Vanjski provajderi prijave + Detalji iznimaka + Zapisnik + Unutarnja iznimka + Poveži svoj + Poništi povezivanje svoje veze + račun + Odaberi urednika + Odaberite predložak + Ovo će obrisati čvor i sve njegove jezike. Ako želite obrisati jedan jezik, poništite radije objavu na tom jeziku. + %0%.]]> + %0% iz grupe %1%]]> + Da, ukloni + Brišete izgled + Promjena izgleda će rezultirati gubitkom podataka za bilo koji postojeći sadržaj koji je zasnovan na ovoj konfiguraciji. + + + + Da bi uvezli stavku iz rječnika, pronađite ".udt" datoteku na svom računalu klikom na + gumb "Uvezi" (na sljedećem ekranu će se tražiti da potvrdite) + + Stavka iz rječnika ne postoji. + Nadređena stavka ne postoji. + Ne postoje stavke iz rječnika. + U ovoj datoteci nema stavki iz rječnika. + Nisu pronađene stavke iz rječnika. + Kreirajte stavku iz rječnika + + + %0%' ispod + ]]> + Kultura + + Pregled riječnika + + + Konfigurirani pretraživači + Prikazuje svojstva i alate za bilo koji konfigurirani pretraživač (npr. kao multi-indeksni pretraživač) + Vrijednosti polja + Status zdravlja + Status zdravlja indeksa i da li se može pročitati + Indeksi + Indeks info + Sadržaj u indeksu + Navodi svojstva indeksa + Upravljanje Examine-ovim indeksima + Omogućava vam pregled detalja svakog indeksa i pruža neke alate za upravljanje indeksima + Obnovi indeks + + Ovisno o količini sadržaja vaše web stranice, ovo bi moglo potrajati.
+ Nije preporučljivo obnavljati index-e tijekom velike posjećenosti vaše web stranice ili u vrijeme kada urednik uređuje sadržaj stranice. + ]]> +
+ Pretraživači + Pretražite indeks i pogledajte rezultate + Alati + Alati za upravljanje indeksima + polja + Indeks se ne može pročitati i morat će se ponovo obnoviti + Proces traje duže od očekivanog, provjerite Umbraco log zapis da vidite + da li je bilo grešaka tijekom ove operacije + + Ovaj indeks se ne može ponovo obnoviti jer mu nije dodijeljen + IIndexPopulator + + + Upišite svoje korisničko ime + Upišite svoju lozinku + Potvrdite lozinku + Imenujte %0%... + Upišite ime... + Upišite email... + Upišite korisničko ime... + Oznaka... + Upišite opis... + Upišite za pretragu... + Upišite za filtriranje... + Upišite da dodate oznake (pritisnite enter nakon svake oznake)... + Upišite vaš email + Upišite poruku... + Vaše korisničko ime je obično vaš email + #value ili ?key=value + Upišite alias... + Generiranje aliasa... + Kreiraj stavku + Uredi + Naziv + + + Kreirajte prilagođeni prikaz liste + Ukloni prilagođeni prikaz liste + Tip sadržaja, tip medija ili tip člana s ovim aliasom već postoji + + + Preimenovano + Upišite novi naziv mape + %0% je preimenovan u %1% + + + Dodajte vrijednost + Tip baze podataka + Uređivač svojstva GUID + Uređivač svojstva + Gumbi + Omogući napredne postavke za + Omogući kontekstni meni + Maksimalna zadana veličina umetnutih slika + Povezani stilovi + Prikaži oznaku + Širina i visina + Odaberite mapu za premještanje + do u strukturi stabla ispod + je premeštena ispod + + + Vaši podaci su spremljeni, ali prije nego što možete objaviti ovu stranicu postoje neke + greške koje prvo morate ispraviti: + + Trenutni provajder članstva ne podržava promjenu lozinke + (Omogući preuzimanje lozinke mora biti uključeno) + + %0% već postoji + Bilo je grešaka: + Bilo je grešaka: + Lozinka treba imati najmanje %0% znakova i sadržavati najmanje %1% + znakova koji nisu alfanumerički + + %0% mora biti cijeli broj + Polje %0% na kartici %1% je obavezno + %0% je obavezno polje + %0% na %1% nije u ispravnom formatu + %0% nije u ispravnom formatu + + + Primljena greška sa servera + Administrator je zabranio navedeni tip datoteke + NAPOMENA! Iako je CodeMirror omogućen konfiguracijom, on je onemogućen u + Internet Explorer-u jer nije dovoljno stabilan. + + Unesite i alias i ime na novu vrstu svojstva! + Postoji problem sa pristupom za čitanje/pisanje određenoj datoteci ili mapi + Greška pri učitavanju skripte parcijalnog prikaza (datoteka: %0%) + Unesite naslov + Molimo odaberite tip + Napravit ćete sliku veću od originalne veličine. Jeste li sigurni + da želite nastaviti? + + Početni čvor je obrisan, kontaktirajte svog administratora + Molimo označite sadržaj prije promjene stila + Nema dostupnih aktivnih stilova + Postavite kursor lijevo od dvije ćelije koje želite spojiti + Ne možete podijeliti ćeliju koja nije spojena. + Ovo svojstvo je nevažeće + + + O + Akcija + Akcije + Dodaj + Alias + Sve + Jeste li sigurni? + Nazad + Nazad na pregled + Rub + od + Odustani + Margina ćelije + Odaberi + Očisti + Zatvori + Zatvori prozor + Zatvori panel + Komentar + Potvrdi + Ograniči + Ograniči proporcije + Sadržaj + Nastavi + Kopiraj + Kreiraj + Baza podataka + Datum + Zadano + Obriši + Obrisano + Brisanje... + Dizajn + Riječnik + Dimenzije + Otkaži + Dolje + Preuzmi + Uredi + Uređeno + Elementi + Email + Greška + Polje + Pronađi + Prvi + Fokusna točka + Općenito + Grupe + Grupa + Visina + Pomoć + Sakrij + Povijest + Ikona + Id + Uvezi + Pretraži samo ovu mapu + Info + Unutrašnja margina + Umetni + Instaliraj + Nevažeće + Poravnaj + Oznaka + Jezik + Zadnji + Izgled + Linkovi + Učitavanje + Zaključano + Prijava + Odjava + Odjava + Makro + Obavezno + Poruka + Pomakni + Ime + Novo + Sljedeći + Ne + Ime čvora + od + Isključeno + OK + Otvori + Opcije + Uključeno + ili + Poredaj po + Lozinka + Putanja + Trenutak molim... + Prethodno + Svojstva + Pročitaj više + Ponovo izgradi + Email za primanje obrasca + Koš za smeće + Vaš koš za smeće je prazn + Osvježi + Preostalo + Izbriši + Preimenuj + Obnovi + Obavezno + Povratiti + Pokušaj ponovo + Dozvole + Planirano objavljivanje + Umbraco info + Traži + Žao nam je, ne možemo pronaći ono što tražite. + Nije dodana nijedna stavka + Server + Postavke + Prikaži + Prikaži stranicu nprilikom Slanja + Veličina + Sortiranje + Status + Potvrdi + Uspjeh + Tip + Ime tipa + Upišite za pretragu... + ispod + Gore + Ažuriraj + Nadogradi + Prenesi + URL + Korisnik + Korisničko ime + Vrijednost + Pogled + Dobrodošli... + Širina + Da + Mapa + Rezultati pretrage + Promijeni redosljed + Završeno sortiranje + Pregled + Promijeni lozinku + do + Prikaz liste + Spremanje... + trenutni + Ugradi + odabran + Ostalo + Članci + Videi + Avatar za + Zaglavlje + sistemsko polje + Posljednje ažurirano + + + Plava + + + Dodaj grupu + Dodaj svojstvo + Dodaj urednika + Dodaj predložak + Dodajte podređeni čvor + Dodaj dijete + Uredite tip podataka + Krećite se po odjeljcima + Prečice + Prikaži prečice + Uključi prikaz liste + Uključi dozvoli kao root + Redovi za komentiranje/dekomentiranje + Uklonite liniju + Kopiraj linije gore + Kopiraj linije dole + Pomakni linije gore + Pomakni linije dole + Općenito + Uređivač + Uključi dozvoli varijante kulture + + + Boja pozadine + Podebljano + Boja teksta + Font + Tekst + + + Stranica + + + Instalacija se ne može povezati s bazom podataka. + Nije moguće spremiti web.config datoteku. Molimo izmijenite konekcijski string + ručno. + + Vaša baza podataka je pronađena i identificirana je kao + Konfiguracija baze podataka + + Instaliraj za instalaciju Umbraco %0% baze podataka + ]]> + + + Dalje da nastavite.]]> + Baza podataka nije pronađena! Provjerite jesu li informacije u "konekcijskom string" u "web.config" datoteci ispravne.

+

Da nastavite, uredite "web.config" datoteku. (koristeći Visual Studio ili vaš omiljeni uređivač teksta), skorlajte do dna, dodajte konekcijski string za vašu bazu podataka u svojstvo nazvan "UmbracoDbDSN" i spremite datoteku.

+

+ Kliknite na gumb pokušaj ponovo kada završite.
+ + Više informacija o uređivanju web.config datoteke možete pronaći ovdje.

]]>
+ + Molimo kontaktirajte svog ISP-a ako je potrebno. + Ako instalirate na lokalnoj mašini ili serveru, možda će vam trebati informacije od administratora sistema.]]> + + Pritisnite nadogradnja za nadogradnju vaše baze podataka na Umbraco %0%

+

+ Ne brinite - nijedan sadržaj neće biti obrisan i sve će nastaviti raditi nakon toga! +

+ ]]>
+ Pritisnite Dalje da nastavite.]]> + + Dalje da nastavite sa čarobnjakom za konfiguraciju]]> + + Zadanu korisničku lozinku treba promijeniti!]]> + + Zadani korisnik je onemogućen ili nema pristup Umbraco-u!

Ne treba preduzimati nikakve daljnje radnje. Pritisnite Dalje da nastavite.]]> + + Zadana korisnička lozinka je uspješno promijenjena od instalacije!

Ne treba preduzimati nikakve daljnje radnje. Pritisnite Dalje da nastavite.]]> + Lozinka je promijenjena! + Započnite odlično, pogledajte naše uvodne video zapise + Klikom na sljedeći gumb (ili modifikacijom umbracoConfigurationStatus u web.config), + prihvaćate licencu za ovaj softver kao što je navedeno u polju ispod. Primijetite da se ova Umbraco distribucija + sastoji se od dvije različite licence, open source MIT licence za okvir i licence za besplatni softver Umbraco + koji pokriva korisničko sučelje. + + Još nije instalirano. + Zahvaćene datoteke i mape + Više informacija o postavljanju dozvola za Umbraco ovdje + Morate dodijeliti dozvole za izmjenu ASP.NET-a za sljedeće + datoteke/mape + + Vaše postavke dozvola su gotovo savršene!

+ Možete pokrenuti Umbraco bez problema, ali nećete moći instalirati pakete koji se preporučuju da biste u potpunosti iskoristili Umbraco.]]>
+ Kako riješiti + Kliknite ovdje da pročitate tekstualnu verziju + + video tutorijale o postavljanju dozvola foldera za Umbraco ili pročitajte tekstualnu verziju.]]> + Vaše postavke dozvola mogu biti problem! +

+ Možete pokrenuti Umbraco bez problema, ali nećete moći kreirati mape ili instalirati pakete koji se preporučuju da biste u potpunosti iskoristili Umbraco.]]>
+ Vaše postavke dozvola nisu spremne za Umbraco! +

+ Da biste pokrenuli Umbraco, morat ćete ažurirati postavke dozvola.]]>
+ Vaše postavke dozvola su savršene!

+ Spremni ste da pokrenete Umbraco i instalirate pakete!]]>
+ Rješavanje problema sa mapom + Pratite ovu vezu za više informacija o problemima sa ASP.NET i + kreiranje mapa + + Postavljanje dozvola za mape + + Želim početi od nule + naučite kako) + I dalje možete odabrati da kasnije instalirate Runway. Molimo idite na odjeljak Developer i odaberite Paketi. + ]]> + Upravo ste postavili čistu Umbraco platformu. Šta želite sljedeće učiniti? + Runway je instaliran + + Ovo je naša lista preporučenih modula, označite one koje želite da instalirate ili pogledajte punu listu modula + ]]> + Preporučuje se samo iskusnim korisnicima + Želim početi s jednostavnom web-stranicom + + "Runway" je jednostavna web stranica koja nudi neke osnovne tipove dokumenata i predloške. Instalacijski čarobnjak može postaviti Runway za vas automatski, + ali ga možete lako urediti, proširiti ili ukloniti. Nije potrebno i možete savršeno koristiti Umbraco i bez njega. Kako god, + Runway nudi laku osnovu zasnovanu na najboljim praksama za početak brže nego ikad. + Ako se odlučite za instalaciju Runway, opcionalo možete odabrati osnovne građevne blokove tzv. Runway Modules da poboljšate svoje Runway stranice. +

+ + Uključeno u Runway: Početna stranica, Stranica za početak, Stranica za instaliranje modula.
+ Dodatni moduli: Navigacija, Sitemap, Kontakt, Galerija. +
+ ]]>
+ Što je Runway + Korak 1/5: Prihvatite licencu + Korak 2/5: Konfiguracija baze podataka + Korak 3/5: Potvrđivanje dozvola za datoteke + Korak 4/5: Provjerite Umbraco sigurnost + Korak 5/5: Umbraco je spreman za početak + Hvala vam što ste odabrali Umbraco + Pregledajte svoju novu stranicu +Instalirali ste Runway, pa zašto ne biste vidjeli kako izgleda vaša nova web stranica.]]> + Dodatna pomoć i informacije +Potražite pomoć od naše nagrađivane zajednice, pregledajte dokumentaciju ili pogledajte nekoliko besplatnih videozapisa o tome kako napraviti jednostavnu stranicu, kako koristiti pakete i brzi vodič za terminologiju Umbraco]]> + Umbraco %0% je instaliran i spreman za upotrebu + /web.config datoteku i ažurirati svojstvo unutar AppSetting UmbracoConfigurationStatus na dnu do vrijednosti od '%0%'.]]> + započeti odmah klikom na "Pokreni Umbraco" gumb ispod.
Ako ste novi u Umbraco-u, +možete pronaći mnogo resursa na našim stranicama za početak.]]>
+ Pokreni Umbraco +Da bi upravljali svojom web lokacijom, jednostavno otvorite Umbraco backoffice i počnite dodavati sadržaj, ažurirati predloške i stilove ili dodati novu funkcionalnost]]> + Povezivanje s bazom podataka nije uspjelo. + Umbraco Verzija 3 + Umbraco Verzija 4 + Gledaj + Umbraco %0% za novu instalaciju ili nadogradnju sa verzije 3.0. +

+ Pritisnite "Dalje" da pokrenete čarobnjaka.]]>
+ + + Kod kulture + Naziv kulture + + + Bili ste u stanju mirovanja i automatski će doći do odjave + Obnovite sada da sačuvate svoj rad + + + Sretna super nedelja + Sretan divan ponedeljak + Sretan specifičan utorak + Sretna divna srijeda + Sretan gromoglasan četvrtak + Sretan zanimljiv petak + Sretna opuštena subota + Prijavite se u nastavku + Prijav sa + Isteklo je vrijeme sesije + + © 2001 - %0%
Umbraco.com

]]>
+ Zaboravljena lozinka? + E-mail poruka bit će poslana na navedenu adresu sa linkom za resetiranje lozinke + E-mail poruka s uputama za poništavanje lozinke će biti poslana na navedenu adresu ukoliko odgovara našoj evidenciji + Prikaži lozinku + Sakrij lozinku + Povratak na obrazac za prijavu + Molimo unesite novu lozinku + Vaša lozinka je ažurirana + Link na koji ste kliknuli je nevažeći ili je istekao + Umbraco: Reset lozinke + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Zatraženo je ponovno postavljanje lozinke +

+

+ Vaše korisničko ime za prijavu na Umbraco backoffice je: %0% +

+

+ + + + + + +
+ + Kliknite na ovaj link da poništite lozinku + +
+

+

Ukoliko ne možete kliknuti na link, kopirajte i zalijepite ovaj URL u prozor vašeg pretraživača:

+ + + + +
+ +%1% + +
+

+
+
+


+
+
+ + + ]]>
+ Umbraco: Sigurnosni kod + Vaš sigurnosni kod je: %0% + Posljednji korak + Omogućili ste 2-faktorsku autentifikaciju i morate potvrditi svoj identitet. + Molimo odaberite 2-faktor provajdera + Verifikacijski kod + Unesite verifikacijski kod + Unesen je nevažeći kod + + + Kontrolna ploča + Sekcije + Sadržaj + + + Odaberite stranicu iznad... + %0% je kopiran u %1% + Odaberite gdje dokument %0% treba kopirati ispod + %0% je premješten u %1% + Odaberite gdje dokument %0% treba premjestiti ispod + je odabrano kao korijen vašeg novog sadržaja, kliknite na 'Uredu' ispod. + Još nije odabran čvor, molimo odaberite čvor na gornjoj listi prije nego kliknete na 'Uredu' + Trenutni čvor nije dozvoljen pod odabranim čvorom zbog njegovog tipa + Trenutni čvor se ne može premjestiti na jednu od njegovih podstranica niti roditelj i odredište mogu biti isti + Trenutni čvor ne može postojati u korijenu + Radnja nije dozvoljena jer nemate dovoljna dopuštenja za 1 ili više djece + dokumenata. + + Povežite kopirane stavke s originalom + + + %0%]]> + Postavke obavijesti su spremljene za + + Sljedeći jezici su izmijenjeni %0% + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Pozdrav %0%, +

+

+ Ovo je automatiziran email koja vas obavještava da je zadatak '%1%' izvršen na stranici '%2%' od korisnika '%3%' +

+ + + + + + +
+ +
+EDIT
+
+

+

Sažetak izmjena:

+ %6% +

+

+ Ugodan dan!

+ Pozdrav od Umbraco robota +

+
+
+


+
+
+ + + ]]>
+ Sljedeći jezici su izmijenjeni:

+ %0% + ]]>
+ [%0%] Obavijesti o %1% izvedena na %2% + Obavijesti + + + Akcije + Kreirano + Kreiraj paket + + i odaberite paket. Umbraco paketi uglavnom imaju ".umb" ili ".zip" ekstenziju. + ]]> + Ovo će obrisati paket + Uključi sve podređene čvorove + Instalirano + Instalirani paketi + Uputstva za instalaciju + Ovaj paket nema prikaz konfiguracije + Još nije kreiran nijedan paket + Nijedan paket nije instaliran + + 'Paketi' u gornjem desnom uglu ekrana]]> + Sadržaj paketa + Licenca + Pretražite pakete + Rezultati za + Nismo mogli pronaći ništa za + Pokušajte potražiti drugi paket ili pregledajte kategorije + + Popularno + Promocije + Nova izdanja + ima + karma bodovi + Informacije + Vlasnik + Suradnici + Kreirano + Trenutna verzija + .NET verzija + Preuzimanja + Lajkovi + Kompatibilnost + Ovaj paket je kompatibilan sa sljedećim verzijama Umbraco-a, kako su + prijavili članovi zajednice. Potpuna kompatibilnost se ne može garantirati za dolje navedene verzije 100% + + Vanjske poveznice + Autor + Dokumentacija + Meta podaci paketa + Naziv paketa + Paket ne sadrži nikakve stavke +
+ Ovo možete bezbjedno ukloniti iz sistema klikom na "deinstaliraj paket".]]>
+ Opcije paketa + Pokrenite migracije paketa na čekanju + Readme paketa + Repozitorij paketa + Potvrdi deinstalaciju paketa + Paket je deinstaliran + Paket je uspješno deinstaliran + Deinstaliraj paket + + Bilješka: svi dokumenti, mediji itd. u zavisnosti od stavki koje uklonite, prestat će raditi i mogu dovesti do nestabilnosti sistema, + pa deinstalirajte sa oprezom. Ako ste u nedoumici, kontaktirajte autora paketa.]]> + Verzija paketa + Provjereno za rad na Umbraco Cloud + + + Zalijepi s punim formatiranjem (nije preporučljivo) + Tekst koji pokušavate zalijepiti sadrži posebne znakove ili formatiranje. Ovo bi moglo biti + uzrokovano kopiranjem teksta iz programa Microsoft Word. Umbraco može automatski ukloniti posebne znakove ili formatiranje, tako da + zalijepljeni sadržaj će biti prikladniji za web. + + Zalijepite kao običan tekst bez ikakvog oblikovanja + Zalijepi, ali ukloni oblikovanje (preporučeno) + + + Grupna zaštita + Ako želite dodijeliti pristup svim članovima određenih grupa članova + Morate kreirati grupu članova prije nego što možete koristiti grupnu autentifikaciju + Stranica sa greškom + Koristi se kada su ljudi prijavljeni, ali nemaju pristup + %0%]]> + %0% je sada zaštićen]]> + %0%]]> + Stranica za prijavu + Odaberite stranicu koja sadrži obrazac za prijavu + Uklonite zaštitu... + + %0%?]]> + Odaberite stranice koje sadrže obrazac za prijavu i poruke o greškama + %0%]]> + %0%]]> + Posebna zaštita članova + Ako želite dati pristup određenim članovima + + + + + + + + + + Uključite neobjavljene podstranice + Objavljivanje u toku - molimo pričekajte... + %0% od %1% stranica je objavljeno... + %0% je objavljeno + %0% i objavljene su podstranice + Objavi %0% i sve njegove podstranice + Objavi za objavu %0% i na taj način svoj sadržaj učiniti javno dostupnim.

+ Ovu stranicu i sve njene podstranice možete objaviti odabirom Uključi neobjavljene podstranice. + ]]>
+ + + Niste konfigurirali nijednu odobrenu boju + + + Možete odabrati samo stavke tipa: %0% + Odabrali ste stavku sadržaja koja je trenutno obrisana ili je u košu za smeće + Odabrali ste stavke sadržaja koje su trenutno obrisane ili su u košu za smeće + + + Izbrisana stavka + Odabrali ste medijsku stavku koja je trenutno obrisana ili je u košu za smeće + Odabrali ste medijske stavke koje su trenutno obrisane ili su u košu za smeće + Otpad + Otvorite u biblioteci medija + Promjena medijske stavke + Uredi %0% od %1% + Odbaci kreiranje? + + Izmijenili ste ovaj sadržaj. Jeste li sigurni da ga želite + odbaciti? + + Uklonite sve medije? + Međuspremnik + Nije dozvoljeno + Otvorite odabir medija + + + unesite vanjski link + izaberite internu stranicu + Naslov + Link + Otvori u novom prozoru + unesite natpis na ekranu + Unesite link + + + Resetirajte izrezivanje + Gotovo + Poništi izmjene + Korisnički definirano + + + Promjene + Kreirano + Trenutna verzija + + Crveni tekst bit će uklonjen u odabranoj verziji, zeleni tekst će biti dodan]]> + Nema razlike između trenutne verzije i odabrane verzije + Dokument je vraćen + Odaberite verziju koju želite usporediti sa trenutnom verzijom + Ovo prikazuje odabranu verziju kao HTML, ako želite vidjeti razliku između dvije + verzije u isto vrijeme, koristite pogled diff + + Vratite se na + Odaberite verziju + Pogled + + + Uredite datoteku skripte + + + Portirnica + Sadržaj + Kurir + Developer + Forme + Pomoć + Umbraco Konfiguracijski Čarobnjak + Mediji + Članovi + Newsletteri + Paketi + Marketplace + Postavke + Statistika + Prijevodi + Korisnici + + + Ture + Najbolji Umbraco video tutorijali + Posjetite our.umbraco.com + Posjetite umbraco.tv + Pogledajte naše besplatne video tutorijale + na Umbraco Learning Base + + + Zadani predložak + Da biste uvezli vrstu dokumenta, pronađite ".udt" datoteku na svom računalu klikom na + gumb "Uvezi" (na sljedećem ekranu će se tražiti da potvrdite) + + Naslov nove kartice + Tip čvora + Tip + Stilovi + Skripte + Kartica + Naslov kartice + Kartice + Glavni tip sadržaja je omogućen + Ovaj tip sadržaja koristi + Nema definiranih svojstava na ovoj kartici. Kliknite na vezu "dodaj novu nekretninu" na + vrh za kreiranje novog svojstva. + + Kreirajte odgovarajući predložak + Dodaj ikonu + + + Redoslijed sortiranja + Datum kreiranja + Sortiranje završeno. + Povucite različite stavke gore ili dolje ispod da postavite kako bi trebale biti raspoređene. Ili kliknite na + zaglavlja kolona za sortiranje cijele kolekcije stavki + + + + + Validacija + Greške u validaciji moraju biti ispravljene prije nego što se stavka može spremiti + Nije uspjelo + Spremljeno + Nedovoljne korisničke dozvole, operacija se ne može dovršiti + Otkazano + Operaciju je otkazao dodatak treće strane + Ova datoteka se učitava kao dio fasckile, ali kreiranje nove mape ovdje nije dozvoljeno + Kreiranje nove mape ovdje nije dozvoljeno + Objavljivanje je otkazao dodatak treće strane + Tip svojstva već postoji + Tip svojstva kreiran + Tip podatka: %1%]]> + Tip svojstva obrisan + Tip dokumenta spremljen + Kartica kreirana + Kartica je obrisana + Kartica sa id-em: %0% je obrisana + Stilovi nisu spremljeni + Stilovi spremljeni + Stilovi spremljeni bez ikakvih grešaka + Tip podatka spremljen + Stavka riječnika je spremljena + Objavljivanje nije uspjelo jer nadređena stranica nije objavljena + Sadržaj objavljen + i vidljivo na web stranici + Predložak sadržaja je spremljen + Promjene su uspješno spremljene + Sadržaj spremljen + Ne zaboravite objaviti da promjene budu vidljive + Poslano na odobrenje + Promjene su poslane na odobrenje + Medij spremljen + Medij spremljen bez ikakvih grešaka + Član spremljen + Svojstvo stilova spremljeno + Stilovi spremljeni + Predložak spremljen + Greška pri spremanju korisnika (provjerite log zapis) + Korisnik spremljen + Tip korisnika spremljen + Grupa korisnika spremljena + Kulture i imena hostova su spremljeni + Greška pri spremanju kultura i imena hostova + Datoteka nije spremljena + Datoteka nije mogla biti spremljena. Molimo provjerite dozvole za datoteke + Datoteke spremljene + Datoteka spremljena bez ikakvih grešaka + Jezik spremljen + Tip medija spremljen + Tip člana spremljen + Grupa članova spremljena + Druga grupa članova sa istim imenom već postoji + Predložak nije spremljen + Uvjerite se da nemate 2 predloška sa istim aliasom + Predložak spremljen + Predložak spremljen bez ikakvih grešaka! + Sadržaj nije objavljen + Parcijalni prikaz spremljen + Parcijalni prikaz spremljen bez ikakvih grešaka! + Parcijalni prikaz nije spremljen + Došlo je do greške prilikom spremanja datoteke. + Dozvole su spremljene za + Izbrisano je %0% grupa korisnika + %0% je obrisano + Omogućeno %0% korisnika + Onemogućeno %0% korisnika + %0% je sada omogućen + %0% je sada onemogućen + Grupe korisnika su postavljene + Otključano %0% korisnika + %0% je sada otključan + Član je izvezen u datoteku + Došlo je do greške prilikom izvoza člana + Korisnik %0% je obrisan + Pozovi korisnika + Pozivnica je ponovo poslana na %0% + Tip dokumenta je izvezen u datoteku + Došlo je do greške prilikom izvoza tipa dokumenta + Stavke iz riječnika su izvezene u datoteku + Došlo je do greške prilikom izvoza stavki rječnika + Sljedeće stavke iz rječnika su uvezene! + Domene nisu konfigurirane za višejezične stranice, molimo kontaktirajte administratora, + pogledajte log zapise za više informacija + + Nijedna domena nije konfigurirana za %0%, molimo kontaktirajte administratora + Vaše sistemske informacije su uspješno kopirane u međuspremnik + Nije moguće kopirati vaše sistemske informacije u međuspremnik + + + Dodaj stil + Uredi stil + Stilovi za uređivanje bogatog teksta + Definirajte stilove koji bi trebali biti dostupni u uređivaču obogaćenog teksta za ove + stilove + + Uredi stilove + Uredi svojstvo stilova + Ime prikazano u uređivaču odabira stilova + Pregled + Kako će tekst izgledati u uređivaču obogaćenog teksta. + Selektor + Koristite CSS sintaksu, npr. "h1" ili ".redHeader" + Stilovi + CSS koji treba primijeniti u uređivaču obogaćenog teksta, npr. "color:red;" + Kod + Uređivač + + + Produkcija.]]> + Brisanje predloška sa ID-om %0% nije uspjelo + Uredi predložak + Sekcije + Umetnite područje sadržaja + Umetnite čuvara mjesta u području sadržaja + Umetni + Odaberite što ćete umetnuti u svoj predložak + Stavka iz rječnika + Stavka rječnika je čuvar mjesta za prevodljiv dio teksta, koji + olakšava kreiranje dizajna za višejezične web stranice. + + Makro + + Makro je komponenta koja se može konfigurirati i odlična je za + višenamjenske dijelove vašeg dizajna, gdje vam je potrebna opcija za pružanje parametara, + kao što su galerije, obrasci i liste. + + Vrijednost + Prikazuje vrijednost polja sa trenutne stranice po aliasu s opcijama za izmjenu vrijednosti ili povratak na alternativne vrijednosti. + Parcijalni prikaz + + Parcijalni prikaz je zasebna datoteka predloška koja se može prikazati unutar drugog + predložka, odličan je za ponovnu upotrebu markupa ili za odvajanje složenih predložaka u zasebne datoteke. + + Glavni predložak + Nema glavnog predloška + Renderirajte podređeni predložak + @RenderBody(). + ]]> + Definirajte imenovanu sekciju + @section { ... }. Ovo se može prikazati u + određenom području nadređenog predloška, koristeći @RenderSection. + ]]> + Renderirajte imenovanu sekciju + @RenderSection(name). + Ovo prikazuje područje podređenog predloška koje je umotano u odgovarajuću @section [name]{ ... } definiciju. + ]]> + Naziv sekcije + Sekcija je obavezna + @section, u suprotnom se prikazuje greška. + ]]> + Generiranje upita + stavaka vraćeno, u + Želim + sav sadržaj + sadržaj tipa "%0%" + sa + moje web stranice + gdje + i + je + nije + prije + prije (uključujući odabrani datum) + poslije + poslije (uključujući odabrani datum) + jednako + nije jednako + sadrži + ne sadrži + veće od + veće ili jednako + manje od + manje ili jednako + Id + Naziv + Kreirano + Ažurirano + poredaj po + uzlazno + silazno + Predložak + + + Slika + Makro + Odaberite tip sadržaja + Odaberite izgled + Dodaj redak + Dodaj sadržaj + Ispusti sadržaj + Postavke su primijenjene + Ovaj sadržaj ovdje nije dozvoljen + Ovaj sadržaj je ovdje dozvoljen + Kliknite za ugradnju + Kliknite da umetnete sliku + Kliknite da umetnete makro + Pišite ovdje... + Raspored mreže + Izgledi su cjelokupno radno područje za uređivač mreže, obično vam je potreban samo jedan ili + dva različita izgleda + + Dodajte raspored mreže + Uredite raspored mreže + Prilagodite izgled postavljanjem širine kolona i dodavanjem dodatnih odjeljaka + Konfiguracije redova + Redovi su predefinirani za raspored vodoravno + Dodajte konfiguraciju reda + Uredite konfiguraciju reda + Podesite red postavljanjem širine ćelija i dodavanjem dodatnih ćelija + Nije dostupna dodatna konfiguracija + Kolone + Ukupan kombinirani broj kolona u rasporedu mreže + Postavke + Konfigurirajte koje postavke urednici mogu promijeniti + Stilovi + Konfigurirajte šta uređivači stilova mogu promijeniti + Dozvoli svim urednicima + Dozvoli sve konfiguracije redaka + Maksimalan broj stavki + Ostavite prazno ili postavite na 0 za neograničeno + Postavi kao zadano + Odaberite extra + Odaberite zadano + su dodani + Upozorenje + + Promjena imena konfiguracije reda će rezultirati gubitkom podataka za bilo koji postojeći sadržaj koji se temelji na ovoj konfiguraciji.

Izmjena samo oznake neće rezultirati gubitkom podataka.

]]>
+ Brišete konfiguraciju reda + + Brisanje imena konfiguracije reda će rezultirati gubitkom podataka za bilo koji postojeći sadržaj koji je zasnovan na ovome + konfiguraciju. + + Brišete izgled + Izmjena izgleda će rezultirati gubitkom podataka za bilo koji postojeći sadržaj koji je zasnovan + na ovoj konfiguraciji. + + + + Kompozicije + Grupa + Niste dodali nijednu grupu + Dodaj grupu + Naslijeđeno od + Dodaj svojstvo + Obavezna oznaka + Omogući prikaz liste + Konfigurira stavku sadržaja da prikaže njenu listu koja se može sortirati i pretraživati djecu, djeca neće biti prikazana u stablu + + Dozvoljeni predlošci + Odaberite koje predloške urednici mogu koristiti na sadržaju ove vrste + + Dozvoli kao korijen + Dozvolite urednicima da kreiraju sadržaj ovog tipa u korijenu stabla sadržaja. + + Dozvoljeni tipovi podređenih čvorova + Dozvolite da se sadržaj navedenih tipova kreira ispod sadržaja ovog tipa. + + Odaberite podređeni čvor + Naslijediti kartice i svojstva iz postojeće vrste dokumenta. Nove kartice bit će + dodane trenutnoj vrsti dokumenta ili spojene ako postoji kartica s identičnim imenom. + + Ovaj tip sadržaja se koristi u kompoziciji i stoga se ne može sam sastaviti. + + Nema dostupnih tipova sadržaja za upotrebu kao kompozicija. + Uklanjanje kompozicije će obrisati sve povezane podatke o svojstvu. Jednom kada spremite tip dokumenta, nema povratka. + + Napravi novi + Koristite postojeće + Postavke urednika + Konfiguracija + Da, izbriši + je premještena ispod + je kopirano ispod + Odaberite mapu za premještanje + Odaberite mapu za kopiranje + do u strukturi stabla ispod + Svi tipovi dokumenata + Svi dokumenti + Sve medijske stavke + korištenje ovog tipa dokumenta bit će trajno izbrisano, potvrdite da želite obrisati ove također. + korištenje ove vrste medija će biti trajno izbrisano, potvrdite da želite obrisati ove također. + + korištenje ove vrste člana će biti trajno izbrisano, potvrdite da želite obrisati ove također + i svi dokumenti koji koriste ovu vrstu + i sve medijske stavke koje koriste ovu vrstu + i svi članovi koji koriste ovaj tip + Član može uređivati + Dozvolite da ovu vrijednost svojstva da uređuje član na svojoj stranici profila + + Osjetljivi podaci + Sakrij ovu vrijednost svojstva od urednika sadržaja koji nemaju pristup pregledu + osjetljive informacije + + Prikaži na profilu člana + Dozvolite da se ova vrijednost svojstva prikaže na stranici profila člana + + kartica nema redoslijed sortiranja + Gdje se koristi ovaj sastav? + Ovaj sastav se trenutno koristi u sastavu sljedećih + tipa sadržaja: + + Dozvoli varijacije + Dozvolite varirati u zavisnosti od kulture + Dozvoli segmentaciju + Varijacije po kulturi + Varijacije po segmentima + Dozvolite urednicima da kreiraju sadržaj ove vrste na različitim jezicima. + Dozvolite urednicima da kreiraju sadržaj na različitim jezicima. + Dozvolite urednicima da kreiraju segmente ovog sadržaja. + Dozvolite varijaciju po kulturi + Dozvoli segmentaciju + Tip elementa + Je li tip elementa + Tip elementa je namijenjen za korištenje na primjer u ugniježđenom sadržaju, a ne u stablu. + + Tip dokumenta se ne može promijeniti u tip elementa nakon što je naviknut + kreirati jednu ili više stavki sadržaja. + + Ovo nije primjenjivo za tip elementa + Napravili ste promjene on ovom svojstvu. Jeste li sigurni da ih želite odbaciti? + Izgled + Oznaka iznad (puna širina) + Uklanjate podređeni čvor + Uklanjanje podređenog čvora ograničit će opcije urednika da kreiraju drugačiji sadržaj + tipovi ispod čvora. + + korištenjem ovog uređivača bit će ažurirane nove postavke. + Brisanje povijesti + Dozvoli zaobilaženje postavki čišćenja globalne povijesti. + Neka sve verzije budu novije od dana + Čuvajte najnoviju verziju po danu danima + Spriječi čišćenje + Omogući čišćenje + BILJEŠKA! Čišćenje povijesnih verzija sadržaja onemogućeno je globalno. Ove postavke neće stupiti na snagu prije nego što se omogući.]]> + + + Dodaj jezik + ISO kod + Obavezan jezik + Svojstva na ovom jeziku moraju biti popunjena prije nego što se čvor može objaviti. + + Zadani jezik + Umbraco stranica može imati samo jedan zadani jezik. + Promjena zadanog jezika može rezultirati nedostatkom zadanog sadržaja. + Vraća se na + Nema zamjenskog jezika + Da se omogući višejezični sadržaj da se vrati na drugi jezik ako ne + bude prisutan na traženom jeziku, odaberite ga ovdje. + + Zamjenski jezik + niti jedan + + + Dodaj parameter + Uredi parameter + Unesite naziv makroa + Parametri + Definirajte parametre koji bi trebali biti dostupni kada koristite ovaj makro. + Odaberite parcijalni prikaz makro datoteke + + + Kreiranje modela + ovo može potrajati, ne brinite + Modeli generirani + Modeli ne mogu biti generirani + Generiranje modela nije uspjelo, pogledajte iznimku u log zapisima + + + Dodajte zadanu vrijednost + Zadana vrijednost + Rezervno polje + Zadana vrijednost + Veličina slova + Kodiranje + Odaberite polje + Pretvorite prijelome redaka + Zamjenjuje prijelome reda sa 'br' html oznakom + Prilagođena polja + Samo datum + Formatiraj kao datum + Kodirati kao HTML + Zamijenit će specijalne znakove njihovim HTML ekvivalentom. + Bit će umetnuto iza vrednosti polja + Bit će umetnuto ispred vrednosti polja + Mala slova + Nema + Izlazni uzorak + Umetnuti nakon polja + Umetnuti ispred polja + Rekurzivno + Da, neka bude rekurzivno + Standardna polja + Velika slova + Kodirati kao URL + Formatirat će posebne znakove u URL-ovima + Koristit će se samo kada su vrijednosti polja iznad prazne + Ovo polje će se koristiti samo ako je primarno polje prazno + Datum i vrijeme + + + Detalji prijevoda + Preuzmi XML DTD + Polja + Uključi podstranice + + Nije pronađen niti jedan korisnik prevoditelja. Molimo kreirajte korisnika prevoditelja prije nego počnete slati + sadržaj u prijevod + + Stranica '%0%' je poslana na prijevod + Pošaljite stranicu '%0%' na prijevod + Ukupno riječi + Prevedi na + Prijevod završen. + Možete pregledati stranice koje ste upravo preveli klikom ispod. + Prijevod nije uspio, XML datoteka je možda oštećena + Opcije prevođenja + Prevoditelj + Uvezite XML prijevod + + + Sadržaj + Predlošci sadržaja + Mediji + Pretraživač predmemorije + Koš za smeće + Kreirani paketi + Tipovi podataka + Riječnik + Instalirani paketi + Instaliraj skin + Instaliraj starter kit + Jezici + Instaliraj lokalni paket + Makroi + Tipovi medija + Članovi + Grupe članova + Uloge članova + Tipovi članova + Tipovi dokumenata + Tipovi relacija + Paketi + Paketi + Parcijalni pogledi + Parcijalni pregledi makro datoteka + Instaliraj iz repozitorija + Instaliraj Runway + Runway moduli + Skripte + Skripte + Stilovi + Predlošci + Log preglednik + Korisnici + Postavke + Predložak + Treća strana + + + Postoji nova verzija + %0% je spremno, kliknite ovdje za preuzimanje + Veza sa serverom je prekinuta + Pogreška prilikom provjeravanja ažuriranja. Za dodtne informacije provjerite trace-stack + + + Pristup + Na temelju dodijeljenih grupa i početnih čvorova, korisnik ima pristup sljedećim čvorovima + + Dodijeli pristup + Administrator + Polje kategorije + Korisnik kreiran + Promijeni lozinku + Promijeni sliku + Nova lozinka + Najmanje %0% znakova! + Trebalo bi biti najmanje %0% specijalnih znakova. + nije zaključan + Lozinka nije promijenjena + Potvrdite novu lozinku + Možete promijeniti svoju lozinku za pristup Umbraco Back Officeu tako da ispunite donji obrazac i kliknete gumb "Promjeni lozinku" + Kanal sadržaja + Kreiraj drugog korisnika + Kreirajte nove korisnike kako biste im omogućili pristup Umbraco-u. Kada se kreira novi korisnik, stvorit će se lozinka koju možete podijeliti s korisnikom. + + Polje kratkog opisa + Onemogući korisnika + Tip dokumenta + Urednik + Obavezno - unesite email adresu za ovog korisnika + Polje izvoda + Neuspjeli pokušaji prijave + Idite na korisnički profil + Dodajte grupe za dodjelu pristupa i dozvola + Pozovite drugog korisnika + Pozovite nove korisnike da im omogućite pristup Umbraco-u. Korisniku bit će poslan email s pozivnicom i informacijama o tome kako se prijaviti u Umbraco. Pozivnice traju 72 sata. + + Jezik + Postavite jezik koji ćete vidjeti u izbornicima i dijaloškim okvirima + Zadnji datum zaključavanja + Zadnja prijava + Lozinka zadnje promijenjena + Korisničko ime + Medijski početni čvor + Ograničite biblioteku medija na određeni početni čvor + Medijski početni čvorovi + Ograničite biblioteku medija na određene početne čvorove + Sekcije + Obavezno - unesite ime za ovog korisnika + Onemogućite pristup Umbraco-u + se još nije prijavio + Stara lozinka + Lozinka + Resetiraj lozinku + Vaša lozinka je promijenjena! + Lozinka je promijenjena + Molimo potvrdite novu lozinku + Unesite novu lozinku + Vaša nova lozinka ne može biti prazna! + Trenutna lozinka + Nevažeća trenutna lozinka + Postoji razlika između nove lozinke i potvrđene lozinke. Molimo pokušajte ponovo! + Potvrđena lozinka ne odgovara novoj lozinki! + Zamijenite dozvole podređenog čvora + Trenutno mijenjate dozvole za stranice: + Odaberite stranice da promijenite njihove dozvole + Ukloni sliku + Zadane dozvole + Detaljne dozvole + Postavite dozvole za određene čvorove + Profil + Pretražite svu djecu + Ograničite jezike kojima korisnici imaju pristup za uređivanje + Dodajte odjeljke da korisnicima omogućite pristup + Odaberite grupe korisnika + Nije odabran početni čvor + Nije odabran nijedan početni čvor + Početni čvor sadržaja + Ograničite stablo sadržaja na određeni početni čvor + Početni čvorovi sadržaja + Ograničite stablo sadržaja na određene početne čvorove + Korisnik zadnji put ažuriran + je kreiran + Novi korisnik je uspješno kreiran. Za prijavu na Umbraco koristite + lozinka ispod. + + Upravljanje korisnicima + Korisničko ime + Korisničke dozvole + Grupa korisnika + je pozvan + Novom korisniku je poslana pozivnica s detaljima o tome kako se prijaviti u Umbraco. + Pozdrav i dobrodošli u Umbraco! Jedna minuta bit će vam potrebna da postavite svoju lozinku i dodate svoju profilnu sliku. + Dobrodošli u Umbraco! Nažalost pozivnica je istekla. Molimo kontaktirajte svog administratora i od njega tražite da ju ponovno pošalje. + AAko postavite svoju fotografiju, drugi korisnici će vas lako prepoznati. Kliknite krug iznad da biste prenijeli svoju fotografiju. + Pisac + Promjeni + Vaš profil + Vaša nedavna povijest + Sesija ističe za + Pozovi korisnika + Kreiraj korisnika + Pošalji pozivnicu + Nazad na korisnike + Umbraco: Pozivnica + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Pozdrav %0%, +

+

+ Pozvani ste od %1% u Umbraco administraciju. +

+

+ Poruka od %1%: +
+%2% +

+ + + + + + +
+ + + + + + +
+ + Kliknite na ovaj link da prihvatite pozivnicu + +
+
+

Ukoliko ne možete kliknuti na link, kopirajte i zalijepite ovaj URL u prozor vašeg pretraživača:

+ + + + +
+ +%3% + +
+

+
+
+


+
+
+ +]]>
+ Ponovno slanje pozivnice... + Obriši korisnika + Jeste li sigurni da želite obrisati ovaj korisnički račun? + Sve + Aktivan + Onemogućen + Zaključan + Odobren + Pozvan + Neaktivan + Naziv (A-Z) + Naziv (Z-A) + Najstarije + Najnovije + Zadnja prijava + Nijedna korisnička grupa nije dodana + Ako želite onemogućiti ovaj dvofaktorski provajder, onda morate unjeti kod prikazan na vašem uređaju za autentifikaciju: + Ovaj dvofaktorski provajder je omogućen + Ovaj dvofaktorski provajder je sada onemogućen + Nešto je pošlo po zlu s pokušajem da se onemogući ovaj dvofaktorski provajder + Želite li onemogućiti ovaj dvofaktorski provajder za ovog korisnika? + + + Validacija + Nema validacije + Potvrdi kao adresu e-pošte + Potvrdite kao broj + Potvrdi kao URL + ...ili unesite prilagođenu validaciju + Polje je obavezno + Upišite prilagođenu poruku o grešci validacije (opcionalno) + Upišite regularni izraz + Upišite prilagođenu poruku o grešci validacije (opcionalno) + Morate dodati najmanje + možete jedino imati + Dodajte do + stavke + URL-ovi + Odabrani URL-ovi + odabrane stavke + Nevažeći datum + Nije broj + Nije važeća brojčana veličina koraka + Nevažeći email + Vrijednost ne može biti null + Vrijednost ne može biti prazna + Vrijednost je nevažeća, ne odgovara ispravnom uzorku + Prilagođena validacija + %1% više.]]> + %1% previše.]]> + Zahtjevi za količinu sadržaja nisu ispunjeni za jedno ili više područja. + + + + Vrijednost je postavljena na preporučenu vrijednost: '%0%'. + Očekivana vrijednost '%1%' za '%2%' u konfiguracijskoj datoteci '%3%', ali je pronađeno '%0%'. + Pronađena neočekivana vrijednost '%0%' za '%2%' u konfiguracijskoj datoteci '%3%'. + + + Makro greške su postavljene na '%0%'. + Greške makroa su postavljene na '%0%' što će spriječiti potpuno učitavanje nekih ili svih stranica + na vašem sajtu ako postoje greške u makroima. Ako ovo ispravite, vrijednost će biti postavljena na '%1%'. + + + + Certifikat Vaše web stranice je važeći. + Greška u validaciji certifikata: '%0%' + SSL certifikat vaše web stranice je istekao. + SSL certifikat vaše web stranice istjeće za %0% dana. + Greška pri pinganju URL-a %0% - '%1%' + Trenutno %0% pregledavate stranicu koristeći HTTPS protokol. + AppSetting 'Umbraco:CMS:Global:UseHttps' je postavljen na 'false' u + vašoj appSettings.json datoteci. Jednom kada pristupite ovoj stranici koristeći HTTPS protokol, to bi trebalo biti postavljeno na 'true'. + + Postavka aplikacije 'Umbraco:CMS:Global:UseHttps' je postavljena na '%0%' u vašoj + appSettings.json datoteci, vaši kolačići su %1% označeni kao sigurni. + + + Način kompilacije otklanjanja grešaka je onemogućen. + Način kompilacije za otklanjanje grešaka je trenutno omogućen. Preporučuje se da se + onemogućite ovu postavku prije korištenja u produkciji. + + + + + %0%.]]> + AppSetting 'Umbraco:CMS:WebRouting:UmbracoApplicationUrl' nije postavljen. + + X-Frame-Options koji se koristi za kontrolu da li neko mjesto može biti IFRAMED od strane drugog je pronađen.]]> + + X-Frame-Options koji se koristi za kontrolu da li neko mjesto može biti IFRAMED od strane drugog nije pronađen.]]> + + X-Content-Type-Options koji se koristi za zaštitu od ranjivosti MIME sniffinga je pronađen.]]> + + X-Content-Type-Options koji se koristi za zaštitu od ranjivosti MIME sniffinga nije pronađen.]]> + + Strict-Transport-Security, također poznat kao HSTS-header, je pronađen.]]> + + Strict-Transport-Security nije pronađeno.]]> + + Strict-Transport-Security, također poznat kao HSTS-header, je pronađen. Ovo zaglavlje ne bi trebalo biti prisutno na lokalnom hostu.]]> + + + Strict-Transport-Security nije pronađeno. Ovo zaglavlje ne bi trebalo biti prisutno na lokalnom hostu.]]> + + X-XSS-Protection je pronađeno.]]> + + X-XSS-Protection nije pronađeno.]]> + + + %0%.]]> + Nisu pronađena zaglavlja koja otkrivaju informacije o tehnologiji web stranice. + + U datoteci Web.config, system.net/mailsettings nije moguće pronaći. + U datoteci Web.config, system.net/mailsettings, host + nije konfiguriran. + + SMTP postavke su ispravno konfigurirane i usluga radi + kao što je očekivano. + + SMTP server konfiguriran sa hostom '%0%' i portom '%1%' ne može biti + dohvaćen. Provjerite jesu li SMTP postavke u datoteci Web.config, system.net/mailsettings ispravne. + + + %0%.]]> + + %0%.]]> + +

Rezultati zakazanih Umbraco provjera zdravlja koji se pokreću na %0% na %1% su sljedeći:

%2%]]>
+ Status provjere zdravlja Umbraco: %0% + Provjerite grupu + + Provjera zdravlja procjenjuje različita područja vaše web lokacije u pogledu postavki najboljih praksi, konfiguracija, potencijalnih problema itd. Možete jednostavno riješiti probleme pritiskom na gumb. + Možete dodati svoje zdravstvene preglede, pogledajte dokumentaciju za više informacija o prilagođenim zdravstvenim pregledima.

+ ]]> +
+ + + Onemogući URL praćenje + Omogući URL praćenje + Originalni URL + Preusmjerno na + Preusmjeravanje URL-ova + Sljedeći URL-ovi preusmjeravaju na ovu stavku sadržaja: + Nisu napravljena nikakva preusmjeravanja + Kada se objavljena stranica preimenuje ili premjesti, preusmjeravanje će automatski biti + napravljeno na novu stranicu. + + Preusmjeravanje uklonjeno. + Greška pri uklanjanju preusmjeravanja. + Ovo će ukloniti preusmjeravanje + Jeste li sigurni da želite onemogućiti praćenje URL-ovač? + URL praćenje je sada onemogućeno. + Greška pri onemogućavanju praćenja URL-ova, više informacija možete pronaći u vašem log zapisu. + URL praćenje je sada omogućeno. + Greška pri omogućavanju praćenja URL-ova, više informacija možete pronaći u vašem log zapisu. + + + Nema stavki iz rječnika za odabir + + + %0% preostalo znakova.]]> + %1% previše.]]> + + + Sadržaj u otpadu s ID-om: {0} povezan je s originalnim nadređenim sadržajem s ID-om: {1} + Medij u otpadu s ID-om: {0} povezan je s originalnim nadređenim medijem s ID-om: {1} + Nije moguće automatski vratiti ovu stavku + Ne postoji lokacija na kojoj se ova stavka može automatski vratiti. Možete ručno premjestiti stavku koristeći stablo ispod. + + je restauriran pod + + + Smjer + Roditelj djetetu + Bidirectional + Roditelj + Dijete + Broj + Relacija + Relacije + Kreirano + Komentar + Naziv + Nema relacija za ovu vrstu odnosa + Tip relacije + Relacije + Je zavisan + Da + Ne + + + Početak rada + Preusmjeravanje URL-ova + Sadržaj + Dobrodošli + Examine menadžment + Status stranice + Generator modela + Provjera zdravlja + Profilisanje + Početak rada + Instaliraj Umbraco Forms + + + Vrati se + Aktivan raspored: + Skoči na + grupa + prošao + upozorenje + neuspješno + prijedlog + Provjera prošla + Provjera nije uspjela + Otvorite backoffice pretragu + Otvori/Zatvori pomoć za backoffice + Opcije otvaranja/zatvaranja profila + Postavite kulturu i imena hostova za %0% + Kreirajte novi čvor ispod %0% + Postavite ograničenja pristupa uključena %0% + Dozvole za postavljanje su uključene %0% + Promijenite redoslijed sortiranja za %0% + Kreirajte predložak sadržaja na osnovu %0% + Otvorite kontekstni meni za + Trenutni jezik + Prebaci jezik na + Kreirajte novi folder + Parcijalni pogled + Makro za pracijalni prikaz + Član + Tip podatka + Pretražite kontrolnu ploču za preusmjeravanje + Pretražite odjeljak korisničke grupe + Pretražite odjeljak korisnika + Kreiraj stavku + Kreiraj + Uredi + Naziv + Dodaj novi red + Pogledajte više opcija + Pogledajte više opcija + Potražite čvorove sadržaja, medijske čvorove itd. u backofficeu. + Kada su dostupni rezultati autodovršavanja, pritisnite strelice gore i dolje ili koristite + tipku tab i koristite tipku enter za odabir. + + Putanja: + Pronađeno u + Ima prijevod + Nedostaje prijevod + Stavke iz rječnika + Odaberite jednu od opcija za uređivanje čvora. + Izvršite akciju %0% na čvoru %1%. + Dodajte opis slike + Pretraži stablo sadržaja + Maksimalni iznos + + + Reference + Ovaj tip podataka nema reference. + Ova stavka nema reference. + Koristi se u tipovima dokumenata + Koristi se u tipovima medija + Koristi se u tipovima članova + Koristi + Stavke u upotrebi + Potomci u upotrebi + Ova stavka ili njeni potomci se koriste. Brisanje može dovesti do neispravnih veza na vašoj web stranici. + Ova stavka ili njeni potomci se koriste. Poništavanje objavljivanja može dovesti do neispravnih veza na vašoj web stranici. Molimo poduzmite odgovarajuće radnje. + Ova stavka ili njeni potomci se koriste. Stoga je brisanje onemogućeno. + Sljedeće stavke koje pokušavate %0% koriste drugi sadržaj. + + + Obriši spremljene pretrage + Razine loga + Označi sve + Odznači sve + Spremljene pretrage + Spremi pretragu + Unesite prijateljski naziv za vaš upit za pretragu + Filtriraj pretragu + Ukupno + Vrijeme + Razina + Uređaj + Poruka + Izuzetak + Svojstva + Pretraži pomoću Google-a + Pretraži ovu poruku pomoću Google-a + Pretraži pomoću Bing-a + Pretraži ovu poruku pomoću Bing-a + Pretraži Our Umbraco + Pretraži ovu poruku na Our Umbraco forumu i dokumentaciji + Pretraži Our Umbraco pomoću Google-a + Pretraži Our Umbraco forume pomoću Google-a + Pretraži Umbraco Source + Pretraži Umbraco source code on Github-u + Pretraži Umbraco Issues + Pretraži Umbraco Issues na Github-u + Obriši ovu pretragu + Pronađi logove sa ID-om zatjeva + Pronađi logove sa namespace-om + Pronađi logove sa nazivom uređaja + Otvori + Provjera + Svakih 2 sekunde + Svakih 5 sekundi + Svakih 10 sekundi + Svakih 20 sekundi + Svakih 30 sekundi + Provjera svakih 2s + Provjera svakih 5s + Provjera svakih 10s + Provjera svakih 20s + Provjera svakih 30s + + + Kopiraj %0% + %0% od %1% + Zbirka od %0% + Uklonite sve stavke + Očisti međuspremnik + + + Otvorite radnje svojstva + Zatvorite Property Actions + + + Osvježi status + Predmemorija + + + + Ponovo učitaj + Predmemorija baze podataka + + Obnova može biti skupa. + Koristite ga kada ponovno učitavanje nije dovoljno, a mislite da predmemorija baze podataka nije bila + pravilno generirana; što bi ukazivalo na neko kritično pitanje Umbraco. + ]]> + + Ponovo obnovi + Unutrašnjost + + ne morate koristiti. + ]]> + + Skupiti + Objavljeni status predmemorije + Predmemorije + + + Profiliranje performansi + + + Umbraco trenutno radi u načinu za otklanjanje grešaka. To znači da možete koristiti ugrađeni profiler performansi za procjenu performansi prilikom renderiranja stranica. +

+

+ Ako želite aktivirati profiler za određeno prikazivanje stranice, jednostavno dodajte umbDebug=true na string upita kada tražite stranicu. +

+

+ Ako želite da se profilator aktivira prema zadanim postavkama za sve prikaze stranica, možete koristiti prekidač ispod. + On će postaviti kolačić u vaš pretraživač, koji zatim automatski aktivira profiler. + Drugim riječima, profiler će biti aktivan samo po defaultu u vašen pretraživaču. +

+ ]]> +
+ Zadano aktivirajte profiler + Prijateljski podsjetnik + + + Nikada ne bi trebali dozvoliti da produkcijska lokacija radi u načinu za otklanjanje grešaka. Režim za otklanjanje grešaka se isključuje podešavanjem Umbraco:CMS:Hosting:Debug na false u appsettings.json, appsettings.{Environment}.json ili preko varijable okruženja. +

+ ]]> +
+ + + Umbraco trenutno ne radi u načinu za otklanjanje grešaka, tako da ne možete koristiti ugrađeni profiler. Ovako bi trebalo da bude za proizvodnu lokaciju. +

+

+ Režim za otklanjanje grešaka se uključuje podešavanjem Umbraco:CMS:Hosting:Debug na true u appsettings.json, appsettings.{Environment}.json ili preko varijable okruženja. +

+ ]]> +
+ + + Sati Umbraco trening videa udaljeni su samo jedan klik + + Želite naučiti Umbraco? Provedite nekoliko minuta učeći najbolje prakse gledajući jedan od ovih videozapisa o korištenju Umbraco-a. I posjetite umbraco.tv za još više Umbraco videa

+ ]]> +
+ + Želite savladati Umbraco? Provedite nekoliko minuta učeći najbolje prakse gledajući jedan od ovih videozapisa o korištenju Umbraco-a Umbraco Learning Base Youtube kanal. Ovdje možete pronaći gomilu video materijala koji pokriva mnoge aspekte Umbraco-a.

+ ]]> +
+ Za početak + + + Počni ovdje + Ovaj odjeljak sadrži blokove za izgradnju vaše Umbraco stranice. Slijedite dolje + veze da saznate više o radu sa stavkama u odjeljku Postavke + + Saznajte više + + u odjeljku Dokumentacija na Our Umbraco + ]]> + + + Forumu zajednice + ]]> + + + video tutorijale na Umbraco Learning Base + ]]> + + + alatima za povećanje produktivnosti i komercijalna podrška + ]]> + + + obuke i certifikacije + ]]> + + + + Dobrodošli u The Friendly CMS + Hvala vam što ste odabrali Umbraco - mislimo da bi ovo mogao biti početak nečeg divnog. Iako se u početku može činiti neodoljivim, učinili smo puno da učenje bude što lakše i brže što je moguće više. + + + + Umbraco Forms + Kreirajte obrasce pomoću intuitivnog 'drag and drop' sučelja. Od jednostavnih kontakt obrazaca + koji šalje e-mailove do naprednih obrazaca koji se mogu integrirati sa CRM sustavima. Vašim klijentima će se svidjeti! + + + + Odaberite tip elementa + Priložite postavke na tip elementa + Odaberite prikaz + Odaberite stil + Odaberite sličicu + Kreirajte novi tip elementa + Prilagođeni stil + Dodaj stil + Izgled bloka + Modeli podataka + Izgled kataloga + Boja pozadine + Boja ikone + Model sadržaja + Oznaka + Prilagođeni prikaz + Prikaži opis prilagođenog prikaza + Zamjenite način na koji se ovaj blok pojavljuje u korisničkom sučelju backofficea. Odaberite .html datoteku + koja sadrži vaš dizajn. + + Model postavki + Veličina uređivača preklapanja + Dodaj prilagođeni prikaz + Dodaj postavke + + %0%?]]> + + %0%?]]> + Sadržaj ovog bloka bit će i dalje prisutan, uređivanje ovog sadržaja + više neće biti dostupno i bit će prikazan kao nepodržani sadržaj. + + + %0% i sve konfiguracije ovog bloka?]]> + Sadržaj ovih blokova će i dalje biti prisutan, uređivanje ovog sadržaja + više neće biti dostupan i bit će prikazan kao nepodržani sadržaj. + + + Ne može se uređivati jer tip elementa ne postoji. + Sličica + Dodaj sličicu + Kreiraj prazno + Međuspremnik + Postavke + Napredno + Sakrij uređivač sadržaja + Sakrij gumb za uređivanje sadržaja i uređivač sadržaja iz preklapanja Block Editor. + Inline uređivanje + Omogućava inline uređivanje za prvo svojstvo. Dodatna svojstva se mogu uređivati u prekrivaču. + Izmijenili ste ovaj sadržaj. Jeste li sigurni da ih želite odbaciti? + Odbaciti kreiranje? + + Greška! + Tip elementa ovog bloka više ne postoji + Dodaj sadržaj + Dodaj %0% + Svojstvo '%0%' koristi uređivač '%1%' koji nije podržan u blokovima. + Postavite fokus na blok kontejnera + Identifikacija + Validacija + %0% mora biti prisutan barem %2% puta.]]> + %0% mora biti maksimalno prisutan %3% puta.]]> + Broj blokova + Dozvolite samo određene tipove blokova + Dozvoljene vrste blokova + Definirajte tipove blokova koji su dozvoljeni u ovom području i opcionalo koliko svakog tipa treba biti prisutan. + Jeste li sigurni da želite obrisati ovo područje? + Svi blokovi koji su trenutno kreirani unutar ovog područja bit će obrisani. + Opcije rasporeda + Strukturno + Opcije veličine + Definirajte jednu ili više opcija veličine, ovo omogućava promjenu veličine bloka + Definirajte jednu ili više opcija veličine, ovo omogućava promjenu veličine bloka + Definirajte različit broj kolona preko kojih ovaj blok može se protezati. Ovo ne sprječava postavljanje blokova u područja s manjim rasponom kolona. + Dostupni rasponi redova + Definirajte raspon redova rasporeda preko kojih se ovaj blok može protezati. + Dozvolite u korijenu + Učinite ovaj blok dostupnim u korijenu izgleda. + Dozvolite u područjima + Učinite ovaj blok dostupnim prema zadanim postavkama unutar područja drugih blokova (osim ako za ova područja nisu postavljene eksplicitne dozvole). + Prema zadanim postavkama, svi tipovi blokova su dozvoljeni u području. Koristite ovu opciju da dozvolite samo odabrane tipove. + Područja + Mrežne kolone za područja + Definirajte koliko će stupaca biti dostupno za područja. Ako nije definiran, koristit će se broj kolona definiranih za cijeli izgled. + Područja + Da biste omogućili ugniježđenje blokova unutar ovog bloka, definirajte jedno ili više područja. Područja slijede raspored definiran njihovom vlastitom konfiguracijom stupca mreže. 'Raspon kolone' i 'raspon reda' za svako područje može se podesiti korištenjem okvira za rukovanje skalom u donjem desnom uglu odabranog područja. + %0% nije dozvoljeno na ovom mjestu.]]> + Zadani raspored stilova + Nedozvoljeni sadržaj je odbijen + + + + + Povucite za povečanje + Kreiraj oznaku gumba + Nadjačajte tekst oznake za dodavanje novog bloka u ovo područje, primjer: 'Dodaj widget' + Prikaži opcije promjene veličine + Dodaj blok + Dodaj grupu + Odaberi grupu ili blok + Postavite minimalni zahtjev + Postavite maksimalan zahtjev + Blok + Blok + Postavke + Područja + Napredno + Dozvole + Instalirajte uzorak konfiguracije + + Instaliraj + Način sortiranja + Završi način sortiranja + Ovaj alias područja mora biti jedinstven u usporedbi sa drugim područjima ovog bloka. + Konfiguriraj područje + Obriši područje + Dodajte opciju raspona %0% kolona + + + Što su predlošci sadržaja? + Predlošci sadržaja su unaprijed definirani sadržaj koji se može odabrati prilikom kreiranja novog + sadržaj čvora. + + Kako da kreiram predložak sadržaja? + + Postoje dva načina za kreiranje predloška sadržaja:

+
    +
  • Desnom tipkom miša kliknite čvor sadržaja i odaberite "Kreiraj predložak sadržaja" da bi kreirali novi predložak sadržaja.
  • +
  • Kliknite desnom tipkom miša na stablo predložaka sadržaja u odjeljku Postavke i odaberite vrstu dokumenta za koju želite kreirati predložak sadržaja.
  • +
+

Nakon što upišete ime, urednici mogu početi koristiti predložak sadržaja kao osnovu za svoju novu stranicu.

+ ]]> +
+ Kako upravljati predlošcima sadržaja? + Možete uređivati i brisati predloške sadržaja iz stabla "Predlošci sadržaja" u + sekciji postavke. Proširite vrstu dokumenta na kojoj se temelji predložak sadržaja i kliknite na nju da biste uredili ili obrisali. + + + + Kraj + Završi način pregleda + Pregledajte web stranicu + Otvorite web stranicu u načinu pregleda + Pregledajte web stranicu? + Završili ste način pregleda, želite li ga ponovo omogućiti da vidite + najnovije spremljene verzije vaše web stranice? + + Pregledajte najnoviju verziju + Pogledajte objavljenu verziju + Pogledajte objavljenu verziju? + Nalazite se u načinu pregleda, želite li izaći da biste vidjeli + objavljenu verziju Vaše web stranice? + + Pogledajte objavljenu verziju + Ostanite u načinu pregleda + + + Kreiranje mape + Pisanje datoteka za pakete + Pisanje datoteka + Kreiranje medijskog foldera + + + stavka vraćena + stavke vraćene + + From dbd009ffcd64a701aaeaef43fc88e580fa8fc993 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 16 Jun 2023 11:21:11 +0200 Subject: [PATCH 08/34] Update label in Danish translation (cherry picked from commit 575f00d6f00895a011e97a823c983e5073265116) --- src/Umbraco.Core/EmbeddedResources/Lang/da.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index dd68711783..b7f970ea9c 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -54,8 +54,8 @@ Sæt rettigheder Lås op Opret indholdsskabelon - Gensend Invitation - Standard værdi + Gensend invitation + Standardværdi Indhold From f93f3d88dae07bd5b7b565a1e7f8ff87ff7b6b6d Mon Sep 17 00:00:00 2001 From: Paul Seal Date: Fri, 16 Jun 2023 12:11:23 +0100 Subject: [PATCH 09/34] Fix/delete language error message 14333 (#14396) * Update the tour text to the correct text and to save confusion * Added extra information to the delete language warning to inform the user that it will also delete all content related to the language * Updated language files with same message re deletion --------- Co-authored-by: Paul Seal Co-authored-by: Emma Garland (cherry picked from commit 542d0f7f741718f7c7b8f46cf76e779d6502ce40) --- src/Umbraco.Core/EmbeddedResources/Lang/en.xml | 2 +- src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml | 2 +- src/Umbraco.Core/EmbeddedResources/Lang/hr.xml | 2 +- .../src/views/languages/overlays/delete.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 1d3f563c9c..2e05c3c953 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -488,7 +488,7 @@ Insert link Click to add a Macro Insert table - This will delete the language + This will delete the language and all content related to the language Changing the culture for a language may be an expensive operation and will result in the content cache and indexes being rebuilt diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index d38a8f0c06..e2cb267d88 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -503,7 +503,7 @@ Insert link Click to add a Macro Insert table - This will delete the language + This will delete the language and all content related to the language Changing the culture for a language may be an expensive operation and will result in the content cache and indexes being rebuilt diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml index 492eb3155a..c3328dfead 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml @@ -479,7 +479,7 @@ Umetni link Klikni za dodavanje makro Umetni tablicu - Ovo će obrisati jezik + Ovo će izbrisati jezik i sav sadržaj povezan s jezikom Promjena kulture jezika može biti skupa operacija i rezultirat će promjenama u predmemoriji sadržaja i indeksima koji se rekonstruiraju Zadnje uređivano diff --git a/src/Umbraco.Web.UI.Client/src/views/languages/overlays/delete.html b/src/Umbraco.Web.UI.Client/src/views/languages/overlays/delete.html index 90e1d755b6..38ee080d4f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/languages/overlays/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/languages/overlays/delete.html @@ -1,7 +1,7 @@
- This will delete the language {{model.language.name}} [{{model.language.culture}}]. + This will delete the language and all content related to the language {{model.language.name}} [{{model.language.culture}}].
Are you sure you want to delete? From 2f35967bb28ca118a443580209e74fac42a682a3 Mon Sep 17 00:00:00 2001 From: Marco Teodoro Date: Fri, 16 Jun 2023 13:40:19 +0100 Subject: [PATCH 10/34] fix: #12253 Anchor picker does not appear (#13492) * fix: #12253 Anchor picker does not appear * Update ContentServiceExtensions.cs fix: #12253 #13492 ; implement PR suggestions, test the new changes to confirm that they fix the issue with the anchorlink implementation on RTE from blocklist, blockgrid and normal RTE properties Co-Authored-By: Laura Neto <12862535+lauraneto@users.noreply.github.com> * Removed unnecessary using and code * Small adjustments --------- Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com> (cherry picked from commit 1ba834a9ca4a44a38d24843c72f8d87f481f6f34) --- .../Services/ContentServiceExtensions.cs | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs index b042612b1a..aaba622412 100644 --- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Text.RegularExpressions; @@ -16,7 +16,8 @@ public static class ContentServiceExtensions { #region RTE Anchor values - private static readonly Regex AnchorRegex = new("", RegexOptions.Compiled); + private static readonly Regex AnchorRegex = new(@"", RegexOptions.Compiled); + private static readonly string[] _propertyTypesWithRte = new[] { Constants.PropertyEditors.Aliases.TinyMce, Constants.PropertyEditors.Aliases.BlockList, Constants.PropertyEditors.Aliases.BlockGrid }; public static IEnumerable? GetByIds(this IContentService contentService, IEnumerable ids) { @@ -67,21 +68,22 @@ public static class ContentServiceExtensions public static IEnumerable GetAnchorValuesFromRTEs(this IContentService contentService, int id, string? culture = "*") { var result = new List(); + + culture = culture is not "*" ? culture : null; + IContent? content = contentService.GetById(id); - if (content is not null) + if (content is null) { - foreach (IProperty contentProperty in content.Properties) + return result; + } + + foreach (IProperty contentProperty in content.Properties.Where(s => _propertyTypesWithRte.Contains(s.PropertyType.PropertyEditorAlias))) + { + var value = contentProperty.GetValue(culture)?.ToString(); + if (!string.IsNullOrEmpty(value)) { - if (contentProperty.PropertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases - .TinyMce)) - { - var value = contentProperty.GetValue(culture)?.ToString(); - if (!string.IsNullOrEmpty(value)) - { - result.AddRange(contentService.GetAnchorValuesFromRTEContent(value)); - } - } + result.AddRange(contentService.GetAnchorValuesFromRTEContent(value)); } } @@ -96,7 +98,7 @@ public static class ContentServiceExtensions MatchCollection matches = AnchorRegex.Matches(rteContent); foreach (Match match in matches) { - result.Add(match.Value.Split(Constants.CharArrays.DoubleQuote)[1]); + result.Add(match.Groups[1].Value); } return result; From ebe375d23b44070b9caf2ee1f855ae97c0a29ad7 Mon Sep 17 00:00:00 2001 From: Jason Elkin Date: Fri, 16 Jun 2023 14:45:49 +0100 Subject: [PATCH 11/34] Add ContentDeletingNotification to EmptyRecycleBin (#14397) * Add ContentDeletingNotification to EmptyRecycleBin * Fix formatting. (cherry picked from commit 118f91415887c0c33f757ebca282c51d561f200f) --- src/Umbraco.Core/Services/ContentService.cs | 47 +++++++++++---------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index f889ce424d..fc02ad3ce1 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -36,20 +36,20 @@ public class ContentService : RepositoryService, IContentService #region Constructors - public ContentService( - ICoreScopeProvider provider, - ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - IDocumentRepository documentRepository, - IEntityRepository entityRepository, - IAuditRepository auditRepository, - IContentTypeRepository contentTypeRepository, - IDocumentBlueprintRepository documentBlueprintRepository, - ILanguageRepository languageRepository, - Lazy propertyValidationService, - IShortStringHelper shortStringHelper, - ICultureImpactFactory cultureImpactFactory) - : base(provider, loggerFactory, eventMessagesFactory) + public ContentService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IDocumentRepository documentRepository, + IEntityRepository entityRepository, + IAuditRepository auditRepository, + IContentTypeRepository contentTypeRepository, + IDocumentBlueprintRepository documentBlueprintRepository, + ILanguageRepository languageRepository, + Lazy propertyValidationService, + IShortStringHelper shortStringHelper, + ICultureImpactFactory cultureImpactFactory) + : base(provider, loggerFactory, eventMessagesFactory) { _documentRepository = documentRepository; _entityRepository = entityRepository; @@ -59,7 +59,7 @@ public class ContentService : RepositoryService, IContentService _languageRepository = languageRepository; _propertyValidationService = propertyValidationService; _shortStringHelper = shortStringHelper; - _cultureImpactFactory = cultureImpactFactory; + _cultureImpactFactory = cultureImpactFactory; _logger = loggerFactory.CreateLogger(); } @@ -1158,7 +1158,7 @@ public class ContentService : RepositoryService, IContentService // if culture is '*', then publish them all (including variants) // this will create the correct culture impact even if culture is * or null - var impact = _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content); + var impact = _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content); // publish the culture(s) // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now. @@ -1845,7 +1845,7 @@ public class ContentService : RepositoryService, IContentService // publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed IProperty[]? invalidProperties = null; - var impact = _cultureImpactFactory.ImpactExplicit(culture, IsDefaultCulture(allLangs.Value, culture)); + var impact = _cultureImpactFactory.ImpactExplicit(culture, IsDefaultCulture(allLangs.Value, culture)); var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact); if (invalidProperties != null && invalidProperties.Length > 0) @@ -1929,14 +1929,14 @@ public class ContentService : RepositoryService, IContentService { return culturesToPublish.All(culture => { - var impact = _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content); + var impact = _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content); return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact); }); } - return content.PublishCulture(_cultureImpactFactory.ImpactInvariant()) - && _propertyValidationService.Value.IsPropertyDataValid(content, out _, _cultureImpactFactory.ImpactInvariant()); + return content.PublishCulture(_cultureImpactFactory.ImpactInvariant()) + && _propertyValidationService.Value.IsPropertyDataValid(content, out _, _cultureImpactFactory.ImpactInvariant()); } // utility 'ShouldPublish' func used by SaveAndPublishBranch @@ -2434,7 +2434,7 @@ public class ContentService : RepositoryService, IContentService /// Optional Id of the User moving the Content public void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId) { - if(content.ParentId == parentId) + if (content.ParentId == parentId) { return; } @@ -2585,7 +2585,8 @@ public class ContentService : RepositoryService, IContentService IContent[] contents = _documentRepository.Get(query).ToArray(); var emptyingRecycleBinNotification = new ContentEmptyingRecycleBinNotification(contents, eventMessages); - if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification)) + var deletingContentNotification = new ContentDeletingNotification(contents, eventMessages); + if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification) || scope.Notifications.PublishCancelable(deletingContentNotification)) { scope.Complete(); return OperationResult.Cancel(eventMessages); @@ -2950,7 +2951,7 @@ public class ContentService : RepositoryService, IContentService { scope.Notifications.Publish(new ContentPublishedNotification(published, eventMessages)); } - + return OperationResult.Succeed(eventMessages); } From 76ad99beb015d390ad6fd8f91bf464d9a01c321d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mads=20M=C3=B8rch=20Schou?= Date: Fri, 16 Jun 2023 19:22:39 +0200 Subject: [PATCH 12/34] Makes sure the nucache is not truncated when its null (#14395) * Makes sure the nucache is not truncated when its null * Removed parentheses (cherry picked from commit 0a4a938b887bac7f2c4a74715fda2c7780b03c03) --- .../Persistence/NuCacheContentRepository.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs index b7fac1e7bc..75208b2b16 100644 --- a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs +++ b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs @@ -109,9 +109,9 @@ public class NuCacheContentRepository : RepositoryBase, INuCacheContentRepositor | ContentCacheDataSerializerEntityType.Member); // If contentTypeIds, mediaTypeIds and memberTypeIds are null, truncate table as all records will be deleted (as these 3 are the only types in the table). - if ((contentTypeIds == null || !contentTypeIds.Any()) - && (mediaTypeIds == null || !mediaTypeIds.Any()) - && (memberTypeIds == null || !memberTypeIds.Any())) + if (contentTypeIds != null && !contentTypeIds.Any() + && mediaTypeIds != null && !mediaTypeIds.Any() + && memberTypeIds != null && !memberTypeIds.Any()) { if (Database.DatabaseType == DatabaseType.SqlServer2012) { From 62fd860a9ea56ce12e97a2e8f1ea25cc1401d647 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Mon, 19 Jun 2023 13:48:03 +0200 Subject: [PATCH 13/34] Show SVG icons for toolbar in configuration of richtext editor (#14404) * Load default icons to show them next to checkboxes in configuration of richtext editor * More mapping of icons * Fix vertical align items centered not reflected * Wrap mapping in function * Map a few more icons * Cleanup and formatting * Cleanup * Cleanup * Add period in description (cherry picked from commit 583965cf9d8880ae0896eb728506a9be569a2f52) --- .../Models/RichTextEditorSettings.cs | 8 +- src/Umbraco.Web.UI.Client/src/less/main.less | 11 +- .../rte/rte.prevalues.controller.js | 124 +++++++++++++++--- .../propertyeditors/rte/rte.prevalues.html | 7 +- 4 files changed, 120 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs index 55fa7b2c5f..5631ab66d7 100644 --- a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs @@ -116,18 +116,18 @@ public class RichTextEditorSettings new Dictionary { ["entity_encoding"] = "raw" }; /// - /// HTML RichText Editor TinyMCE Commands + /// HTML RichText Editor TinyMCE Commands. /// /// WB-TODO Custom Array of objects public RichTextEditorCommand[] Commands { get; set; } = Default_commands; /// - /// HTML RichText Editor TinyMCE Plugins + /// HTML RichText Editor TinyMCE Plugins. /// public string[] Plugins { get; set; } = Default_plugins; /// - /// HTML RichText Editor TinyMCE Custom Config + /// HTML RichText Editor TinyMCE Custom Config. /// /// WB-TODO Custom Dictionary public IDictionary CustomConfig { get; set; } = Default_custom_config; @@ -138,7 +138,7 @@ public class RichTextEditorSettings public string ValidElements { get; set; } = StaticValidElements; /// - /// Invalid HTML elements for RichText Editor + /// Invalid HTML elements for RichText Editor. /// [DefaultValue(StaticInvalidElements)] public string InvalidElements { get; set; } = StaticInvalidElements; diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 2fce7dfe65..cf284188f9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -354,20 +354,25 @@ label:not([for]) { margin: 0 !important; } +.controls > .vertical-align-items, .controls-row > .vertical-align-items { display: flex; align-items: center; } +.controls > .vertical-align-items > input.umb-property-editor-tiny, +.controls > .vertical-align-items > input.umb-property-editor-small, .controls-row > .vertical-align-items > input.umb-property-editor-tiny, .controls-row > .vertical-align-items > input.umb-property-editor-small { - margin-left: 5px; - margin-right: 5px; + margin-left: 5px; + margin-right: 5px; } +.controls > .vertical-align-items > input.umb-property-editor-tiny:first-child +.controls > .vertical-align-items > input.umb-property-editor-small:first-child, .controls-row > .vertical-align-items > input.umb-property-editor-tiny:first-child .controls-row > .vertical-align-items > input.umb-property-editor-small:first-child { - margin-left: 0; + margin-left: 0; } .thumbnails .selected { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js index 47d1f401c7..2a17974dcc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js @@ -1,12 +1,12 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", - function ($scope, $timeout, $log, tinyMceService, stylesheetResource, assetsService) { + function ($scope, $sce, tinyMceService, stylesheetResource, assetsService) { var cfg = tinyMceService.defaultPrevalues(); - if($scope.model.value){ - if(Utilities.isString($scope.model.value)){ + if($scope.model.value) { + if (Utilities.isString($scope.model.value)){ $scope.model.value = cfg; } - }else{ + }else { $scope.model.value = cfg; } @@ -23,14 +23,14 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", $scope.model.value.mode = "classic"; } - tinyMceService.configuration().then(function(config){ - $scope.tinyMceConfig = config; - + tinyMceService.configuration().then(config => { + $scope.tinyMceConfig = config; + // extend commands with properties for font-icon and if it is a custom command - $scope.tinyMceConfig.commands = _.map($scope.tinyMceConfig.commands, function (obj) { - var icon = getFontIcon(obj.alias); - - var objCmd = Utilities.extend(obj, { + $scope.tinyMceConfig.commands = _.map($scope.tinyMceConfig.commands, obj => { + const icon = getIcon(obj.alias); + + const objCmd = Utilities.extend(obj, { fontIcon: icon.name, isCustom: icon.isCustom, selected: $scope.model.value.toolbar.indexOf(obj.alias) >= 0, @@ -39,9 +39,30 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", return objCmd; }); + + assetsService.loadJs("lib/tinymce/icons/default/icons.js", $scope).then(() => { + const icons = tinymce.IconManager.get('default').icons; + const parser = new DOMParser(); + + Utilities.forEach($scope.tinyMceConfig.commands, cmd => { + let icon = getTinyIcon(cmd.alias); + + if (!cmd.isCustom && icons.hasOwnProperty(icon)) { + const svg = icons[icon]; + const frag = parser.parseFromString(svg, 'text/html').body.childNodes[0]; + const width = frag.getAttribute("width"); + const height = frag.getAttribute("height"); + + frag.setAttribute("viewBox", `0 0 ${width} ${height}`); + cmd.svgIcon = $sce.trustAsHtml(frag.outerHTML); + cmd.icon = null; + } + }); + }); + }); - stylesheetResource.getAll().then(function(stylesheets){ + stylesheetResource.getAll().then(stylesheets => { $scope.stylesheets = stylesheets; // if the CSS directory changes, previously assigned stylesheets are retained, but will not be visible @@ -59,14 +80,14 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", $scope.selectCommand = function(command){ var index = $scope.model.value.toolbar.indexOf(command.alias); - if(command.selected && index === -1){ + if (command.selected && index === -1){ $scope.model.value.toolbar.push(command.alias); - }else if(index >= 0){ + }else if (index >= 0){ $scope.model.value.toolbar.splice(index, 1); } }; - $scope.selectStylesheet = function (css) { + $scope.selectStylesheet = css => { // find out if the stylesheet is already selected; first look for the full stylesheet path (current format) var index = $scope.model.value.stylesheets.indexOf(css.path); @@ -75,15 +96,80 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", index = $scope.model.value.stylesheets.indexOf(css.name); } - if(index === -1){ + if (index === -1){ $scope.model.value.stylesheets.push(css.path); - }else{ + } else { $scope.model.value.stylesheets.splice(index, 1); } }; - // map properties for specific commands - function getFontIcon(alias) { + // Map command alias to icon name. + function getTinyIcon(alias) { + let icon = alias; + + switch (alias) { + case "ace": + case "code": + icon = "sourcecode"; + break; + case "anchor": + icon = "bookmark"; + break; + case "alignleft": + icon = "align-left"; + break; + case "aligncenter": + icon = "align-center"; + break; + case "alignright": + icon = "align-right"; + break; + case "alignjustify": + icon = "align-justify"; + break; + case "charmap": + icon = "insert-character"; + break; + case "hr": + icon = "horizontal-rule"; + break; + case "bullist": + icon = "unordered-list"; + break; + case "numlist": + icon = "ordered-list"; + break; + case "strikethrough": + icon = "strike-through"; + break; + case "removeformat": + icon = "remove-formatting"; + break; + case "blockquote": + icon = "quote"; + break; + case "forecolor": + icon = "text-color"; + break; + case "hilitecolor": + icon = "highlight-bg-color"; + break; + case "wordcount": + icon = "character-count"; + break; + case "emoticons": + icon = "emoji"; + break; + case "codesample": + icon = "code-sample"; + break; + } + + return icon; + } + + // Map properties for specific commands + function getIcon(alias) { var icon = { name: alias, isCustom: false }; switch (alias) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html index 96de0cd040..cc717a0486 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html @@ -5,22 +5,21 @@ + + + {{cmd.name}}
- -
From 7fd7a8715d12159086537dfcd2f06d45acdb9a41 Mon Sep 17 00:00:00 2001 From: christophnz Date: Sat, 17 Jun 2023 21:48:38 +1200 Subject: [PATCH 14/34] Fixed typo "optinal" to "optional" for translation dictionary import dialog and 2 other occurrences (cherry picked from commit 77a2e4b55728ab39400d4e8498b1bf11f2846a92) --- src/Umbraco.Core/EmbeddedResources/Lang/de.xml | 2 +- src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js | 4 ++-- src/Umbraco.Web.UI.Client/src/views/dictionary/import.html | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/de.xml b/src/Umbraco.Core/EmbeddedResources/Lang/de.xml index 4bcf80994e..cdc7b42fe4 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/de.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/de.xml @@ -2059,7 +2059,7 @@ Benutzergruppe wurde eingeladen Eine Einladung mit Anweisungen zur Anmeldung im Umbraco-Back-Office wurde dem neuen Benutzer zugeschickt. - Hallo und Willkommen bei Umbraco! In nur einer Minute sind Sie bereit loszulegen, Sie müssen nur ein Kennwort festlegen und optinal Ihrem Avatar ein Bild hinzufügen. + Hallo und Willkommen bei Umbraco! In nur einer Minute sind Sie bereit loszulegen, Sie müssen nur ein Kennwort festlegen und optional Ihrem Avatar ein Bild hinzufügen. Willkommen bei Umbraco! Bedauerlicherweise ist Ihre Einladung verfallen. Bitte kontaktieren Sie Ihren Administrator und bitten Sie ihn, diese erneut zu schicken. Laden Sie ein Foto von sich hoch, um es anderen Benutzern zu erleichtern, sie zu erkennen. Klicken Sie auf den Kreis oben, um Ihr Foto hochzuladen. Autor diff --git a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js index 60118dbdb3..5822e6df89 100644 --- a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js +++ b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js @@ -1,4 +1,4 @@ -// needs Markdown.Converter.js at the moment +// needs Markdown.Converter.js at the moment (function () { @@ -1590,7 +1590,7 @@ }; // takes the line as entered into the add link/as image dialog and makes - // sure the URL and the optinal title are "nice". + // sure the URL and the optional title are "nice". function properlyEncoded(linkdef) { return linkdef.replace(/^\s*(.*?)(?:\s+"(.+)")?\s*$/, function (wholematch, link, title) { link = link.replace(/\?.*$/, function (querypart) { diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/import.html b/src/Umbraco.Web.UI.Client/src/views/dictionary/import.html index 64f810f4dc..8f0a1ec15d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dictionary/import.html +++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/import.html @@ -43,7 +43,7 @@ Chose where to import dictionary items. - (optinal) + (optional)

Date: Tue, 27 Jun 2023 16:50:11 +0200 Subject: [PATCH 15/34] Added missing Danish translation for `general_change` (#14461) * Added missing Danish translation for `general_change` * Update en_us.xml (cherry picked from commit a090997aa19a78821836d8a1f5e2237befed7fd5) --- src/Umbraco.Core/EmbeddedResources/Lang/da.xml | 1 + src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index b7f970ea9c..099af43d2f 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -717,6 +717,7 @@ af Fortryd Celle margen + Skift Vælg Ryd Luk diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index e2cb267d88..b24583cd07 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -757,6 +757,7 @@ by Cancel Cell margin + Change Choose Clear Close From 30ec9d9bc7b1dfbdec09d2e016797e2573dde64a Mon Sep 17 00:00:00 2001 From: Nikolaj Brask-Nielsen Date: Tue, 27 Jun 2023 18:18:07 +0200 Subject: [PATCH 16/34] fix: Allows copying from unsaved content (#14464) #14459 (cherry picked from commit 1d14158d82ce5d9e3ed9766e8e11faddb455be19) --- .../src/common/services/clipboard.service.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js index abf173b129..238d9a8ee6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js @@ -344,10 +344,14 @@ function clipboardService($window, notificationsService, eventsService, localSto // Clean up each entry var copiedDatas = datas.map(data => prepareEntryForStorage(type, data, firstLevelClearupMethod)); - // remove previous copies of this entry: + // remove previous copies of this entry (Make sure to not remove copies from unsaved content): storage.entries = storage.entries.filter( (entry) => { - return entry.unique !== uniqueKey; + if (entry.unique === 0) { + return displayLabel !== entry.label; + } else { + return entry.unique !== uniqueKey; + } } ); From 3f196a9de44bf11b5dd132d90fa239b435a29962 Mon Sep 17 00:00:00 2001 From: Maarten Date: Tue, 4 Jul 2023 09:37:13 +0200 Subject: [PATCH 17/34] Fix broken CookieAuthenticationRedirect caused by PR #14036 for non-api requests (#14399) * Fix broken CookieAuthenticationRedirect caused by PR #14036 when not in an API controller * Added Integration Tests for the MemberAuthorizationFilter * Fix merge conflict --------- Co-authored-by: Elitsa (cherry picked from commit 1d239a30ca0bf741316fe73f2b307876eaad0264) --- .../Filters/UmbracoMemberAuthorizeFilter.cs | 6 +- .../Security/ConfigureMemberCookieOptions.cs | 13 +- .../Security/MemberAuthorizeTests.cs | 126 ++++++++++++++++++ 3 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Web.Website/Security/MemberAuthorizeTests.cs diff --git a/src/Umbraco.Web.Common/Filters/UmbracoMemberAuthorizeFilter.cs b/src/Umbraco.Web.Common/Filters/UmbracoMemberAuthorizeFilter.cs index 95c4ae5cec..2f56cdb51f 100644 --- a/src/Umbraco.Web.Common/Filters/UmbracoMemberAuthorizeFilter.cs +++ b/src/Umbraco.Web.Common/Filters/UmbracoMemberAuthorizeFilter.cs @@ -60,14 +60,14 @@ public class UmbracoMemberAuthorizeFilter : IAsyncAuthorizationFilter { context.HttpContext.SetReasonPhrase( "Resource restricted: the member is not of a permitted type or group."); + context.HttpContext.Response.StatusCode = 403; context.Result = new ForbidResult(); } } else { - context.HttpContext.SetReasonPhrase( - "Resource restricted: the member is not logged in."); - context.Result = new UnauthorizedResult(); + context.HttpContext.Response.StatusCode = 401; + context.Result = new ForbidResult(); } } diff --git a/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs b/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs index 66714be9e6..128737f76d 100644 --- a/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs +++ b/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs @@ -1,10 +1,12 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.Controllers; using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.Security; @@ -58,7 +60,16 @@ public sealed class ConfigureMemberCookieOptions : IConfigureNamedOptions { - ctx.Response.StatusCode = StatusCodes.Status403Forbidden; + // When the controller is an UmbracoAPIController, we want to return a StatusCode instead of a redirect. + // All other cases should use the default Redirect of the CookieAuthenticationEvent. + var controllerDescriptor = ctx.HttpContext.GetEndpoint()?.Metadata + .OfType() + .FirstOrDefault(); + + if (!controllerDescriptor?.ControllerTypeInfo.IsSubclassOf(typeof(UmbracoApiController)) ?? false) + { + new CookieAuthenticationEvents().OnRedirectToAccessDenied(ctx); + } return Task.CompletedTask; }, diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Web.Website/Security/MemberAuthorizeTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Web.Website/Security/MemberAuthorizeTests.cs new file mode 100644 index 0000000000..0fc1dfa85d --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Web.Website/Security/MemberAuthorizeTests.cs @@ -0,0 +1,126 @@ +using System.Net; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Tests.Integration.TestServerTest; +using Umbraco.Cms.Web.Common.Controllers; +using Umbraco.Cms.Web.Common.Filters; +using Umbraco.Cms.Web.Common.Security; +using Umbraco.Cms.Web.Website.Controllers; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.Website.Security +{ + public class MemberAuthorizeTests : UmbracoTestServerTestBase + { + private Mock _memberManagerMock = new(); + + protected override void ConfigureTestServices(IServiceCollection services) + { + _memberManagerMock = new Mock(); + services.Remove(new ServiceDescriptor(typeof(IMemberManager), typeof(MemberManager), ServiceLifetime.Scoped)); + services.Remove(new ServiceDescriptor(typeof(MemberManager), ServiceLifetime.Scoped)); + services.AddScoped(_ => _memberManagerMock.Object); + } + + [Test] + public async Task Secure_SurfaceController_Should_Return_Redirect_WhenNotLoggedIn() + { + _memberManagerMock.Setup(x => x.IsLoggedIn()).Returns(false); + + var url = PrepareSurfaceControllerUrl(x => x.Secure()); + + var response = await Client.GetAsync(url); + + var cookieAuthenticationOptions = Services.GetService>(); + Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode); + Assert.AreEqual(cookieAuthenticationOptions.Value.AccessDeniedPath.ToString(), response.Headers.Location?.AbsolutePath); + } + + [Test] + public async Task Secure_SurfaceController_Should_Return_Redirect_WhenNotAuthorized() + { + _memberManagerMock.Setup(x => x.IsLoggedIn()).Returns(true); + _memberManagerMock.Setup(x => x.IsMemberAuthorizedAsync( + It.IsAny>(), + It.IsAny>(), + It.IsAny>())) + .ReturnsAsync(false); + + var url = PrepareSurfaceControllerUrl(x => x.Secure()); + + var response = await Client.GetAsync(url); + + var cookieAuthenticationOptions = Services.GetService>(); + Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode); + Assert.AreEqual(cookieAuthenticationOptions.Value.AccessDeniedPath.ToString(), response.Headers.Location?.AbsolutePath); + } + + + [Test] + public async Task Secure_ApiController_Should_Return_Unauthorized_WhenNotLoggedIn() + { + _memberManagerMock.Setup(x => x.IsLoggedIn()).Returns(false); + var url = PrepareApiControllerUrl(x => x.Secure()); + + var response = await Client.GetAsync(url); + + Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Test] + public async Task Secure_ApiController_Should_Return_Forbidden_WhenNotAuthorized() + { + _memberManagerMock.Setup(x => x.IsLoggedIn()).Returns(true); + _memberManagerMock.Setup(x => x.IsMemberAuthorizedAsync( + It.IsAny>(), + It.IsAny>(), + It.IsAny>())) + .ReturnsAsync(false); + + var url = PrepareApiControllerUrl(x => x.Secure()); + + var response = await Client.GetAsync(url); + + Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode); + } + } + + public class TestSurfaceController : SurfaceController + { + public TestSurfaceController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base( + umbracoContextAccessor, + databaseFactory, + services, + appCaches, + profilingLogger, + publishedUrlProvider) + { + } + + [UmbracoMemberAuthorize] + public IActionResult Secure() => NoContent(); + } + + public class TestApiController : UmbracoApiController + { + [UmbracoMemberAuthorize] + public IActionResult Secure() => NoContent(); + } +} From d86e4b2d38aba8f909288b9b4ab7dd0cdc35c7d4 Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Wed, 5 Jul 2023 10:46:14 +0200 Subject: [PATCH 18/34] Bumb version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 0c594e5b7e..964482479f 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.6.0-rc", + "version": "10.6.0", "assemblyVersion": { "precision": "build" }, From c61e6c490172a1f83c733a67aeeabdb555f9aab9 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Wed, 5 Jul 2023 13:38:02 +0200 Subject: [PATCH 19/34] Revert "Ensure package view path is generated with consideration the current section (#14268)" (#14508) This reverts commit 5382820e584c79dbe2a5d0e4793ea14a744709e7. --- .../Controllers/BackOfficeServerVariables.cs | 5 +-- .../Trees/ApplicationTreeController.cs | 2 +- .../Trees/ScriptsTreeController.cs | 2 - .../Trees/StylesheetsTreeController.cs | 2 - .../Trees/UrlHelperExtensions.cs | 39 +++++-------------- .../common/mocks/umbraco.servervariables.js | 2 +- .../src/common/services/navigation.service.js | 7 ++-- .../src/common/services/tree.service.js | 28 ++++++------- src/Umbraco.Web.UI.Client/src/routes.js | 18 ++++----- .../unit/common/services/tree-service.spec.js | 4 +- 10 files changed, 38 insertions(+), 71 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index 9325f9e8ae..6dbd5f1e79 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -704,9 +704,6 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [DataMember(Name = "packageFolder")] public string? PackageFolder { get; set; } - - [DataMember(Name = "sectionAlias")] - public string? SectionAlias { get; set; } } private IEnumerable GetPluginTrees() @@ -738,7 +735,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers continue; } - yield return new PluginTree { Alias = tree.TreeAlias, PackageFolder = pluginController.AreaName, SectionAlias = tree.SectionAlias }; + yield return new PluginTree { Alias = tree.TreeAlias, PackageFolder = pluginController.AreaName }; } } diff --git a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs index 28f685bf0d..461d1fc82f 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -360,7 +360,7 @@ public class ApplicationTreeController : UmbracoAuthorizedApiController ControllerActionDescriptor? actionDescriptor = _actionDescriptorCollectionProvider.ActionDescriptors.Items .Cast() .First(x => - (x.ControllerTypeInfo.FullName ?? string.Empty).Equals(controllerType.FullName) && + x.ControllerName.Equals(controllerName) && x.ActionName == action); var actionContext = new ActionContext(HttpContext, routeData, actionDescriptor); diff --git a/src/Umbraco.Web.BackOffice/Trees/ScriptsTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ScriptsTreeController.cs index 0c46e809d2..630584a839 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ScriptsTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ScriptsTreeController.cs @@ -3,12 +3,10 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Trees; -using Umbraco.Cms.Web.Common.Attributes; namespace Umbraco.Cms.Web.BackOffice.Trees; [CoreTree] -[PluginController(Constants.Web.Mvc.BackOfficeTreeArea)] [Tree(Constants.Applications.Settings, Constants.Trees.Scripts, TreeTitle = "Scripts", SortOrder = 10, TreeGroup = Constants.Trees.Groups.Templating)] public class ScriptsTreeController : FileSystemTreeController { diff --git a/src/Umbraco.Web.BackOffice/Trees/StylesheetsTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/StylesheetsTreeController.cs index 32f2a3e465..3ff7a7ecfc 100644 --- a/src/Umbraco.Web.BackOffice/Trees/StylesheetsTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/StylesheetsTreeController.cs @@ -3,12 +3,10 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Trees; -using Umbraco.Cms.Web.Common.Attributes; namespace Umbraco.Cms.Web.BackOffice.Trees; [CoreTree] -[PluginController(Constants.Web.Mvc.BackOfficeTreeArea)] [Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, TreeTitle = "Stylesheets", SortOrder = 9, TreeGroup = Constants.Trees.Groups.Templating)] public class StylesheetsTreeController : FileSystemTreeController { diff --git a/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs b/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs index d8506f5692..1688a99ec2 100644 --- a/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs @@ -47,15 +47,14 @@ public static class UrlHelperExtensions string nodeId, FormCollection? queryStrings) { - var actionName = "GetNodes"; - var actionUrl = urlHelper.GetUmbracoApiService(umbracoApiControllerTypeCollection, actionName, treeType); - actionUrl = StartOrContinueQueryString(actionUrl, actionName); - - // Now we need to append the query strings - // Always ignore the custom start node id when generating URLs for tree nodes since this is a custom once-only parameter - // that should only ever be used when requesting a tree to render (root), not a tree node - actionUrl += "id=" + nodeId.EnsureEndsWith('&') + queryStrings?.ToQueryString("id", TreeQueryStringParameters.StartNodeId); + var actionUrl = urlHelper.GetUmbracoApiService(umbracoApiControllerTypeCollection, "GetNodes", treeType)? + .EnsureEndsWith('?'); + //now we need to append the query strings + actionUrl += "id=" + nodeId.EnsureEndsWith('&') + queryStrings?.ToQueryString("id", + //Always ignore the custom start node id when generating URLs for tree nodes since this is a custom once-only parameter + // that should only ever be used when requesting a tree to render (root), not a tree node + TreeQueryStringParameters.StartNodeId); return actionUrl; } @@ -66,29 +65,11 @@ public static class UrlHelperExtensions string nodeId, FormCollection? queryStrings) { - var actionName = "GetMenu"; - var actionUrl = urlHelper.GetUmbracoApiService(umbracoApiControllerTypeCollection, actionName, treeType); - actionUrl = StartOrContinueQueryString(actionUrl, actionName); + var actionUrl = urlHelper.GetUmbracoApiService(umbracoApiControllerTypeCollection, "GetMenu", treeType)? + .EnsureEndsWith('?'); - // now we need to append the query strings + //now we need to append the query strings actionUrl += "id=" + nodeId.EnsureEndsWith('&') + queryStrings?.ToQueryString("id"); return actionUrl; } - - /// - /// Check the provided string already includes a querystring fragment - /// If so, result has "&" appended, else has "?" appended. - /// - /// - /// - /// - private static string? StartOrContinueQueryString(string? actionUrl, string? delimiter) - { - if (actionUrl is null) - { - return actionUrl; - } - - return actionUrl.EnsureEndsWith(actionUrl.Contains($"{delimiter}?") ? "&" : "?"); - } } diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js index 4b8e8fa146..51029234f5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js @@ -32,7 +32,7 @@ Umbraco.Sys.ServerVariables = { }, umbracoPlugins: { trees: [ - { alias: "myTree", packageFolder: "MyPackage", sectionAlias: "myPackageSectionAlias" } + { alias: "myTree", packageFolder: "MyPackage" } ] }, isDebuggingEnabled: true, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 248e78880a..77b97545b6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -616,7 +616,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService if (!treeAlias) { throw "Could not get tree alias for node " + args.node.id; } - templateUrl = this.getTreeTemplateUrl(treeAlias, args.action.alias, args.node.section); + templateUrl = this.getTreeTemplateUrl(treeAlias, args.action.alias); } setMode("dialog"); @@ -633,7 +633,6 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * * @param {string} treeAlias the alias of the tree to look up * @param {string} action the view file name - * @param {string} sectionAlias the alias of the current section * @description * creates the templateUrl based on treeAlias and action * by convention we will look into the /views/{treetype}/{action}.html @@ -641,8 +640,8 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * we will also check for a 'packageName' for the current tree, if it exists then the convention will be: * for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html */ - getTreeTemplateUrl: function (treeAlias, action, sectionAlias) { - var packageTreeFolder = treeService.getTreePackageFolder(treeAlias, sectionAlias); + getTreeTemplateUrl: function (treeAlias, action) { + var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); if (packageTreeFolder) { return (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + "/" + packageTreeFolder + diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index fa5d297a88..ba9ebc1b00 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -166,27 +166,23 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * * @description * Determines if the current tree is a plugin tree and if so returns the package folder it has declared - * so we know where to find its views, otherwise it will just return undefined. + * so we know where to find it's views, otherwise it will just return undefined. * * @param {String} treeAlias The tree alias to check - * @param {String} sectionAlias The current section */ - getTreePackageFolder: function (treeAlias, sectionAlias) { + getTreePackageFolder: function (treeAlias) { //we determine this based on the server variables - if (!Umbraco.Sys.ServerVariables.umbracoPlugins || !Utilities.isArray(Umbraco.Sys.ServerVariables.umbracoPlugins.trees)) { - return undefined; - } + if (Umbraco.Sys.ServerVariables.umbracoPlugins && + Umbraco.Sys.ServerVariables.umbracoPlugins.trees && + Utilities.isArray(Umbraco.Sys.ServerVariables.umbracoPlugins.trees)) { - let found; - if (sectionAlias !== undefined) { - found = Umbraco.Sys.ServerVariables.umbracoPlugins.trees.find(item => - invariantEquals(item.alias, treeAlias) && invariantEquals(item.sectionAlias, sectionAlias)); - } else { - found = Umbraco.Sys.ServerVariables.umbracoPlugins.trees.find(item => - invariantEquals(item.alias, treeAlias)); - } + var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function (item) { + return invariantEquals(item.alias, treeAlias); + }); - return found ? found.packageFolder : undefined; + return found ? found.packageFolder : undefined; + } + return undefined; }, /** @@ -872,7 +868,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //start var wrappedPromise = doSync(); - //then wrap it + //then wrap it wrappedPromise.then(function (args) { deferred.resolve(args); }, function (args) { diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index e6d97ea18f..7e65346d1b 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -1,5 +1,5 @@ window.app.config(function ($routeProvider) { - + /** * This determines if the route can continue depending on authentication and initialization requirements * @param {boolean} authRequired If true, it checks if the user is authenticated and will resolve successfully @@ -117,9 +117,9 @@ window.app.config(function ($routeProvider) { template: "
", //This controller will execute for this route, then we can execute some code in order to set the template Url controller: function ($scope, $route, $routeParams, $location, sectionService) { - + //We are going to check the currently loaded sections for the user and if the section we are navigating - //to has a custom route path we'll use that + //to has a custom route path we'll use that sectionService.getSectionsForUser().then(function(sections) { //find the one we're requesting var found = _.find(sections, function(s) { @@ -175,9 +175,8 @@ window.app.config(function ($routeProvider) { if ($routeParams.section.toLowerCase() === "users" && $routeParams.tree.toLowerCase() === "users" && usersPages.indexOf($routeParams.method.toLowerCase()) === -1) { $scope.templateUrl = "views/users/overview.html"; return; - } - - $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method, $routeParams.section); + } + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, resolve: canRoute(true) @@ -191,9 +190,8 @@ window.app.config(function ($routeProvider) { if (!$routeParams.tree || !$routeParams.method) { $scope.templateUrl = "views/common/dashboard.html"; return; - } - - $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method, $routeParams.section); + } + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, reloadOnUrl: false, @@ -201,7 +199,7 @@ window.app.config(function ($routeProvider) { }) .otherwise({ redirectTo: '/login' }); }).config(function ($locationProvider) { - + $locationProvider.html5Mode(false); //turn html5 mode off $locationProvider.hashPrefix(''); }); diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js index 63800a9e12..4d19cf557a 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js @@ -298,13 +298,13 @@ describe('tree service tests', function () { it('can find a plugin based tree', function () { //we know this exists in the mock umbraco server vars - var found = treeService.getTreePackageFolder("myTree", "MyPackageSectionAlias"); + var found = treeService.getTreePackageFolder("myTree"); expect(found).toBe("MyPackage"); }); it('returns undefined for a not found tree', function () { //we know this does not exist in the mock umbraco server vars - var found = treeService.getTreePackageFolder("asdfasdf", "fdsafdsa"); + var found = treeService.getTreePackageFolder("asdfasdf"); expect(found).not.toBeDefined(); }); From f18d4b2078498c428899f5bbe0ec71f1ec1bafae Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 6 Jul 2023 07:52:11 +0000 Subject: [PATCH 20/34] Render all tabs in the background to ensure they register their initial state on any $scope.model in the stack. (#14493) Sometimes a tab, e.g. a "block list settings model" might register some default values, but if the user never clicks on the tab then they are not registered due to how AngularJS renders views with "ng-if". --- .../src/views/components/editor/umb-editor-sub-views.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html index 56c7a9cf48..be6f21ed96 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html @@ -5,7 +5,7 @@ ng-repeat="subView in subViews track by subView.alias" ng-class="'sub-view-' + subView.name" val-sub-view="subView" - ng-if="subView.active" + ng-show="subView.active" >
From dc5c7a14a576afc4c349877785c5f009275d630c Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Thu, 6 Jul 2023 10:40:50 +0200 Subject: [PATCH 21/34] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 964482479f..c88476851d 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.6.0", + "version": "10.6.1", "assemblyVersion": { "precision": "build" }, From d116366b283bde323997008696fe15f0ec155a26 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Fri, 7 Jul 2023 11:12:04 +0200 Subject: [PATCH 22/34] V12: Deprecate XPath (#14372) * Deprecate all outward facing methods that uses XPath * Add more obsolete messages --------- Co-authored-by: Zeegaan --- .../Configuration/Models/ContentErrorPage.cs | 3 +++ src/Umbraco.Core/Extensions/XmlExtensions.cs | 8 ++++++++ .../Models/PublishedContent/IPublishedProperty.cs | 2 ++ .../Models/PublishedContent/IPublishedPropertyType.cs | 1 + .../Models/PublishedContent/PublishedPropertyBase.cs | 1 + .../Models/PublishedContent/PublishedPropertyType.cs | 1 + .../Models/PublishedContent/RawValueProperty.cs | 1 + .../PropertyEditors/IPropertyValueConverter.cs | 1 + .../PropertyEditors/PropertyValueConverterBase.cs | 1 + .../PropertyEditors/TextStringValueConverter.cs | 1 + .../ValueConverters/ContentPickerValueConverter.cs | 1 + .../ValueConverters/DatePickerValueConverter.cs | 1 + .../MultipleTextStringValueConverter.cs | 1 + .../ValueConverters/SimpleTinyMceValueConverter.cs | 1 + .../ValueConverters/YesNoValueConverter.cs | 1 + src/Umbraco.Core/PublishedCache/IPublishedCache.cs | 10 ++++++++++ .../Internal/InternalPublishedContentCache.cs | 6 ++++++ .../Internal/InternalPublishedProperty.cs | 2 ++ src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs | 11 +++++++++++ .../PublishedCache/PublishedElementPropertyBase.cs | 1 + src/Umbraco.Core/Xml/DynamicContext.cs | 3 +++ src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs | 1 + src/Umbraco.Core/Xml/XPath/INavigableContent.cs | 1 + src/Umbraco.Core/Xml/XPath/INavigableContentType.cs | 1 + src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs | 1 + src/Umbraco.Core/Xml/XPath/INavigableSource.cs | 1 + src/Umbraco.Core/Xml/XPath/MacroNavigator.cs | 1 + src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs | 2 ++ src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs | 1 + src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs | 2 ++ src/Umbraco.Core/Xml/XPathVariable.cs | 1 + src/Umbraco.Core/Xml/XmlHelper.cs | 5 +++++ src/Umbraco.Core/Xml/XmlNodeListFactory.cs | 1 + src/Umbraco.Infrastructure/IPublishedContentQuery.cs | 3 +++ .../ValueConverters/MarkdownEditorValueConverter.cs | 1 + src/Umbraco.Infrastructure/PublishedContentQuery.cs | 3 +++ src/Umbraco.PublishedCache.NuCache/ContentCache.cs | 6 ++++++ src/Umbraco.PublishedCache.NuCache/MediaCache.cs | 3 +++ src/Umbraco.PublishedCache.NuCache/Property.cs | 1 + .../Controllers/EntityController.cs | 1 + src/Umbraco.Web.Common/UmbracoHelper.cs | 3 +++ 41 files changed, 97 insertions(+) diff --git a/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs b/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs index 415240e017..e5424fb636 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs @@ -24,6 +24,8 @@ public class ContentErrorPage : ValidatableEntryBase /// /// Gets or sets a value for the content XPath. /// + + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public string? ContentXPath { get; set; } /// @@ -39,6 +41,7 @@ public class ContentErrorPage : ValidatableEntryBase /// /// Gets a value indicating whether the field is populated. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public bool HasContentXPath => !string.IsNullOrEmpty(ContentXPath); /// diff --git a/src/Umbraco.Core/Extensions/XmlExtensions.cs b/src/Umbraco.Core/Extensions/XmlExtensions.cs index 34e2b7b2aa..bb9e6c69b5 100644 --- a/src/Umbraco.Core/Extensions/XmlExtensions.cs +++ b/src/Umbraco.Core/Extensions/XmlExtensions.cs @@ -34,6 +34,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNodeList? SelectNodes(this XmlNode source, string expression, IEnumerable? variables) { XPathVariable[]? av = variables?.ToArray(); @@ -56,6 +57,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNodeList? SelectNodes(this XmlNode source, XPathExpression expression, IEnumerable? variables) { XPathVariable[]? av = variables?.ToArray(); @@ -78,6 +80,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNodeList? SelectNodes(this XmlNode source, string? expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) @@ -105,6 +108,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNodeList SelectNodes(this XmlNode source, XPathExpression expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) @@ -132,6 +136,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNode? SelectSingleNode(this XmlNode source, string expression, IEnumerable? variables) { XPathVariable[]? av = variables?.ToArray(); @@ -154,6 +159,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, IEnumerable? variables) { XPathVariable[]? av = variables?.ToArray(); @@ -176,6 +182,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNode? SelectSingleNode(this XmlNode source, string expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) @@ -202,6 +209,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs index a7bff33ba4..ed175e418d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs @@ -69,6 +69,8 @@ public interface IPublishedProperty /// It must be either null, or a string, or an XPathNavigator. /// It has been fully prepared and processed by the appropriate converter. /// + + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] object? GetXPathValue(string? culture = null, string? segment = null); /// diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs index 83ca0c49df..45d36abb6a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs @@ -113,6 +113,7 @@ public interface IPublishedPropertyType /// /// The XPath value can be either a string or an XPathNavigator. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs index a06d2006ba..c36809600b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs @@ -46,6 +46,7 @@ public abstract class PublishedPropertyBase : IPublishedProperty public abstract object? GetValue(string? culture = null, string? segment = null); /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract object? GetXPathValue(string? culture = null, string? segment = null); /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 0efa4e7653..848e961d0b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -273,6 +273,7 @@ namespace Umbraco.Cms.Core.Models.PublishedContent } /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { if (!_initialized) diff --git a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs index 3d29744bac..013f969805 100644 --- a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs @@ -58,6 +58,7 @@ public class RawValueProperty : PublishedPropertyBase public override object? GetValue(string? culture = null, string? segment = null) => string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _objectValue.Value : null; + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? GetXPathValue(string? culture = null, string? segment = null) => string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _xpathValue.Value : null; diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs index 37d6b82475..74dbeaebae 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs @@ -128,5 +128,6 @@ public interface IPropertyValueConverter : IDiscoverable /// the cache levels of property values. It is not meant to be used by the converter. /// /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs index 3066086e7b..33c86f3ef4 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs @@ -49,6 +49,7 @@ public abstract class PropertyValueConverterBase : IPropertyValueConverter => inter; /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public virtual object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { var d = new XmlDocument(); diff --git a/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs index 6c9c80ce81..fc7651b801 100644 --- a/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs @@ -51,6 +51,7 @@ public class TextStringValueConverter : PropertyValueConverterBase, IDeliveryApi // source should come from ConvertSource and be a string (or null) already inter ?? string.Empty; + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => // source should come from ConvertSource and be a string (or null) already diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs index 06bfd1b1f2..94f2533548 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs @@ -86,6 +86,7 @@ public class ContentPickerValueConverter : PropertyValueConverterBase, IDelivery return content ?? inter; } + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { if (inter == null) diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs index 7941946964..73ef424ba4 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs @@ -39,6 +39,7 @@ public class DatePickerValueConverter : PropertyValueConverterBase } // default ConvertSourceToObject just returns source ie a DateTime value + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? ConvertIntermediateToXPath( IPublishedElement owner, IPublishedPropertyType propertyType, diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs index 3d631afead..e3dcd6ae78 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs @@ -57,6 +57,7 @@ public class MultipleTextStringValueConverter : PropertyValueConverterBase : values.ToArray(); } + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { var d = new XmlDocument(); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs index 7503e6711f..8e0aa50acf 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs @@ -31,6 +31,7 @@ public class SimpleTinyMceValueConverter : PropertyValueConverterBase // source should come from ConvertSource and be a string (or null) already new HtmlEncodedString(inter == null ? string.Empty : (string)inter); + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => // source should come from ConvertSource and be a string (or null) already diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs index ab7f99e7f8..7bc940f90f 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs @@ -56,6 +56,7 @@ public class YesNoValueConverter : PropertyValueConverterBase } // default ConvertSourceToObject just returns source ie a boolean value + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => // source should come from ConvertSource and be a boolean already diff --git a/src/Umbraco.Core/PublishedCache/IPublishedCache.cs b/src/Umbraco.Core/PublishedCache/IPublishedCache.cs index 0ee2ca38ed..78ebd19d6a 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedCache.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedCache.cs @@ -102,6 +102,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The content, or null. /// The value of overrides defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IPublishedContent? GetSingleByXPath(bool preview, string xpath, params XPathVariable[] vars); /// @@ -111,6 +112,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The content, or null. /// Considers published or unpublished content depending on defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IPublishedContent? GetSingleByXPath(string xpath, params XPathVariable[] vars); /// @@ -121,6 +123,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The content, or null. /// The value of overrides defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IPublishedContent? GetSingleByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars); /// @@ -130,6 +133,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The content, or null. /// Considers published or unpublished content depending on defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IPublishedContent? GetSingleByXPath(XPathExpression xpath, params XPathVariable[] vars); /// @@ -140,6 +144,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The contents. /// The value of overrides defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IEnumerable GetByXPath(bool preview, string xpath, params XPathVariable[] vars); /// @@ -149,6 +154,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The contents. /// Considers published or unpublished content depending on defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IEnumerable GetByXPath(string xpath, params XPathVariable[] vars); /// @@ -159,6 +165,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The contents. /// The value of overrides defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IEnumerable GetByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars); /// @@ -168,6 +175,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The contents. /// Considers published or unpublished content depending on defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IEnumerable GetByXPath(XPathExpression xpath, params XPathVariable[] vars); /// @@ -179,6 +187,7 @@ public interface IPublishedCache : IXPathNavigable /// The value of overrides the context. /// The navigator is already a safe clone (no need to clone it again). /// + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] XPathNavigator CreateNavigator(bool preview); /// @@ -196,6 +205,7 @@ public interface IPublishedCache : IXPathNavigable /// /// If the node does not exist, returns null. /// + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] XPathNavigator? CreateNodeNavigator(int id, bool preview); /// diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs index e4e9010f5b..315136612a 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs @@ -38,20 +38,26 @@ public sealed class InternalPublishedContentCache : PublishedCacheBase, IPublish public override IEnumerable GetAtRoot(bool preview, string? culture = null) => _content.Values.Where(x => x.Parent == null); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) => throw new NotImplementedException(); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) => throw new NotImplementedException(); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) => throw new NotImplementedException(); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) => throw new NotImplementedException(); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override XPathNavigator CreateNavigator(bool preview) => throw new NotImplementedException(); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override XPathNavigator CreateNodeNavigator(int id, bool preview) => throw new NotImplementedException(); public override bool HasContent(bool preview) => _content.Count > 0; diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs index a90897e221..c0d76aceb8 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs @@ -13,6 +13,7 @@ public class InternalPublishedProperty : IPublishedProperty public bool SolidHasValue { get; set; } + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public object? SolidXPathValue { get; set; } public object? SolidDeliveryApiValue { get; set; } @@ -25,6 +26,7 @@ public class InternalPublishedProperty : IPublishedProperty public virtual object? GetValue(string? culture = null, string? segment = null) => SolidValue; + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public virtual object? GetXPathValue(string? culture = null, string? segment = null) => SolidXPathValue; public virtual object? GetDeliveryApiValue(bool expanding, string? culture = null, string? segment = null) => SolidDeliveryApiValue; diff --git a/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs b/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs index 3e961ce434..c20ebf9284 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs @@ -40,31 +40,42 @@ public abstract class PublishedCacheBase : IPublishedCache public IEnumerable GetAtRoot(string? culture = null) => GetAtRoot(PreviewDefault, culture); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract IPublishedContent? GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IPublishedContent? GetSingleByXPath(string xpath, XPathVariable[] vars) => GetSingleByXPath(PreviewDefault, xpath, vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract IPublishedContent? GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IPublishedContent? GetSingleByXPath(XPathExpression xpath, XPathVariable[] vars) => GetSingleByXPath(PreviewDefault, xpath, vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IEnumerable GetByXPath(string xpath, XPathVariable[] vars) => GetByXPath(PreviewDefault, xpath, vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IEnumerable GetByXPath(XPathExpression xpath, XPathVariable[] vars) => GetByXPath(PreviewDefault, xpath, vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract XPathNavigator CreateNavigator(bool preview); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public XPathNavigator CreateNavigator() => CreateNavigator(PreviewDefault); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract XPathNavigator? CreateNodeNavigator(int id, bool preview); public abstract bool HasContent(bool preview); diff --git a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs index b4e56897a7..05348a138c 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs @@ -201,6 +201,7 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase } } + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? GetXPathValue(string? culture = null, string? segment = null) { GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCacheLevel referenceCacheLevel); diff --git a/src/Umbraco.Core/Xml/DynamicContext.cs b/src/Umbraco.Core/Xml/DynamicContext.cs index fd86866348..321024f486 100644 --- a/src/Umbraco.Core/Xml/DynamicContext.cs +++ b/src/Umbraco.Core/Xml/DynamicContext.cs @@ -118,6 +118,7 @@ namespace Umbraco.Cms.Core.Xml /// /// Same as . /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override bool PreserveWhitespace(XPathNavigator node) { return true; @@ -140,6 +141,7 @@ namespace Umbraco.Cms.Core.Xml /// /// The expression to compile /// A compiled . + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XPathExpression? Compile(string xpath) { return new XmlDocument().CreateNavigator()?.Compile(xpath); @@ -203,6 +205,7 @@ namespace Umbraco.Cms.Core.Xml /// /// See . Not used in our implementation. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IXsltContextFunction ResolveFunction(string prefix, string name, XPathResultType[] argTypes) => throw new NotImplementedException(); /// diff --git a/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs b/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs index 2a01d42dc7..87d4d459fa 100644 --- a/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs +++ b/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs @@ -6,6 +6,7 @@ namespace Umbraco.Cms.Core.Xml; /// This is used to parse our customize Umbraco XPath expressions (i.e. that include special tokens like $site) into /// a real XPath statement /// +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public class UmbracoXPathPathSyntaxParser { [Obsolete("This will be removed in Umbraco 13. Use ParseXPathQuery which accepts a parentId instead")] diff --git a/src/Umbraco.Core/Xml/XPath/INavigableContent.cs b/src/Umbraco.Core/Xml/XPath/INavigableContent.cs index b9359b4fef..8d9d48a0a0 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableContent.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableContent.cs @@ -3,6 +3,7 @@ namespace Umbraco.Cms.Core.Xml.XPath; /// /// Represents a content that can be navigated via XPath. /// +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public interface INavigableContent { /// diff --git a/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs b/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs index 08a7c1a0f6..eeb775e07b 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs @@ -3,6 +3,7 @@ namespace Umbraco.Cms.Core.Xml.XPath; /// /// Represents the type of a content that can be navigated via XPath. /// +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public interface INavigableContentType { /// diff --git a/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs b/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs index 28fa46e84b..faaa612474 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs @@ -4,6 +4,7 @@ namespace Umbraco.Cms.Core.Xml.XPath; /// Represents the type of a field of a content that can be navigated via XPath. /// /// A field can be an attribute or a property. +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public interface INavigableFieldType { /// diff --git a/src/Umbraco.Core/Xml/XPath/INavigableSource.cs b/src/Umbraco.Core/Xml/XPath/INavigableSource.cs index 1f8500725b..459f1bf606 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableSource.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableSource.cs @@ -3,6 +3,7 @@ namespace Umbraco.Cms.Core.Xml.XPath; /// /// Represents a source of content that can be navigated via XPath. /// +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public interface INavigableSource { /// diff --git a/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs b/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs index dd27e6124c..3d706eed80 100644 --- a/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs @@ -7,6 +7,7 @@ namespace Umbraco.Cms.Core.Xml.XPath /// /// Provides a cursor model for navigating {macro /} as if it were XML. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public class MacroNavigator : XPathNavigator { private readonly XmlNameTable _nameTable; diff --git a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs b/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs index 3529f55922..020f753165 100644 --- a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs @@ -21,6 +21,8 @@ namespace Umbraco.Cms.Core.Xml.XPath; /// /// Provides a cursor model for navigating Umbraco data as if it were XML. /// + +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public class NavigableNavigator : XPathNavigator { // "The XmlNameTable stores atomized strings of any local name, namespace URI, diff --git a/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs b/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs index 1b710c8db5..63fa9ef263 100644 --- a/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs @@ -3,6 +3,7 @@ using System.Xml.XPath; namespace Umbraco.Cms.Core.Xml.XPath; +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public class RenamedRootNavigator : XPathNavigator { private readonly XPathNavigator _navigator; diff --git a/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs b/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs index 44cda2c691..09bd34f692 100644 --- a/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs +++ b/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs @@ -18,6 +18,7 @@ public static class XPathNavigatorExtensions /// An XPath expression. /// A set of XPathVariables. /// An iterator over the nodes matching the specified expression. + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XPathNodeIterator Select(this XPathNavigator navigator, string expression, params XPathVariable[] variables) { if (variables == null || variables.Length == 0 || variables[0] == null) @@ -50,6 +51,7 @@ public static class XPathNavigatorExtensions /// An XPath expression. /// A set of XPathVariables. /// An iterator over the nodes matching the specified expression. + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XPathNodeIterator Select(this XPathNavigator navigator, XPathExpression expression, params XPathVariable[] variables) { if (variables == null || variables.Length == 0 || variables[0] == null) diff --git a/src/Umbraco.Core/Xml/XPathVariable.cs b/src/Umbraco.Core/Xml/XPathVariable.cs index 4c2d2d0f4e..675485e551 100644 --- a/src/Umbraco.Core/Xml/XPathVariable.cs +++ b/src/Umbraco.Core/Xml/XPathVariable.cs @@ -6,6 +6,7 @@ namespace Umbraco.Cms.Core.Xml; /// Represents a variable in an XPath query. /// /// The name must be foo in the constructor and $foo in the XPath query. +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public class XPathVariable { /// diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs index ad97120c93..a2165fdd39 100644 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ b/src/Umbraco.Core/Xml/XmlHelper.cs @@ -76,6 +76,7 @@ public class XmlHelper /// /// The xml string. /// An XPathDocument created from the xml string. + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XPathDocument CreateXPathDocument(string xml) => new XPathDocument(new XmlTextReader(new StringReader(xml))); @@ -85,6 +86,7 @@ public class XmlHelper /// The xml string. /// The XPath document. /// A value indicating whether it has been possible to create the document. + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static bool TryCreateXPathDocument(string xml, out XPathDocument? doc) { try @@ -106,6 +108,7 @@ public class XmlHelper /// The XPath document. /// A value indicating whether it has been possible to create the document. /// The value can be anything... Performance-wise, this is bad. + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static bool TryCreateXPathDocumentFromPropertyValue(object value, out XPathDocument? doc) { // DynamicNode.ConvertPropertyValueByDataType first cleans the value by calling @@ -155,6 +158,7 @@ public class XmlHelper /// The parent node. /// An XPath expression to select children of to sort. /// A function returning the value to order the nodes by. + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static void SortNodes( XmlNode parentNode, string childNodesXPath, @@ -187,6 +191,7 @@ public class XmlHelper /// Assuming all nodes but are sorted, this will move the node to /// the right position without moving all the nodes (as SortNodes would do) - should improve perfs. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static bool SortNode( XmlNode parentNode, string childNodesXPath, diff --git a/src/Umbraco.Core/Xml/XmlNodeListFactory.cs b/src/Umbraco.Core/Xml/XmlNodeListFactory.cs index 17c2f41843..8031ca0bf0 100644 --- a/src/Umbraco.Core/Xml/XmlNodeListFactory.cs +++ b/src/Umbraco.Core/Xml/XmlNodeListFactory.cs @@ -27,6 +27,7 @@ public class XmlNodeListFactory /// an object inheriting , such as /// . /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNodeList CreateNodeList(XPathNodeIterator? iterator) => new XmlNodeListIterator(iterator); #endregion Public members diff --git a/src/Umbraco.Infrastructure/IPublishedContentQuery.cs b/src/Umbraco.Infrastructure/IPublishedContentQuery.cs index cc034e5768..6e82239dac 100644 --- a/src/Umbraco.Infrastructure/IPublishedContentQuery.cs +++ b/src/Umbraco.Infrastructure/IPublishedContentQuery.cs @@ -18,6 +18,7 @@ public interface IPublishedContentQuery IPublishedContent? Content(object id); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IPublishedContent? ContentSingleAtXPath(string xpath, params XPathVariable[] vars); IEnumerable Content(IEnumerable ids); @@ -26,8 +27,10 @@ public interface IPublishedContentQuery IEnumerable Content(IEnumerable ids); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars); IEnumerable ContentAtRoot(); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index 9aeeb02d92..178778483d 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -56,6 +56,7 @@ public class MarkdownEditorValueConverter : PropertyValueConverterBase, IDeliver return new HtmlEncodedString(inter == null ? string.Empty : mark.Transform((string)inter)); } + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => // source should come from ConvertSource and be a string (or null) already diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs index ef16aeccc6..d075e8b9d2 100644 --- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs +++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs @@ -129,6 +129,7 @@ public class PublishedContentQuery : IPublishedContentQuery return null; } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IPublishedContent? ContentSingleAtXPath(string xpath, params XPathVariable[] vars) => ItemByXPath(xpath, vars, _publishedSnapshot.Content); @@ -141,9 +142,11 @@ public class PublishedContentQuery : IPublishedContentQuery public IEnumerable Content(IEnumerable ids) => ids.Select(Content).WhereNotNull(); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars) => ItemsByXPath(xpath, vars, _publishedSnapshot.Content); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) => ItemsByXPath(xpath, vars, _publishedSnapshot.Content); diff --git a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs index d8a5c0bc04..d854de4b95 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs @@ -360,6 +360,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab ? _snapshot.IsEmpty == false : _snapshot.GetAtRoot().Any(x => x.PublishedModel != null); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IPublishedContent? GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); @@ -367,6 +368,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab return GetSingleByXPath(iterator); } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IPublishedContent? GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); @@ -386,6 +388,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab return xcontent?.InnerContent; } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); @@ -393,6 +396,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab return GetByXPath(iterator); } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); @@ -416,6 +420,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab } } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override XPathNavigator CreateNavigator(bool preview) { var source = new Source(this, preview); @@ -423,6 +428,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab return navigator; } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override XPathNavigator? CreateNodeNavigator(int id, bool preview) { var source = new Source(this, preview); diff --git a/src/Umbraco.PublishedCache.NuCache/MediaCache.cs b/src/Umbraco.PublishedCache.NuCache/MediaCache.cs index 626e2fe36c..014140e884 100644 --- a/src/Umbraco.PublishedCache.NuCache/MediaCache.cs +++ b/src/Umbraco.PublishedCache.NuCache/MediaCache.cs @@ -95,6 +95,7 @@ public class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableDa #region XPath + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IPublishedContent? GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); @@ -102,6 +103,7 @@ public class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableDa return GetSingleByXPath(iterator); } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IPublishedContent? GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); @@ -109,6 +111,7 @@ public class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableDa return GetSingleByXPath(iterator); } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); diff --git a/src/Umbraco.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index d50553d95f..e86f6f4ad9 100644 --- a/src/Umbraco.PublishedCache.NuCache/Property.cs +++ b/src/Umbraco.PublishedCache.NuCache/Property.cs @@ -288,6 +288,7 @@ internal class Property : PublishedPropertyBase return value; } + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? GetXPathValue(string? culture = null, string? segment = null) { _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index 750d60d67e..036397cb4d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -533,6 +533,7 @@ public class EntityController : UmbracoAuthorizedJsonController /// /// /// + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public ActionResult? GetByXPath(string query, int nodeContextId, int? parentId, UmbracoEntityTypes type) { if (type != UmbracoEntityTypes.Document) diff --git a/src/Umbraco.Web.Common/UmbracoHelper.cs b/src/Umbraco.Web.Common/UmbracoHelper.cs index 56181b29da..17729f3364 100644 --- a/src/Umbraco.Web.Common/UmbracoHelper.cs +++ b/src/Umbraco.Web.Common/UmbracoHelper.cs @@ -174,6 +174,7 @@ public class UmbracoHelper private IPublishedContent? ContentForObject(object id) => _publishedContentQuery.Content(id); + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IPublishedContent? ContentSingleAtXPath(string xpath, params XPathVariable[] vars) => _publishedContentQuery.ContentSingleAtXPath(xpath, vars); @@ -286,9 +287,11 @@ public class UmbracoHelper /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable Content(IEnumerable ids) => _publishedContentQuery.Content(ids); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars) => _publishedContentQuery.ContentAtXPath(xpath, vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) => _publishedContentQuery.ContentAtXPath(xpath, vars); From a5ba32bfd7478f44a0aff3c9c0fcdad08fe51d96 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Fri, 7 Jul 2023 15:23:32 +0300 Subject: [PATCH 23/34] Store tags as text (instead of nvarchar) (#14510) * Migrate tags from NVarchar To NText * Cleanup --- .../Migrations/Upgrade/UmbracoPlan.cs | 4 ++ .../MigrateTagsFromNVarcharToNText.cs | 46 +++++++++++++++++++ .../PropertyEditors/TagsPropertyEditor.cs | 3 +- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_7_0/MigrateTagsFromNVarcharToNText.cs diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 62a37a8e98..aaffb07839 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -6,6 +6,7 @@ using Umbraco.Cms.Infrastructure.Migrations.Upgrade.Common; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_0_0; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_2_0; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_5_0; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_7_0; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_0; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_1; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_1_0; @@ -303,5 +304,8 @@ public class UmbracoPlan : MigrationPlan // to 10.5.0 To("{83AF7945-DADE-4A02-9041-F3F6EBFAC319}"); + + // to 10.7.0 + To("{EF93F398-1385-4F07-808A-D3C518984442}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_7_0/MigrateTagsFromNVarcharToNText.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_7_0/MigrateTagsFromNVarcharToNText.cs new file mode 100644 index 0000000000..462e3772fa --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_7_0/MigrateTagsFromNVarcharToNText.cs @@ -0,0 +1,46 @@ +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_7_0; + +public class MigrateTagsFromNVarcharToNText : MigrationBase +{ + public MigrateTagsFromNVarcharToNText(IMigrationContext context) + : base(context) + { + } + + protected override void Migrate() + { + // Firstly change the storage type for the Umbraco.Tags property editor + Sql updateDbTypeForTagsQuery = Database.SqlContext.Sql() + .Update(x => x.Set(dt => dt.DbType, ValueStorageType.Ntext.ToString())) + .Where(dt => dt.EditorAlias == Constants.PropertyEditors.Aliases.Tags); + + Database.Execute(updateDbTypeForTagsQuery); + + // Then migrate the data from "varcharValue" column to "textValue" + Sql tagsDataTypeIdQuery = Database.SqlContext.Sql() + .Select(dt => dt.NodeId) + .From() + .Where(dt => dt.EditorAlias == Constants.PropertyEditors.Aliases.Tags); + + Sql tagsPropertyTypeIdQuery = Database.SqlContext.Sql() + .Select(pt => pt.Id) + .From() + .WhereIn(pt => pt.DataTypeId, tagsDataTypeIdQuery); + + Sql updatePropertyDataColumnsQuery = Database.SqlContext.Sql() + .Update() + .Append("SET textValue = varcharValue, varcharValue = null") + .WhereIn(pd => pd.PropertyTypeId, tagsPropertyTypeIdQuery) + .Where(pd => pd.TextValue == null) + .Where(pd => pd.VarcharValue != null); + + Database.Execute(updatePropertyDataColumnsQuery); + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs index 6f82c8ab3c..99ae8f378a 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs @@ -25,7 +25,8 @@ namespace Umbraco.Cms.Core.PropertyEditors; "Tags", "tags", Icon = "icon-tags", - ValueEditorIsReusable = true)] + ValueEditorIsReusable = true, + ValueType = ValueTypes.Text)] public class TagsPropertyEditor : DataEditor { private readonly IEditorConfigurationParser _editorConfigurationParser; From 45083b673596f83d063d27f843af8ecd4a2fd6e6 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:49:08 +0200 Subject: [PATCH 24/34] Removed usage of sending umbracoApi to our testHelpers (#14526) * Cleaned up in Advanced * Cleaned up in Areas * Cleaned up in Content * Cleaned up in Rendering * Cleaned up in Settings * Updated the timeout to 40 seconds, 30 seconds is not always enough anymore. * Bumped version of testhelpers and builders --- .../package-lock.json | 18 ++-- .../Umbraco.Tests.AcceptanceTest/package.json | 4 +- .../playwright.config.ts | 10 +- .../Content/blockGridEditorAdvanced.spec.ts | 16 +-- .../blockGridEditorAreasContent.spec.ts | 98 +++++++++---------- .../Content/blockGridEditorContent.spec.ts | 54 +++++----- .../Content/blockGridEditorRendering.spec.ts | 60 ++++++------ .../Content/blockGridEditorSettings.spec.ts | 38 +++---- 8 files changed, 149 insertions(+), 149 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index e4feabc9ae..3eca295aac 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -7,8 +7,8 @@ "name": "acceptancetest", "hasInstallScript": true, "dependencies": { - "@umbraco/json-models-builders": "^1.0.5", - "@umbraco/playwright-testhelpers": "^1.0.22", + "@umbraco/json-models-builders": "^1.0.6", + "@umbraco/playwright-testhelpers": "^1.0.24", "camelize": "^1.0.0", "dotenv": "^16.0.2", "faker": "^4.1.0", @@ -132,20 +132,20 @@ "dev": true }, "node_modules/@umbraco/json-models-builders": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-1.0.5.tgz", - "integrity": "sha512-14xowT8oiW9+DQVOoundRPvcvnNrU0Ey+06G/q/iZyUnqaNRu/i5nUqcbUZGdv6VBCdxaxq2H3WwtSET3gtneA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-1.0.6.tgz", + "integrity": "sha512-bXwfXcpuqG1Ye714L9KJEGXuSzJfckysE/6CuPjdG8FqHWTE1brv28teR2oMw+ih8ca2u2zUboRgdzLEU/1D3Q==", "dependencies": { "camelize": "^1.0.0", "faker": "^4.1.0" } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-1.0.22.tgz", - "integrity": "sha512-hFqqQvEIylagfqFyhQ2rSyYlUP+xpWA5lkhJjkpb2qpxkIISxjwC/FYJTJGvcoBHuUaZrjsSv4lM2aJy2ZWHMA==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-1.0.24.tgz", + "integrity": "sha512-xYOgcnyvcEywgC9DT4Q3OhQDTfdtF1zXLQIXdjNtwr6a4j3SUab1RI/tGxlF01fX+8Ttw3edlV4l+HIrY0hM1Q==", "dependencies": { - "@umbraco/json-models-builders": "^1.0.5", + "@umbraco/json-models-builders": "^1.0.6", "camelize": "^1.0.0", "faker": "^4.1.0", "form-data": "^4.0.0", diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index f651f799ae..e6e4c264ff 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -19,8 +19,8 @@ "wait-on": "^6.0.1" }, "dependencies": { - "@umbraco/json-models-builders": "^1.0.5", - "@umbraco/playwright-testhelpers": "^1.0.22", + "@umbraco/json-models-builders": "^1.0.6", + "@umbraco/playwright-testhelpers": "^1.0.24", "camelize": "^1.0.0", "faker": "^4.1.0", "form-data": "^4.0.0", diff --git a/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts b/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts index d084cd69ba..f3d94a05f7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts @@ -10,7 +10,7 @@ dotenv.config(); const config: PlaywrightTestConfig = { testDir: './tests/', /* Maximum time one test can run for. */ - timeout: 30 * 1000, + timeout: 40 * 1000, expect: { /** * Maximum time expect() should wait for the condition to be met. @@ -27,18 +27,18 @@ const config: PlaywrightTestConfig = { /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: process.env.CI ? 'line' : 'html', outputDir : "./results", - + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ actionTimeout: 0, /* Base URL to use in actions like `await page.goto('/')`. */ // baseURL: 'http://localhost:44332', - + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - // When working locally it can be a good idea to use trace: 'on-first-retry' instead of 'retain-on-failure', it can cut the local test times in half. + // When working locally it can be a good idea to use trace: 'on-first-retry' instead of 'retain-on-failure', it can cut the local test times in half. trace: 'retain-on-failure', - ignoreHTTPSErrors: true, + ignoreHTTPSErrors: true, }, /* Configure projects for major browsers */ diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAdvanced.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAdvanced.spec.ts index 8db631019b..0e18826781 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAdvanced.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAdvanced.spec.ts @@ -72,7 +72,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -134,7 +134,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -160,7 +160,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -186,7 +186,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -213,7 +213,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -250,7 +250,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -288,7 +288,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -336,7 +336,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAreasContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAreasContent.spec.ts index a55c12a7d9..7c5b407786 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAreasContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAreasContent.spec.ts @@ -6,26 +6,26 @@ import {expect} from "@playwright/test"; test.describe('BlockGridEditorAreasContent', () => { const documentName = 'DocumentTest'; const blockGridName = 'BlockGridTest'; - const elementTitleName = 'ElementTitle'; + const elementTitleName = 'ElementTitle'; const titleText = 'ElementTitle'; const titleArea = 'AreaTitle'; const elementBodyName = 'ElementBody'; const bodyText = 'Lorem ipsum dolor sit amet'; - + const elementBodyAlias = AliasHelper.toAlias(elementBodyName); const documentAlias = AliasHelper.toAlias(documentName); const blockGridAlias = AliasHelper.toAlias(blockGridName); const elementTitleAlias = AliasHelper.toAlias(elementTitleName); - - async function createContentWithABlockInAnotherBlock(umbracoApi,elementParent, elementChild, dataType?, document?) { + + async function createContentWithABlockInAnotherBlock(umbracoApi, elementParent, elementChild, dataType?, document?) { if (dataType == null) { - dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(umbracoApi, elementParent, elementChild, blockGridName, titleArea); + dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(elementParent, elementChild, blockGridName, titleArea); } if (document == null) { - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, elementParent, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(elementParent, dataType); } - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -58,7 +58,7 @@ test.describe('BlockGridEditorAreasContent', () => { .build(); return await umbracoApi.content.save(rootContentNode); } - + test.beforeEach(async ({page, umbracoApi, umbracoUi}, testInfo) => { await umbracoApi.report.report(testInfo); await umbracoApi.login(); @@ -80,12 +80,12 @@ test.describe('BlockGridEditorAreasContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); - const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(umbracoApi, element, elementBody, blockGridName, titleArea); - - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(element, elementBody, blockGridName, titleArea); + + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); - + // Adds a body to the area await page.locator('[data-content-element-type-key="' + element['key'] + '"] >> [data-area-alias="' + titleArea + '"]').click(); await page.locator('[name="infiniteEditorForm"]').locator('[data-element="editor-container"]').getByRole('button', {name: elementBodyName}).click(); @@ -166,9 +166,9 @@ test.describe('BlockGridEditorAreasContent', () => { .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - + await createContentWithABlockInAnotherBlock(umbracoApi, element, elementBody, dataType); - + await umbracoUi.navigateToContent(blockGridName); // Adds another body to the area @@ -204,7 +204,7 @@ test.describe('BlockGridEditorAreasContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -212,12 +212,12 @@ test.describe('BlockGridEditorAreasContent', () => { // Checks if the area in content has the value that is defined in the block grid editor await expect(page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-area-alias="' + titleArea + '"]')).toHaveAttribute('data-area-col-span', '6'); }); - + test('can add two different blocks in a area with different grid columns', async ({page, umbracoApi, umbracoUi}) => { const secondArea = 'AreaFour'; const columnSpanEight = "8"; const columnSpanFour = "4"; - + const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); @@ -239,8 +239,8 @@ test.describe('BlockGridEditorAreasContent', () => { .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -275,14 +275,14 @@ test.describe('BlockGridEditorAreasContent', () => { await umbracoApi.content.save(rootContentNode); await umbracoUi.navigateToContent(blockGridName); - + // Adds a block to the area with a different column span await page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-area-alias="' + secondArea + '"]').getByRole('button', {name: 'Add content'}).click(); await page.locator('[name="infiniteEditorForm"]').locator('[data-element="editor-container"]').getByRole('button', {name: elementBodyName}).click(); await page.locator('[id="sub-view-0"]').locator('[id="title"]').fill('BodyTwoText'); await page.locator('[label="Create"]').click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); - + // Assert await umbracoUi.isSuccessNotificationVisible(); // Checks if there are two block in the ElementTitle Area @@ -311,14 +311,14 @@ test.describe('BlockGridEditorAreasContent', () => { .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); + + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); await umbracoUi.navigateToContent(blockGridName); // Checks if the button has the correct row span await expect(page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-area-row-span="3"]').getByRole('button', {name: 'Add content'})).toBeVisible(); - + // Adds a block to the area await page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-area-alias="' + titleArea + '"]').getByRole('button', {name: 'Add content'}).click(); await page.locator('[name="infiniteEditorForm"]').locator('[data-element="editor-container"]').getByRole('button', {name: elementBodyName}).click(); @@ -336,7 +336,7 @@ test.describe('BlockGridEditorAreasContent', () => { await expect(page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-content-element-type-alias="' + elementBodyAlias + '"]')).toHaveAttribute('data-row-span', '3'); }); }); - + test.describe('Create Button Label', () => { test('can add a create button label for an area in block grid editor', async ({page, umbracoApi, umbracoUi}) => { const newButtonLabel = 'NewAreaBlock'; @@ -358,9 +358,9 @@ test.describe('BlockGridEditorAreasContent', () => { .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); - + + await umbracoApi.content.createDefaultContentWithABlockGridEditor( element, dataType, false); + await umbracoUi.navigateToContent(blockGridName); // Assert @@ -371,7 +371,7 @@ test.describe('BlockGridEditorAreasContent', () => { await createButtonLocator.click(); }); }); - + test.describe('Number of blocks', () => { test('can add a minimum number of blocks for a block grid editor', async ({page, umbracoApi, umbracoUi}) => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); @@ -418,7 +418,7 @@ test.describe('BlockGridEditorAreasContent', () => { test('can add a maximum number of blocks for a block grid editor', async ({page, umbracoApi, umbracoUi}) => { const bodyTextTwo = 'AnotherBody'; - + const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); @@ -437,7 +437,7 @@ test.describe('BlockGridEditorAreasContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -522,7 +522,7 @@ test.describe('BlockGridEditorAreasContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -551,7 +551,7 @@ test.describe('BlockGridEditorAreasContent', () => { await umbracoApi.content.save(rootContentNode); await umbracoUi.navigateToContent(blockGridName); - + // Adds a ElementBody await page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-area-alias="' + titleArea + '"]').click(); // Since the ElementBody is added as the only Specified Allowance for the area of the Element, then we should be instantly directed to it, instead of having to pick it. @@ -586,7 +586,7 @@ test.describe('BlockGridEditorAreasContent', () => { const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); await createContentWithABlockInAnotherBlock(umbracoApi, element, elementBody, dataType, null); - + await umbracoUi.navigateToContent(blockGridName); // Checks if a validation error is visible @@ -630,7 +630,7 @@ test.describe('BlockGridEditorAreasContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -736,7 +736,7 @@ test.describe('BlockGridEditorAreasContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor( element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -778,7 +778,7 @@ test.describe('BlockGridEditorAreasContent', () => { await umbracoApi.content.save(rootContentNode); await umbracoUi.navigateToContent(blockGridName); - + // Checks if there is validation error for both blocks await expect(page.locator('[key="blockEditor_areaValidationEntriesExceed"]', {hasText: elementBodyName})).toBeVisible(); await expect(page.locator('[key="blockEditor_areaValidationEntriesShort"]', {hasText: elementFooterName})).toBeVisible(); @@ -808,7 +808,7 @@ test.describe('BlockGridEditorAreasContent', () => { await expect(page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-content-element-type-alias="' + elementBodyAlias + '"]')).toHaveCount(1); await expect(page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-content-element-type-alias="' + elementFooterAlias + '"]')).toHaveCount(1); - // Clean + // Clean await umbracoApi.documentTypes.ensureNameNotExists(elementFooterName); }); }); @@ -818,9 +818,9 @@ test.describe('BlockGridEditorAreasContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); - const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(umbracoApi, element, elementBody, blockGridName, titleArea); + const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(element, elementBody, blockGridName, titleArea); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -869,9 +869,9 @@ test.describe('BlockGridEditorAreasContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); - const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(umbracoApi, element, elementBody, blockGridName, titleArea); + const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(element, elementBody, blockGridName, titleArea); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -935,9 +935,9 @@ test.describe('BlockGridEditorAreasContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); - const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(umbracoApi, element, elementBody, blockGridName, titleArea); + const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(element, elementBody, blockGridName, titleArea); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -1002,9 +1002,9 @@ test.describe('BlockGridEditorAreasContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); - const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(umbracoApi, element, elementBody, blockGridName, titleArea); + const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(element, elementBody, blockGridName, titleArea); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -1041,14 +1041,14 @@ test.describe('BlockGridEditorAreasContent', () => { const blockParentUdi = rootContentNode.variants[0].properties[0].value.contentData[1].udi; await umbracoUi.navigateToContent(blockGridName); - + // Copies and pastes the block into another block await page.locator('[data-element-udi="' + blockParentUdi + '"]').getByRole('button', {name: 'Copy'}).nth(1).click(); await page.locator('[data-element="property-' + blockGridAlias + '"]').getByRole('button', {name: 'Clipboard'}).click(); await page.locator('[data-element="editor-container"]').locator('umb-block-card', {hasText: elementTitleName}).click(); - + // Checks if the blocks were copied await expect(page.locator('[data-content-element-type-alias="' + elementTitleAlias + '"]')).toHaveCount(2); }); }); -}); \ No newline at end of file +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorContent.spec.ts index 9eb4d75e92..9c610da191 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorContent.spec.ts @@ -27,8 +27,8 @@ test.describe('BlockGridEditorContent', () => { }); test('can create content with a block grid editor', async ({page, umbracoApi, umbracoUi}) => { - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, null, null); - + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(null, null); + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -58,11 +58,11 @@ test.describe('BlockGridEditorContent', () => { const newContentValue = 'UpdatedTitle'; const newSettingValue = 'UpdatedSetting'; - const element = await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, null, null, null); + const element = await umbracoApi.content.createDefaultContentWithABlockGridEditor(null, null, null); await umbracoUi.navigateToContent(blockGridName); - // Updates the already created content text + // Updates the already created content text await page.locator('[data-content-element-type-key="' + element['key'] + '"]', {hasText: elementName}).click(); await page.locator('[id="sub-view-0"]').locator('[id="title"]').fill(newContentValue); await umbracoUi.clickDataElementByElementName('sub-view-settings'); @@ -71,7 +71,7 @@ test.describe('BlockGridEditorContent', () => { await page.locator('[label="Submit"]').click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); - // Assert + // Assert await umbracoUi.isSuccessNotificationVisible(); // Checks if the Content and Setting were updated after it was saved await page.locator('[data-content-element-type-key="' + element['key'] + '"]', {hasText: elementName}).click(); @@ -81,13 +81,13 @@ test.describe('BlockGridEditorContent', () => { }); test('can delete a block grid editor in content', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, null, null, null); + const element = await umbracoApi.content.createDefaultContentWithABlockGridEditor(null, null, null); await umbracoUi.navigateToContent(blockGridName); // Deletes the block grid editor inside of the content await page.getByTitle("Delete").click(); - + // Can't use our constant helper because the action for delete does not contain an s. The correct way is 'action-delete' await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey('actions_delete')); @@ -101,7 +101,7 @@ test.describe('BlockGridEditorContent', () => { }); test('can copy block grid content and paste it', async ({page, umbracoApi, umbracoUi}) => { - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, null, null, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(null, null, null); await umbracoUi.navigateToContent(blockGridName); // Checks to make sure that there is only one item @@ -134,7 +134,7 @@ test.describe('BlockGridEditorContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); - const dataType = await umbracoApi.dataTypes.createDefaultBlockGrid(umbracoApi, blockGridName, element); + const dataType = await umbracoApi.dataTypes.createDefaultBlockGrid(blockGridName, element); const docType = new DocumentTypeBuilder() .withName(documentName) @@ -157,7 +157,7 @@ test.describe('BlockGridEditorContent', () => { .build(); await umbracoApi.documentTypes.save(docType); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, true); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, true); await umbracoUi.navigateToContent(blockGridName); @@ -192,9 +192,9 @@ test.describe('BlockGridEditorContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); // We give the dataType a label so we can differentiate between the top and bottom block in the content editor. - const dataType = await umbracoApi.dataTypes.createDefaultBlockGrid(umbracoApi, blockGridName, element, '{{' + element.groups[0].properties[0].alias + '}}'); + const dataType = await umbracoApi.dataTypes.createDefaultBlockGrid(blockGridName, element, '{{' + element.groups[0].properties[0].alias + '}}'); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -226,7 +226,7 @@ test.describe('BlockGridEditorContent', () => { await umbracoApi.content.save(rootContentNode); await umbracoUi.navigateToContent(blockGridName); - + // Drag and Drop const dragFromLocator = await page.locator('[data-content-element-type-key="' + element['key'] + '"]', {hasText: bottomBlock}); const dragToLocator = await page.locator('[data-content-element-type-key="' + element['key'] + '"]', {hasText: topBlock}); @@ -253,7 +253,7 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -319,7 +319,7 @@ test.describe('BlockGridEditorContent', () => { await umbracoApi.media.createDefaultImage(imageName); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, null); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, null); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -374,7 +374,7 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); await umbracoUi.navigateToContent(blockGridName); // Checks if there is validation for needing 2 entries or more @@ -408,7 +408,7 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -470,7 +470,7 @@ test.describe('BlockGridEditorContent', () => { test.describe('Live editing mode', () => { test('can use live editing mode in content with a block grid editor', async ({page, umbracoApi, umbracoUi}) => { const newText = 'LiveUpdatedContent'; - + const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); const dataTypeBlockGrid = new BlockGridDataTypeBuilder() @@ -485,17 +485,17 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); await umbracoUi.navigateToContent(blockGridName); // Checks if the block contains the correct text before being edited await expect(page.locator('[data-content-element-type-alias="'+elementAlias +'"]')).toContainText('aliasTest'); - + // Updates the text without saving the changes. await page.locator('[data-content-element-type-key="' + element['key'] + '"]').click(); await page.locator('[id="sub-view-0"]').locator('[id="title"]').fill(newText); - + // Checks if the block is being live updated as the content is being updated. await expect(page.locator('[data-content-element-type-alias="'+elementAlias +'"]')).toContainText(newText); }); @@ -515,7 +515,7 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); await umbracoUi.navigateToContent(blockGridName); @@ -540,7 +540,7 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); await umbracoUi.navigateToContent(blockGridName); @@ -571,8 +571,8 @@ test.describe('BlockGridEditorContent', () => { .withLayoutStylesheet(stylesheetDataPath) .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -600,7 +600,7 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); // Goes to the created Content await umbracoUi.goToSection(ConstantHelper.sections.content); @@ -614,4 +614,4 @@ test.describe('BlockGridEditorContent', () => { await page.locator('[data-element="property-' + blockGridAlias + '"] >> .umb-block-grid__actions', {hasText: newButtonLabel}).click(); }); }); -}); \ No newline at end of file +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts index d95d39ccaa..0d8bdd58ac 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts @@ -9,14 +9,14 @@ test.describe('BlockGridEditorRending', () => { const blockGridName = 'BlockGridTest'; const elementTitleName = 'ElementTitle'; const contentText = "ContentTest"; - + const documentAlias = 'documentTest'; const blockGridAlias = 'blockGridTest'; const elementTitleAlias = "elementTitle"; const elementTitleLabel = 'Title'; const elementTitleLabelAlias = AliasHelper.toAlias(elementTitleLabel); - + test.beforeEach(async ({page, umbracoApi, umbracoUi}, testInfo) => { await umbracoApi.report.report(testInfo); await umbracoApi.login(); @@ -51,7 +51,7 @@ test.describe('BlockGridEditorRending', () => { .build(); return await umbracoApi.documentTypes.save(element); } - + async function createDocumentWithTemplateAndDataType(umbracoApi, dataType){ const docType = new DocumentTypeBuilder() .withName(documentName) @@ -69,14 +69,14 @@ test.describe('BlockGridEditorRending', () => { .build(); return await umbracoApi.documentTypes.save(docType); } - + async function editDefaultTemplate(umbracoApi){ await umbracoApi.templates.edit(documentName, '@using Umbraco.Cms.Web.Common.PublishedModels;\n' + '@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage\n' + '@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;' + '\n' + '@await Html.GetBlockGridHtmlAsync(Model.' + blockGridName + ')'); } - + async function createPartialViewWithArea(umbracoApi, elementAlias, elementLabelAlias){ const partialViewElementTitle = new PartialViewBuilder() .withName(elementAlias) @@ -91,7 +91,7 @@ test.describe('BlockGridEditorRending', () => { partialViewElementTitle.virtualPath = "/Views/Partials/blockgrid/Components/"; return await umbracoApi.partialViews.save(partialViewElementTitle); } - + async function createPartialViewWithBlock(umbracoApi,elementAlias, elementLabelAlias){ const partialViewElementBody = new PartialViewBuilder() .withName(elementAlias) @@ -105,11 +105,11 @@ test.describe('BlockGridEditorRending', () => { partialViewElementBody.virtualPath = "/Views/Partials/blockgrid/Components/"; await umbracoApi.partialViews.save(partialViewElementBody); } - + test('can render content with a block grid editor', async ({page, umbracoApi, umbracoUi}) => { const element = await createElementWithRTE(umbracoApi, elementTitleName, elementTitleAlias, elementTitleLabel, elementTitleLabelAlias); - const dataType = await umbracoApi.dataTypes.createDefaultBlockGrid(umbracoApi, blockGridName, element); + const dataType = await umbracoApi.dataTypes.createDefaultBlockGrid(blockGridName, element); await createDocumentWithTemplateAndDataType(umbracoApi, dataType); @@ -140,7 +140,7 @@ test.describe('BlockGridEditorRending', () => { .withTemplateAlias(documentName) .build(); await umbracoApi.content.save(rootContentNode); - + // Assert await page.goto(umbracoConfig.environment.baseUrl); await expect(page).toHaveScreenshot('Block-grid-editor.png'); @@ -158,7 +158,7 @@ test.describe('BlockGridEditorRending', () => { const element = await createElementWithRTE(umbracoApi, elementTitleName, elementTitleAlias, elementTitleLabel, elementTitleLabelAlias); const elementBody = await createElementWithRTE(umbracoApi, elementBodyName, elementBodyAlias, elementBodyLabel, elementBodyLabelAlias); - + const dataTypeBlockGrid = new BlockGridDataTypeBuilder() .withName(blockGridName) .addBlock() @@ -179,7 +179,7 @@ test.describe('BlockGridEditorRending', () => { // Creates partial view for the ElementBody await createPartialViewWithBlock(umbracoApi, elementBodyAlias, elementBodyLabelAlias); - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.publish) @@ -210,12 +210,12 @@ test.describe('BlockGridEditorRending', () => { .withTemplateAlias(documentName) .build(); await umbracoApi.content.save(rootContentNode); - + // Assert await page.goto(umbracoConfig.environment.baseUrl); await expect(page).toHaveScreenshot('Block-grid-editor-with-two-elements.png'); - // Clean + // Clean await umbracoApi.documentTypes.ensureNameNotExists(elementBodyName); await umbracoApi.partialViews.ensureNameNotExists('blockgrid/Components', elementBodyAlias + '.cshtml'); }); @@ -257,7 +257,7 @@ test.describe('BlockGridEditorRending', () => { // Creates partial view for the ElementBody await createPartialViewWithBlock(umbracoApi, elementBodyAlias, elementBodyLabelAlias); - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.publish) @@ -291,12 +291,12 @@ test.describe('BlockGridEditorRending', () => { .withTemplateAlias(documentName) .build(); await umbracoApi.content.save(rootContentNode); - + // Assert await page.goto(umbracoConfig.environment.baseUrl); await expect(page).toHaveScreenshot('Block-grid-editor-with-area.png'); - - // Clean + + // Clean await umbracoApi.documentTypes.ensureNameNotExists(elementBodyName); await umbracoApi.partialViews.ensureNameNotExists('blockgrid/Components', elementBodyAlias + '.cshtml'); }) @@ -392,7 +392,7 @@ test.describe('BlockGridEditorRending', () => { .withTemplateAlias(documentName) .build(); await umbracoApi.content.save(rootContentNode); - + // Assert await page.goto(umbracoConfig.environment.baseUrl); await expect(page).toHaveScreenshot('Block-grid-editor-with-multiple-areas.png'); @@ -433,7 +433,7 @@ test.describe('BlockGridEditorRending', () => { .done() .done() .addBlock() - .withContentElementTypeKey(elementBody['key']) + .withContentElementTypeKey(elementBody['key']) .addArea() .withAlias('bodyArea') .done() @@ -551,7 +551,7 @@ test.describe('BlockGridEditorRending', () => { .build(); partialViewElement.virtualPath = "/Views/Partials/blockgrid/Components/"; await umbracoApi.partialViews.save(partialViewElement); - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.publish) @@ -595,7 +595,7 @@ test.describe('BlockGridEditorRending', () => { // ElementBody const elementBodyName = "ElementBody"; const elementBodyAlias = AliasHelper.toAlias(elementBodyName); - + await umbracoApi.documentTypes.ensureNameNotExists(elementBodyName); await umbracoApi.media.ensureNameNotExists(stylesheetTitleName); await umbracoApi.media.ensureNameNotExists(stylesheetBodyName); @@ -695,7 +695,7 @@ test.describe('BlockGridEditorRending', () => { // Assert await page.goto(umbracoConfig.environment.baseUrl); await expect(page).toHaveScreenshot('Block-grid-editor-with-two-custom-stylesheets.png'); - + // Clean await umbracoApi.documentTypes.ensureNameNotExists(elementBodyName); await umbracoApi.media.ensureNameNotExists(stylesheetTitleName); @@ -703,7 +703,7 @@ test.describe('BlockGridEditorRending', () => { await umbracoApi.partialViews.ensureNameNotExists('blockgrid/Components', elementBodyAlias + '.cshtml'); }); }); - + test.describe('Image', () => { test('can render a block grid with an image', async ({page, umbracoApi, umbracoUi}) => { // Image @@ -714,9 +714,9 @@ test.describe('BlockGridEditorRending', () => { const imageMimeType = "image/png"; await umbracoApi.media.ensureNameNotExists(imageName); - + const imageData = await umbracoApi.media.createImageWithFile(imageName, umbracoFileValue, imageFileName, imagePath, imageMimeType); - + const element = new DocumentTypeBuilder() .withName(elementTitleName) .withAlias(elementTitleAlias) @@ -756,7 +756,7 @@ test.describe('BlockGridEditorRending', () => { .build(); partialViewImage.virtualPath = "/Views/Partials/blockgrid/Components/"; await umbracoApi.partialViews.save(partialViewImage); - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.publish) @@ -771,7 +771,7 @@ test.describe('BlockGridEditorRending', () => { .addImage() .withMediaKey(imageData.key) .done() - .done() + .done() .addLayout() .withContentUdi(element['key']) .done() @@ -780,12 +780,12 @@ test.describe('BlockGridEditorRending', () => { .done() .build(); await umbracoApi.content.save(rootContentNode); - + // Assert await page.goto(umbracoConfig.environment.baseUrl); await expect(page).toHaveScreenshot('Block-grid-editor-with-image.png'); - - // Clean + + // Clean await umbracoApi.media.ensureNameNotExists(imageName); }); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorSettings.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorSettings.spec.ts index 358a979a43..0f5773ba04 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorSettings.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorSettings.spec.ts @@ -29,7 +29,7 @@ test.describe('BlockGridEditorSettings', () => { test.describe('General', () => { test('can see label in content for a block grid editor', async ({page, umbracoApi, umbracoUi}) => { const newLabel = "New Label"; - + const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); const dataTypeBlockGrid = new BlockGridDataTypeBuilder() @@ -41,14 +41,14 @@ test.describe('BlockGridEditorSettings', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); - + // Assert // Checks if the element contains the correct label await expect(page.locator('[data-content-element-type-alias="' + elementAlias + '"]')).toContainText(newLabel); - // Checks if the element is clickable + // Checks if the element is clickable await page.locator('[data-content-element-type-alias="' + elementAlias + '"]').click(); }); }); @@ -65,9 +65,9 @@ test.describe('BlockGridEditorSettings', () => { .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); - + + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -77,21 +77,21 @@ test.describe('BlockGridEditorSettings', () => { .done() .build(); await umbracoApi.content.save(rootContentNode); - + await umbracoUi.navigateToContent(blockGridName); - + // Checks if adding a block is disabled await expect(page.locator('[data-element="property-'+blockGridAlias+'"]').locator('umb-block-grid-root').locator('[disabled="disabled"]')).toBeDisabled(); // Checks if the button is not clickable await expect(page.locator('[data-element="property-' + blockGridAlias + '"]').locator('[key="blockEditor_addBlock"]')).not.toBeEnabled(); }); - + test('can set allow in areas to false for an element in a block grid editor', async ({page, umbracoApi, umbracoUi}) => { const elementBodyName = 'BodyElement'; const elementBodyAlias = AliasHelper.toAlias(elementBodyName); - + await umbracoApi.documentTypes.ensureNameNotExists(elementBodyName); - + const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); @@ -110,14 +110,14 @@ test.describe('BlockGridEditorSettings', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi,element,dataType,false); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); await umbracoUi.navigateToContent(blockGridName); - + // Assert // Checks if the elementTitle is the only element selectable to be in a area await expect(page.locator('[data-area-alias="titleArea"]').locator('[key="blockEditor_addThis"]')).toContainText('Add ' + elementName); - + // Clean await umbracoApi.documentTypes.ensureNameNotExists(elementBodyName); }); @@ -136,20 +136,20 @@ test.describe('BlockGridEditorSettings', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); - + // Drags the blocks from a columnSpan of 12 to 6 const dragFrom = await page.locator('[data-content-element-type-key="' + element['key'] + '"]', {hasText: elementName}).locator('[title="Drag to scale"]'); const dragTo = await page.locator('[data-content-element-type-key="' + element['key'] + '"]', {hasText: elementName}); await umbracoUi.dragAndDrop(dragFrom,dragTo, 0, 0 ,10); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); - // Assert + // Assert await umbracoUi.isSuccessNotificationVisible(); // Checks if the block is resized to a column span of 6 await expect(page.locator('[data-content-element-type-key="' + element['key'] + '"], [data-col-span="6"]', {hasText: elementName})).toBeVisible(); }); }); -}); \ No newline at end of file +}); From 313fdf79fecf4f53977463fc83bc9b46fc113f10 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 10 Jul 2023 10:38:43 +0200 Subject: [PATCH 25/34] Added test for checking if settings model contains default value (#14513) * Added test for checking if a settings model contains default value. Fixed tests * Bumped version * Moved createSliderWithDefaultValue function to our testHelpers. Removed the use of count in test since there was only one --- .../blockListEditorContent.spec.ts | 248 ++++++++++++------ 1 file changed, 172 insertions(+), 76 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts index 8dcadef04d..0c99d46ab5 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts @@ -6,24 +6,26 @@ import {BlockListDataTypeBuilder} from "@umbraco/json-models-builders/dist/lib/b test.describe('BlockListEditorContent', () => { const documentName = 'DocumentTestName'; const blockListName = 'BlockListTest'; - const elementName = 'TestElement'; + const contentElementName = 'ContentElement'; + const settingElementName = 'SettingsElement'; const documentAlias = AliasHelper.toAlias(documentName); const blockListAlias = AliasHelper.toAlias(blockListName); - // Won't work if I use the to alias for the elementAlias - const elementAlias = 'testElement'; - + // Won't work if we use the to alias for the elementAliases + const contentElementAlias = 'contentElement'; + const settingsElementAlias = 'settingsElement'; + test.beforeEach(async ({page, umbracoApi, umbracoUi}, testInfo) => { await umbracoApi.report.report(testInfo); await umbracoApi.login(); await umbracoApi.documentTypes.ensureNameNotExists(documentName); - await umbracoApi.documentTypes.ensureNameNotExists(elementName); + await umbracoApi.documentTypes.ensureNameNotExists(contentElementName); await umbracoApi.dataTypes.ensureNameNotExists(blockListName); }); - + test.afterEach(async ({page, umbracoApi, umbracoUi}) => { await umbracoApi.documentTypes.ensureNameNotExists(documentName); - await umbracoApi.documentTypes.ensureNameNotExists(elementName); + await umbracoApi.documentTypes.ensureNameNotExists(contentElementName); await umbracoApi.dataTypes.ensureNameNotExists(blockListName); }); @@ -31,23 +33,22 @@ test.describe('BlockListEditorContent', () => { const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .addBlock() - .withContentElementTypeKey(element['key']) - .withSettingsElementTypeKey(element['key']) + .withContentElementTypeKey(element['key']) + .withSettingsElementTypeKey(element['key']) .done() .build(); return await umbracoApi.dataTypes.save(dataTypeBlockList); } - - async function createDocumentWithOneBlockListEditor(umbracoApi, element, dataType){ - + + async function createDocumentWithOneBlockListEditor(umbracoApi, element, dataType, elementName?, elementAlias?){ if(element == null) { element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); } - + if(dataType == null) { dataType = await createDefaultBlockList(umbracoApi, blockListName, element); } - + const docType = new DocumentTypeBuilder() .withName(documentName) .withAlias(documentAlias) @@ -61,16 +62,15 @@ test.describe('BlockListEditorContent', () => { .done() .build(); await umbracoApi.documentTypes.save(docType); - + return element; } - - async function createContentWithOneBlockListEditor(umbracoApi, element) { - + + async function createContentWithOneBlockListEditor(umbracoApi, element, datatype?, elementName?, elementAlias?) { if(element == null) { - element = await createDocumentWithOneBlockListEditor(umbracoApi, null, null); + element = await createDocumentWithOneBlockListEditor(umbracoApi, null, datatype, elementName, elementAlias); } - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -89,13 +89,35 @@ test.describe('BlockListEditorContent', () => { .done() .build(); await umbracoApi.content.save(rootContentNode); - + return element; } + async function createElementWithSlider(umbracoApi, name, alias, sliderId, sliderName?, sliderDefaultValue?) { + if (sliderId == null) { + const sliderData = await umbracoApi.dataTypes.createSliderWithDefaultValue(sliderName, sliderDefaultValue); + sliderId = sliderData.id; + } + + const contentElement = new DocumentTypeBuilder() + .withName(name) + .withAlias(alias) + .AsElementType() + .addGroup() + .withName('Content') + .withAlias('content') + .addCustomProperty(sliderId) + .withLabel('Slider') + .withAlias('slider') + .done() + .done() + .build(); + return await umbracoApi.documentTypes.save(contentElement); + } + test('can create content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - await createDocumentWithOneBlockListEditor(umbracoApi, null, null); - + await createDocumentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -113,7 +135,7 @@ test.describe('BlockListEditorContent', () => { await umbracoUi.clickDataElementByElementName('tree-item-' + blockListName); // Adds TestElement - await page.locator('[key="blockEditor_addThis"]', {hasText: elementName}).click(); + await page.locator('[key="blockEditor_addThis"]', {hasText: contentElementName}).click(); await page.locator('[id="sub-view-0"]').locator('[id="title"]').fill('Testing...'); await page.locator('[label="Create"]').click(); @@ -121,17 +143,17 @@ test.describe('BlockListEditorContent', () => { // Assert await umbracoUi.isSuccessNotificationVisible(); - + // Checks if the content was created await expect(page.locator('.umb-block-list__block--view')).toHaveCount(1); - await expect(page.locator('.umb-block-list__block--view').nth(0)).toHaveText(elementName); + await expect(page.locator('.umb-block-list__block--view')).toHaveText(contentElementName); // Checks if the content contains the correct value - await page.locator('.umb-block-list__block--view').nth(0).click(); + await page.locator('.umb-block-list__block--view', {hasText: contentElementName}).click(); await expect(page.locator('[id="sub-view-0"]').locator('[id="title"]')).toHaveValue('Testing...'); }); test('can update content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - await createContentWithOneBlockListEditor(umbracoApi, null); + await createContentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -154,7 +176,7 @@ test.describe('BlockListEditorContent', () => { }); test('can delete a block list editor in content', async ({page, umbracoApi, umbracoUi}) => { - await createContentWithOneBlockListEditor(umbracoApi, null); + await createContentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -172,13 +194,13 @@ test.describe('BlockListEditorContent', () => { // Assert await umbracoUi.isSuccessNotificationVisible(); - + // Checks if the content is actually deleted await expect(page.locator('[ui-sortable="vm.sortableOptions"]').nth(0)).not.toBeVisible(); }); test('can copy block list content and paste it', async ({page, umbracoApi, umbracoUi}) => { - await createContentWithOneBlockListEditor(umbracoApi, null); + await createContentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -194,7 +216,7 @@ test.describe('BlockListEditorContent', () => { await expect(page.locator('.alert-success', {hasText: 'Copied to clipboard'})).toBeVisible(); // Pastes block list content await page.locator('[title="Clipboard"]').click(); - await page.locator('umb-block-card', {hasText: elementName}).click(); + await page.locator('umb-block-card', {hasText: contentElementName}).click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); // Assert @@ -204,9 +226,9 @@ test.describe('BlockListEditorContent', () => { }); test('can copy block list content and paste it into another group with the same block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); - const dataType = await createDefaultBlockList(umbracoApi, blockListName, element); + const dataType = await createDefaultBlockList(umbracoApi, blockListName, contentElement); const docType = new DocumentTypeBuilder() .withName(documentName) @@ -227,7 +249,7 @@ test.describe('BlockListEditorContent', () => { .build(); await umbracoApi.documentTypes.save(docType); - await createContentWithOneBlockListEditor(umbracoApi, element); + await createContentWithOneBlockListEditor(umbracoApi, contentElement); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -245,7 +267,7 @@ test.describe('BlockListEditorContent', () => { await expect(page.locator('.alert-success', {hasText: 'Copied to clipboard'})).toBeVisible(); // Pastes into the second group await page.locator('[title="Clipboard"]').nth(1).click(); - await page.locator('umb-block-card', {hasText: elementName}).click(); + await page.locator('umb-block-card', {hasText: contentElementName}).click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); await expect(page.locator('.alert-success', {hasText: 'Content Published'})).toBeVisible(); @@ -257,20 +279,20 @@ test.describe('BlockListEditorContent', () => { }); test('can set a minimum of required blocks in content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .withMin(2) .addBlock() - .withContentElementTypeKey(element['key']) + .withContentElementTypeKey(contentElement['key']) .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockList); - await createDocumentWithOneBlockListEditor(umbracoApi, element, dataType); - - await createContentWithOneBlockListEditor(umbracoApi, element); + await createDocumentWithOneBlockListEditor(umbracoApi, contentElement, dataType); + + await createContentWithOneBlockListEditor(umbracoApi, contentElement); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -294,18 +316,18 @@ test.describe('BlockListEditorContent', () => { }); test('can set a maximum of required blocks in content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .withMax(2) .addBlock() - .withContentElementTypeKey(element['key']) + .withContentElementTypeKey(contentElement['key']) .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockList); - await createDocumentWithOneBlockListEditor(umbracoApi, element, dataType); + await createDocumentWithOneBlockListEditor(umbracoApi, contentElement, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -317,16 +339,16 @@ test.describe('BlockListEditorContent', () => { .withAlias(blockListAlias) .addBlockListValue() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "aliasTest") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "aliasTest") .done() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "aliasTests") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "aliasTests") .done() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "aliasTester") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "aliasTester") .done() .done() .done() @@ -357,20 +379,20 @@ test.describe('BlockListEditorContent', () => { }); test('can use inline editing mode in content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .withUseInlineEditingAsDefault(true) .addBlock() - .withContentElementTypeKey(element['key']) + .withContentElementTypeKey(contentElement['key']) .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockList); - await createDocumentWithOneBlockListEditor(umbracoApi, element, dataType); - - await createContentWithOneBlockListEditor(umbracoApi, element); + await createDocumentWithOneBlockListEditor(umbracoApi, contentElement, dataType); + + await createContentWithOneBlockListEditor(umbracoApi, contentElement); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -387,11 +409,11 @@ test.describe('BlockListEditorContent', () => { test('can see rendered content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { await umbracoApi.templates.ensureNameNotExists(documentName); - await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',elementAlias + '.cshtml'); + await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',contentElementAlias + '.cshtml'); - const element = new DocumentTypeBuilder() - .withName(elementName) - .withAlias(elementAlias) + const contentElement = new DocumentTypeBuilder() + .withName(contentElementName) + .withAlias(contentElementAlias) .AsElementType() .addGroup() .withName("TestString") @@ -406,9 +428,9 @@ test.describe('BlockListEditorContent', () => { .done() .done() .build(); - await umbracoApi.documentTypes.save(element); + await umbracoApi.documentTypes.save(contentElement); - const dataType = await createDefaultBlockList(umbracoApi, blockListName, element); + const dataType = await createDefaultBlockList(umbracoApi, blockListName, contentElement); const docType = new DocumentTypeBuilder() .withName(documentName) @@ -418,7 +440,7 @@ test.describe('BlockListEditorContent', () => { .addGroup() .withName('BlockListGroup') .addCustomProperty(dataType['id']) - .withAlias(elementAlias) + .withAlias(contentElementAlias) .done() .done() .build(); @@ -431,15 +453,15 @@ test.describe('BlockListEditorContent', () => { '\n Layout = null;' + '\n}' + '\n' + - '@Html.GetBlockListHtml(Model.' + elementName + ')'); + '@Html.GetBlockListHtml(Model.' + contentElementName + ')'); const partialView = new PartialViewBuilder() - .withName(elementAlias) + .withName(contentElementAlias) .withContent("@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage;\n" + "@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;\n" + "@{\n" + - "var content = (ContentModels." + elementName + ")Model.Content;\n" + - "var settings = (ContentModels." + elementName + ")Model.Settings;\n" + + "var content = (ContentModels." + contentElementName + ")Model.Content;\n" + + "var settings = (ContentModels." + contentElementName + ")Model.Settings;\n" + "}\n" + "\n" + "

@content.Title

" + @@ -459,15 +481,15 @@ test.describe('BlockListEditorContent', () => { .withSave(true) .withPublish(true) .addProperty() - .withAlias(elementAlias) + .withAlias(contentElementAlias) .addBlockListValue() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "ContentTest") - .appendContentProperties(element.groups[0].properties[1].alias, "RTEContent") - .withSettingsTypeKey(element['key']) - .appendSettingsProperties(element.groups[0].properties[0].alias, "SettingTest") - .appendSettingsProperties(element.groups[0].properties[1].alias, "RTESetting") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "ContentTest") + .appendContentProperties(contentElement.groups[0].properties[1].alias, "RTEContent") + .withSettingsTypeKey(contentElement['key']) + .appendSettingsProperties(contentElement.groups[0].properties[0].alias, "SettingTest") + .appendSettingsProperties(contentElement.groups[0].properties[1].alias, "RTESetting") .done() .done() .done() @@ -479,9 +501,83 @@ test.describe('BlockListEditorContent', () => { // Ensure that the view gets rendered correctly const expected = `

ContentTest

RTEContent

SettingTest

RTESetting

`; await expect(await umbracoApi.content.verifyRenderedContent('/', expected, true)).toBeTruthy(); - + // Clean await umbracoApi.templates.ensureNameNotExists(documentName); - await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',elementAlias + '.cshtml'); + await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',contentElementAlias + '.cshtml'); }); -}); \ No newline at end of file + + test('Checks if the settings content model contains a default value when created', async ({page, umbracoApi, umbracoUi}) => { + const contentSliderName = 'ContentSlider'; + const contentSliderDefaultValue = 5; + const settingsSliderName = 'SettingsSlider'; + const settingsSliderDefaultValue = 9; + + await umbracoApi.documentTypes.ensureNameNotExists(settingElementName); + await umbracoApi.dataTypes.ensureNameNotExists(contentSliderName); + await umbracoApi.dataTypes.ensureNameNotExists(settingsSliderName); + + const contentElement = await createElementWithSlider(umbracoApi, contentElementName, contentElementAlias, null, contentSliderName, contentSliderDefaultValue); + + const settingsElement = await createElementWithSlider(umbracoApi, settingElementName, settingsElementAlias, null, settingsSliderName, settingsSliderDefaultValue); + + const blockList = new BlockListDataTypeBuilder() + .withName(blockListName) + .addBlock() + .withContentElementTypeKey(contentElement['key']) + .withSettingsElementTypeKey(settingsElement['key']) + .done() + .build(); + const blockListDataType = await umbracoApi.dataTypes.save(blockList); + + const document = new DocumentTypeBuilder() + .withName(documentName) + .withAlias(documentAlias) + .withAllowAsRoot(true) + .addGroup() + .withName('Content') + .withAlias('content') + .addCustomProperty(blockListDataType['id']) + .withLabel('BlockListTest') + .withAlias('blockListTest') + .done() + .done() + .build(); + await umbracoApi.documentTypes.save(document); + + const content = new ContentBuilder() + .withContentTypeAlias(documentAlias) + .withAction(ConstantHelper.actions.publish) + .addVariant() + .withName(blockListName) + .withSave(true) + .withPublish(true) + .done() + .build(); + const contentNode = await umbracoApi.content.save(content); + + await umbracoUi.goToSection(ConstantHelper.sections.content); + await umbracoUi.refreshContentTree(); + await umbracoUi.clickDataElementByElementName('tree-item-' + blockListName); + await page.locator('[key="blockEditor_addThis"]', {hasText: contentElementName}).click(); + await page.locator('[label="Create"]').click(); + await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); + + // Assert + // Checks if the content contains the BlockListEditor block + await umbracoUi.clickDataElementByElementName("tree-root"); + await umbracoUi.refreshContentTree(); + await umbracoUi.clickDataElementByElementName('tree-item-' + blockListName); + await expect(page.locator('.umb-block-list__block--view')).toHaveCount(1); + + // Checks if both the content and settings have been assigned their DefaultValues + const contentData = await umbracoApi.content.getContent(contentNode.id); + await expect(contentData.variants[0].tabs[0].properties[0].value.contentData[0].slider).toEqual(contentSliderDefaultValue.toString()); + await expect(contentData.variants[0].tabs[0].properties[0].value.settingsData[0].slider).toEqual(settingsSliderDefaultValue.toString()); + + // Clean + await umbracoApi.documentTypes.ensureNameNotExists(settingElementName); + await umbracoApi.dataTypes.ensureNameNotExists(contentSliderName); + await umbracoApi.dataTypes.ensureNameNotExists(settingsSliderName); + }); +}); From 82eae48d098b9deecbdf86cf288b2b18020e1fed Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 13 Jul 2023 06:03:39 +0200 Subject: [PATCH 26/34] Merge pull request from GHSA-h8wc-r4jh-mg7m * Don't login after install * Fail the install if database is not created --------- Co-authored-by: Zeegaan Co-authored-by: Nikolaj --- .../Runtime/RuntimeState.cs | 2 +- .../Install/InstallApiController.cs | 27 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs index ec1cfc5d6e..8731f7acc8 100644 --- a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs @@ -213,7 +213,7 @@ public class RuntimeState : IRuntimeState if (_globalSettings.Value.InstallMissingDatabase || _databaseProviderMetadata.CanForceCreateDatabase(_databaseFactory)) { // ok to install on a configured but missing database - Level = RuntimeLevel.Install; + Level = RuntimeLevel.BootFailed; Reason = RuntimeLevelReason.InstallMissingDatabase; return; } diff --git a/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs b/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs index 555c14ff96..b710933791 100644 --- a/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs +++ b/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs @@ -1,5 +1,6 @@ using System.Reflection; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using Umbraco.Cms.Core; @@ -25,8 +26,6 @@ namespace Umbraco.Cms.Web.BackOffice.Install; [Area(Constants.Web.Mvc.InstallArea)] public class InstallApiController : ControllerBase { - private readonly IBackOfficeSignInManager _backOfficeSignInManager; - private readonly IBackOfficeUserManager _backOfficeUserManager; private readonly DatabaseBuilder _databaseBuilder; private readonly InstallStatusTracker _installStatusTracker; private readonly InstallStepCollection _installSteps; @@ -34,6 +33,7 @@ public class InstallApiController : ControllerBase private readonly IProfilingLogger _proflog; private readonly IRuntime _runtime; + [Obsolete("Use the constructor without IBackOfficeUserManager & IBackOfficeSignInManager instead, scheduled for removal in v14")] public InstallApiController( DatabaseBuilder databaseBuilder, IProfilingLogger proflog, @@ -44,14 +44,25 @@ public class InstallApiController : ControllerBase IRuntime runtime, IBackOfficeUserManager backOfficeUserManager, IBackOfficeSignInManager backOfficeSignInManager) + : this(databaseBuilder, proflog, logger, installHelper, installSteps, installStatusTracker, runtime) + { + } + + [ActivatorUtilitiesConstructor] + public InstallApiController( + DatabaseBuilder databaseBuilder, + IProfilingLogger proflog, + ILogger logger, + InstallHelper installHelper, + InstallStepCollection installSteps, + InstallStatusTracker installStatusTracker, + IRuntime runtime) { _databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder)); _proflog = proflog ?? throw new ArgumentNullException(nameof(proflog)); _installSteps = installSteps; _installStatusTracker = installStatusTracker; _runtime = runtime; - _backOfficeUserManager = backOfficeUserManager; - _backOfficeSignInManager = backOfficeSignInManager; InstallHelper = installHelper; _logger = logger; } @@ -88,16 +99,8 @@ public class InstallApiController : ControllerBase [HttpPost] public async Task CompleteInstall() { - RuntimeLevel levelBeforeRestart = _runtime.State.Level; - await _runtime.RestartAsync(); - if (levelBeforeRestart == RuntimeLevel.Install) - { - BackOfficeIdentityUser identityUser = await _backOfficeUserManager.FindByIdAsync(Core.Constants.Security.SuperUserIdAsString); - _backOfficeSignInManager.SignInAsync(identityUser, false); - } - return NoContent(); } From 596284f32dace3b50f0ad54b1ca94ecd51aaa0a7 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Thu, 13 Jul 2023 06:07:59 +0200 Subject: [PATCH 27/34] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index f64e6fe584..f621eed7fd 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "11.4.1", + "version": "11.4.2", "assemblyVersion": { "precision": "build" }, From 1f26f2c6f3428833892cde5c6d8441fb041e410e Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+zeegaan@users.noreply.github.com> Date: Thu, 13 Jul 2023 06:03:39 +0200 Subject: [PATCH 28/34] Merge pull request from GHSA-h8wc-r4jh-mg7m * Don't login after install * Fail the install if database is not created --------- Co-authored-by: Zeegaan Co-authored-by: Nikolaj --- .../Runtime/RuntimeState.cs | 2 +- .../Install/InstallApiController.cs | 31 +++++++++---------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs index 6597fadf61..2e2e5dbe59 100644 --- a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs @@ -212,7 +212,7 @@ public class RuntimeState : IRuntimeState if (_globalSettings.Value.InstallMissingDatabase || _databaseProviderMetadata.CanForceCreateDatabase(_databaseFactory)) { // ok to install on a configured but missing database - Level = RuntimeLevel.Install; + Level = RuntimeLevel.BootFailed; Reason = RuntimeLevelReason.InstallMissingDatabase; return; } diff --git a/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs b/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs index 52068c6f8d..b710933791 100644 --- a/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs +++ b/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs @@ -1,5 +1,6 @@ using System.Reflection; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using Umbraco.Cms.Core; @@ -25,8 +26,6 @@ namespace Umbraco.Cms.Web.BackOffice.Install; [Area(Constants.Web.Mvc.InstallArea)] public class InstallApiController : ControllerBase { - private readonly IBackOfficeSignInManager _backOfficeSignInManager; - private readonly IBackOfficeUserManager _backOfficeUserManager; private readonly DatabaseBuilder _databaseBuilder; private readonly InstallStatusTracker _installStatusTracker; private readonly InstallStepCollection _installSteps; @@ -34,6 +33,7 @@ public class InstallApiController : ControllerBase private readonly IProfilingLogger _proflog; private readonly IRuntime _runtime; + [Obsolete("Use the constructor without IBackOfficeUserManager & IBackOfficeSignInManager instead, scheduled for removal in v14")] public InstallApiController( DatabaseBuilder databaseBuilder, IProfilingLogger proflog, @@ -44,14 +44,25 @@ public class InstallApiController : ControllerBase IRuntime runtime, IBackOfficeUserManager backOfficeUserManager, IBackOfficeSignInManager backOfficeSignInManager) + : this(databaseBuilder, proflog, logger, installHelper, installSteps, installStatusTracker, runtime) + { + } + + [ActivatorUtilitiesConstructor] + public InstallApiController( + DatabaseBuilder databaseBuilder, + IProfilingLogger proflog, + ILogger logger, + InstallHelper installHelper, + InstallStepCollection installSteps, + InstallStatusTracker installStatusTracker, + IRuntime runtime) { _databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder)); _proflog = proflog ?? throw new ArgumentNullException(nameof(proflog)); _installSteps = installSteps; _installStatusTracker = installStatusTracker; _runtime = runtime; - _backOfficeUserManager = backOfficeUserManager; - _backOfficeSignInManager = backOfficeSignInManager; InstallHelper = installHelper; _logger = logger; } @@ -88,20 +99,8 @@ public class InstallApiController : ControllerBase [HttpPost] public async Task CompleteInstall() { - RuntimeLevel levelBeforeRestart = _runtime.State.Level; - await _runtime.RestartAsync(); - if (levelBeforeRestart == RuntimeLevel.Install) - { - BackOfficeIdentityUser? identityUser = - await _backOfficeUserManager.FindByIdAsync(Core.Constants.Security.SuperUserIdAsString); - if (identityUser is not null) - { - _backOfficeSignInManager.SignInAsync(identityUser, false); - } - } - return NoContent(); } From 2a601ae0d0682737d6c12f6d2e6c641b51bda717 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Thu, 13 Jul 2023 06:17:34 +0200 Subject: [PATCH 29/34] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 531f830eba..222a809220 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "12.0.0", + "version": "12.0.1", "assemblyVersion": { "precision": "build" }, From 20a4e475c8d7b91d263e4e103ef19f3644e7b569 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+zeegaan@users.noreply.github.com> Date: Thu, 13 Jul 2023 06:03:39 +0200 Subject: [PATCH 30/34] Merge pull request from GHSA-h8wc-r4jh-mg7m * Don't login after install * Fail the install if database is not created --------- Co-authored-by: Zeegaan Co-authored-by: Nikolaj --- .../Runtime/RuntimeState.cs | 2 +- .../Install/InstallApiController.cs | 31 +++++++++---------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs index 6597fadf61..2e2e5dbe59 100644 --- a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs @@ -212,7 +212,7 @@ public class RuntimeState : IRuntimeState if (_globalSettings.Value.InstallMissingDatabase || _databaseProviderMetadata.CanForceCreateDatabase(_databaseFactory)) { // ok to install on a configured but missing database - Level = RuntimeLevel.Install; + Level = RuntimeLevel.BootFailed; Reason = RuntimeLevelReason.InstallMissingDatabase; return; } diff --git a/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs b/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs index 52068c6f8d..b710933791 100644 --- a/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs +++ b/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs @@ -1,5 +1,6 @@ using System.Reflection; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using Umbraco.Cms.Core; @@ -25,8 +26,6 @@ namespace Umbraco.Cms.Web.BackOffice.Install; [Area(Constants.Web.Mvc.InstallArea)] public class InstallApiController : ControllerBase { - private readonly IBackOfficeSignInManager _backOfficeSignInManager; - private readonly IBackOfficeUserManager _backOfficeUserManager; private readonly DatabaseBuilder _databaseBuilder; private readonly InstallStatusTracker _installStatusTracker; private readonly InstallStepCollection _installSteps; @@ -34,6 +33,7 @@ public class InstallApiController : ControllerBase private readonly IProfilingLogger _proflog; private readonly IRuntime _runtime; + [Obsolete("Use the constructor without IBackOfficeUserManager & IBackOfficeSignInManager instead, scheduled for removal in v14")] public InstallApiController( DatabaseBuilder databaseBuilder, IProfilingLogger proflog, @@ -44,14 +44,25 @@ public class InstallApiController : ControllerBase IRuntime runtime, IBackOfficeUserManager backOfficeUserManager, IBackOfficeSignInManager backOfficeSignInManager) + : this(databaseBuilder, proflog, logger, installHelper, installSteps, installStatusTracker, runtime) + { + } + + [ActivatorUtilitiesConstructor] + public InstallApiController( + DatabaseBuilder databaseBuilder, + IProfilingLogger proflog, + ILogger logger, + InstallHelper installHelper, + InstallStepCollection installSteps, + InstallStatusTracker installStatusTracker, + IRuntime runtime) { _databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder)); _proflog = proflog ?? throw new ArgumentNullException(nameof(proflog)); _installSteps = installSteps; _installStatusTracker = installStatusTracker; _runtime = runtime; - _backOfficeUserManager = backOfficeUserManager; - _backOfficeSignInManager = backOfficeSignInManager; InstallHelper = installHelper; _logger = logger; } @@ -88,20 +99,8 @@ public class InstallApiController : ControllerBase [HttpPost] public async Task CompleteInstall() { - RuntimeLevel levelBeforeRestart = _runtime.State.Level; - await _runtime.RestartAsync(); - if (levelBeforeRestart == RuntimeLevel.Install) - { - BackOfficeIdentityUser? identityUser = - await _backOfficeUserManager.FindByIdAsync(Core.Constants.Security.SuperUserIdAsString); - if (identityUser is not null) - { - _backOfficeSignInManager.SignInAsync(identityUser, false); - } - } - return NoContent(); } From 291f90d03dc0da21f3b0b64468bdf43cddf4b1f3 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Thu, 13 Jul 2023 10:48:43 +0200 Subject: [PATCH 31/34] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index f621eed7fd..fe28088e74 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "11.4.2", + "version": "11.4.3", "assemblyVersion": { "precision": "build" }, From c12cce6d8c077f49a2ecde4f879ed42f6567f072 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 14 Jul 2023 09:27:02 +0200 Subject: [PATCH 32/34] Fixes an incorrect property name used for generating JSON schema. (#14542) --- src/JsonSchema/AppSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs index e1a6d619ec..717f826859 100644 --- a/src/JsonSchema/AppSettings.cs +++ b/src/JsonSchema/AppSettings.cs @@ -88,7 +88,7 @@ namespace JsonSchema public HelpPageSettings? HelpPage { get; set; } - public InstallDefaultDataSettings? DefaultDataCreation { get; set; } + public InstallDefaultDataSettings? InstallDefaultData { get; set; } public DataTypesSettings? DataTypes { get; set; } From af44f7f59da7ea68576b747a42286c7983b699f9 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Fri, 14 Jul 2023 09:38:16 +0200 Subject: [PATCH 33/34] Fix migration plan --- src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index e739d61744..cade3369f8 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -1,5 +1,6 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_7_0; namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade; @@ -77,6 +78,7 @@ public class UmbracoPlan : MigrationPlan // to 11.4.3 // This is here twice since it was added in 10, so if you had already upgraded you wouldn't get it // But we still need the first once, since if you upgraded to 10.7.0 then the "From" state would be unknown otherwise. - To("{EF93F398-1385-4F07-808A-D3C518984442}"); + // This has it's own key, we essentially consider it a separate migration. + To("{2CA0C5BB-170B-45E5-8179-E73DA4B41A46}"); } } From ba423a010831844a542169a53a8f7eef36b1c611 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Tue, 18 Jul 2023 11:53:14 +0300 Subject: [PATCH 34/34] V12: Add ISO codes to make the migration from language IDs easier (#14567) * Change the obsoletion messages for language IDs to target V14 instead of V13. * Wrong Language file * Add ISO codes required to migrate custom code from language IDs * Population of the new language FallbackIsoCode prop * Changing obsoletion msgs from v13 to v14 * Fix breaking changes --- .../Models/ContentEditing/Language.cs | 2 +- src/Umbraco.Core/Models/DictionaryItem.cs | 2 +- .../Models/DictionaryItemExtensions.cs | 4 +-- .../Models/DictionaryTranslation.cs | 32 ++++++++++++++--- .../Models/IDictionaryTranslation.cs | 14 +++++--- src/Umbraco.Core/Models/ILanguage.cs | 19 +++++++++- src/Umbraco.Core/Models/Language.cs | 11 ++++-- .../Persistence/Factories/LanguageFactory.cs | 3 +- .../Implement/LanguageRepository.cs | 35 ++++++++++++++----- .../Builders/LanguageBuilder.cs | 2 +- 10 files changed, 98 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Core/Models/ContentEditing/Language.cs b/src/Umbraco.Core/Models/ContentEditing/Language.cs index 112aeb5aac..99c011d608 100644 --- a/src/Umbraco.Core/Models/ContentEditing/Language.cs +++ b/src/Umbraco.Core/Models/ContentEditing/Language.cs @@ -22,7 +22,7 @@ public class Language [DataMember(Name = "isMandatory")] public bool IsMandatory { get; set; } - [Obsolete("This will be replaced by fallback language ISO code in V13.")] + [Obsolete("This will be replaced by fallback language ISO code in V14.")] [DataMember(Name = "fallbackLanguageId")] public int? FallbackLanguageId { get; set; } } diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index 90576a85e3..b0e787de02 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -34,7 +34,7 @@ public class DictionaryItem : EntityBase, IDictionaryItem _translations = new List(); } - [Obsolete("This will be removed in V13.")] + [Obsolete("This will be removed in V14.")] public Func? GetLanguage { get; set; } /// diff --git a/src/Umbraco.Core/Models/DictionaryItemExtensions.cs b/src/Umbraco.Core/Models/DictionaryItemExtensions.cs index 09654d5137..341f185ff9 100644 --- a/src/Umbraco.Core/Models/DictionaryItemExtensions.cs +++ b/src/Umbraco.Core/Models/DictionaryItemExtensions.cs @@ -10,7 +10,7 @@ public static class DictionaryItemExtensions /// /// /// - [Obsolete("This will be replaced in V13 by a corresponding method accepting language ISO code instead of language ID.")] + [Obsolete("This will be replaced in V14 by a corresponding method accepting language ISO code instead of language ID.")] public static string? GetTranslatedValue(this IDictionaryItem d, int languageId) { IDictionaryTranslation? trans = d.Translations.FirstOrDefault(x => x.LanguageId == languageId); @@ -22,7 +22,7 @@ public static class DictionaryItemExtensions /// /// /// - [Obsolete("Warning: This method ONLY works in very specific scenarios. It will be removed in V13.")] + [Obsolete("Warning: This method ONLY works in very specific scenarios. It will be removed in V14.")] public static string? GetDefaultValue(this IDictionaryItem d) { IDictionaryTranslation? defaultTranslation = d.Translations.FirstOrDefault(x => x.Language?.Id == 1); diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index ab79b77e44..7f4471785c 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -1,5 +1,8 @@ using System.Runtime.Serialization; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Core.Models; @@ -11,6 +14,7 @@ namespace Umbraco.Cms.Core.Models; public class DictionaryTranslation : EntityBase, IDictionaryTranslation { private ILanguage? _language; + private string? _languageIsoCode; // note: this will be memberwise cloned private string _value; @@ -20,6 +24,7 @@ public class DictionaryTranslation : EntityBase, IDictionaryTranslation _language = language ?? throw new ArgumentNullException("language"); LanguageId = _language.Id; _value = value; + LanguageIsoCode = language.IsoCode; } public DictionaryTranslation(ILanguage language, string value, Guid uniqueId) @@ -27,17 +32,18 @@ public class DictionaryTranslation : EntityBase, IDictionaryTranslation _language = language ?? throw new ArgumentNullException("language"); LanguageId = _language.Id; _value = value; + LanguageIsoCode = language.IsoCode; Key = uniqueId; } - [Obsolete("Please use constructor that accepts ILanguage. This will be removed in V13.")] + [Obsolete("Please use constructor that accepts ILanguage. This will be removed in V14.")] public DictionaryTranslation(int languageId, string value) { LanguageId = languageId; _value = value; } - [Obsolete("Please use constructor that accepts ILanguage. This will be removed in V13.")] + [Obsolete("Please use constructor that accepts ILanguage. This will be removed in V14.")] public DictionaryTranslation(int languageId, string value, Guid uniqueId) { LanguageId = languageId; @@ -58,7 +64,7 @@ public class DictionaryTranslation : EntityBase, IDictionaryTranslation /// returned /// on a callback. /// - [Obsolete("This will be removed in V13. From V13 onwards you should get languages by ISO code from ILanguageService.")] + [Obsolete("This will be removed in V14. From V14 onwards you should get languages by ISO code from ILanguageService.")] [DataMember] [DoNotClone] public ILanguage? Language @@ -86,7 +92,7 @@ public class DictionaryTranslation : EntityBase, IDictionaryTranslation } } - [Obsolete("This will be replaced by language ISO code in V13.")] + [Obsolete("This will be replaced by language ISO code in V14.")] public int LanguageId { get; private set; } /// @@ -99,6 +105,24 @@ public class DictionaryTranslation : EntityBase, IDictionaryTranslation set => SetPropertyValueAndDetectChanges(value, ref _value!, nameof(Value)); } + /// + public string LanguageIsoCode + { + get + { + // TODO: this won't be necessary after obsoleted ctors are removed in v14. + if (_languageIsoCode is null) + { + var _languageService = StaticServiceProvider.Instance.GetRequiredService(); + _languageIsoCode = _languageService.GetLanguageById(LanguageId)?.IsoCode ?? string.Empty; + } + + return _languageIsoCode; + } + + private set => SetPropertyValueAndDetectChanges(value, ref _languageIsoCode!, nameof(LanguageIsoCode)); + } + protected override void PerformDeepClone(object clone) { base.PerformDeepClone(clone); diff --git a/src/Umbraco.Core/Models/IDictionaryTranslation.cs b/src/Umbraco.Core/Models/IDictionaryTranslation.cs index 45d71e3f9b..8f8d9ffaa4 100644 --- a/src/Umbraco.Core/Models/IDictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/IDictionaryTranslation.cs @@ -6,18 +6,24 @@ namespace Umbraco.Cms.Core.Models; public interface IDictionaryTranslation : IEntity, IRememberBeingDirty { /// - /// Gets or sets the for the translation + /// Gets or sets the for the translation. /// - [Obsolete("This will be removed in V13. From V13 onwards you should get languages by ISO code from ILanguageService.")] + [Obsolete("This will be removed in V14. From V14 onwards you should get languages by ISO code from ILanguageService.")] [DataMember] ILanguage? Language { get; set; } - [Obsolete("This will be replaced by language ISO code in V13.")] + [Obsolete("This will be replaced by language ISO code in V14.")] int LanguageId { get; } /// - /// Gets or sets the translated text + /// Gets or sets the translated text. /// [DataMember] string Value { get; set; } + + /// + /// Gets the ISO code of the language. + /// + [DataMember] + string LanguageIsoCode => Language?.IsoCode ?? string.Empty; } diff --git a/src/Umbraco.Core/Models/ILanguage.cs b/src/Umbraco.Core/Models/ILanguage.cs index 88c76ae7b0..885833cd5c 100644 --- a/src/Umbraco.Core/Models/ILanguage.cs +++ b/src/Umbraco.Core/Models/ILanguage.cs @@ -55,7 +55,24 @@ public interface ILanguage : IEntity, IRememberBeingDirty /// define fallback strategies when a value does not exist for a requested language. /// /// - [Obsolete("This will be replaced by fallback language ISO code in V13.")] + [Obsolete("This will be replaced by fallback language ISO code in V14.")] [DataMember] int? FallbackLanguageId { get; set; } + + + /// + /// Gets or sets the ISO code of a fallback language. + /// + /// + /// + /// The fallback language can be used in multi-lingual scenarios, to help + /// define fallback strategies when a value does not exist for a requested language. + /// + /// + [DataMember] + string? FallbackIsoCode + { + get => null; + set { } + } } diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index 9871cf3eed..62a65f086b 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -1,6 +1,5 @@ using System.Globalization; using System.Runtime.Serialization; -using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -14,6 +13,7 @@ public class Language : EntityBase, ILanguage { private string _cultureName; private int? _fallbackLanguageId; + private string? _fallbackLanguageIsoCode; private bool _isDefaultVariantLanguage; private string _isoCode; private bool _mandatory; @@ -74,10 +74,17 @@ public class Language : EntityBase, ILanguage } /// - [Obsolete("This will be replaced by fallback language ISO code in V13.")] + [Obsolete("This will be replaced by fallback language ISO code in V14.")] public int? FallbackLanguageId { get => _fallbackLanguageId; set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageId, nameof(FallbackLanguageId)); } + + /// + public string? FallbackIsoCode + { + get => _fallbackLanguageIsoCode; + set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageIsoCode, nameof(FallbackIsoCode)); + } } diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/LanguageFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/LanguageFactory.cs index 9ab958c306..6ef14238af 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/LanguageFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/LanguageFactory.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories; internal static class LanguageFactory { - public static ILanguage BuildEntity(LanguageDto dto) + public static ILanguage BuildEntity(LanguageDto dto, string? fallbackIsoCode) { ArgumentNullException.ThrowIfNull(dto); if (dto.IsoCode is null) @@ -22,6 +22,7 @@ internal static class LanguageFactory IsDefault = dto.IsDefault, IsMandatory = dto.IsMandatory, FallbackLanguageId = dto.FallbackLanguageId, + FallbackIsoCode = fallbackIsoCode }; // Reset dirty initial properties diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs index 398a55ebaf..590fae26c0 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs @@ -120,7 +120,19 @@ internal class LanguageRepository : EntityRepositoryBase, ILangu new FullDataSetRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ false); protected ILanguage ConvertFromDto(LanguageDto dto) - => LanguageFactory.BuildEntity(dto); + { + // yes, we want to lock _codeIdMap + lock (_codeIdMap) + { + string? fallbackIsoCode = null; + if (dto.FallbackLanguageId.HasValue && _idCodeMap.TryGetValue(dto.FallbackLanguageId.Value, out fallbackIsoCode) == false) + { + throw new ArgumentException($"The ISO code map did not contain ISO code for fallback language ID: {dto.FallbackLanguageId}. Please reload the caches."); + } + + return LanguageFactory.BuildEntity(dto, fallbackIsoCode); + } + } // do NOT leak that language, it's not deep-cloned! private ILanguage GetDefault() @@ -172,20 +184,25 @@ internal class LanguageRepository : EntityRepositoryBase, ILangu sql.OrderBy(x => x.Id); // get languages - var languages = Database.Fetch(sql).Select(ConvertFromDto).OrderBy(x => x.Id).ToList(); + List? languageDtos = Database.Fetch(sql) ?? new List(); - // initialize the code-id map - lock (_codeIdMap) + // initialize the code-id map if we've reloaded the entire set of languages + if (ids?.Any() == false) { - _codeIdMap.Clear(); - _idCodeMap.Clear(); - foreach (ILanguage language in languages) + lock (_codeIdMap) { - _codeIdMap[language.IsoCode] = language.Id; - _idCodeMap[language.Id] = language.IsoCode.ToLowerInvariant(); + _codeIdMap.Clear(); + _idCodeMap.Clear(); + foreach (LanguageDto languageDto in languageDtos) + { + ArgumentException.ThrowIfNullOrEmpty(languageDto.IsoCode, nameof(LanguageDto.IsoCode)); + _codeIdMap[languageDto.IsoCode] = languageDto.Id; + _idCodeMap[languageDto.Id] = languageDto.IsoCode; + } } } + var languages = languageDtos.Select(ConvertFromDto).OrderBy(x => x.Id).ToList(); return languages; } diff --git a/tests/Umbraco.Tests.Common/Builders/LanguageBuilder.cs b/tests/Umbraco.Tests.Common/Builders/LanguageBuilder.cs index 16283e0adf..0aa81a7381 100644 --- a/tests/Umbraco.Tests.Common/Builders/LanguageBuilder.cs +++ b/tests/Umbraco.Tests.Common/Builders/LanguageBuilder.cs @@ -95,7 +95,7 @@ public class LanguageBuilder return this; } - [Obsolete("This will be replaced in V13 by a corresponding method accepting language ISO code instead of language ID.")] + [Obsolete("This will be replaced in V14 by a corresponding method accepting language ISO code instead of language ID.")] public LanguageBuilder WithFallbackLanguageId(int fallbackLanguageId) { _fallbackLanguageId = fallbackLanguageId;