diff --git a/.github/README.md b/.github/README.md index 7e7d51d712..e633679795 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,4 +1,11 @@ -# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/contrib)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Twitter](https://img.shields.io/twitter/follow/umbraco.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=umbraco) [![Discord](https://img.shields.io/discord/869656431308189746)](https://discord.gg/umbraco) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=v11%2Fcontrib&repo=10601208&machine=basicLinux32gb&devcontainer_path=.devcontainer%2Fdevcontainer.json&location=WestEurope) +# [Umbraco CMS](https://umbraco.com) + +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) +[![Follow Umbraco on Twitter](https://img.shields.io/badge/Follow-blue?logo=twitter&logoColor=fff)](https://twitter.com/intent/follow?screen_name=umbraco) +[![Chat about Umbraco on Discord](https://img.shields.io/discord/869656431308189746?logo=discord&logoColor=fff)](https://discord.gg/umbraco) +[![Build status](https://img.shields.io/azure-devops/build/umbraco/Umbraco%2520Cms/301?logo=azurepipelines&label=Azure%20Pipelines)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=301) +[![Open in GitHub Codespaces](https://img.shields.io/badge/Open%20in%20GitHub%20Codespaces-525252?logo=github)](https://github.com/codespaces/new?hide_repo_select=true&ref=contrib&repo=10601208&machine=basicLinux32gb&devcontainer_path=.devcontainer%2Fdevcontainer.json&location=WestEurope) Umbraco is the friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 500,000 websites worldwide. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social. diff --git a/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj b/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj index b4a9683095..6ecc446e46 100644 --- a/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj +++ b/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj @@ -17,6 +17,9 @@ + + + diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/ByRouteContentApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/ByRouteContentApiController.cs index 2209c1a330..02181f6129 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/ByRouteContentApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/ByRouteContentApiController.cs @@ -58,7 +58,8 @@ public class ByRouteContentApiController : ContentApiItemControllerBase path = WebUtility.UrlDecode(path); } - path = path.EnsureStartsWith("/"); + path = path.TrimStart("/"); + path = path.Length == 0 ? "/" : path; IPublishedContent? contentItem = GetContent(path); if (contentItem is not null) diff --git a/src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs index 37d84bc273..3c10318b07 100644 --- a/src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,7 +1,6 @@ using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.DependencyInjection; -using Umbraco.Cms.Api.Common.Configuration; using Umbraco.Cms.Api.Common.DependencyInjection; using Umbraco.Cms.Api.Delivery.Accessors; using Umbraco.Cms.Api.Delivery.Configuration; @@ -33,6 +32,7 @@ public static class UmbracoBuilderExtensions builder.Services.ConfigureOptions(); builder.AddUmbracoApiOpenApiUI(); + builder.AddUmbracoEFCoreDbContext(); builder .Services .AddControllers() @@ -47,3 +47,4 @@ public static class UmbracoBuilderExtensions return builder; } } + diff --git a/src/Umbraco.Cms.Api.Management/Umbraco.Cms.Api.Management.csproj b/src/Umbraco.Cms.Api.Management/Umbraco.Cms.Api.Management.csproj index ed2c42bc5f..604636b4cb 100644 --- a/src/Umbraco.Cms.Api.Management/Umbraco.Cms.Api.Management.csproj +++ b/src/Umbraco.Cms.Api.Management/Umbraco.Cms.Api.Management.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/EFCoreSqlServerComposer.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/EFCoreSqlServerComposer.cs new file mode 100644 index 0000000000..a852fe2c45 --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/EFCoreSqlServerComposer.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Persistence.EFCore.Migrations; + +namespace Umbraco.Cms.Persistence.EFCore.SqlServer; + +public class EFCoreSqlServerComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + } +} diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20230622184303_InitialCreate.Designer.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20230622184303_InitialCreate.Designer.cs new file mode 100644 index 0000000000..602cd3a279 --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20230622184303_InitialCreate.Designer.cs @@ -0,0 +1,266 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Umbraco.Cms.Persistence.EFCore.SqlServer.Migrations +{ + [DbContext(typeof(UmbracoDbContext))] + [Migration("20230622184303_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClientSecret") + .HasColumnType("nvarchar(max)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("DisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayNames") + .HasColumnType("nvarchar(max)"); + + b.Property("Permissions") + .HasColumnType("nvarchar(max)"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("nvarchar(max)"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("RedirectUris") + .HasColumnType("nvarchar(max)"); + + b.Property("Requirements") + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique() + .HasFilter("[ClientId] IS NOT NULL"); + + b.ToTable("umbracoOpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ApplicationId") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("Scopes") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("nvarchar(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("umbracoOpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Descriptions") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayNames") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("Resources") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasFilter("[Name] IS NOT NULL"); + + b.ToTable("umbracoOpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ApplicationId") + .HasColumnType("nvarchar(450)"); + + b.Property("AuthorizationId") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("ExpirationDate") + .HasColumnType("datetime2"); + + b.Property("Payload") + .HasColumnType("nvarchar(max)"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("RedemptionDate") + .HasColumnType("datetime2"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("nvarchar(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique() + .HasFilter("[ReferenceId] IS NOT NULL"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("umbracoOpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20230622184303_InitialCreate.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20230622184303_InitialCreate.cs new file mode 100644 index 0000000000..9bf5191959 --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20230622184303_InitialCreate.cs @@ -0,0 +1,166 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Umbraco.Cms.Persistence.EFCore.SqlServer.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "umbracoOpenIddictApplications", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + ClientId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ClientSecret = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyToken = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ConsentType = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + DisplayName = table.Column(type: "nvarchar(max)", nullable: true), + DisplayNames = table.Column(type: "nvarchar(max)", nullable: true), + Permissions = table.Column(type: "nvarchar(max)", nullable: true), + PostLogoutRedirectUris = table.Column(type: "nvarchar(max)", nullable: true), + Properties = table.Column(type: "nvarchar(max)", nullable: true), + RedirectUris = table.Column(type: "nvarchar(max)", nullable: true), + Requirements = table.Column(type: "nvarchar(max)", nullable: true), + Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_umbracoOpenIddictApplications", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "umbracoOpenIddictScopes", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + ConcurrencyToken = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Descriptions = table.Column(type: "nvarchar(max)", nullable: true), + DisplayName = table.Column(type: "nvarchar(max)", nullable: true), + DisplayNames = table.Column(type: "nvarchar(max)", nullable: true), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + Properties = table.Column(type: "nvarchar(max)", nullable: true), + Resources = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_umbracoOpenIddictScopes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "umbracoOpenIddictAuthorizations", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + ApplicationId = table.Column(type: "nvarchar(450)", nullable: true), + ConcurrencyToken = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + CreationDate = table.Column(type: "datetime2", nullable: true), + Properties = table.Column(type: "nvarchar(max)", nullable: true), + Scopes = table.Column(type: "nvarchar(max)", nullable: true), + Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), + Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_umbracoOpenIddictAuthorizations", x => x.Id); + table.ForeignKey( + name: "FK_umbracoOpenIddictAuthorizations_umbracoOpenIddictApplications_ApplicationId", + column: x => x.ApplicationId, + principalTable: "umbracoOpenIddictApplications", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "umbracoOpenIddictTokens", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + ApplicationId = table.Column(type: "nvarchar(450)", nullable: true), + AuthorizationId = table.Column(type: "nvarchar(450)", nullable: true), + ConcurrencyToken = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + CreationDate = table.Column(type: "datetime2", nullable: true), + ExpirationDate = table.Column(type: "datetime2", nullable: true), + Payload = table.Column(type: "nvarchar(max)", nullable: true), + Properties = table.Column(type: "nvarchar(max)", nullable: true), + RedemptionDate = table.Column(type: "datetime2", nullable: true), + ReferenceId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), + Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_umbracoOpenIddictTokens", x => x.Id); + table.ForeignKey( + name: "FK_umbracoOpenIddictTokens_umbracoOpenIddictApplications_ApplicationId", + column: x => x.ApplicationId, + principalTable: "umbracoOpenIddictApplications", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_umbracoOpenIddictTokens_umbracoOpenIddictAuthorizations_AuthorizationId", + column: x => x.AuthorizationId, + principalTable: "umbracoOpenIddictAuthorizations", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_umbracoOpenIddictApplications_ClientId", + table: "umbracoOpenIddictApplications", + column: "ClientId", + unique: true, + filter: "[ClientId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_umbracoOpenIddictAuthorizations_ApplicationId_Status_Subject_Type", + table: "umbracoOpenIddictAuthorizations", + columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_umbracoOpenIddictScopes_Name", + table: "umbracoOpenIddictScopes", + column: "Name", + unique: true, + filter: "[Name] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_umbracoOpenIddictTokens_ApplicationId_Status_Subject_Type", + table: "umbracoOpenIddictTokens", + columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_umbracoOpenIddictTokens_AuthorizationId", + table: "umbracoOpenIddictTokens", + column: "AuthorizationId"); + + migrationBuilder.CreateIndex( + name: "IX_umbracoOpenIddictTokens_ReferenceId", + table: "umbracoOpenIddictTokens", + column: "ReferenceId", + unique: true, + filter: "[ReferenceId] IS NOT NULL"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "umbracoOpenIddictScopes"); + + migrationBuilder.DropTable( + name: "umbracoOpenIddictTokens"); + + migrationBuilder.DropTable( + name: "umbracoOpenIddictAuthorizations"); + + migrationBuilder.DropTable( + name: "umbracoOpenIddictApplications"); + } + } +} diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/UmbracoOpenIddictDbContextModelSnapshot.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/UmbracoOpenIddictDbContextModelSnapshot.cs new file mode 100644 index 0000000000..fcaa36348f --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/UmbracoOpenIddictDbContextModelSnapshot.cs @@ -0,0 +1,264 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Umbraco.Cms.Persistence.EFCore; + +#nullable disable + +namespace Umbraco.Cms.Persistence.EFCore.SqlServer.Migrations +{ + [DbContext(typeof(UmbracoDbContext))] + partial class UmbracoOpenIddictDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClientSecret") + .HasColumnType("nvarchar(max)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("DisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayNames") + .HasColumnType("nvarchar(max)"); + + b.Property("Permissions") + .HasColumnType("nvarchar(max)"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("nvarchar(max)"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("RedirectUris") + .HasColumnType("nvarchar(max)"); + + b.Property("Requirements") + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique() + .HasFilter("[ClientId] IS NOT NULL"); + + b.ToTable("umbracoOpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ApplicationId") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("Scopes") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("nvarchar(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("umbracoOpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Descriptions") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayNames") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("Resources") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasFilter("[Name] IS NOT NULL"); + + b.ToTable("umbracoOpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ApplicationId") + .HasColumnType("nvarchar(450)"); + + b.Property("AuthorizationId") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("ExpirationDate") + .HasColumnType("datetime2"); + + b.Property("Payload") + .HasColumnType("nvarchar(max)"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("RedemptionDate") + .HasColumnType("datetime2"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("nvarchar(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique() + .HasFilter("[ReferenceId] IS NOT NULL"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("umbracoOpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProvider.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProvider.cs new file mode 100644 index 0000000000..45e431eb8f --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProvider.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore; +using Umbraco.Cms.Persistence.EFCore.Migrations; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Persistence.EFCore.SqlServer; + +public class SqlServerMigrationProvider : IMigrationProvider +{ + private readonly IDbContextFactory _dbContextFactory; + + public SqlServerMigrationProvider(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; + + public string ProviderName => "Microsoft.Data.SqlClient"; + + public async Task MigrateAsync(EFCoreMigration migration) + { + UmbracoDbContext context = await _dbContextFactory.CreateDbContextAsync(); + await context.MigrateDatabaseAsync(GetMigrationType(migration)); + } + + public async Task MigrateAllAsync() + { + UmbracoDbContext context = await _dbContextFactory.CreateDbContextAsync(); + await context.Database.MigrateAsync(); + } + + private static Type GetMigrationType(EFCoreMigration migration) => + migration switch + { + EFCoreMigration.InitialCreate => typeof(Migrations.InitialCreate), + _ => throw new ArgumentOutOfRangeException(nameof(migration), $@"Not expected migration value: {migration}") + }; +} diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProviderSetup.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProviderSetup.cs new file mode 100644 index 0000000000..6b161fc47f --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/SqlServerMigrationProviderSetup.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using Umbraco.Cms.Persistence.EFCore.Migrations; + +namespace Umbraco.Cms.Persistence.EFCore.SqlServer; + +public class SqlServerMigrationProviderSetup : IMigrationProviderSetup +{ + public string ProviderName => "Microsoft.Data.SqlClient"; + + public void Setup(DbContextOptionsBuilder builder, string? connectionString) + { + builder.UseSqlServer(connectionString, x => x.MigrationsAssembly(GetType().Assembly.FullName)); + } +} diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj new file mode 100644 index 0000000000..946c08556f --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj @@ -0,0 +1,15 @@ + + + Umbraco CMS - EF Core - SqlServer migrations + + false + + + + + + + + + + diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/EFCoreSqliteComposer.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/EFCoreSqliteComposer.cs new file mode 100644 index 0000000000..afa3237db5 --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/EFCoreSqliteComposer.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Persistence.EFCore.Migrations; + +namespace Umbraco.Cms.Persistence.EFCore.Sqlite; + +public class EFCoreSqliteComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + } +} diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20230622183638_InitialCreate.Designer.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20230622183638_InitialCreate.Designer.cs new file mode 100644 index 0000000000..611f1c31cb --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20230622183638_InitialCreate.Designer.cs @@ -0,0 +1,258 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Umbraco.Cms.Persistence.EFCore.Sqlite.Migrations +{ + [DbContext(typeof(UmbracoDbContext))] + [Migration("20230622183638_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.7"); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClientSecret") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("DisplayNames") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("TEXT"); + + b.Property("Properties") + .HasColumnType("TEXT"); + + b.Property("RedirectUris") + .HasColumnType("TEXT"); + + b.Property("Requirements") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("umbracoOpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApplicationId") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Properties") + .HasColumnType("TEXT"); + + b.Property("Scopes") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("umbracoOpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Descriptions") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("DisplayNames") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Properties") + .HasColumnType("TEXT"); + + b.Property("Resources") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("umbracoOpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApplicationId") + .HasColumnType("TEXT"); + + b.Property("AuthorizationId") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Payload") + .HasColumnType("TEXT"); + + b.Property("Properties") + .HasColumnType("TEXT"); + + b.Property("RedemptionDate") + .HasColumnType("TEXT"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("umbracoOpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20230622183638_InitialCreate.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20230622183638_InitialCreate.cs new file mode 100644 index 0000000000..ea1b21ac3f --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20230622183638_InitialCreate.cs @@ -0,0 +1,163 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Umbraco.Cms.Persistence.EFCore.Sqlite.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "umbracoOpenIddictApplications", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + ClientId = table.Column(type: "TEXT", maxLength: 100, nullable: true), + ClientSecret = table.Column(type: "TEXT", nullable: true), + ConcurrencyToken = table.Column(type: "TEXT", maxLength: 50, nullable: true), + ConsentType = table.Column(type: "TEXT", maxLength: 50, nullable: true), + DisplayName = table.Column(type: "TEXT", nullable: true), + DisplayNames = table.Column(type: "TEXT", nullable: true), + Permissions = table.Column(type: "TEXT", nullable: true), + PostLogoutRedirectUris = table.Column(type: "TEXT", nullable: true), + Properties = table.Column(type: "TEXT", nullable: true), + RedirectUris = table.Column(type: "TEXT", nullable: true), + Requirements = table.Column(type: "TEXT", nullable: true), + Type = table.Column(type: "TEXT", maxLength: 50, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_umbracoOpenIddictApplications", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "umbracoOpenIddictScopes", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + ConcurrencyToken = table.Column(type: "TEXT", maxLength: 50, nullable: true), + Description = table.Column(type: "TEXT", nullable: true), + Descriptions = table.Column(type: "TEXT", nullable: true), + DisplayName = table.Column(type: "TEXT", nullable: true), + DisplayNames = table.Column(type: "TEXT", nullable: true), + Name = table.Column(type: "TEXT", maxLength: 200, nullable: true), + Properties = table.Column(type: "TEXT", nullable: true), + Resources = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_umbracoOpenIddictScopes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "umbracoOpenIddictAuthorizations", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + ApplicationId = table.Column(type: "TEXT", nullable: true), + ConcurrencyToken = table.Column(type: "TEXT", maxLength: 50, nullable: true), + CreationDate = table.Column(type: "TEXT", nullable: true), + Properties = table.Column(type: "TEXT", nullable: true), + Scopes = table.Column(type: "TEXT", nullable: true), + Status = table.Column(type: "TEXT", maxLength: 50, nullable: true), + Subject = table.Column(type: "TEXT", maxLength: 400, nullable: true), + Type = table.Column(type: "TEXT", maxLength: 50, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_umbracoOpenIddictAuthorizations", x => x.Id); + table.ForeignKey( + name: "FK_umbracoOpenIddictAuthorizations_umbracoOpenIddictApplications_ApplicationId", + column: x => x.ApplicationId, + principalTable: "umbracoOpenIddictApplications", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "umbracoOpenIddictTokens", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + ApplicationId = table.Column(type: "TEXT", nullable: true), + AuthorizationId = table.Column(type: "TEXT", nullable: true), + ConcurrencyToken = table.Column(type: "TEXT", maxLength: 50, nullable: true), + CreationDate = table.Column(type: "TEXT", nullable: true), + ExpirationDate = table.Column(type: "TEXT", nullable: true), + Payload = table.Column(type: "TEXT", nullable: true), + Properties = table.Column(type: "TEXT", nullable: true), + RedemptionDate = table.Column(type: "TEXT", nullable: true), + ReferenceId = table.Column(type: "TEXT", maxLength: 100, nullable: true), + Status = table.Column(type: "TEXT", maxLength: 50, nullable: true), + Subject = table.Column(type: "TEXT", maxLength: 400, nullable: true), + Type = table.Column(type: "TEXT", maxLength: 50, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_umbracoOpenIddictTokens", x => x.Id); + table.ForeignKey( + name: "FK_umbracoOpenIddictTokens_umbracoOpenIddictApplications_ApplicationId", + column: x => x.ApplicationId, + principalTable: "umbracoOpenIddictApplications", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_umbracoOpenIddictTokens_umbracoOpenIddictAuthorizations_AuthorizationId", + column: x => x.AuthorizationId, + principalTable: "umbracoOpenIddictAuthorizations", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_umbracoOpenIddictApplications_ClientId", + table: "umbracoOpenIddictApplications", + column: "ClientId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_umbracoOpenIddictAuthorizations_ApplicationId_Status_Subject_Type", + table: "umbracoOpenIddictAuthorizations", + columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_umbracoOpenIddictScopes_Name", + table: "umbracoOpenIddictScopes", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_umbracoOpenIddictTokens_ApplicationId_Status_Subject_Type", + table: "umbracoOpenIddictTokens", + columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_umbracoOpenIddictTokens_AuthorizationId", + table: "umbracoOpenIddictTokens", + column: "AuthorizationId"); + + migrationBuilder.CreateIndex( + name: "IX_umbracoOpenIddictTokens_ReferenceId", + table: "umbracoOpenIddictTokens", + column: "ReferenceId", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "umbracoOpenIddictScopes"); + + migrationBuilder.DropTable( + name: "umbracoOpenIddictTokens"); + + migrationBuilder.DropTable( + name: "umbracoOpenIddictAuthorizations"); + + migrationBuilder.DropTable( + name: "umbracoOpenIddictApplications"); + } + } +} diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/UmbracoOpenIddictDbContextModelSnapshot.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/UmbracoOpenIddictDbContextModelSnapshot.cs new file mode 100644 index 0000000000..da82993433 --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/UmbracoOpenIddictDbContextModelSnapshot.cs @@ -0,0 +1,256 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Umbraco.Cms.Persistence.EFCore; + +#nullable disable + +namespace Umbraco.Cms.Persistence.EFCore.Sqlite.Migrations +{ + [DbContext(typeof(UmbracoDbContext))] + partial class UmbracoOpenIddictDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.7"); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClientSecret") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("DisplayNames") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("TEXT"); + + b.Property("Properties") + .HasColumnType("TEXT"); + + b.Property("RedirectUris") + .HasColumnType("TEXT"); + + b.Property("Requirements") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("umbracoOpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApplicationId") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Properties") + .HasColumnType("TEXT"); + + b.Property("Scopes") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("umbracoOpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Descriptions") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("DisplayNames") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Properties") + .HasColumnType("TEXT"); + + b.Property("Resources") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("umbracoOpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApplicationId") + .HasColumnType("TEXT"); + + b.Property("AuthorizationId") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Payload") + .HasColumnType("TEXT"); + + b.Property("Properties") + .HasColumnType("TEXT"); + + b.Property("RedemptionDate") + .HasColumnType("TEXT"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("umbracoOpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs new file mode 100644 index 0000000000..b3b9897f80 --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProvider.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Umbraco.Cms.Persistence.EFCore.Migrations; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Persistence.EFCore.Sqlite; + +public class SqliteMigrationProvider : IMigrationProvider +{ + private readonly IDbContextFactory _dbContextFactory; + + public SqliteMigrationProvider(IDbContextFactory dbContextFactory) + => _dbContextFactory = dbContextFactory; + + public string ProviderName => "Microsoft.Data.Sqlite"; + + public async Task MigrateAsync(EFCoreMigration migration) + { + UmbracoDbContext context = await _dbContextFactory.CreateDbContextAsync(); + await context.MigrateDatabaseAsync(GetMigrationType(migration)); + } + + public async Task MigrateAllAsync() + { + UmbracoDbContext context = await _dbContextFactory.CreateDbContextAsync(); + + if (context.Database.CurrentTransaction is not null) + { + throw new InvalidOperationException("Cannot migrate all when a transaction is active."); + } + + await context.Database.MigrateAsync(); + } + + private static Type GetMigrationType(EFCoreMigration migration) => + migration switch + { + EFCoreMigration.InitialCreate => typeof(Migrations.InitialCreate), + _ => throw new ArgumentOutOfRangeException(nameof(migration), $@"Not expected migration value: {migration}") + }; +} diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProviderSetup.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProviderSetup.cs new file mode 100644 index 0000000000..4cba457768 --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/SqliteMigrationProviderSetup.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using Umbraco.Cms.Persistence.EFCore.Migrations; + +namespace Umbraco.Cms.Persistence.EFCore.Sqlite; + +public class SqliteMigrationProviderSetup : IMigrationProviderSetup +{ + public string ProviderName => "Microsoft.Data.Sqlite"; + + public void Setup(DbContextOptionsBuilder builder, string? connectionString) + { + builder.UseSqlite(connectionString, x => x.MigrationsAssembly(GetType().Assembly.FullName)); + } +} diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj new file mode 100644 index 0000000000..f95f1cd1e1 --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Umbraco.Cms.Persistence.EFCore.Sqlite.csproj @@ -0,0 +1,15 @@ + + + Umbraco CMS - EF Core - Sqlite migrations + + false + + + + + + + + + + diff --git a/src/Umbraco.Cms.Persistence.EFCore/Composition/UmbracoEFCoreComposer.cs b/src/Umbraco.Cms.Persistence.EFCore/Composition/UmbracoEFCoreComposer.cs new file mode 100644 index 0000000000..7d5bf0dd11 --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore/Composition/UmbracoEFCoreComposer.cs @@ -0,0 +1,56 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Infrastructure.Migrations; +using Umbraco.Cms.Infrastructure.Migrations.Notifications; +using Umbraco.Cms.Persistence.EFCore; + +namespace Umbraco.Cms.Persistence.EFCore.Composition; + +public class UmbracoEFCoreComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddSingleton(); + + builder.AddNotificationAsyncHandler(); + builder.AddNotificationAsyncHandler(); + builder.Services.AddOpenIddict() + + // Register the OpenIddict core components. + .AddCore(options => + { + options + .UseEntityFrameworkCore() + .UseDbContext(); + }); + } +} + + +public class EFCoreCreateTablesNotificationHandler : INotificationAsyncHandler, INotificationAsyncHandler +{ + private readonly IEFCoreMigrationExecutor _iefCoreMigrationExecutor; + + public EFCoreCreateTablesNotificationHandler(IEFCoreMigrationExecutor iefCoreMigrationExecutor) + { + _iefCoreMigrationExecutor = iefCoreMigrationExecutor; + } + + public async Task HandleAsync(UnattendedInstallNotification notification, CancellationToken cancellationToken) + { + await HandleAsync(); + } + + public async Task HandleAsync(DatabaseSchemaAndDataCreatedNotification notification, CancellationToken cancellationToken) + { + await HandleAsync(); + } + + private async Task HandleAsync() + { + await _iefCoreMigrationExecutor.ExecuteAllMigrationsAsync(); + } +} diff --git a/src/Umbraco.Cms.Persistence.EFCore/EfCoreMigrationExecutor.cs b/src/Umbraco.Cms.Persistence.EFCore/EfCoreMigrationExecutor.cs new file mode 100644 index 0000000000..8de8e1f1b8 --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore/EfCoreMigrationExecutor.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Infrastructure.Migrations; +using Umbraco.Cms.Persistence.EFCore.Migrations; + +namespace Umbraco.Cms.Persistence.EFCore; + +public class EfCoreMigrationExecutor : IEFCoreMigrationExecutor +{ + private readonly IEnumerable _migrationProviders; + private readonly IOptions _options; + + // We need to do migrations out side of a scope due to sqlite + public EfCoreMigrationExecutor( + IEnumerable migrationProviders, + IOptions options) + { + _migrationProviders = migrationProviders; + _options = options; + } + + public async Task ExecuteSingleMigrationAsync(EFCoreMigration migration) + { + IMigrationProvider? provider = _migrationProviders.FirstOrDefault(x => x.ProviderName == _options.Value.ProviderName); + + if (provider is not null) + { + await provider.MigrateAsync(migration); + } + } + + public async Task ExecuteAllMigrationsAsync() + { + IMigrationProvider? provider = _migrationProviders.FirstOrDefault(x => x.ProviderName == _options.Value.ProviderName); + + if (provider is not null) + { + await provider.MigrateAllAsync(); + } + } + +} diff --git a/src/Umbraco.Cms.Persistence.EFCore/Extensions/BackOfficeAuthBuilderOpenIddictExtensions.cs b/src/Umbraco.Cms.Persistence.EFCore/Extensions/BackOfficeAuthBuilderOpenIddictExtensions.cs new file mode 100644 index 0000000000..2672b3b849 --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore/Extensions/BackOfficeAuthBuilderOpenIddictExtensions.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Persistence.EFCore; + +namespace Umbraco.Extensions; + +public static class BackOfficeAuthBuilderOpenIddictExtensions +{ + public static IUmbracoBuilder AddUmbracoEFCoreDbContext(this IUmbracoBuilder builder) + { + builder.Services.AddUmbracoEFCoreContext((options, connectionString, providerName) => + { + // Register the entity sets needed by OpenIddict. + options.UseOpenIddict(); + }); + + return builder; + } +} diff --git a/src/Umbraco.Cms.Persistence.EFCore/Extensions/DbContextExtensions.cs b/src/Umbraco.Cms.Persistence.EFCore/Extensions/DbContextExtensions.cs index 573f57e75f..98a7b5779d 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Extensions/DbContextExtensions.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Extensions/DbContextExtensions.cs @@ -2,6 +2,7 @@ using System.Data; using System.Data.Common; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage; namespace Umbraco.Extensions; @@ -50,4 +51,21 @@ public static class DbContextExtensions var result = await dbCommand.ExecuteScalarAsync(); return (T?)result; } + + public static async Task MigrateDatabaseAsync(this DbContext context, Type targetMigration) + { + MigrationAttribute? migrationAttribute = targetMigration.GetCustomAttribute(false); + + if (migrationAttribute is null) + { + throw new ArgumentException("The type does not have a MigrationAttribute", nameof(targetMigration)); + } + + await context.MigrateDatabaseAsync(migrationAttribute.Id); + } + + public static async Task MigrateDatabaseAsync(this DbContext context, string targetMigrationId) + { + await context.GetService().MigrateAsync(targetMigrationId); + } } diff --git a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs index 4d47e64448..52c187dba3 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs @@ -1,8 +1,11 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DistributedLocking; using Umbraco.Cms.Persistence.EFCore.Locking; +using Umbraco.Cms.Persistence.EFCore.Migrations; using Umbraco.Cms.Persistence.EFCore.Scoping; namespace Umbraco.Extensions; @@ -11,6 +14,55 @@ public static class UmbracoEFCoreServiceCollectionExtensions { public delegate void DefaultEFCoreOptionsAction(DbContextOptionsBuilder options, string? providerName, string? connectionString); + public static IServiceCollection AddUmbracoEFCoreContext(this IServiceCollection services, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null) + where T : DbContext + { + defaultEFCoreOptionsAction ??= DefaultOptionsAction; + + services.AddDbContext( + (provider, builder) => SetupDbContext(defaultEFCoreOptionsAction, provider, builder), + optionsLifetime: ServiceLifetime.Transient); + + + + services.AddDbContextFactory((provider, builder) => SetupDbContext(defaultEFCoreOptionsAction, provider, builder)); + + services.AddUnique, AmbientEFCoreScopeStack>(); + services.AddUnique, EFCoreScopeAccessor>(); + services.AddUnique, EFCoreScopeProvider>(); + services.AddSingleton>(); + services.AddSingleton>(); + + return services; + } + + private static void SetupDbContext(DefaultEFCoreOptionsAction defaultEFCoreOptionsAction, IServiceProvider provider, DbContextOptionsBuilder builder) + { + ConnectionStrings connectionStrings = GetConnectionStringAndProviderName(provider); + IEnumerable migrationProviders = provider.GetServices(); + IMigrationProviderSetup? migrationProvider = + migrationProviders.FirstOrDefault(x => x.ProviderName == connectionStrings.ProviderName); + migrationProvider?.Setup(builder, connectionStrings.ConnectionString); + defaultEFCoreOptionsAction(builder, connectionStrings.ConnectionString, connectionStrings.ProviderName); + } + + private static ConnectionStrings GetConnectionStringAndProviderName(IServiceProvider serviceProvider) + { + string? connectionString = null; + string? providerName = null; + + ConnectionStrings connectionStrings = serviceProvider.GetRequiredService>().CurrentValue; + + // Replace data directory + string? dataDirectory = AppDomain.CurrentDomain.GetData(Constants.System.DataDirectoryName)?.ToString(); + if (string.IsNullOrEmpty(dataDirectory) is false) + { + connectionStrings.ConnectionString = connectionStrings.ConnectionString?.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory); + } + + return connectionStrings; + } + public static IServiceCollection AddUmbracoEFCoreContext(this IServiceCollection services, string connectionString, string providerName, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null) where T : DbContext { diff --git a/src/Umbraco.Cms.Persistence.EFCore/Migrations/IMigrationProvider.cs b/src/Umbraco.Cms.Persistence.EFCore/Migrations/IMigrationProvider.cs new file mode 100644 index 0000000000..175a8f2ee6 --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore/Migrations/IMigrationProvider.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Cms.Persistence.EFCore.Migrations; + +public interface IMigrationProvider +{ + string ProviderName { get; } + + Task MigrateAsync(EFCoreMigration migration); + + Task MigrateAllAsync(); +} diff --git a/src/Umbraco.Cms.Persistence.EFCore/Migrations/IMigrationProviderSetup.cs b/src/Umbraco.Cms.Persistence.EFCore/Migrations/IMigrationProviderSetup.cs new file mode 100644 index 0000000000..09206d0eee --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore/Migrations/IMigrationProviderSetup.cs @@ -0,0 +1,10 @@ +using Microsoft.EntityFrameworkCore; + +namespace Umbraco.Cms.Persistence.EFCore.Migrations; + +public interface IMigrationProviderSetup +{ + string ProviderName { get; } + + void Setup(DbContextOptionsBuilder builder, string? connectionString); +} diff --git a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj index c73eb3c3ed..f8e3851ccd 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj @@ -8,7 +8,6 @@ - diff --git a/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs new file mode 100644 index 0000000000..2c940602ac --- /dev/null +++ b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Umbraco.Cms.Core; + +namespace Umbraco.Cms.Persistence.EFCore; + +/// +/// To autogenerate migrations use the following commands +/// and insure the 'src/Umbraco.Web.UI/appsettings.json' have a connection string set with the right provider. +/// +/// dotnet ef migrations add %Name% -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.SqlServer -- --provider SqlServer +/// dotnet ef migrations add %Name% -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.Sqlite -- --provider Sqlite +/// +/// To find documentation about this way of working with the context see +/// https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/providers?tabs=dotnet-core-cli#using-one-context-type +/// +public class UmbracoDbContext : DbContext +{ + public UmbracoDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + foreach (IMutableEntityType entity in modelBuilder.Model.GetEntityTypes()) + { + entity.SetTableName(Constants.DatabaseSchema.TableNamePrefix + entity.GetTableName()); + } + } +} diff --git a/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs b/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs index 11ea4faf77..135afe068a 100644 --- a/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs +++ b/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs @@ -10,6 +10,6 @@ public sealed class ApiContentBuilder : ApiContentBuilderBase, IApi { } - protected override IApiContent Create(IPublishedContent content, Guid id, string name, string contentType, IApiContentRoute route, IDictionary properties) - => new ApiContent(id, name, contentType, route, properties); + protected override IApiContent Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary properties) + => new ApiContent(content.Key, name, content.ContentType.Alias, content.CreateDate, content.UpdateDate, route, properties); } diff --git a/src/Umbraco.Core/DeliveryApi/ApiContentBuilderBase.cs b/src/Umbraco.Core/DeliveryApi/ApiContentBuilderBase.cs index ae70f0fdde..8ffcd6d849 100644 --- a/src/Umbraco.Core/DeliveryApi/ApiContentBuilderBase.cs +++ b/src/Umbraco.Core/DeliveryApi/ApiContentBuilderBase.cs @@ -17,7 +17,7 @@ public abstract class ApiContentBuilderBase _outputExpansionStrategyAccessor = outputExpansionStrategyAccessor; } - protected abstract T Create(IPublishedContent content, Guid id, string name, string contentType, IApiContentRoute route, IDictionary properties); + protected abstract T Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary properties); public virtual T? Build(IPublishedContent content) { @@ -34,9 +34,7 @@ public abstract class ApiContentBuilderBase return Create( content, - content.Key, _apiContentNameProvider.GetName(content), - content.ContentType.Alias, route, properties); } diff --git a/src/Umbraco.Core/DeliveryApi/ApiContentResponseBuilder.cs b/src/Umbraco.Core/DeliveryApi/ApiContentResponseBuilder.cs index eb9cea6961..a551115a1e 100644 --- a/src/Umbraco.Core/DeliveryApi/ApiContentResponseBuilder.cs +++ b/src/Umbraco.Core/DeliveryApi/ApiContentResponseBuilder.cs @@ -13,7 +13,7 @@ public sealed class ApiContentResponseBuilder : ApiContentBuilderBase _apiContentRouteBuilder = apiContentRouteBuilder; - protected override IApiContentResponse Create(IPublishedContent content, Guid id, string name, string contentType, IApiContentRoute route, IDictionary properties) + protected override IApiContentResponse Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary properties) { var routesByCulture = new Dictionary(); @@ -35,6 +35,6 @@ public sealed class ApiContentResponseBuilder : ApiContentBuilderBaseSæt rettigheder Lås op Opret indholdsskabelon - Gensend Invitation - Standard værdi + Gensend invitation + Standardværdi Indhold 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.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 3b8b70c360..58d6787768 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 @@ -2275,7 +2275,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont items URL(s) URL(s) selected - items selected + item(s) selected Invalid date Not a number Not a valid numeric step size diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index cade14512f..c7ad610ef0 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 @@ -2371,7 +2371,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont items URL(s) URL(s) selected - items selected + item(s) selected Invalid date Not a number Not a valid numeric step size diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml new file mode 100644 index 0000000000..c3328dfead --- /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 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 + 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 + +
diff --git a/src/Umbraco.Core/Models/DeliveryApi/ApiContent.cs b/src/Umbraco.Core/Models/DeliveryApi/ApiContent.cs index 30a186d6e8..0b6830db61 100644 --- a/src/Umbraco.Core/Models/DeliveryApi/ApiContent.cs +++ b/src/Umbraco.Core/Models/DeliveryApi/ApiContent.cs @@ -2,14 +2,20 @@ namespace Umbraco.Cms.Core.Models.DeliveryApi; public class ApiContent : ApiElement, IApiContent { - public ApiContent(Guid id, string name, string contentType, IApiContentRoute route, IDictionary properties) + public ApiContent(Guid id, string name, string contentType, DateTime createDate, DateTime updateDate, IApiContentRoute route, IDictionary properties) : base(id, contentType, properties) { Name = name; + CreateDate = createDate; + UpdateDate = updateDate; Route = route; } public string Name { get; } + public DateTime CreateDate { get; } + + public DateTime UpdateDate { get; } + public IApiContentRoute Route { get; } } diff --git a/src/Umbraco.Core/Models/DeliveryApi/ApiContentResponse.cs b/src/Umbraco.Core/Models/DeliveryApi/ApiContentResponse.cs index d692ff464e..ce57cf7417 100644 --- a/src/Umbraco.Core/Models/DeliveryApi/ApiContentResponse.cs +++ b/src/Umbraco.Core/Models/DeliveryApi/ApiContentResponse.cs @@ -4,8 +4,8 @@ namespace Umbraco.Cms.Core.Models.DeliveryApi; public class ApiContentResponse : ApiContent, IApiContentResponse { - public ApiContentResponse(Guid id, string name, string contentType, IApiContentRoute route, IDictionary properties, IDictionary cultures) - : base(id, name, contentType, route, properties) + public ApiContentResponse(Guid id, string name, string contentType, DateTime createDate, DateTime updateDate, IApiContentRoute route, IDictionary properties, IDictionary cultures) + : base(id, name, contentType, createDate, updateDate, route, properties) => Cultures = cultures; // a little DX; by default this dictionary will be serialized as the first part of the response due to the inner workings of the serializer. diff --git a/src/Umbraco.Core/Models/DeliveryApi/IApiContent.cs b/src/Umbraco.Core/Models/DeliveryApi/IApiContent.cs index 0f605eda19..9192cbe7d5 100644 --- a/src/Umbraco.Core/Models/DeliveryApi/IApiContent.cs +++ b/src/Umbraco.Core/Models/DeliveryApi/IApiContent.cs @@ -4,5 +4,9 @@ public interface IApiContent : IApiElement { string? Name { get; } + public DateTime CreateDate { get; } + + public DateTime UpdateDate { get; } + IApiContentRoute Route { get; } } diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index c624a7b8c1..5bbd60b620 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 @@ -2436,7 +2436,7 @@ public class ContentService : RepositoryService, IContentService { EventMessages eventMessages = EventMessagesFactory.Get(); - if(content.ParentId == parentId) + if (content.ParentId == parentId) { return OperationResult.Succeed(eventMessages); } @@ -2587,7 +2587,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); 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; diff --git a/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs b/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs index 3053da44cf..f4f333f0ad 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs @@ -41,6 +41,7 @@ public class UmbracoContentIndex : UmbracoExamineIndex, IUmbracoContentIndex if (namedOptions.Validator is IContentValueSetValidator contentValueSetValidator) { PublishedValuesOnly = contentValueSetValidator.PublishedValuesOnly; + SupportProtectedContent = contentValueSetValidator.SupportProtectedContent; } } diff --git a/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs b/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs index 969c58ac55..5faccf581f 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoExamineIndex.cs @@ -47,6 +47,8 @@ public abstract class UmbracoExamineIndex : LuceneIndex, IUmbracoIndex, IIndexDi public bool PublishedValuesOnly { get; protected set; } = false; + public bool SupportProtectedContent { get; protected set; } = true; + /// /// override to check if we can actually initialize. /// diff --git a/src/Umbraco.Infrastructure/CompatibilitySuppressions.xml b/src/Umbraco.Infrastructure/CompatibilitySuppressions.xml index d6cb24b308..85cec7c6fc 100644 --- a/src/Umbraco.Infrastructure/CompatibilitySuppressions.xml +++ b/src/Umbraco.Infrastructure/CompatibilitySuppressions.xml @@ -337,4 +337,18 @@ lib/net7.0/Umbraco.Infrastructure.dll true - \ No newline at end of file + + CP0006 + M:Umbraco.Cms.Infrastructure.Search.IUmbracoIndexingHandler.RemoveProtectedContent + lib/net7.0/Umbraco.Infrastructure.dll + lib/net7.0/Umbraco.Infrastructure.dll + true + + + CP0006 + P:Umbraco.Cms.Infrastructure.Examine.IUmbracoIndex.SupportProtectedContent + lib/net7.0/Umbraco.Infrastructure.dll + lib/net7.0/Umbraco.Infrastructure.dll + true + + diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs index fb3adb9219..5139cba48f 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs @@ -59,6 +59,7 @@ public static partial class UmbracoBuilderExtensions builder.Services.AddSingleton(); builder.AddNotificationHandler(); + builder.AddNotificationHandler(); builder.AddNotificationHandler(); builder.AddNotificationHandler(); builder.AddNotificationHandler(); diff --git a/src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs b/src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs index ec4dff77f4..c6e1de7ff2 100644 --- a/src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs +++ b/src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs @@ -41,6 +41,7 @@ public class ContentValueSetValidator : ValueSetValidator, IContentValueSetValid _scopeProvider = scopeProvider; } + [Obsolete("This constructor is obsolete, the IScopeProvider will change to Infrastructure.Scoping.ScopeProvider instead, this will be removed in Umbraco 14.")] public ContentValueSetValidator( bool publishedValuesOnly, bool supportProtectedContent, diff --git a/src/Umbraco.Infrastructure/Examine/ExamineUmbracoIndexingHandler.cs b/src/Umbraco.Infrastructure/Examine/ExamineUmbracoIndexingHandler.cs index ea3727f31a..c77606e97a 100644 --- a/src/Umbraco.Infrastructure/Examine/ExamineUmbracoIndexingHandler.cs +++ b/src/Umbraco.Infrastructure/Examine/ExamineUmbracoIndexingHandler.cs @@ -4,6 +4,7 @@ using Examine.Search; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.HostedServices; using Umbraco.Cms.Infrastructure.Search; using Umbraco.Extensions; @@ -25,6 +26,7 @@ internal class ExamineUmbracoIndexingHandler : IUmbracoIndexingHandler private readonly IPublishedContentValueSetBuilder _publishedContentValueSetBuilder; private readonly ICoreScopeProvider _scopeProvider; private readonly ExamineIndexingMainDomHandler _mainDomHandler; + private readonly IPublicAccessService _publicAccessService; public ExamineUmbracoIndexingHandler( ILogger logger, @@ -35,7 +37,8 @@ internal class ExamineUmbracoIndexingHandler : IUmbracoIndexingHandler IPublishedContentValueSetBuilder publishedContentValueSetBuilder, IValueSetBuilder mediaValueSetBuilder, IValueSetBuilder memberValueSetBuilder, - ExamineIndexingMainDomHandler mainDomHandler) + ExamineIndexingMainDomHandler mainDomHandler, + IPublicAccessService publicAccessService) { _logger = logger; _scopeProvider = scopeProvider; @@ -46,6 +49,7 @@ internal class ExamineUmbracoIndexingHandler : IUmbracoIndexingHandler _mediaValueSetBuilder = mediaValueSetBuilder; _memberValueSetBuilder = memberValueSetBuilder; _mainDomHandler = mainDomHandler; + _publicAccessService = publicAccessService; _enabled = new Lazy(IsEnabled); } @@ -122,6 +126,20 @@ internal class ExamineUmbracoIndexingHandler : IUmbracoIndexingHandler } } + /// + public void RemoveProtectedContent() + { + var actions = DeferredActions.Get(_scopeProvider); + if (actions != null) + { + actions.Add(new DeferredRemoveProtectedContent(_backgroundTaskQueue, this, _publicAccessService)); + } + else + { + DeferredRemoveProtectedContent.Execute(_backgroundTaskQueue, this, _publicAccessService); + } + } + /// public void DeleteDocumentsForContentTypes(IReadOnlyCollection removedContentTypes) { @@ -391,5 +409,50 @@ internal class ExamineUmbracoIndexingHandler : IUmbracoIndexingHandler } } + /// + /// Removes all protected content from applicable indexes on a background thread + /// + private class DeferredRemoveProtectedContent : IDeferredAction + { + private readonly IBackgroundTaskQueue _backgroundTaskQueue; + private readonly ExamineUmbracoIndexingHandler _examineUmbracoIndexingHandler; + private readonly IPublicAccessService _publicAccessService; + + public DeferredRemoveProtectedContent(IBackgroundTaskQueue backgroundTaskQueue, ExamineUmbracoIndexingHandler examineUmbracoIndexingHandler, IPublicAccessService publicAccessService) + { + _backgroundTaskQueue = backgroundTaskQueue; + _examineUmbracoIndexingHandler = examineUmbracoIndexingHandler; + _publicAccessService = publicAccessService; + } + + public void Execute() => Execute(_backgroundTaskQueue, _examineUmbracoIndexingHandler, _publicAccessService); + + public static void Execute(IBackgroundTaskQueue backgroundTaskQueue, ExamineUmbracoIndexingHandler examineUmbracoIndexingHandler, IPublicAccessService publicAccessService) + => backgroundTaskQueue.QueueBackgroundWorkItem(cancellationToken => + { + using ICoreScope scope = examineUmbracoIndexingHandler._scopeProvider.CreateCoreScope(autoComplete: true); + + var protectedContentIds = publicAccessService.GetAll().Select(entry => entry.ProtectedNodeId).ToArray(); + if (protectedContentIds.Any() is false) + { + return Task.CompletedTask; + } + + foreach (IUmbracoContentIndex index in examineUmbracoIndexingHandler._examineManager.Indexes + .OfType() + .Where(x => x is { EnableDefaultEventHandler: true, SupportProtectedContent: false })) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.CompletedTask; + } + + index.DeleteFromIndex(protectedContentIds.Select(id => id.ToString())); + } + + return Task.CompletedTask; + }); + } + #endregion } diff --git a/src/Umbraco.Infrastructure/Examine/IUmbracoIndex.cs b/src/Umbraco.Infrastructure/Examine/IUmbracoIndex.cs index a94201894a..0091618ec0 100644 --- a/src/Umbraco.Infrastructure/Examine/IUmbracoIndex.cs +++ b/src/Umbraco.Infrastructure/Examine/IUmbracoIndex.cs @@ -21,4 +21,12 @@ public interface IUmbracoIndex : IIndex, IIndexStats /// * non-published Variants /// bool PublishedValuesOnly { get; } + + /// + /// Whether the index can contain protected content + /// + /// + /// To retain backwards compatability, the default value is true + /// + bool SupportProtectedContent { get; } } diff --git a/src/Umbraco.Infrastructure/Migrations/EFCoreMigration.cs b/src/Umbraco.Infrastructure/Migrations/EFCoreMigration.cs new file mode 100644 index 0000000000..8086b68f24 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/EFCoreMigration.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Persistence.EFCore.Migrations; + +public enum EFCoreMigration +{ + InitialCreate = 0 +} diff --git a/src/Umbraco.Infrastructure/Migrations/IEFCoreMigrationExecutor.cs b/src/Umbraco.Infrastructure/Migrations/IEFCoreMigrationExecutor.cs new file mode 100644 index 0000000000..e61468e979 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/IEFCoreMigrationExecutor.cs @@ -0,0 +1,10 @@ +using Umbraco.Cms.Persistence.EFCore.Migrations; + +namespace Umbraco.Cms.Infrastructure.Migrations; + +public interface IEFCoreMigrationExecutor +{ + Task ExecuteSingleMigrationAsync(EFCoreMigration efCoreMigration); + + Task ExecuteAllMigrationsAsync(); +} diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs index 229a875da7..c260b3e7c2 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs @@ -291,6 +291,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install using (var scope = _scopeProvider.CreateCoreScope()) { var result = CreateSchemaAndData(scope); + scope.Notifications.Publish(new DatabaseSchemaAndDataCreatedNotification()); scope.Complete(); return result; } diff --git a/src/Umbraco.Infrastructure/Migrations/Notifications/DatabaseSchemaAndDataCreatedNotification.cs b/src/Umbraco.Infrastructure/Migrations/Notifications/DatabaseSchemaAndDataCreatedNotification.cs new file mode 100644 index 0000000000..77ef6aaaea --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Notifications/DatabaseSchemaAndDataCreatedNotification.cs @@ -0,0 +1,8 @@ +using Umbraco.Cms.Core.Notifications; + +namespace Umbraco.Cms.Infrastructure.Migrations.Notifications; + +public class DatabaseSchemaAndDataCreatedNotification : INotification +{ + +} diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index e2974ffe1d..0c3e8d9695 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -80,6 +80,7 @@ public class UmbracoPlan : MigrationPlan // To 12.1.0 To("{1187192D-EDB5-4619-955D-91D48D738871}"); + To("{47DE85CE-1E16-42A0-8AF6-3EC3BCEF5471}"); // To 14.0.0 To("{419827A0-4FCE-464B-A8F3-247C6092AF55}"); diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_12_1_0/AddOpenIddict.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_12_1_0/AddOpenIddict.cs new file mode 100644 index 0000000000..3bf188deff --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_12_1_0/AddOpenIddict.cs @@ -0,0 +1,20 @@ +using Umbraco.Cms.Persistence.EFCore.Migrations; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_12_1_0; + +public class AddOpenIddict : UnscopedMigrationBase +{ + private readonly IEFCoreMigrationExecutor _iefCoreMigrationExecutor; + + public AddOpenIddict(IMigrationContext context, IEFCoreMigrationExecutor iefCoreMigrationExecutor) + : base(context) + { + _iefCoreMigrationExecutor = iefCoreMigrationExecutor; + } + + protected override void Migrate() + { + _iefCoreMigrationExecutor.ExecuteSingleMigrationAsync(EFCoreMigration.InitialCreate).GetAwaiter().GetResult(); + } +} + diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index 3efbce8fb9..9aeeb02d92 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -72,7 +72,6 @@ public class MarkdownEditorValueConverter : PropertyValueConverterBase, IDeliver return string.Empty; } - var mark = new Markdown(); - return mark.Transform(markdownString); + return markdownString; } } diff --git a/src/Umbraco.Infrastructure/Search/IUmbracoIndexingHandler.cs b/src/Umbraco.Infrastructure/Search/IUmbracoIndexingHandler.cs index 53f9988052..20ff27b287 100644 --- a/src/Umbraco.Infrastructure/Search/IUmbracoIndexingHandler.cs +++ b/src/Umbraco.Infrastructure/Search/IUmbracoIndexingHandler.cs @@ -19,6 +19,11 @@ public interface IUmbracoIndexingHandler void ReIndexForMedia(IMedia sender, bool isPublished); + /// + /// Removes any content that is flagged as protected + /// + void RemoveProtectedContent(); + /// /// Deletes all documents for the content type Ids /// diff --git a/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.Content.cs b/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.Content.cs index 66eb61ce3c..8ab2c14f89 100644 --- a/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.Content.cs +++ b/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.Content.cs @@ -9,7 +9,9 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Search; -public sealed class ContentIndexingNotificationHandler : INotificationHandler +public sealed class ContentIndexingNotificationHandler : + INotificationHandler, + INotificationHandler { private readonly IContentService _contentService; private readonly IUmbracoIndexingHandler _umbracoIndexingHandler; @@ -158,4 +160,7 @@ public sealed class ContentIndexingNotificationHandler : INotificationHandler _umbracoIndexingHandler.RemoveProtectedContent(); } 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) { 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" }, { diff --git a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs index 57732a9898..e91384f86a 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs @@ -1,4 +1,6 @@ using System.Globalization; +using System.Text.RegularExpressions; +using HeyRed.MarkdownSharp; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Actions; @@ -303,6 +305,51 @@ internal class ContentMapDefinition : IMapDefinition { Properties = context.MapEnumerable(source.Properties).WhereNotNull() }; + var markdown = new Markdown(); + var linkCheck = new Regex("]+>", RegexOptions.IgnoreCase); + var evaluator = new MatchEvaluator(AddRelNoReferrer); + foreach (TVariant variant in target.Variants) + { + foreach (Tab tab in variant.Tabs) + { + if (tab.Properties == null) + { + continue; + } + + foreach (ContentPropertyDisplay property in tab.Properties) + { + if (string.IsNullOrEmpty(property.Description)) + { + continue; + } + + var description = markdown.Transform(property.Description); + property.Description = linkCheck.Replace(description, evaluator); + } + } + } + } + + private string AddRelNoReferrer(Match m) + { + string result = m.Value; + if (!result.Contains("rel=", StringComparison.Ordinal)) + { + result = result.Replace(">", " rel=\"noreferrer\">"); + } + + if (!result.Contains("class=", StringComparison.Ordinal)) + { + result = result.Replace(">", " class=\"underline\">"); + } + + if (!result.Contains("target=", StringComparison.Ordinal)) + { + result = result.Replace(">", " target=\"_blank\">"); + } + + return result; } // Umbraco.Code.MapAll -Segment -Language -DisplayName @@ -359,7 +406,7 @@ internal class ContentMapDefinition : IMapDefinition { currentUser = currentIUserBackofficeUser; } - else if(_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser is not null) + else if (_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser is not null) { currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; } diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs index 2587ae9a58..a6e4e96d8b 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs @@ -8,7 +8,7 @@ using Umbraco.Cms.Core.Configuration.Models; namespace Umbraco.Cms.Web.BackOffice.Security; /// -/// Antiforgery implementation for the Umbraco back office +/// Anti-forgery implementation for the Umbraco back office /// /// /// This is a wrapper around the global/default .net service. Because this service is a @@ -33,14 +33,12 @@ public class BackOfficeAntiforgery : IBackOfficeAntiforgery { x.HeaderName = Constants.Web.AngularHeadername; x.Cookie.Name = Constants.Web.CsrfValidationCookieName; + x.Cookie.SecurePolicy = globalSettings.CurrentValue.UseHttps ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest; }); ServiceProvider container = services.BuildServiceProvider(); _internalAntiForgery = container.GetRequiredService(); _globalSettings = globalSettings.CurrentValue; - globalSettings.OnChange(x => - { - _globalSettings = x; - }); + globalSettings.OnChange(x => _globalSettings = x); } /// diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj index 08e89c375f..00e6c46273 100644 --- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj +++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj @@ -12,6 +12,7 @@
+ @@ -32,4 +33,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs b/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs index 247a2a09ba..0e0cf5587d 100644 --- a/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs @@ -1,4 +1,5 @@ using System.Buffers; +using System.Globalization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Options; @@ -42,7 +43,7 @@ public sealed class JsonDateTimeFormatAttribute : TypeFilterAttribute { var serializerSettings = new JsonSerializerSettings(); serializerSettings.Converters.Add( - new IsoDateTimeConverter { DateTimeFormat = _format }); + new IsoDateTimeConverter { DateTimeFormat = _format, Culture = CultureInfo.InvariantCulture }); objectResult.Formatters.Clear(); objectResult.Formatters.Add( new AngularJsonMediaTypeFormatter(serializerSettings, _arrayPool, _options)); 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/assets/img/nonodesbg.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/nonodesbg.jpg deleted file mode 100644 index 61e1803924..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/nonodesbg.jpg and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/nonodesbg.webp b/src/Umbraco.Web.UI.Client/src/assets/img/nonodesbg.webp new file mode 100644 index 0000000000..32ae6bb4e1 Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/nonodesbg.webp differ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index 5a30f81d4b..ccc9f92caf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -4,96 +4,96 @@ * @restrict E **/ (function () { - 'use strict'; + 'use strict'; - angular - .module("umbraco.directives") - .component('umbProperty', { - templateUrl: 'views/components/property/umb-property.html', - controller: UmbPropertyController, - controllerAs: 'vm', - transclude: true, - require: { - parentUmbProperty: '?^^umbProperty', - parentForm: '?^^form' - }, - bindings: { - property: "=", + angular + .module("umbraco.directives") + .component('umbProperty', { + templateUrl: 'views/components/property/umb-property.html', + controller: UmbPropertyController, + controllerAs: 'vm', + transclude: true, + require: { + parentUmbProperty: '?^^umbProperty', + parentForm: '?^^form' + }, + bindings: { + property: "=", node: "<", - elementKey: "@", - // optional, if set this will be used for the property alias validation path (hack required because NC changes the actual property.alias :/) - propertyAlias: "@", - showInherit: "<", + elementKey: "@", + // optional, if set this will be used for the property alias validation path (hack required because NC changes the actual property.alias :/) + propertyAlias: "@", + showInherit: "<", inheritsFrom: "<", hideLabel: " s && s.vm && s.vm.constructor.name === "UmbPropertyController"); - vm.parentUmbProperty = found ? found.vm : null; - } - - if (vm.property.description) { - // split by lines containing only '--' - var descriptionParts = vm.property.description.split(/^--$/gim); - if (descriptionParts.length > 1) { - // if more than one part, we have an extended description, - // combine to one extended description, and remove leading linebreak - vm.property.extendedDescription = descriptionParts.splice(1).join("--").substring(1); - vm.property.extendedDescriptionVisible = false; - - // set propertydescription to first part, and remove trailing linebreak - vm.property.description = descriptionParts[0].slice(0, -1); - } - } - } + vm.$onInit = onInit; + vm.setDirty = function () { + // NOTE: We need to use scope because we haven't changd it to vm.propertyForm in the html and that + // might mean a breaking change. + $scope.propertyForm.$setDirty(); } + vm.setPropertyError = function (errorMsg) { + vm.property.propertyErrorMessage = errorMsg; + }; + + vm.propertyActions = []; + vm.setPropertyActions = function (actions) { + vm.propertyActions = actions; + }; + + // returns the validation path for the property to be used as the validation key for server side validation logic + vm.getValidationPath = function () { + + var parentValidationPath = vm.parentUmbProperty ? vm.parentUmbProperty.getValidationPath() : null; + var propAlias = vm.propertyAlias ? vm.propertyAlias : vm.property.alias; + // the elementKey will be empty when this is not a nested property + var valPath = vm.elementKey ? vm.elementKey + "/" + propAlias : propAlias; + return serverValidationManager.createPropertyValidationKey(valPath, parentValidationPath); + } + + function onInit() { + vm.controlLabelTitle = null; + if (Umbraco.Sys.ServerVariables.isDebuggingEnabled) { + userService.getCurrentUser().then(function (u) { + if (u.allowedSections.indexOf("settings") !== -1 ? true : false) { + vm.controlLabelTitle = vm.property.alias; + } + }); + } + + if (!vm.parentUmbProperty) { + // not found, then fallback to searching the scope chain, this may be needed when DOM inheritance isn't maintained but scope + // inheritance is (i.e.infinite editing) + var found = angularHelper.traverseScopeChain($scope, s => s && s.vm && s.vm.constructor.name === "UmbPropertyController"); + vm.parentUmbProperty = found ? found.vm : null; + } + + if (vm.property.description) { + // split by lines containing only '--' + var descriptionParts = vm.property.description.split(/^--$/gim); + if (descriptionParts.length > 1) { + // if more than one part, we have an extended description, + // combine to one extended description, and remove leading linebreak + vm.property.extendedDescription = descriptionParts.splice(1).join("--").substring(1); + vm.property.extendedDescriptionVisible = false; + + // set propertydescription to first part, and remove trailing linebreak + vm.property.description = descriptionParts[0].slice(0, -1); + } + } + } + + } + })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js deleted file mode 100644 index 58d5b0ed91..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js +++ /dev/null @@ -1,20 +0,0 @@ -/** -* @ngdoc filter -* @name umbraco.filters.simpleMarkdown -* @description -* Used when rendering a string as Markdown as HTML (i.e. with ng-bind-html). Allows use of **bold**, *italics*, ![images](url) and [links](url) -**/ -angular.module("umbraco.filters").filter('simpleMarkdown', function () { - return function (text) { - if (!text) { - return ''; - } - - return text - .replace(/\*\*(.*)\*\*/gim, '$1') - .replace(/\*(.*)\*/gim, '$1') - .replace(/!\[(.*?)\]\((.*?)\)/gim, "$1") - .replace(/\[(.*?)\]\((.*?)\)/gim, "$1") - .replace(/\n/g, '
').trim(); - }; -}); diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js.js b/src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js.js deleted file mode 100644 index d33b96916a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js.js +++ /dev/null @@ -1,20 +0,0 @@ -/** -* @ngdoc filter -* @name umbraco.filters.simpleMarkdown -* @description -* Used when rendering a string as Markdown as HTML (i.e. with ng-bind-html). Allows use of **bold**, *italics*, ![images](url) and [links](url) -**/ -angular.module("umbraco.filters").filter('simpleMarkdown', function () { - return function (text) { - if (!text) { - return ''; - } - - return text - .replace(/\*\*(.*)\*\*/gim, '$1') - .replace(/\*(.*)\*/gim, '$1') - .replace(/!\[(.*?)\]\((.*?)\)/gim, "$1") - .replace(/\[(.*?)\]\((.*?)\)/gim, "$1") - .replace(/\n/g, '
').trim(); - }; -}); diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less b/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less index 7c493ab18e..1bf9aab829 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less @@ -243,7 +243,7 @@ a:visited { } section { - background-image: url(../img/nonodesbg.jpg); + background-image: url(../img/nonodesbg.webp); background-position: center center; background-size: cover; height: 100%; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/mediacard/umb-media-card.html b/src/Umbraco.Web.UI.Client/src/views/components/mediacard/umb-media-card.html index e54cc3e898..a4aa220eb5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/mediacard/umb-media-card.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/mediacard/umb-media-card.html @@ -12,13 +12,14 @@ Not allowed

- + {{vm.media.name}} + alt="{{vm.media.name}}" + draggable="false" /> - +
- +