diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 0f5a12b34b..a6303468cc 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,36 +1,15 @@ -# [Choice] .NET Core version: 5.0, 3.1, 2.1 -ARG VARIANT=3.1 -FROM mcr.microsoft.com/vscode/devcontainers/dotnetcore:0-${VARIANT} +# [Choice] .NET version: 6.0, 3.1, 6.0-bullseye, 3.1-bullseye, 6.0-focal, 3.1-focal +ARG VARIANT=6.0-bullseye +FROM mcr.microsoft.com/vscode/devcontainers/dotnet:0-${VARIANT} -# [Option] Install Node.js -ARG INSTALL_NODE="true" -ARG NODE_VERSION="lts/*" -RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi - -# [Option] Install Azure CLI -ARG INSTALL_AZURE_CLI="false" -COPY library-scripts/azcli-debian.sh /tmp/library-scripts/ -RUN if [ "$INSTALL_AZURE_CLI" = "true" ]; then bash /tmp/library-scripts/azcli-debian.sh; fi \ - && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts - -# Install SQL Tools: SQLPackage and sqlcmd -COPY mssql/installSQLtools.sh installSQLtools.sh -RUN bash ./installSQLtools.sh \ - && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts - -# Update args in docker-compose.yaml to set the UID/GID of the "vscode" user. -ARG USER_UID=1000 -ARG USER_GID=$USER_UID -RUN if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then groupmod --gid $USER_GID vscode && usermod --uid $USER_UID --gid $USER_GID vscode; fi +# [Choice] Node.js version: none, lts/*, 18, 16, 14 +ARG NODE_VERSION="none" +RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi # [Optional] Uncomment this section to install additional OS packages. -# Following added by Warren... -# Needed to add as Gifsicle used by gulp-imagemin does not ship a Linux binary and has to be compiled from source -# And this Linux package is needed in order to build it -# https://github.com/imagemin/imagemin-gifsicle/issues/40#issuecomment-616487214 -RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ - && apt-get -y install --no-install-recommends dh-autoreconf chromium-browser +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends # [Optional] Uncomment this line to install global node packages. # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 @@ -42,7 +21,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # Needing to set unsafe-perm as root is the user setup # https://docs.npmjs.com/cli/v6/using-npm/config#unsafe-perm # Default: false if running as root, true otherwise (we are ROOT) -RUN npm -g config set user vscode && npm -g config set unsafe-perm +#RUN npm -g config set user vscode && npm -g config set unsafe-perm # Generate and trust a local developer certificate for Kestrel # This is needed for Kestrel to bind on https diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index dfda3a4f94..4b1f593281 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,26 +1,13 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.158.0/containers/dotnet-mssql +// https://github.com/microsoft/vscode-dev-containers/tree/main/containers/dotnet { - "name": "C# (.NET) and MS SQL", + "name": "C# (.NET) Umbraco & SMTP4Dev", "dockerComposeFile": "docker-compose.yml", "service": "app", "workspaceFolder": "/workspace", - + // Set *default* container specific settings.json values on container create. - "settings": { - "terminal.integrated.shell.linux": "/bin/bash", - "mssql.connections": [ - { - "server": "localhost,1433", - "database": "", - "authenticationType": "SqlLogin", - "user": "sa", - "password": "P@ssw0rd", - "emptyPasswordInput": false, - "savePassword": false, - "profileName": "mssql-container" - } - ], + "settings": { "omnisharp.defaultLaunchSolution": "umbraco.sln", "omnisharp.enableDecompilationSupport": true, "omnisharp.enableRoslynAnalyzers": true @@ -28,14 +15,11 @@ // Add the IDs of extensions you want installed when the container is created. "extensions": [ - "ms-dotnettools.csharp", - "ms-mssql.mssql" + "ms-dotnettools.csharp" ], // Use 'forwardPorts' to make a list of ports inside the container available locally. - // 1433 for SQL if you want to connect from local into the one running inside the container - // Can connect to the SQL Server running in the image on local with 'host.docker.internal' as hostname - "forwardPorts": [1433, 9000, 5000, 25], + "forwardPorts": [9000, 5000, 25] // [Optional] To reuse of your local HTTPS dev cert: // @@ -56,6 +40,4 @@ // 2. Drag ~/.aspnet/https/aspnetapp.pfx into the root of the file explorer // 3. Open a terminal in VS Code and run "mkdir -p /home/vscode/.aspnet/https && mv aspnetapp.pfx /home/vscode/.aspnet/https" - // postCreateCommand.sh parameters: $1=SA password, $2=dacpac path, $3=sql script(s) path - "postCreateCommand": "bash .devcontainer/mssql/postCreateCommand.sh 'P@ssw0rd' './bin/Debug/' './.devcontainer/mssql/'" } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index e88327b779..a071d3a1b8 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -6,15 +6,10 @@ services: context: . dockerfile: Dockerfile args: - # [Choice] Update 'VARIANT' to pick a .NET Core version: 2.1, 3.1, 5.0 - VARIANT: 5.0 + # [Choice] .NET version: 6.0, 3.1, 6.0-bullseye, 3.1-bullseye, 6.0-focal, 3.1-focal + VARIANT: 6.0-bullseye # Options - INSTALL_NODE: "true" NODE_VERSION: "lts/*" - INSTALL_AZURE_CLI: "false" - # On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000. - USER_UID: 1000 - USER_GID: 1000 volumes: - ..:/workspace:cached @@ -22,9 +17,6 @@ services: # Overrides default command so things don't shut down after the process ends. command: sleep infinity - # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. - network_mode: service:db - # Uncomment the next line to use a non-root user for all processes. # user: vscode @@ -34,7 +26,8 @@ services: # DotNetCore ENV Variables # https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0#environment-variables environment: - - ConnectionStrings__umbracoDbDSN=server=localhost;database=UmbracoUnicore;user id=sa;password='P@ssw0rd' + - ConnectionStrings__umbracoDbDSN=Data Source=|DataDirectory|/Umbraco.sqlite.db;Cache=Shared;Foreign Keys=True;Pooling=True + - ConnectionStrings__umbracoDbDSN_ProviderName=Microsoft.Data.Sqlite - Umbraco__CMS__Unattended__InstallUnattended=true - Umbraco__CMS__Unattended__UnattendedUserName=Admin - Umbraco__CMS__Unattended__UnattendedUserEmail=test@umbraco.com @@ -43,16 +36,6 @@ services: - Umbraco__CMS__Global__Smtp__Port=25 - Umbraco__CMS__Global__Smtp__From=noreply@umbraco.test - db: - image: mcr.microsoft.com/mssql/server:2019-latest - restart: unless-stopped - environment: - SA_PASSWORD: P@ssw0rd - ACCEPT_EULA: Y - - # Add "forwardPorts": ["1433"] to **devcontainer.json** to forward MSSQL locally. - # (Adding the "ports" property to this file will not forward from a Codespace.) - smtp4dev: image: rnwood/smtp4dev:v3 restart: always diff --git a/.devcontainer/library-scripts/azcli-debian.sh b/.devcontainer/library-scripts/azcli-debian.sh deleted file mode 100644 index b03dcb0f04..0000000000 --- a/.devcontainer/library-scripts/azcli-debian.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. -#------------------------------------------------------------------------------------------------------------- -# -# Docs: https://github.com/microsoft/vscode-dev-containers/blob/master/script-library/docs/azcli.md -# -# Syntax: ./azcli-debian.sh - -set -e - -if [ "$(id -u)" -ne 0 ]; then - echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' - exit 1 -fi - -export DEBIAN_FRONTEND=noninteractive - -# Install curl, apt-transport-https, lsb-release, or gpg if missing -if ! dpkg -s apt-transport-https curl ca-certificates lsb-release > /dev/null 2>&1 || ! type gpg > /dev/null 2>&1; then - if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then - apt-get update - fi - apt-get -y install --no-install-recommends apt-transport-https curl ca-certificates lsb-release gnupg2 -fi - -# Install the Azure CLI -echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" > /etc/apt/sources.list.d/azure-cli.list -curl -sL https://packages.microsoft.com/keys/microsoft.asc | (OUT=$(apt-key add - 2>&1) || echo $OUT) -apt-get update -apt-get install -y azure-cli -echo "Done!" \ No newline at end of file diff --git a/.devcontainer/mssql/BlankDb.sql b/.devcontainer/mssql/BlankDb.sql deleted file mode 100644 index 44546efebf..0000000000 --- a/.devcontainer/mssql/BlankDb.sql +++ /dev/null @@ -1,11 +0,0 @@ -/* - This will generate a blank database when the container is spun up - that you can use to connect to for the SQL configuration in the web installer flow - - ---- NOTE ---- - Any .sql files in this folder will be executed - Along with any .dacpac will be restored as databases - See postCreateCommand.sh for specifics -*/ -CREATE DATABASE UmbracoUnicore; -GO \ No newline at end of file diff --git a/.devcontainer/mssql/installSQLtools.sh b/.devcontainer/mssql/installSQLtools.sh deleted file mode 100644 index 3fa6a67a09..0000000000 --- a/.devcontainer/mssql/installSQLtools.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -set -echo -echo "Installing mssql-tools" -curl -sSL https://packages.microsoft.com/keys/microsoft.asc | (OUT=$(apt-key add - 2>&1) || echo $OUT) -DISTRO=$(lsb_release -is | tr '[:upper:]' '[:lower:]') -CODENAME=$(lsb_release -cs) -echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-${DISTRO}-${CODENAME}-prod ${CODENAME} main" > /etc/apt/sources.list.d/microsoft.list -apt-get update -ACCEPT_EULA=Y apt-get -y install unixodbc-dev msodbcsql17 libunwind8 mssql-tools - -echo "Installing sqlpackage" -curl -sSL -o sqlpackage.zip "https://aka.ms/sqlpackage-linux" -mkdir /opt/sqlpackage -unzip sqlpackage.zip -d /opt/sqlpackage -rm sqlpackage.zip -chmod a+x /opt/sqlpackage/sqlpackage diff --git a/.devcontainer/mssql/postCreateCommand.sh b/.devcontainer/mssql/postCreateCommand.sh deleted file mode 100644 index e25583e0ff..0000000000 --- a/.devcontainer/mssql/postCreateCommand.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash -dacpac="false" -sqlfiles="false" -SApassword=$1 -dacpath=$2 -sqlpath=$3 - -echo "SELECT * FROM SYS.DATABASES" | dd of=testsqlconnection.sql -for i in {1..60}; -do - /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SApassword -d master -i testsqlconnection.sql > /dev/null - if [ $? -eq 0 ] - then - echo "SQL server ready" - break - else - echo "Not ready yet..." - sleep 1 - fi -done -rm testsqlconnection.sql - -for f in $dacpath/* -do - if [ $f == $dacpath/*".dacpac" ] - then - dacpac="true" - echo "Found dacpac $f" - fi -done - -for f in $sqlpath/* -do - if [ $f == $sqlpath/*".sql" ] - then - sqlfiles="true" - echo "Found SQL file $f" - fi -done - -if [ $sqlfiles == "true" ] -then - for f in $sqlpath/* - do - if [ $f == $sqlpath/*".sql" ] - then - echo "Executing $f" - /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SApassword -d master -i $f - fi - done -fi - -if [ $dacpac == "true" ] -then - for f in $dacpath/* - do - if [ $f == $dacpath/*".dacpac" ] - then - dbname=$(basename $f ".dacpac") - echo "Deploying dacpac $f" - /opt/sqlpackage/sqlpackage /Action:Publish /SourceFile:$f /TargetServerName:localhost /TargetDatabaseName:$dbname /TargetUser:sa /TargetPassword:$SApassword - fi - done -fi \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.yml b/.github/ISSUE_TEMPLATE/01_bug_report.yml index a60d373b9c..47f6fe038d 100644 --- a/.github/ISSUE_TEMPLATE/01_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/01_bug_report.yml @@ -6,7 +6,7 @@ body: - type: input id: "version" attributes: - label: "Which *exact* Umbraco version are you using? For example: 9.0.1 - don't just write v9" + label: "Which Umbraco version are you using? (Please write the *exact* version, example: 10.1.0)" description: "Use the help icon in the Umbraco backoffice to find the version you're using" validations: required: true diff --git a/.github/workflows/add-issues-to-review-project.yml b/.github/workflows/add-issues-to-review-project.yml index 4590c173fc..0d89451373 100644 --- a/.github/workflows/add-issues-to-review-project.yml +++ b/.github/workflows/add-issues-to-review-project.yml @@ -5,6 +5,9 @@ on: types: - opened +permissions: + contents: read + jobs: get-user-type: runs-on: ubuntu-latest @@ -43,6 +46,8 @@ jobs: core.setOutput("ignored", isIgnoredUser); console.log("Ignored is", isIgnoredUser); add-to-project: + permissions: + repository-projects: write # for actions/add-to-project if: needs.get-user-type.outputs.ignored == 'false' runs-on: ubuntu-latest needs: [get-user-type] diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a3ffc10a1d..ae89c0a6bc 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -7,9 +7,16 @@ on: # The branches below must be a subset of the branches above branches: ['*/dev','*/contrib'] +permissions: + contents: read + jobs: CodeQL-Build: + permissions: + actions: read # for github/codeql-action/init to get workflow details + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/analyze to upload SARIF results runs-on: ubuntu-latest permissions: actions: read diff --git a/.vscode/launch.json b/.vscode/launch.json index ab97269d1f..65a3d08583 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,9 +9,8 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "Dotnet build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/src/Umbraco.Web.UI/bin/Debug/net5.0/Umbraco.Web.UI.dll", - "args": [], + "program": "dotnet", + "args": ["run"], "cwd": "${workspaceFolder}/src/Umbraco.Web.UI", "stopAtEntry": false, "requireExactSource": false, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 99876bc77e..b8f058c18a 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -54,7 +54,7 @@ "type": "process", "args": [ "build", - "${workspaceFolder}/src/umbraco.sln", + "${workspaceFolder}/umbraco.sln", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -69,6 +69,7 @@ "args": [ "watch", "run", + "--project", "${workspaceFolder}/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" diff --git a/Directory.Build.props b/Directory.Build.props index 487c193812..8ae2e0baec 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,7 +8,7 @@ all - 3.5.109 + 3.5.107 diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs index fb9db8387a..18d83b53a3 100644 --- a/src/JsonSchema/AppSettings.cs +++ b/src/JsonSchema/AppSettings.cs @@ -56,8 +56,6 @@ namespace JsonSchema public LoggingSettings? Logging { get; set; } - public MemberPasswordConfigurationSettings? MemberPassword { get; set; } - public NuCacheSettings? NuCache { get; set; } public RequestHandlerSettings? RequestHandler { get; set; } @@ -70,8 +68,6 @@ namespace JsonSchema public TypeFinderSettings? TypeFinder { get; set; } - public UserPasswordConfigurationSettings? UserPassword { get; set; } - public WebRoutingSettings? WebRouting { get; set; } public UmbracoPluginSettings? Plugins { get; set; } diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml index 406c311312..be1b741535 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml @@ -16,7 +16,7 @@ - + has been selected as the root of your new content, click 'ok' below. No node selected yet, please select a node in the list above before clicking 'ok' The current node is not allowed under the chosen node because of its type - The current node cannot be moved to one of its subpages + The current node cannot be moved to one of its subpages neither can the parent and destination be the same The current node cannot exist at the root The action isn't allowed since you have insufficient permissions on 1 or more child documents. @@ -1873,6 +1873,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Keep all versions newer than days Keep latest version per day for days Prevent cleanup + Enable cleanup NOTE! The cleanup of historically content versions are disabled globally. These settings will not take effect before it is enabled.]]> diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 88030198a3..33a33bbfe6 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -1203,7 +1203,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont has been selected as the root of your new content, click 'ok' below. No node selected yet, please select a node in the list above before clicking 'ok' The current node is not allowed under the chosen node because of its type - The current node cannot be moved to one of its subpages + The current node cannot be moved to one of its subpages neither can the parent and destination be the same The current node cannot exist at the root The action isn't allowed since you have insufficient permissions on 1 or more child documents. @@ -1947,6 +1947,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Keep all versions newer than days Keep latest version per day for days Prevent cleanup + Enable cleanup NOTE! The cleanup of historically content versions are disabled globally. These settings will not take effect before it is enabled.]]> Changing a data type with stored values is disabled. To allow this you can change the Umbraco:CMS:DataTypes:CanBeChanged setting in appsettings.json. diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/es.xml b/src/Umbraco.Core/EmbeddedResources/Lang/es.xml index 3e59088ef9..4012566597 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/es.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/es.xml @@ -71,6 +71,10 @@ Permitir acceso para guardar un nodo Permitir acceso para crear una Plantilla de Contenido + + Contenido + Información + Permiso denegado. Añadir nuevo dominio @@ -1173,6 +1177,24 @@ Mostrar en perfil de miembro pestaña no tiene orden + + Agregar idioma + Código ISO + Idioma obligatorio + + Las propiedades en este idioma deben completarse antes de que se pueda publicar el nodo. + + Idioma predeterminado + Un sitio de Umbraco solo puede tener un conjunto de idiomas predeterminado. + Cambiar el idioma predeterminado puede provocar que falte el contenido predeterminado. + Vuelve a caer + Sin lenguaje alternativo + + Para permitir que el contenido multilingüe retroceda a otro idioma si no está presente en el idioma solicitado, selecciónelo aquí. + + Idioma de retroceso + ninguno + Construyendo modelos esto puede llevar un rato, no te preocupes @@ -1260,6 +1282,7 @@ Roles Tipos de miembros Tipos de documento + Tipos de relaciones Paquetes Paquetes Vistas Parciales @@ -1271,7 +1294,9 @@ Scripts Hojas de estilo Plantillas + Visor de registro Usuarios + Ajustes Existe una nueva actualización diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml index 28793081b7..53ec669c1b 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml @@ -1693,6 +1693,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Bewaar alle versies nieuwer dan dagen Bewaar de laatste versie per dag voor dagen Voorkom opschonen + Opschonen aanzetten Geschiedenis opschonen is globaal uitgeschakeld. Deze instellingen worden pas van kracht nadat ze zijn ingeschakeld. diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 790444c4bc..6842cc6681 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -419,7 +419,8 @@ public static class StringExtensions /// returns . /// public static bool IsNullOrWhiteSpace(this string? value) => string.IsNullOrWhiteSpace(value); - + + [return: NotNullIfNotNull("defaultValue")] public static string? IfNullOrWhiteSpace(this string? str, string? defaultValue) => str.IsNullOrWhiteSpace() ? defaultValue : str; diff --git a/src/Umbraco.Core/Models/IDataType.cs b/src/Umbraco.Core/Models/IDataType.cs index 6f0002c779..64ffe6489d 100644 --- a/src/Umbraco.Core/Models/IDataType.cs +++ b/src/Umbraco.Core/Models/IDataType.cs @@ -28,14 +28,26 @@ public interface IDataType : IUmbracoEntity, IRememberBeingDirty ValueStorageType DatabaseType { get; set; } /// - /// Gets or sets the configuration object. + /// Gets or sets the configuration object. /// /// - /// The configuration object is serialized to Json and stored into the database. - /// - /// The serialized Json is deserialized by the property editor, which by default should - /// return a Dictionary{string, object} but could return a typed object e.g. MyEditor.Configuration. - /// + /// The configuration object is serialized to Json and stored into the database. + /// The serialized Json is deserialized by the property editor, which by default should + /// return a Dictionary{string, object} but could return a typed object e.g. MyEditor.Configuration. /// object? Configuration { get; set; } + + /// + /// Creates a deep clone of the current entity with its identity/alias reset + /// We have the default implementation here to avoid breaking changes for the user + /// + /// + IDataType DeepCloneWithResetIdentities() + { + var clone = (DataType)DeepClone(); + clone.Key = Guid.Empty; + clone.ResetIdentity(); + return clone; + } } + diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs index f8d158dce9..5bd5ff23cc 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs @@ -61,6 +61,22 @@ public interface IPublishedSnapshotService : IDisposable IReadOnlyCollection? mediaTypeIds = null, IReadOnlyCollection? memberTypeIds = null); + + /// + /// Rebuilds all internal database caches (but does not reload). + /// + /// + /// + /// Forces the snapshot service to rebuild its internal database caches. For instance, some caches + /// may rely on a database table to store pre-serialized version of documents. + /// + /// + /// This does *not* reload the caches. Caches need to be reloaded, for instance via + /// RefreshAllPublishedSnapshot method. + /// + /// + void RebuildAll() => Rebuild(Array.Empty(), Array.Empty(), Array.Empty()); + /* An IPublishedCachesService implementation can rely on transaction-level events to update * its internal, database-level data, as these events are purely internal. However, it cannot * rely on cache refreshers CacheUpdated events to update itself, as these events are external diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 63a05dcd6e..915b92d78d 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2432,6 +2432,11 @@ public class ContentService : RepositoryService, IContentService /// Optional Id of the User moving the Content public void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId) { + if(content.ParentId == parentId) + { + return; + } + // if moving to the recycle bin then use the proper method if (parentId == Constants.System.RecycleBinContent) { diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 98a7195fbf..7cf63445a9 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -845,6 +845,10 @@ public abstract class ContentTypeServiceBase : ContentTypeSe public Attempt?> Move(TItem moving, int containerId) { EventMessages eventMessages = EventMessagesFactory.Get(); + if(moving.ParentId == containerId) + { + return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedNotAllowedByPath, eventMessages); + } var moveInfo = new List>(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 1fdbb4a79b..de3f96d834 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -396,6 +396,11 @@ namespace Umbraco.Cms.Core.Services.Implement public Attempt?> Move(IDataType toMove, int parentId) { EventMessages evtMsgs = EventMessagesFactory.Get(); + if (toMove.ParentId == parentId) + { + return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedNotAllowedByPath, evtMsgs); + } + var moveInfo = new List>(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) @@ -436,6 +441,39 @@ namespace Umbraco.Cms.Core.Services.Implement return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs); } + public Attempt?> Copy(IDataType copying, int containerId) + { + var evtMsgs = EventMessagesFactory.Get(); + + IDataType copy; + using (var scope = ScopeProvider.CreateCoreScope()) + { + try + { + if (containerId > 0) + { + var container = _dataTypeContainerRepository.Get(containerId); + if (container is null) + { + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback + } + } + copy = copying.DeepCloneWithResetIdentities(); + + copy.Name += " (copy)"; // might not be unique + copy.ParentId = containerId; + _dataTypeRepository.Save(copy); + scope.Complete(); + } + catch (DataOperationException ex) + { + return OperationResult.Attempt.Fail(ex.Operation, evtMsgs); // causes rollback + } + } + + return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs, copy); + } + /// /// Saves an /// diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index effb4573b4..02bc51a8f6 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -100,4 +100,15 @@ public interface IDataTypeService : IService IEnumerable GetByEditorAlias(string propertyEditorAlias); Attempt?> Move(IDataType toMove, int parentId); + + /// + /// Copies the give to a given container + /// We have the default implementation here to avoid breaking changes for the user + /// + /// The data type that will be copied + /// The container ID under where the data type will be copied + /// + /// + Attempt?> Copy(IDataType copying, int containerId) => throw new NotImplementedException(); + } diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index ec600efab7..b2b2b5d8d5 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -90,6 +90,39 @@ public interface IMemberService : IMembershipMemberService /// IMember CreateMember(string username, string email, string name, IMemberType memberType); + /// + /// Creates and persists a Member + /// + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// + /// Username of the Member to create + /// Email of the Member to create + /// Alias of the MemberType the Member should be based on + /// + /// + /// + IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias) => + throw new NotImplementedException(); + + /// + /// Creates and persists a Member + /// + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// + /// Username of the Member to create + /// Email of the Member to create + /// Alias of the MemberType the Member should be based on + /// Whether the member is approved or not + /// + /// + /// + IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias, bool isApproved) => + throw new NotImplementedException(); + /// /// Creates and persists a Member /// @@ -106,6 +139,24 @@ public interface IMemberService : IMembershipMemberService /// IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias); + /// + /// Creates and persists a Member + /// + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// Alias of the MemberType the Member should be based on + /// Whether the member is approved or not + /// + /// + /// + IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias, bool isApproved) + => throw new NotImplementedException(); + /// /// Creates and persists a Member /// diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index d8f9f787aa..3e5464edd9 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -177,13 +177,13 @@ namespace Umbraco.Cms.Core.Services => CreateMemberWithIdentity(username, email, username, string.Empty, memberTypeAlias); public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias, bool isApproved) - => CreateMemberWithIdentity(username, email, string.Empty, string.Empty, memberTypeAlias, isApproved); + => CreateMemberWithIdentity(username, email, username, string.Empty, memberTypeAlias, isApproved); public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias) - => CreateMemberWithIdentity(username, email, string.Empty, string.Empty, memberTypeAlias); + => CreateMemberWithIdentity(username, email, name, string.Empty, memberTypeAlias); public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias, bool isApproved) - => CreateMemberWithIdentity(username, string.Empty, name, string.Empty, memberTypeAlias, isApproved); + => CreateMemberWithIdentity(username, email, name, string.Empty, memberTypeAlias, isApproved); /// /// Creates and persists a Member diff --git a/src/Umbraco.Core/Snippets/PartialViewSnippetCollection.cs b/src/Umbraco.Core/Snippets/PartialViewSnippetCollection.cs index 5a0cda96e9..7b07d554c0 100644 --- a/src/Umbraco.Core/Snippets/PartialViewSnippetCollection.cs +++ b/src/Umbraco.Core/Snippets/PartialViewSnippetCollection.cs @@ -56,7 +56,7 @@ namespace Umbraco.Cms.Core.Snippets return content; } - private string CleanUpContents(string content) + private static string CleanUpContents(string content) { // Strip the @inherits if it's there var headerMatch = new Regex("^@inherits\\s+?.*$", RegexOptions.Multiline); @@ -64,7 +64,9 @@ namespace Umbraco.Cms.Core.Snippets return newContent .Replace("Model.Content.", "Model.") - .Replace("(Model.Content)", "(Model)"); + .Replace("(Model.Content)", "(Model)") + .Replace("Model?.Content.", "Model.") + .Replace("(Model?.Content)", "(Model)"); } } } diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/ThreadAbortExceptionEnricher.cs b/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/ThreadAbortExceptionEnricher.cs index 4083aa7311..45495de9e8 100644 --- a/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/ThreadAbortExceptionEnricher.cs +++ b/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/ThreadAbortExceptionEnricher.cs @@ -38,9 +38,9 @@ public class ThreadAbortExceptionEnricher : ILogEventEnricher } } - private static bool IsTimeoutThreadAbortException(Exception? exception) + private static bool IsTimeoutThreadAbortException(Exception exception) { - if (exception is null || !(exception is ThreadAbortException abort)) + if (!(exception is ThreadAbortException abort)) { return false; } @@ -76,7 +76,7 @@ public class ThreadAbortExceptionEnricher : ILogEventEnricher // dump if configured, or if stacktrace contains Monitor.ReliableEnter var dump = _coreDebugSettings.DumpOnTimeoutThreadAbort || - IsMonitorEnterThreadAbortException(logEvent.Exception!); + IsMonitorEnterThreadAbortException(logEvent.Exception); // dump if it is ok to dump (might have a cap on number of dump...) dump &= MiniDump.OkToDump(_hostingEnvironment); diff --git a/src/Umbraco.Infrastructure/Migrations/PostMigrations/PublishedSnapshotRebuilder.cs b/src/Umbraco.Infrastructure/Migrations/PostMigrations/PublishedSnapshotRebuilder.cs index f70fd0ddb3..d86307b1f9 100644 --- a/src/Umbraco.Infrastructure/Migrations/PostMigrations/PublishedSnapshotRebuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/PostMigrations/PublishedSnapshotRebuilder.cs @@ -26,7 +26,7 @@ public class PublishedSnapshotRebuilder : IPublishedSnapshotRebuilder /// public void Rebuild() { - _publishedSnapshotService.Rebuild(); + _publishedSnapshotService.RebuildAll(); _distributedCache.RefreshAllPublishedSnapshot(); } } diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index d8cd543dca..84f8e888c2 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -20,6 +20,9 @@ + + + @@ -41,14 +44,14 @@ - + - + - + diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs index 176a664d42..cb4439f2ae 100644 --- a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs +++ b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs @@ -97,6 +97,7 @@ public class NuCacheContentRepository : RepositoryBase, INuCacheContentRepositor OnRepositoryRefreshed(serializer, member, false); } + /// public void Rebuild( IReadOnlyCollection? contentTypeIds = null, IReadOnlyCollection? mediaTypeIds = null, @@ -107,9 +108,20 @@ public class NuCacheContentRepository : RepositoryBase, INuCacheContentRepositor | ContentCacheDataSerializerEntityType.Media | ContentCacheDataSerializerEntityType.Member); - RebuildContentDbCache(serializer, _nucacheSettings.Value.SqlPageSize, contentTypeIds); - RebuildMediaDbCache(serializer, _nucacheSettings.Value.SqlPageSize, mediaTypeIds); - RebuildMemberDbCache(serializer, _nucacheSettings.Value.SqlPageSize, memberTypeIds); + if(contentTypeIds != null) + { + RebuildContentDbCache(serializer, _nucacheSettings.Value.SqlPageSize, contentTypeIds); + } + + if (mediaTypeIds != null) + { + RebuildMediaDbCache(serializer, _nucacheSettings.Value.SqlPageSize, mediaTypeIds); + } + + if (memberTypeIds != null) + { + RebuildMemberDbCache(serializer, _nucacheSettings.Value.SqlPageSize, memberTypeIds); + } } // assumes content tree lock diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs index 3aa071716e..09855f5682 100644 --- a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs +++ b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs @@ -51,7 +51,7 @@ public class NuCacheContentService : RepositoryService, INuCacheContentService using (_profilingLogger.TraceDuration( $"Rebuilding NuCache database with {serializer} serializer")) { - Rebuild(); + RebuildAll(); _keyValueService.SetValue(NuCacheSerializerKey, serializer.ToString()); } } @@ -113,11 +113,17 @@ public class NuCacheContentService : RepositoryService, INuCacheContentService public void RefreshMember(IMember member) => _repository.RefreshMember(member); + /// + public void RebuildAll() + { + Rebuild(Array.Empty(), Array.Empty(), Array.Empty()); + } + /// public void Rebuild( - IReadOnlyCollection? contentTypeIds = null, - IReadOnlyCollection? mediaTypeIds = null, - IReadOnlyCollection? memberTypeIds = null) + IReadOnlyCollection? contentTypeIds = null, + IReadOnlyCollection? mediaTypeIds = null, + IReadOnlyCollection? memberTypeIds = null) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) { diff --git a/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj b/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj index 4a2f763d36..2d82addc84 100644 --- a/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj +++ b/src/Umbraco.PublishedCache.NuCache/Umbraco.PublishedCache.NuCache.csproj @@ -18,7 +18,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index a524062d36..7de06dfe6d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -383,6 +383,30 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } } + public IActionResult PostCopy(MoveOrCopy copy) + { + var toCopy = _dataTypeService.GetDataType(copy.Id); + if (toCopy is null) + { + return NotFound(); + } + + Attempt?> result = _dataTypeService.Copy(toCopy, copy.ParentId); + if (result.Success) + { + return Content(toCopy.Path, MediaTypeNames.Text.Plain, Encoding.UTF8); + } + + return result.Result?.Result switch + { + MoveOperationStatusType.FailedParentNotFound => NotFound(), + MoveOperationStatusType.FailedCancelledByEvent => ValidationProblem(), + MoveOperationStatusType.FailedNotAllowedByPath => ValidationProblem( + _localizedTextService.Localize("moveOrCopy", "notAllowedByPath")), + _ => throw new ArgumentOutOfRangeException() + }; + } + public IActionResult PostRenameContainer(int id, string name) { var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; diff --git a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs index b9508a501b..8b25275508 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs @@ -260,6 +260,11 @@ public class DictionaryController : BackOfficeNotificationsController return ValidationProblem(_localizedTextService.Localize("dictionary", "itemDoesNotExists")); } + if(dictionaryItem.ParentId == null && move.ParentId == Constants.System.Root) + { + return ValidationProblem(_localizedTextService.Localize("moveOrCopy", "notAllowedByPath")); + } + IDictionaryItem? parent = _localizationService.GetDictionaryItemById(move.ParentId); if (parent == null) { @@ -274,6 +279,11 @@ public class DictionaryController : BackOfficeNotificationsController } else { + if (dictionaryItem.ParentId == parent.Key) + { + return ValidationProblem(_localizedTextService.Localize("moveOrCopy", "notAllowedByPath")); + } + dictionaryItem.ParentId = parent.Key; if (dictionaryItem.Key == parent.ParentId) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs index 9980089248..c8c391d990 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs @@ -34,7 +34,8 @@ public class PublishedSnapshotCacheStatusController : UmbracoAuthorizedApiContro [HttpPost] public string RebuildDbCache() { - _publishedSnapshotService.Rebuild(); + //Rebuild All + _publishedSnapshotService.RebuildAll(); return _publishedSnapshotStatus.GetStatus(); } diff --git a/src/Umbraco.Web.BackOffice/Services/IconService.cs b/src/Umbraco.Web.BackOffice/Services/IconService.cs index fa6bf3eb4c..0f3fcbec98 100644 --- a/src/Umbraco.Web.BackOffice/Services/IconService.cs +++ b/src/Umbraco.Web.BackOffice/Services/IconService.cs @@ -136,6 +136,9 @@ public class IconService : IIconService } } + // Get icons from the web root file provider (both physical and virtual) + icons.UnionWith(GetIconsFiles(_webHostEnvironment.WebRootFileProvider, Constants.SystemDirectories.AppPlugins)); + IDirectoryContents? iconFolder = _webHostEnvironment.WebRootFileProvider.GetDirectoryContents(_globalSettings.IconsPath); @@ -148,6 +151,63 @@ public class IconService : IIconService return icons; } + + /// + /// Finds all SVG icon files based on the specified and . + /// The method will find both physical and virtual (eg. from a Razor Class Library) icons. + /// + /// The file provider to be used. + /// The sub path to start from - should probably always be . + /// A collection of representing the found SVG icon files. + private static IEnumerable GetIconsFiles(IFileProvider fileProvider, string path) + { + // Iterate through all plugin folders, this is necessary because on Linux we'll get casing issues when + // we directly try to access {path}/{pluginDirectory.Name}/{Constants.SystemDirectories.PluginIcons} + foreach (IFileInfo pluginDirectory in fileProvider.GetDirectoryContents(path)) + { + // Ideally there shouldn't be any files, but we'd better check to be sure + if (!pluginDirectory.IsDirectory) + { + continue; + } + + // Iterate through the sub directories of each plugin folder + foreach (IFileInfo subDir1 in fileProvider.GetDirectoryContents($"{path}/{pluginDirectory.Name}")) + { + // Skip files again + if (!subDir1.IsDirectory) + { + continue; + } + + // Iterate through second level sub directories + foreach (IFileInfo subDir2 in fileProvider.GetDirectoryContents($"{path}/{pluginDirectory.Name}/{subDir1.Name}")) + { + // Skip files again + if (!subDir2.IsDirectory) + { + continue; + } + + // Does the directory match the plugin icons folder? (case insensitive for legacy support) + if (!$"/{subDir1.Name}/{subDir2.Name}".InvariantEquals(Constants.SystemDirectories.PluginIcons)) + { + continue; + } + + // Iterate though the files of the second level sub directory. This should be where the SVG files are located :D + foreach (IFileInfo file in fileProvider.GetDirectoryContents($"{path}/{pluginDirectory.Name}/{subDir1.Name}/{subDir2.Name}")) + { + if (file.Name.InvariantEndsWith(".svg")) + { + yield return new FileInfo(file.PhysicalPath); + } + } + } + } + } + } + private IReadOnlyDictionary? GetIconDictionary() => _cache.GetCacheItem( $"{typeof(IconService).FullName}.{nameof(GetIconDictionary)}", () => GetAllIconsFiles() diff --git a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs index 84a32d3659..d0164afa06 100644 --- a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs @@ -181,8 +181,9 @@ public class DataTypeTreeController : TreeController, ISearchableTree menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); } - menu.Items.Add(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false); - } + menu.Items.Add(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false); + menu.Items.Add(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false); + } return menu; } diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj index 7e4a3eb700..3d300d4613 100644 --- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj +++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj @@ -24,7 +24,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all diff --git a/src/Umbraco.Web.Common/Media/MediaPrependBasePathFileProvider.cs b/src/Umbraco.Web.Common/Media/MediaPrependBasePathFileProvider.cs index 45d522d0de..c6ce59456d 100644 --- a/src/Umbraco.Web.Common/Media/MediaPrependBasePathFileProvider.cs +++ b/src/Umbraco.Web.Common/Media/MediaPrependBasePathFileProvider.cs @@ -70,7 +70,7 @@ internal class MediaPrependBasePathFileProvider : IFileProvider if (TryMapSubPath(subpath, out PathString newPath)) { // KJA changed: use explicit newPath.Value instead of implicit newPath string operator (which calls ToString()) - IFileInfo? result = _underlyingFileProvider.GetFileInfo(newPath.Value!); + IFileInfo? result = _underlyingFileProvider.GetFileInfo(newPath.Value); return result; } @@ -84,7 +84,7 @@ internal class MediaPrependBasePathFileProvider : IFileProvider if (TryMapSubPath(filter, out PathString newPath)) { // KJA changed: use explicit newPath.Value instead of implicit newPath string operator (which calls ToString()) - IChangeToken? result = _underlyingFileProvider.Watch(newPath.Value!); + IChangeToken? result = _underlyingFileProvider.Watch(newPath.Value); return result; } diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index 338c9d2c5c..b93e8de58d 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -37,12 +37,14 @@ + + - + - - + + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js index 233d5c2f7d..0c46ada020 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js @@ -249,15 +249,15 @@ For extra details about options and events take a look here: https://refreshless var isVertical = slider.noUiSlider.options.orientation === 'vertical'; var tooltips = slider.noUiSlider.getTooltips(); var origins = slider.noUiSlider.getOrigins(); - + // Move tooltips into the origin element. The default stylesheet handles this. - if(tooltips && tooltips.length !== 0){ - tooltips.forEach(function (tooltip, index) { - if (tooltip) { - origins[index].appendChild(tooltip); - } - }); - } + if(tooltips && tooltips.length !== 0){ + tooltips.forEach(function (tooltip, index) { + if (tooltip) { + origins[index].appendChild(tooltip); + } + }); + } slider.noUiSlider.on('update', function (values, handle, unencoded, tap, positions) { @@ -293,17 +293,17 @@ For extra details about options and events take a look here: https://refreshless for (var j = 0; j < handlesInPool; j++) { var handleNumber = pool[j]; - + if (j === handlesInPool - 1) { var offset = 0; - + poolPositions[poolIndex].forEach(function (value) { - offset += 1000 - 10 * value; + offset += 1000 - value; }); - + var direction = isVertical ? 'bottom' : 'right'; var last = isRtl ? 0 : handlesInPool - 1; - var lastOffset = 1000 - 10 * poolPositions[poolIndex][last]; + var lastOffset = 1000 - poolPositions[poolIndex][last]; offset = (textIsRtl && !isVertical ? 100 : 0) + (offset / handlesInPool) - lastOffset; // Filter to unique values diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index ada64bd3f6..dfac875e5e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -432,15 +432,13 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca throw "args.id cannot be null"; } - var promise = localizationService.localize("contentType_moveFailed"); - return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostMove"), { parentId: args.parentId, id: args.id }, { responseType: 'text' }), - promise); + 'Failed to move content type'); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js index 8f5308ce22..91d74e1efd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js @@ -5,7 +5,7 @@ **/ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { - return { + return { /** * @ngdoc method @@ -30,358 +30,401 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { */ getPreValues: function (editorAlias, dataTypeId) { - if (!dataTypeId) { - dataTypeId = -1; - } + if (!dataTypeId) { + dataTypeId = -1; + } - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetPreValues", - [{ editorAlias: editorAlias }, { dataTypeId: dataTypeId }])), - "Failed to retrieve pre values for editor alias " + editorAlias); - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetPreValues", + [{ editorAlias: editorAlias }, { dataTypeId: dataTypeId }])), + "Failed to retrieve pre values for editor alias " + editorAlias); + }, - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#getReferences - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Retrieves references of a given data type. - * - * @param {Int} id id of datatype to retrieve references for - * @returns {Promise} resourcePromise object. - * - */ - getReferences: function (id) { + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getReferences + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Retrieves references of a given data type. + * + * @param {Int} id id of datatype to retrieve references for + * @returns {Promise} resourcePromise object. + * + */ + getReferences: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetReferences", - { id: id })), - "Failed to retrieve usages for data type of id " + id); - - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetReferences", + { id: id })), + "Failed to retrieve usages for data type of id " + id); - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#getById - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Gets a data type item with a given id - * - * ##usage - *
-         * dataTypeResource.getById(1234)
-         *    .then(function(datatype) {
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id id of data type to retrieve - * @returns {Promise} resourcePromise object. - * - */ - getById: function (id) { + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetById", - [{ id: id }])), - "Failed to retrieve data for data type id " + id); - }, + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getById + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Gets a data type item with a given id + * + * ##usage + *
+     * dataTypeResource.getById(1234)
+     *    .then(function(datatype) {
+     *        alert('its here!');
+     *    });
+     * 
+ * + * @param {Int} id id of data type to retrieve + * @returns {Promise} resourcePromise object. + * + */ + getById: function (id) { - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#getByName - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Gets a data type item with a given name - * - * ##usage - *
-         * dataTypeResource.getByName("upload")
-         *    .then(function(datatype) {
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {String} name Name of data type to retrieve - * @returns {Promise} resourcePromise object. - * - */ - getByName: function (name) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetById", + [{ id: id }])), + "Failed to retrieve data for data type id " + id); + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetByName", - [{ name: name }])), - "Failed to retrieve data for data type with name: " + name); - }, + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getByName + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Gets a data type item with a given name + * + * ##usage + *
+     * dataTypeResource.getByName("upload")
+     *    .then(function(datatype) {
+     *        alert('its here!');
+     *    });
+     * 
+ * + * @param {String} name Name of data type to retrieve + * @returns {Promise} resourcePromise object. + * + */ + getByName: function (name) { - getAll: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetByName", + [{ name: name }])), + "Failed to retrieve data for data type with name: " + name); + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetAll")), - "Failed to retrieve data"); - }, + getAll: function () { - getGroupedDataTypes: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetGroupedDataTypes")), - "Failed to retrieve data"); - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetAll")), + "Failed to retrieve data"); + }, - getGroupedPropertyEditors: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetGroupedPropertyEditors")), - "Failed to retrieve data"); - }, + getGroupedDataTypes: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetGroupedDataTypes")), + "Failed to retrieve data"); + }, - getAllPropertyEditors: function () { + getGroupedPropertyEditors: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetGroupedPropertyEditors")), + "Failed to retrieve data"); + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetAllPropertyEditors")), - "Failed to retrieve data"); - }, + getAllPropertyEditors: function () { - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a scaffold of an empty data type item - * - * The scaffold is used to build editors for data types that has not yet been populated with data. - * - * ##usage - *
-         * dataTypeResource.getScaffold()
-         *    .then(function(scaffold) {
-         *        var myType = scaffold;
-         *        myType.name = "My new data type";
-         *
-         *        dataTypeResource.save(myType, myType.preValues, true)
-         *            .then(function(type){
-         *                alert("Retrieved, updated and saved again");
-         *            });
-         *    });
-         * 
- * - * @returns {Promise} resourcePromise object containing the data type scaffold. - * - */ - getScaffold: function (parentId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetAllPropertyEditors")), + "Failed to retrieve data"); + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetEmpty", { parentId: parentId })), - "Failed to retrieve data for empty datatype"); - }, - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#deleteById - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Deletes a data type with a given id - * - * ##usage - *
-         * dataTypeResource.deleteById(1234)
-         *    .then(function() {
-         *        alert('its gone!');
-         *    });
-         * 
- * - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "DeleteById", - [{ id: id }])), - "Failed to delete item " + id); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffold + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty data type item + * + * The scaffold is used to build editors for data types that has not yet been populated with data. + * + * ##usage + *
+     * dataTypeResource.getScaffold()
+     *    .then(function(scaffold) {
+     *        var myType = scaffold;
+     *        myType.name = "My new data type";
+     *
+     *        dataTypeResource.save(myType, myType.preValues, true)
+     *            .then(function(type){
+     *                alert("Retrieved, updated and saved again");
+     *            });
+     *    });
+     * 
+ * + * @returns {Promise} resourcePromise object containing the data type scaffold. + * + */ + getScaffold: function (parentId) { - deleteContainerById: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetEmpty", { parentId: parentId })), + "Failed to retrieve data for empty datatype"); + }, + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#deleteById + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Deletes a data type with a given id + * + * ##usage + *
+     * dataTypeResource.deleteById(1234)
+     *    .then(function() {
+     *        alert('its gone!');
+     *    });
+     * 
+ * + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "DeleteById", + [{ id: id }])), + "Failed to delete item " + id); + }, - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "DeleteContainer", - [{ id: id }])), - 'Failed to delete content type contaier'); - }, + deleteContainerById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "DeleteContainer", + [{ id: id }])), + 'Failed to delete content type contaier'); + }, - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#getCustomListView - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Returns a custom listview, given a content types alias - * - * - * ##usage - *
-         * dataTypeResource.getCustomListView("home")
-         *    .then(function(listview) {
-         *    });
-         * 
- * - * @returns {Promise} resourcePromise object containing the listview datatype. - * - */ + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#getCustomListView + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Returns a custom listview, given a content types alias + * + * + * ##usage + *
+     * dataTypeResource.getCustomListView("home")
+     *    .then(function(listview) {
+     *    });
+     * 
+ * + * @returns {Promise} resourcePromise object containing the listview datatype. + * + */ - getCustomListView: function (contentTypeAlias) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetCustomListView", - { contentTypeAlias: contentTypeAlias } - )), - "Failed to retrieve data for custom listview datatype"); - }, + getCustomListView: function (contentTypeAlias) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "GetCustomListView", + { contentTypeAlias: contentTypeAlias } + )), + "Failed to retrieve data for custom listview datatype"); + }, - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#createCustomListView - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Creates and returns a custom listview, given a content types alias - * - * ##usage - *
-        * dataTypeResource.createCustomListView("home")
-        *    .then(function(listview) {
-        *    });
-        * 
- * - * @returns {Promise} resourcePromise object containing the listview datatype. - * - */ - createCustomListView: function (contentTypeAlias) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "PostCreateCustomListView", - { contentTypeAlias: contentTypeAlias } - )), - "Failed to create a custom listview datatype"); - }, + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#createCustomListView + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Creates and returns a custom listview, given a content types alias + * + * ##usage + *
+    * dataTypeResource.createCustomListView("home")
+    *    .then(function(listview) {
+    *    });
+    * 
+ * + * @returns {Promise} resourcePromise object containing the listview datatype. + * + */ + createCustomListView: function (contentTypeAlias) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "PostCreateCustomListView", + { contentTypeAlias: contentTypeAlias } + )), + "Failed to create a custom listview datatype"); + }, - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#save - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Saves or update a data type - * - * @param {Object} dataType data type object to create/update - * @param {Array} preValues collection of prevalues on the datatype - * @param {Bool} isNew set to true if type should be create instead of updated - * @returns {Promise} resourcePromise object. - * - */ - save: function (dataType, preValues, isNew) { + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#save + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Saves or update a data type + * + * @param {Object} dataType data type object to create/update + * @param {Array} preValues collection of prevalues on the datatype + * @param {Bool} isNew set to true if type should be create instead of updated + * @returns {Promise} resourcePromise object. + * + */ + save: function (dataType, preValues, isNew) { - var saveModel = umbDataFormatter.formatDataTypePostData(dataType, preValues, "save" + (isNew ? "New" : "")); + var saveModel = umbDataFormatter.formatDataTypePostData(dataType, preValues, "save" + (isNew ? "New" : "")); - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), saveModel), - "Failed to save data for data type id " + dataType.id); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), saveModel), + "Failed to save data for data type id " + dataType.id); + }, - /** - * @ngdoc method - * @name umbraco.resources.dataTypeResource#move - * @methodOf umbraco.resources.dataTypeResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
-         * dataTypeResource.move({ parentId: 1244, id: 123 })
-         *    .then(function() {
-         *        alert("node was moved");
-         *    }, function(err){
-         *      alert("node didnt move:" + err.data.Message); 
-         *    });
-         * 
- * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#move + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+     * dataTypeResource.move({ parentId: 1244, id: 123 })
+     *    .then(function() {
+     *        alert("node was moved");
+     *    }, function(err){
+     *      alert("node didnt move:" + err.data.Message); 
+     *    });
+     * 
+ * @param {Object} args arguments object + * @param {Int} args.id the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }, { responseType: 'text' }), - 'Failed to move content'); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }, { responseType: 'text' }), + 'Failed to move content'); + }, - createContainer: function (parentId, name) { + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#copy + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Copies a node underneath a new parentId + * + * ##usage + *
+     * dataTypeResource.copy({ parentId: 1244, id: 123 })
+     *    .then(function() {
+     *        alert("node has been copied");
+     *    }, function(err){
+     *      alert("node didnt copy:" + err.data.Message);
+     *    });
+     * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to copy + * @param {Int} args.parentId the ID of the parent node to copy to + * @returns {Promise} resourcePromise object. + * + */ + copy: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "PostCreateContainer", - { parentId: parentId, name: encodeURIComponent(name) })), - 'Failed to create a folder under parent id ' + parentId); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostCopy"), + { + parentId: args.parentId, + id: args.id + }, { responseType: 'text' }), + 'Failed to copy content'); + }, + + createContainer: function (parentId, name) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "PostCreateContainer", + { parentId: parentId, name: encodeURIComponent(name) })), + 'Failed to create a folder under parent id ' + parentId); + }, renameContainer: function (id, name) { return umbRequestHelper.resourcePromise( diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js index 62fe2b7367..79f35d0d74 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js @@ -208,15 +208,13 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter, locali throw "args.id cannot be null"; } - var promise = localizationService.localize("mediaType_moveFailed"); - return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostMove"), { parentId: args.parentId, id: args.id }, { responseType: 'text' }), - promise); + 'Failed to move media type'); }, copy: function (args) { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less index 6e1fa29eab..a9b9cf6936 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less @@ -5,18 +5,13 @@ max-width: 100%; height: @appHeaderHeight; padding: 0 20px; + + &__logo { + margin-right: 30px; + flex-shrink: 0; + } } -.umb-app-header__logo { - margin-right: 30px; - flex-shrink: 0; - button { - img { - height: 30px; - } - } - -} @media (max-width: 1279px) { .umb-app-header__logo { display: none; @@ -29,16 +24,13 @@ top: 50px; left: 17px; font-size: 13px; - border-radius: 6px; - width: 160px; padding: 20px 20px; background-color:@white; color: @blueExtraDark; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .14), 0 1px 6px 1px rgba(0, 0, 0, .14); text-decoration: none; - text-align: center; &::before { @@ -55,7 +47,6 @@ img { display: block; height: auto; - width: 120px; margin-left: auto; margin-right: auto; margin-bottom: 3px; @@ -84,6 +75,10 @@ align-items: center; height: @appHeaderHeight; outline: none; + + .umb-icon { + display: block; + } &:focus { .tabbing-active & { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-range-slider.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-range-slider.less index 57273a6a4e..44d69d3856 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-range-slider.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-range-slider.less @@ -1,56 +1,58 @@ +.umb-range-slider { + &.noUi-target { + background: linear-gradient(to bottom, @grayLighter 0%, @grayLighter 100%); + box-shadow: none; + border-radius: 20px; + height: 8px; + border: 1px solid @inputBorder; -.umb-range-slider.noUi-target { - background: linear-gradient(to bottom, @grayLighter 0%, @grayLighter 100%); - box-shadow: none; - border-radius: 20px; - height: 8px; - border: 1px solid @inputBorder; - &:focus, &:focus-within { - border-color: @inputBorderFocus; + &:focus, &:focus-within { + border-color: @inputBorderFocus; + } } -} -.umb-range-slider .noUi-connects { - cursor: pointer; - height: 20px; - top: -6px; -} -.umb-range-slider .noUi-connect { - background-color: @purple-washed; - border: 1px solid @purple-l3; -} -.umb-range-slider .noUi-tooltip { - padding: 2px 6px; -} -.umb-range-slider .noUi-handle { - cursor: grab; - border-radius: 100px; - border: none; - box-shadow: none; - width: 20px !important; - height: 20px !important; - right: -10px !important; // half the handle width - background-color: @blueExtraDark; -} -.umb-range-slider .noUi-horizontal .noUi-handle { - top: -7px; -} + .noUi-connects { + cursor: pointer; + height: 20px; + top: -6px; + } -.umb-range-slider .noUi-handle::before { - display: none; -} + .noUi-connect { + background-color: @purple-washed; + border: 1px solid @purple-l3; + } -.umb-range-slider .noUi-handle::after { - display: none; -} + .noUi-tooltip { + padding: 2px 6px; + } -.umb-range-slider .noUi-marker-large.noUi-marker-horizontal { - height: 10px; -} + .noUi-handle { + cursor: grab; + border-radius: 100px; + border: none; + box-shadow: none; + width: 20px !important; + height: 20px !important; + right: -10px !important; // half the handle width + background-color: @blueExtraDark; -.umb-range-slider .noUi-marker.noUi-marker-horizontal { - width: 1px; + &::before, &::after { + display: none; + } + } + + .noUi-horizontal .noUi-handle { + top: -7px; + } + + .noUi-marker-large.noUi-marker-horizontal { + height: 10px; + } + + .noUi-marker.noUi-marker-horizontal { + width: 1px; + } } .noUi-value { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less index 25073250f5..c6adae65fc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less @@ -57,6 +57,11 @@ text-decoration: none; } + &:disabled { + color: @ui-option-disabled-type; + text-decoration: none; + } + &::after { content: ""; height: 0px; diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less index 6b4a6abb30..b7897e90e5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/navs.less +++ b/src/Umbraco.Web.UI.Client/src/less/navs.less @@ -290,6 +290,12 @@ background: @ui-option-hover; } +.dropdown-menu > li > button:disabled, +.dropdown-submenu:hover > button:disabled { + color: @ui-option-disabled-type; + background: transparent; +} + .nav-tabs .dropdown-menu { .border-radius(0 0 3px 3px); // remove the top rounded corners here since there is a hard edge above the menu } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.controller.js index d3a87791f9..64e3f67fea 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.controller.js @@ -35,6 +35,11 @@ angular.module("umbraco") } } + var activeApp = apps.filter(x => x.active); + if (activeApp.length === 0 && apps.length > 0) { + apps[0].active = true; + } + vm.tabs = apps; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index b19f35034b..a6e10f8e8f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -218,6 +218,9 @@ angular.module("umbraco") } function submitFolder() { + if ($scope.model.creatingFolder) { + return; + } if ($scope.model.newFolderName) { $scope.model.creatingFolder = true; mediaResource diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index a5ac2ea6cb..2ad16e83f6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -73,7 +73,8 @@ size="xxs" state="version.pinningState" action="vm.pinVersion(version, $event)" - label="{{ version.preventCleanup ? 'Enable cleanup' : 'Prevent cleanup' }}"> + label="{{ version.preventCleanup ? 'Enable cleanup' : 'Prevent cleanup' }}" + label-key="{{version.preventCleanup ? 'contentTypeEditor_historyCleanupEnableCleanup' : 'contentTypeEditor_historyCleanupPreventCleanup' }}"> diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html index ce3bf06853..7936a7848e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html @@ -1,40 +1,36 @@
@@ -58,10 +54,9 @@ >Open backoffice search... - + + +
  • @@ -79,10 +74,9 @@ >Open/Close backoffice help... - + + +
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html index d09bc23318..650b3da0b9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html @@ -15,7 +15,7 @@
  • -
    +
    {{::reference.contentTypeName}}
    {{::reference.type}}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html b/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html index df649a9e4a..9480d14fba 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tabs/umb-tabs-nav.html @@ -1,6 +1,6 @@
    • - @@ -23,6 +23,7 @@ ng-class="{'dropdown-menu--active': tab.active}" ng-click="vm.clickTab($event, tab)" role="tab" + ng-disabled="tab.disabled" aria-selected="{{tab.active}}" > {{ tab.label }}
      !
      diff --git a/src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.controller.js b/src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.controller.js new file mode 100644 index 0000000000..3f7634b50b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.controller.js @@ -0,0 +1,62 @@ +angular.module("umbraco") + .controller("Umbraco.Editors.DataType.CopyController", + function ($scope, dataTypeResource, treeService, navigationService, appState) { + + $scope.dialogTreeApi = {}; + $scope.source = _.clone($scope.currentNode); + + function nodeSelectHandler(args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + + $scope.target = args.node; + $scope.target.selected = true; + } + + $scope.copy = function () { + + $scope.busy = true; + $scope.error = false; + + dataTypeResource.copy({ parentId: $scope.target.id, id: $scope.source.id }) + .then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + + //get the currently edited node (if any) + var activeNode = appState.getTreeState("selectedNode"); + + //we need to do a double sync here: first sync to the copied content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was copied!!) + + navigationService.syncTree({ tree: "dataTypes", path: path, forceReload: true, activate: false }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ tree: "dataTypes", path: activeNodePath, forceReload: false, activate: true }); + } + }); + + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + + }); + }; + + $scope.onTreeInit = function () { + $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler); + } + + $scope.close = function () { + navigationService.hideDialog(); + }; + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.html b/src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.html new file mode 100644 index 0000000000..64f815148b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dataTypes/copy.html @@ -0,0 +1,53 @@ +
      + +
      +
      + +

      + Select the folder to copy {{source.name}} to in the tree structure below +

      + + + +
      +
      +
      {{error.errorMsg}}
      +
      {{error.data.message}}
      +
      +
      + +
      +
      + {{source.name}} was copied underneath {{target.name}} +
      + +
      + +
      + +
      + + +
      + +
      +
      +
      + + +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.list.controller.js b/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.list.controller.js index a6121e3afb..788e438936 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.list.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.list.controller.js @@ -7,7 +7,9 @@ * The controller for listting dictionary items */ function DictionaryListController($scope, $location, dictionaryResource, localizationService, appState, navigationService) { - var vm = this; + + const vm = this; + vm.title = "Dictionary overview"; vm.loading = false; vm.items = []; @@ -20,11 +22,13 @@ function DictionaryListController($scope, $location, dictionaryResource, localiz vm.loading = true; dictionaryResource.getList() - .then(function (data) { - vm.items = data; - vm.items.forEach(function(item){ - item.style = { "paddingLeft": item.level * 10 }; + .then(data => { + let items = data || []; + + items.forEach(item => { + item.style = { "paddingLeft": item.level * 10 }; }); + vm.items = items; vm.loading = false; }); } @@ -47,7 +51,7 @@ function DictionaryListController($scope, $location, dictionaryResource, localiz vm.createNewItem = createNewItem; function onInit() { - localizationService.localize("dictionaryItem_overviewTitle").then(function (value) { + localizationService.localize("dictionaryItem_overviewTitle").then(value => { vm.title = value; }); diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/list.html b/src/Umbraco.Web.UI.Client/src/views/dictionary/list.html index 43fe6c45ec..0acd6ae0a3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dictionary/list.html +++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/list.html @@ -1,9 +1,6 @@
      - - - - + - + + + @@ -42,7 +41,7 @@ - +
      @@ -51,7 +50,7 @@ - + @@ -69,8 +68,8 @@
      Dictionary items
      - - There were no dictionary items found. diff --git a/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html b/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html index 7b91125e09..e7aa63eff0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html +++ b/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html @@ -45,8 +45,6 @@ position: absolute; top: 22px; left: 25px; - width: 30px; - height: 30px; z-index: 1; } .error-container { @@ -71,10 +69,12 @@
      - +

      Boot Failed

      diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index 006840bbd5..66db534475 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -176,6 +176,10 @@ function mediaEditController($scope, $routeParams, $location, $http, $q, appStat $scope.save = function () { + if($scope.page.saveButtonState == "busy"){ + return; + } + if (formHelper.submitForm({ scope: $scope })) { $scope.page.saveButtonState = "busy"; diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index ddec00129b..e0e8768b1d 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -25,14 +25,6 @@ - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - diff --git a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index c0b1d2d0e4..500f53cb89 100644 --- a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -23,7 +23,7 @@ - 0.13.2 + 0.13.1 7.0.0-preview.7.22375.6 diff --git a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj index 4fee3d2091..25a6559123 100644 --- a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj +++ b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj @@ -16,7 +16,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/NuCacheRebuildTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/NuCacheRebuildTests.cs index ed0c7ac264..5604419623 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/NuCacheRebuildTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/NuCacheRebuildTests.cs @@ -1,3 +1,4 @@ +using System; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.PublishedCache; @@ -59,7 +60,7 @@ public class NuCacheRebuildTests : UmbracoIntegrationTest Assert.AreEqual("hello", segment); - PublishedSnapshotService.Rebuild(); + PublishedSnapshotService.RebuildAll(); cachedContent = ContentService.GetById(content.Id); segment = urlSegmentProvider.GetUrlSegment(cachedContent); @@ -76,7 +77,7 @@ public class NuCacheRebuildTests : UmbracoIntegrationTest // The page has now been published, so we should see the new url segment Assert.AreEqual("goodbye", segment); - PublishedSnapshotService.Rebuild(); + PublishedSnapshotService.RebuildAll(); cachedContent = ContentService.GetById(content.Id); segment = urlSegmentProvider.GetUrlSegment(cachedContent); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index 20173ac46d..6fea142602 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -85,13 +85,14 @@ - + + all