From ac8da0a00675dce99045492f1669a825c79df356 Mon Sep 17 00:00:00 2001 From: Bjorn Van Acker Date: Wed, 20 Sep 2023 11:12:38 +0200 Subject: [PATCH 01/67] Add ToggleSecret to backend scripts --- .../assets/Backend/webpack/js/Backend.js | 8 +++++++ .../webpack/js/Components/ToggleSecret.js | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/Modules/Backend/assets/Backend/webpack/js/Components/ToggleSecret.js diff --git a/src/Modules/Backend/assets/Backend/webpack/js/Backend.js b/src/Modules/Backend/assets/Backend/webpack/js/Backend.js index c0623ec5f1..f7b6138163 100644 --- a/src/Modules/Backend/assets/Backend/webpack/js/Backend.js +++ b/src/Modules/Backend/assets/Backend/webpack/js/Backend.js @@ -24,6 +24,7 @@ import { Session } from './Components/Session' import { Config } from './Components/Config' import { PasswordGenerator } from './Components/PasswordGenerator' import { PasswordStrenghtMeter } from '../../../../../../Core/assets/js/Components/PasswordStrenghtMeter' +import ToggleSecret from './Components/ToggleSecret' window.bootstrap = bootstrap @@ -52,6 +53,7 @@ export class Backend { Backend.initPasswordGenerators() Backend.initPasswordStrenghtMeters() + Backend.initToggleSecrets() // do not move, should be run as the last item. if (!Config.isDebug()) this.forms.unloadWarning() @@ -68,6 +70,12 @@ export class Backend { element.passwordStrengthMeter = new PasswordStrenghtMeter($(element)) }) } + + static initToggleSecrets () { + $('[data-role="toggle-visibility"]').each((index, element) => { + element.toggleSecret = new ToggleSecret(element) + }) + } } $(window).on('load', () => { diff --git a/src/Modules/Backend/assets/Backend/webpack/js/Components/ToggleSecret.js b/src/Modules/Backend/assets/Backend/webpack/js/Components/ToggleSecret.js new file mode 100644 index 0000000000..64c9a3754d --- /dev/null +++ b/src/Modules/Backend/assets/Backend/webpack/js/Components/ToggleSecret.js @@ -0,0 +1,23 @@ +export default class ToggleSecret { + constructor (element) { + this._element = element + + this.init() + } + + init () { + $(this._element).on('click', () => { + this.toggle() + }) + } + + toggle () { + const target = $('#' + $(this._element).data('target')) + + if (target.attr('type') === 'password') { + target.attr('type', 'text') + } else { + target.attr('type', 'password') + } + } +} From aa9910afad8c6a55d34b8809dd8f8dac30c28e7e Mon Sep 17 00:00:00 2001 From: Daphne Slootmans Date: Fri, 22 Sep 2023 15:17:17 +0200 Subject: [PATCH 02/67] Fix typo --- src/Core/assets/js/Components/Messages.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Core/assets/js/Components/Messages.js b/src/Core/assets/js/Components/Messages.js index 91eb63b2c4..c34ac7c571 100644 --- a/src/Core/assets/js/Components/Messages.js +++ b/src/Core/assets/js/Components/Messages.js @@ -4,8 +4,8 @@ import { StringUtil } from './StringUtil' export class Messages { - // add a new message into the que - static add (type, content, optionalClass = '', dismissable = false) { + // add a new message into the queue + static add (type, content, optionalClass = '', dismissable = true) { const uniqueId = 'e' + new Date().getTime().toString() // switch icon type @@ -41,14 +41,15 @@ export class Messages { dismissableClass = '' } - const html = '
' + + const html = '
' + '
' + - '' + '' + ' ' + content + '
' + + '
' + '
' // prepend From 7dc5731a47a90a637fad514d518edb718092250e Mon Sep 17 00:00:00 2001 From: Daphne Slootmans Date: Fri, 22 Sep 2023 15:21:16 +0200 Subject: [PATCH 03/67] Initialise toast present on page load and add progress bar for autohide --- .../assets/Backend/webpack/js/Backend.js | 2 ++ .../webpack/js/Components/InitToasts.js | 15 ++++++++ .../webpack/scss/components/_toasts.scss | 36 +++++++++++++++++++ .../Backend/templates/base/macros.html.twig | 15 +++++--- 4 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 src/Modules/Backend/assets/Backend/webpack/js/Components/InitToasts.js diff --git a/src/Modules/Backend/assets/Backend/webpack/js/Backend.js b/src/Modules/Backend/assets/Backend/webpack/js/Backend.js index c0623ec5f1..52a89f7f1a 100644 --- a/src/Modules/Backend/assets/Backend/webpack/js/Backend.js +++ b/src/Modules/Backend/assets/Backend/webpack/js/Backend.js @@ -24,6 +24,7 @@ import { Session } from './Components/Session' import { Config } from './Components/Config' import { PasswordGenerator } from './Components/PasswordGenerator' import { PasswordStrenghtMeter } from '../../../../../../Core/assets/js/Components/PasswordStrenghtMeter' +import { InitBsToasts } from './Components/InitToasts' window.bootstrap = bootstrap @@ -49,6 +50,7 @@ export class Backend { this.tableSequenceDragAndDrop = new TableSequenceDragAndDrop() this.session = new Session() this.ajaxContentEditable = new AjaxContentEditable(this.locale) + this.initToasts = new InitBsToasts() Backend.initPasswordGenerators() Backend.initPasswordStrenghtMeters() diff --git a/src/Modules/Backend/assets/Backend/webpack/js/Components/InitToasts.js b/src/Modules/Backend/assets/Backend/webpack/js/Components/InitToasts.js new file mode 100644 index 0000000000..db9765b7d4 --- /dev/null +++ b/src/Modules/Backend/assets/Backend/webpack/js/Components/InitToasts.js @@ -0,0 +1,15 @@ +export class InitBsToasts { + constructor () { + this.initToasts() + } + + // init toasts present on page load + initToasts () { + const messageNodes = document.querySelector('[data-messaging-wrapper]').querySelectorAll('.toast') + const messageNodesList = Array.from(messageNodes) + messageNodesList.forEach(message => { + const toast = window.bootstrap.Toast.getOrCreateInstance(message) + if (message.classList.contains('to-show')) toast.show() + }) + } +} diff --git a/src/Modules/Backend/assets/Backend/webpack/scss/components/_toasts.scss b/src/Modules/Backend/assets/Backend/webpack/scss/components/_toasts.scss index 6b06fa46b0..39c31186da 100644 --- a/src/Modules/Backend/assets/Backend/webpack/scss/components/_toasts.scss +++ b/src/Modules/Backend/assets/Backend/webpack/scss/components/_toasts.scss @@ -20,6 +20,12 @@ font-size: $font-size-base * 0.85; } } + + &[data-bs-autohide="false"] { + .toast-progress { + display: none; + } + } } .toast-body { @@ -49,6 +55,22 @@ } } +.toast-progress { + height: 5px; + background-color: $gray-300; + width: 100%; + + &--inner { + width: 0; + background-color: $gray-600; + animation-name: progress; + animation-fill-mode: forwards; + animation-delay: 0.5s; + animation-duration: 5s; + height: 5px; + } +} + .toast-xs .toast-body { padding: 0.3rem 0.5rem 0.3rem 2.2rem; @@ -69,6 +91,10 @@ a { color: shift-color($value, $alert-color-scale); } + + .toast-progress--inner { + background-color: shift-color($value, $alert-border-scale); + } } } @@ -99,3 +125,13 @@ width: calc(#{$toast-max-width} - #{$grid-gutter-width}); } } + +@keyframes progress { + from { + width: 0; + } + + to { + width: 100%; + } +} diff --git a/src/Modules/Backend/templates/base/macros.html.twig b/src/Modules/Backend/templates/base/macros.html.twig index fbd06caf2c..bee0577e97 100644 --- a/src/Modules/Backend/templates/base/macros.html.twig +++ b/src/Modules/Backend/templates/base/macros.html.twig @@ -47,13 +47,13 @@ {% endif %} {% endmacro %} -{% macro alert(type, message, dismissable = false, id, extraAttributes = [], active = true, showIcon = true) %} +{% macro alert(type, message, dismissable = true, id, extraAttributes = [], active = true, showIcon = true) %} {% import _self as macro %} {% set extraAttributes = extraAttributes|merge({'class': 'toast toast-' ~ type ~ (extraAttributes.class is defined and extraAttributes.class|length > 0 ? ' ' ~ extraAttributes.class|default('')) }) %} {% if active %} - {% set extraAttributes = extraAttributes|merge({'class': extraAttributes.class|default('') ~ ' show'}) %} + {% set extraAttributes = extraAttributes|merge({'class': extraAttributes.class|default('') ~ ' to-show'}) %} {% else %} {% set extraAttributes = extraAttributes|merge({'class': extraAttributes.class|default('') ~ ' hide'}) %} {% endif %} @@ -80,10 +80,14 @@ {% set dismissableClass = '' %} {% endif %} - +
{% if dismissable == true %} - +
+
+ +
+
{% endif %} {% if message %} {% if showIcon == true and type == 'danger' %} @@ -104,6 +108,9 @@ {% endif %} {% endif %}
+ {% if autohide %} +
+ {% endif %}
{% endmacro %} From e6c74dc3615b9a880ebdd7bf0d357c004a410e84 Mon Sep 17 00:00:00 2001 From: Daphne Slootmans Date: Fri, 22 Sep 2023 15:59:21 +0200 Subject: [PATCH 04/67] Select toasts in InitToasts.js by data-attribute instead of class --- src/Core/assets/js/Components/Messages.js | 2 +- .../Backend/assets/Backend/webpack/js/Components/InitToasts.js | 2 +- src/Modules/Backend/templates/base/macros.html.twig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Core/assets/js/Components/Messages.js b/src/Core/assets/js/Components/Messages.js index c34ac7c571..7a21448f7b 100644 --- a/src/Core/assets/js/Components/Messages.js +++ b/src/Core/assets/js/Components/Messages.js @@ -41,7 +41,7 @@ export class Messages { dismissableClass = '' } - const html = '
' + + const html = '
' + '
' + ' +
+{% endblock %} From 8f4137eac1b25aec5dea5c703c88afa53c0b71b5 Mon Sep 17 00:00:00 2001 From: Jonas De Keukelaere Date: Wed, 11 Oct 2023 10:20:28 +0100 Subject: [PATCH 12/67] Rename ToggleSecret to TogglePasswordInputType --- src/Modules/Backend/assets/Backend/webpack/js/Backend.js | 4 ++-- .../{ToggleSecret.js => TogglePasswordInputType.js} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/Modules/Backend/assets/Backend/webpack/js/Components/{ToggleSecret.js => TogglePasswordInputType.js} (91%) diff --git a/src/Modules/Backend/assets/Backend/webpack/js/Backend.js b/src/Modules/Backend/assets/Backend/webpack/js/Backend.js index 35ea732e0b..a3b46b0bb8 100644 --- a/src/Modules/Backend/assets/Backend/webpack/js/Backend.js +++ b/src/Modules/Backend/assets/Backend/webpack/js/Backend.js @@ -25,7 +25,7 @@ import { Config } from './Components/Config' import { PasswordGenerator } from './Components/PasswordGenerator' import { PasswordStrenghtMeter } from '../../../../../../Core/assets/js/Components/PasswordStrenghtMeter' import { InitBsToasts } from './Components/InitToasts' -import ToggleSecret from './Components/ToggleSecret' +import TogglePasswordInputType from './Components/TogglePasswordInputType' window.bootstrap = bootstrap @@ -75,7 +75,7 @@ export class Backend { static initToggleSecrets () { $('[data-role="toggle-visibility"]').each((index, element) => { - element.toggleSecret = new ToggleSecret(element) + element.toggleSecret = new TogglePasswordInputType(element) }) } } diff --git a/src/Modules/Backend/assets/Backend/webpack/js/Components/ToggleSecret.js b/src/Modules/Backend/assets/Backend/webpack/js/Components/TogglePasswordInputType.js similarity index 91% rename from src/Modules/Backend/assets/Backend/webpack/js/Components/ToggleSecret.js rename to src/Modules/Backend/assets/Backend/webpack/js/Components/TogglePasswordInputType.js index 753d397a07..e123cb12bb 100644 --- a/src/Modules/Backend/assets/Backend/webpack/js/Components/ToggleSecret.js +++ b/src/Modules/Backend/assets/Backend/webpack/js/Components/TogglePasswordInputType.js @@ -1,4 +1,4 @@ -export default class ToggleSecret { +export default class TogglePasswordInputType { constructor (element) { this._element = element From e8bab89fee74ab2f9f37641b21d12be2068b6a87 Mon Sep 17 00:00:00 2001 From: Jonas De Keukelaere Date: Wed, 11 Oct 2023 10:30:28 +0100 Subject: [PATCH 13/67] Move TogglePasswordInputType to core --- .../assets}/js/Components/TogglePasswordInputType.js | 2 +- src/Modules/Backend/assets/Backend/webpack/js/Backend.js | 6 +++--- .../Frontend/assets/Frontend/webpack/js/_Components.js | 9 +++++++++ src/Modules/Frontend/templates/base/FormLayout.html.twig | 9 +++++++++ 4 files changed, 22 insertions(+), 4 deletions(-) rename src/{Modules/Backend/assets/Backend/webpack => Core/assets}/js/Components/TogglePasswordInputType.js (91%) diff --git a/src/Modules/Backend/assets/Backend/webpack/js/Components/TogglePasswordInputType.js b/src/Core/assets/js/Components/TogglePasswordInputType.js similarity index 91% rename from src/Modules/Backend/assets/Backend/webpack/js/Components/TogglePasswordInputType.js rename to src/Core/assets/js/Components/TogglePasswordInputType.js index e123cb12bb..8578adbbfc 100644 --- a/src/Modules/Backend/assets/Backend/webpack/js/Components/TogglePasswordInputType.js +++ b/src/Core/assets/js/Components/TogglePasswordInputType.js @@ -1,4 +1,4 @@ -export default class TogglePasswordInputType { +export class TogglePasswordInputType { constructor (element) { this._element = element diff --git a/src/Modules/Backend/assets/Backend/webpack/js/Backend.js b/src/Modules/Backend/assets/Backend/webpack/js/Backend.js index a3b46b0bb8..b9cce08018 100644 --- a/src/Modules/Backend/assets/Backend/webpack/js/Backend.js +++ b/src/Modules/Backend/assets/Backend/webpack/js/Backend.js @@ -25,7 +25,7 @@ import { Config } from './Components/Config' import { PasswordGenerator } from './Components/PasswordGenerator' import { PasswordStrenghtMeter } from '../../../../../../Core/assets/js/Components/PasswordStrenghtMeter' import { InitBsToasts } from './Components/InitToasts' -import TogglePasswordInputType from './Components/TogglePasswordInputType' +import { TogglePasswordInputType } from '../../../../../../Core/assets/js/Components/TogglePasswordInputType' window.bootstrap = bootstrap @@ -55,7 +55,7 @@ export class Backend { Backend.initPasswordGenerators() Backend.initPasswordStrenghtMeters() - Backend.initToggleSecrets() + Backend.initTogglePasswordInputType() // do not move, should be run as the last item. if (!Config.isDebug()) this.forms.unloadWarning() @@ -73,7 +73,7 @@ export class Backend { }) } - static initToggleSecrets () { + static initTogglePasswordInputType () { $('[data-role="toggle-visibility"]').each((index, element) => { element.toggleSecret = new TogglePasswordInputType(element) }) diff --git a/src/Modules/Frontend/assets/Frontend/webpack/js/_Components.js b/src/Modules/Frontend/assets/Frontend/webpack/js/_Components.js index f87e689144..e22960f817 100644 --- a/src/Modules/Frontend/assets/Frontend/webpack/js/_Components.js +++ b/src/Modules/Frontend/assets/Frontend/webpack/js/_Components.js @@ -12,6 +12,7 @@ import * as Vue from 'vue/' import VEmbed from './Vue-components/VEmbed' import VShareButtons from './Vue-components/VShareButtons' import { Data } from '../../../../../../Core/assets/js/Components/Data' +import { TogglePasswordInputType } from '../../../../../../Core/assets/js/Components/TogglePasswordInputType' export class Components { initComponents () { @@ -26,6 +27,8 @@ export class Components { this.twitter = new Twitter() this.consentDialog = new ConsentDialog() + Components.initTogglePasswordInputType() + if ($('[data-v-embed]').length) { window.vembed = new Vue({ el: '[data-v-embed]', @@ -40,4 +43,10 @@ export class Components { }) } } + + static initTogglePasswordInputType () { + $('[data-role="toggle-visibility"]').each((index, element) => { + element.toggleSecret = new TogglePasswordInputType(element) + }) + } } diff --git a/src/Modules/Frontend/templates/base/FormLayout.html.twig b/src/Modules/Frontend/templates/base/FormLayout.html.twig index 9ebd8d4e79..4554d873c1 100644 --- a/src/Modules/Frontend/templates/base/FormLayout.html.twig +++ b/src/Modules/Frontend/templates/base/FormLayout.html.twig @@ -305,3 +305,12 @@ {{ form_widget(form.children|last) }}
{% endblock %} + +{% block toggle_password_widget %} +
+ {{ form_widget(form) }} + +
+{% endblock %} From a0e9cfc95ee0ba2cf8d4bec1fd0097ad102c8b35 Mon Sep 17 00:00:00 2001 From: Jonas De Keukelaere Date: Wed, 11 Oct 2023 12:25:50 +0100 Subject: [PATCH 14/67] Better naming for toggle password visibility --- src/Modules/Backend/assets/Backend/webpack/js/Backend.js | 4 ++-- src/Modules/Backend/templates/base/formTheme.html.twig | 2 +- .../Frontend/assets/Frontend/webpack/js/_Components.js | 4 ++-- src/Modules/Frontend/templates/base/FormLayout.html.twig | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Modules/Backend/assets/Backend/webpack/js/Backend.js b/src/Modules/Backend/assets/Backend/webpack/js/Backend.js index b9cce08018..409f9957c4 100644 --- a/src/Modules/Backend/assets/Backend/webpack/js/Backend.js +++ b/src/Modules/Backend/assets/Backend/webpack/js/Backend.js @@ -74,8 +74,8 @@ export class Backend { } static initTogglePasswordInputType () { - $('[data-role="toggle-visibility"]').each((index, element) => { - element.toggleSecret = new TogglePasswordInputType(element) + document.querySelectorAll('[data-role="toggle-password-visibility"]').forEach((element) => { + element.togglePassword = new TogglePasswordInputType(element) }) } } diff --git a/src/Modules/Backend/templates/base/formTheme.html.twig b/src/Modules/Backend/templates/base/formTheme.html.twig index 618dd92ae8..dd66a722d4 100644 --- a/src/Modules/Backend/templates/base/formTheme.html.twig +++ b/src/Modules/Backend/templates/base/formTheme.html.twig @@ -646,7 +646,7 @@ {% block toggle_password_widget %}
{{ form_widget(form) }} -
diff --git a/src/Modules/Frontend/assets/Frontend/webpack/js/_Components.js b/src/Modules/Frontend/assets/Frontend/webpack/js/_Components.js index e22960f817..943c29cf32 100644 --- a/src/Modules/Frontend/assets/Frontend/webpack/js/_Components.js +++ b/src/Modules/Frontend/assets/Frontend/webpack/js/_Components.js @@ -45,8 +45,8 @@ export class Components { } static initTogglePasswordInputType () { - $('[data-role="toggle-visibility"]').each((index, element) => { - element.toggleSecret = new TogglePasswordInputType(element) + document.querySelectorAll('[data-role="toggle-password-visibility"]').forEach((element) => { + element.togglePassword = new TogglePasswordInputType(element) }) } } diff --git a/src/Modules/Frontend/templates/base/FormLayout.html.twig b/src/Modules/Frontend/templates/base/FormLayout.html.twig index 4554d873c1..6d37192511 100644 --- a/src/Modules/Frontend/templates/base/FormLayout.html.twig +++ b/src/Modules/Frontend/templates/base/FormLayout.html.twig @@ -309,7 +309,7 @@ {% block toggle_password_widget %}
{{ form_widget(form) }} -
From 2e56f912ce40c5291105118a80d8d886a9ce3fbe Mon Sep 17 00:00:00 2001 From: Jonas De Keukelaere Date: Wed, 11 Oct 2023 12:26:22 +0100 Subject: [PATCH 15/67] Rewrite to native js Also move logic from button and input to input-group --- .../js/Components/TogglePasswordInputType.js | 17 ++++++++--------- .../Backend/templates/base/formTheme.html.twig | 4 ++-- .../templates/base/FormLayout.html.twig | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Core/assets/js/Components/TogglePasswordInputType.js b/src/Core/assets/js/Components/TogglePasswordInputType.js index 8578adbbfc..bcb63def7c 100644 --- a/src/Core/assets/js/Components/TogglePasswordInputType.js +++ b/src/Core/assets/js/Components/TogglePasswordInputType.js @@ -1,25 +1,24 @@ export class TogglePasswordInputType { constructor (element) { - this._element = element + this._button = element.querySelector('button') + this._input = element.querySelector('input') this.init() } init () { - $(this._element).on('click', () => { + this._button.addEventListener('click', () => { this.toggle() }) } toggle () { - const target = $('#' + $(this._element).data('target')) - - if (target.attr('type') === 'password') { - target.attr('type', 'text') - $(this._element).html('') + if (this._input.type === 'password') { + this._input.type = 'text' + this._button.innerHTML = '' } else { - target.attr('type', 'password') - $(this._element).html('') + this._input.type = 'password' + this._button.innerHTML = '' } } } diff --git a/src/Modules/Backend/templates/base/formTheme.html.twig b/src/Modules/Backend/templates/base/formTheme.html.twig index dd66a722d4..3b32fd181b 100644 --- a/src/Modules/Backend/templates/base/formTheme.html.twig +++ b/src/Modules/Backend/templates/base/formTheme.html.twig @@ -644,9 +644,9 @@ {% endblock %} {% block toggle_password_widget %} -
+
{{ form_widget(form) }} -
diff --git a/src/Modules/Frontend/templates/base/FormLayout.html.twig b/src/Modules/Frontend/templates/base/FormLayout.html.twig index 6d37192511..180eded7a9 100644 --- a/src/Modules/Frontend/templates/base/FormLayout.html.twig +++ b/src/Modules/Frontend/templates/base/FormLayout.html.twig @@ -307,9 +307,9 @@ {% endblock %} {% block toggle_password_widget %} -
+
{{ form_widget(form) }} -
From fd23b5c56d616de23613003aa483fd1a4f5d6d57 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Thu, 28 Sep 2023 18:17:23 +0200 Subject: [PATCH 16/67] Add package to speed up functional testing --- composer.json | 9 +- composer.lock | 134 +++++++++++++++++- config/bundles.php | 1 + .../test/dama_doctrine_test_bundle.yaml | 4 + config/packages/test/doctrine.yaml | 4 - config/packages/test/framework.yaml | 3 +- .../dama_doctrine_test_bundle.yaml | 4 + config/packages/test_install/framework.yaml | 3 +- config/services_test.yaml | 3 - config/services_test_install.yaml | 3 - docker-compose.yml | 1 - phpunit.xml.dist | 3 + symfony.lock | 12 ++ var/docker/db/scripts/00_init_test_db.sh | 3 - 14 files changed, 164 insertions(+), 23 deletions(-) create mode 100644 config/packages/test/dama_doctrine_test_bundle.yaml delete mode 100644 config/packages/test/doctrine.yaml create mode 100644 config/packages/test_install/dama_doctrine_test_bundle.yaml diff --git a/composer.json b/composer.json index 9756d5f65f..a411b666cf 100644 --- a/composer.json +++ b/composer.json @@ -81,7 +81,9 @@ "doctrine/doctrine-fixtures-bundle": "^3.4", "symfony/stopwatch": "^6.0", "symfony/web-profiler-bundle": "^6.0", - "symfony/browser-kit": "^6.0" + "symfony/browser-kit": "^6.0", + "dama/doctrine-test-bundle": "^7.2", + "symfony/css-selector": "6.*" }, "config": { "bin-dir": "bin" @@ -111,11 +113,6 @@ "**/Test/" ] }, - "autoload-dev": { - "psr-4": { - "ForkCMS\\Tests\\": "tests/" - } - }, "conflict": { "symfony/symfony": "*" }, diff --git a/composer.lock b/composer.lock index 7efeccce04..dbe7764e6e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a13c615208a28a7f2bf1773708121553", + "content-hash": "8b86fe9af31222f133323600f3b5b99c", "packages": [ { "name": "beberlei/assert", @@ -9878,6 +9878,73 @@ } ], "packages-dev": [ + { + "name": "dama/doctrine-test-bundle", + "version": "v7.2.1", + "source": { + "type": "git", + "url": "https://github.com/dmaicher/doctrine-test-bundle.git", + "reference": "175b47153609a369117d97d36049b8a8c3b69dc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/175b47153609a369117d97d36049b8a8c3b69dc1", + "reference": "175b47153609a369117d97d36049b8a8c3b69dc1", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^3.3", + "doctrine/doctrine-bundle": "^2.2.2", + "ext-json": "*", + "php": "^7.3 || ^8.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^5.4 || ^6.0", + "symfony/framework-bundle": "^5.4 || ^6.0" + }, + "require-dev": { + "behat/behat": "^3.0", + "doctrine/cache": "^1.12", + "phpstan/phpstan": "^1.2", + "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0", + "symfony/phpunit-bridge": "^6.0", + "symfony/process": "^5.4 || ^6.0", + "symfony/yaml": "^5.4 || ^6.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "DAMA\\DoctrineTestBundle\\": "src/DAMA/DoctrineTestBundle" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Maicher", + "email": "mail@dmaicher.de" + } + ], + "description": "Symfony bundle to isolate doctrine database tests and improve test performance", + "keywords": [ + "doctrine", + "isolation", + "performance", + "symfony", + "tests" + ], + "support": { + "issues": "https://github.com/dmaicher/doctrine-test-bundle/issues", + "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v7.2.1" + }, + "time": "2023-02-07T10:02:27+00:00" + }, { "name": "doctrine/data-fixtures", "version": "1.6.6", @@ -10428,6 +10495,71 @@ ], "time": "2023-07-06T06:56:43+00:00" }, + { + "name": "symfony/css-selector", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "883d961421ab1709877c10ac99451632a3d6fa57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/883d961421ab1709877c10ac99451632a3d6fa57", + "reference": "883d961421ab1709877c10ac99451632a3d6fa57", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-12T16:00:22+00:00" + }, { "name": "symfony/dom-crawler", "version": "v6.3.1", diff --git a/config/bundles.php b/config/bundles.php index 8b76f913ae..a5d72480f2 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -17,4 +17,5 @@ Knp\Bundle\PaginatorBundle\KnpPaginatorBundle::class => ['all' => true], Pageon\DoctrineDataGridBundle\PageonDoctrineDataGridBundle::class => ['all' => true], Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true], + DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true, 'test_install' => true], ]; diff --git a/config/packages/test/dama_doctrine_test_bundle.yaml b/config/packages/test/dama_doctrine_test_bundle.yaml new file mode 100644 index 0000000000..80b0091170 --- /dev/null +++ b/config/packages/test/dama_doctrine_test_bundle.yaml @@ -0,0 +1,4 @@ +dama_doctrine_test: + enable_static_connection: true + enable_static_meta_data_cache: true + enable_static_query_cache: true diff --git a/config/packages/test/doctrine.yaml b/config/packages/test/doctrine.yaml deleted file mode 100644 index 34c2ebccaf..0000000000 --- a/config/packages/test/doctrine.yaml +++ /dev/null @@ -1,4 +0,0 @@ -doctrine: - dbal: - # "TEST_TOKEN" is typically set by ParaTest - dbname_suffix: '_test%env(default::TEST_TOKEN)%' diff --git a/config/packages/test/framework.yaml b/config/packages/test/framework.yaml index 4db3f6a0e1..9475d9f0d6 100644 --- a/config/packages/test/framework.yaml +++ b/config/packages/test/framework.yaml @@ -1,4 +1,5 @@ framework: test: true session: - storage_factory_id: ForkCMS\Core\Tests\MockArraySessionStorageFactory + storage_factory_id: session.storage.factory.mock_file + save_path: '' diff --git a/config/packages/test_install/dama_doctrine_test_bundle.yaml b/config/packages/test_install/dama_doctrine_test_bundle.yaml new file mode 100644 index 0000000000..4735a92845 --- /dev/null +++ b/config/packages/test_install/dama_doctrine_test_bundle.yaml @@ -0,0 +1,4 @@ +dama_doctrine_test: + enable_static_connection: false + enable_static_meta_data_cache: true + enable_static_query_cache: true diff --git a/config/packages/test_install/framework.yaml b/config/packages/test_install/framework.yaml index e797c674fc..9166293bd4 100644 --- a/config/packages/test_install/framework.yaml +++ b/config/packages/test_install/framework.yaml @@ -9,7 +9,8 @@ framework: # Enables session support. Note that the session will ONLY be started if you read or write from it. # Remove or comment this section to explicitly disable session support. session: - storage_factory_id: ForkCMS\Core\Tests\MockArraySessionStorageFactory + storage_factory_id: session.storage.factory.mock_file + save_path: '' #esi: true #fragments: true diff --git a/config/services_test.yaml b/config/services_test.yaml index 51d13bb8ce..165de0c87f 100644 --- a/config/services_test.yaml +++ b/config/services_test.yaml @@ -2,6 +2,3 @@ parameters: site.multilanguage: true site.default_language: en fork.is_installed: true - -services: - ForkCMS\Core\Tests\MockArraySessionStorageFactory: diff --git a/config/services_test_install.yaml b/config/services_test_install.yaml index f13bb2447e..64b0b8c9b4 100644 --- a/config/services_test_install.yaml +++ b/config/services_test_install.yaml @@ -2,6 +2,3 @@ parameters: site.multilanguage: true site.default_language: en fork.is_installed: false - -services: - ForkCMS\Core\Tests\MockArraySessionStorageFactory: diff --git a/docker-compose.yml b/docker-compose.yml index 28e60e291a..cbd9df0ccc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,7 +37,6 @@ services: # - db-data:/var/lib/mysql:rw # By default, use a bind-mounted host directory instead. It's harder to accidentally lose all your db data! - ./var/docker/db/data6:/var/lib/mysql:rw - - ./tests/data/test_db.sql:/test_db.sql:ro - ./var/docker/db/scripts:/docker-entrypoint-initdb.d:ro volumes: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 99aa3ce6b5..48cb70383d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -52,4 +52,7 @@ + + + diff --git a/symfony.lock b/symfony.lock index f4611d1f3e..5b6d8f717b 100644 --- a/symfony.lock +++ b/symfony.lock @@ -17,6 +17,18 @@ "composer/semver": { "version": "3.2.7" }, + "dama/doctrine-test-bundle": { + "version": "7.2", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "4.0", + "ref": "2c920f73a217f30bd4a37833c91071f4d3dc1ecd" + }, + "files": [ + "config/packages/test/dama_doctrine_test_bundle.yaml" + ] + }, "doctrine/annotations": { "version": "1.13", "recipe": { diff --git a/var/docker/db/scripts/00_init_test_db.sh b/var/docker/db/scripts/00_init_test_db.sh index 66a4e98d15..31ec95470d 100755 --- a/var/docker/db/scripts/00_init_test_db.sh +++ b/var/docker/db/scripts/00_init_test_db.sh @@ -8,9 +8,6 @@ MYSQL_DATABASE_TEST="${MYSQL_DATABASE}_test" echo "Creating database ${MYSQL_DATABASE_TEST}..." mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" -e "create database ${MYSQL_DATABASE_TEST};" -echo "Importing fixtures into database ${MYSQL_DATABASE_TEST}..." -mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" ${MYSQL_DATABASE_TEST} < ../test_db.sql - echo "Granting all privileges for user $MYSQL_USER on database ${MYSQL_DATABASE_TEST}..." mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" -e "GRANT ALL ON \`${MYSQL_DATABASE_TEST}\`.* TO '$MYSQL_USER'@'%' ;" From a7666b04793fb144c8366e7da6587f7447b9c24b Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Thu, 28 Sep 2023 18:19:34 +0200 Subject: [PATCH 17/67] Create test database if needed or requested If there is no test database yet a new one will be created, this will also happen if the environment variable TEST_DATABASE is set to fresh instead of cached. After creating the database fork will be installed in the test database --- .env.test | 1 + phpunit.xml.dist | 10 ++++-- src/Core/tests/bootstrap.php | 66 ++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 src/Core/tests/bootstrap.php diff --git a/.env.test b/.env.test index dbc6d53f02..ece598902d 100644 --- a/.env.test +++ b/.env.test @@ -1 +1,2 @@ FORK_DATABASE_NAME=${FORK_DATABASE_NAME}_test +TEST_DATABASE=cached diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 48cb70383d..e8b0afe0a7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -2,7 +2,7 @@ src/Modules/*/tests src/Modules/Installer src/Modules/*/tests/Controller - src/Modules/*/tests/Actions + src/Modules/*/tests/Backend + src/Modules/*/tests/Frontend + src/Modules/*/tests/Console src/Modules/*/tests/Controller - src/Modules/*/tests/Actions + src/Modules/*/tests/Backend + src/Modules/*/tests/Frontend + src/Modules/*/tests/Console src/Modules/Installer src/Core/tests/Installer diff --git a/src/Core/tests/bootstrap.php b/src/Core/tests/bootstrap.php new file mode 100644 index 0000000000..4d35f5f2a0 --- /dev/null +++ b/src/Core/tests/bootstrap.php @@ -0,0 +1,66 @@ +loadEnv(__DIR__ . '/../../../.env', null, 'test', []); + +function installTest() +{ + $kernel = new Kernel('test_install', true); + $kernel->boot(); + + $application = new Application($kernel); + $application->setAutoExit(false); + + $application->run( + new ArrayInput([ + 'command' => 'cache:clear', + '--no-warmup' => '1', + '--env' => 'test_install', + ]) + ); + $application->run( + new ArrayInput([ + 'command' => 'cache:clear', + '--no-warmup' => '1', + '--env' => 'test', + ]) + ); + + fwrite(STDERR, print_r('Create test database', true)); + $application->run( + new ArrayInput([ + 'command' => 'doctrine:database:drop', + '--if-exists' => '1', + '--force' => '1', + ]) + ); + + $application->run( + new ArrayInput([ + 'command' => 'doctrine:database:create', + ]) + ); + + $application->run( + new ArrayInput([ + 'command' => 'forkcms:installer:install', + ]) + ); + + $kernel->shutdown(); +} + +if ((($_ENV['TEST_DATABASE'] ?? 'cached') === 'fresh') || ForkConnection::get('test_instal')->exec('select 1') === false) { + installTest(); +} + +return $loader; From 2812f5cb1e02156dc83631ff7f410633d78d85d8 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 29 Sep 2023 09:15:52 +0200 Subject: [PATCH 18/67] Implement BackendWebTestCase --- .../Backend/Core/Tests/BackendWebTestCase.php | 57 ---- src.fork5/Common/WebTestCase.php | 138 --------- src/Core/tests/WebTestCase.php | 262 ++++++++++++++++++ .../Backend/tests/BackendWebTestCase.php | 87 ++++++ 4 files changed, 349 insertions(+), 195 deletions(-) delete mode 100644 src.fork5/Backend/Core/Tests/BackendWebTestCase.php create mode 100644 src/Core/tests/WebTestCase.php create mode 100644 src/Modules/Backend/tests/BackendWebTestCase.php diff --git a/src.fork5/Backend/Core/Tests/BackendWebTestCase.php b/src.fork5/Backend/Core/Tests/BackendWebTestCase.php deleted file mode 100644 index 3f614e515f..0000000000 --- a/src.fork5/Backend/Core/Tests/BackendWebTestCase.php +++ /dev/null @@ -1,57 +0,0 @@ -getProvidedData()[0] ?? null; - if ($client instanceof Client) { - $this->logout($client); - } - } - - protected function setUp(): void - { - parent::setUp(); - - if (!defined('APPLICATION')) { - define('APPLICATION', 'Backend'); - } - } - - protected function assertAuthenticationIsNeeded(Client $client, string $url, string $method = 'GET'): void - { - // make sure we aren't logged in with the client - $this->logout($client); - - self::assertGetsRedirected( - $client, - $url, - '/private/en/authentication?querystring=' . rawurlencode($url), - $method - ); - } - - protected function appendCsrfTokenToUrl(Client $client, string $url): string - { - $connectionSymbol = (strpos($url, '?') !== false) ? '&' : '?'; - - $session = $client->getContainer()->get('session'); - - if (!$session instanceof Session) { - // no session so no csrf token - return $url; - } - - return $url . $connectionSymbol . 'token=' . $session->get('csrf_token'); - } -} diff --git a/src.fork5/Common/WebTestCase.php b/src.fork5/Common/WebTestCase.php index 344879a458..a13cab1fed 100644 --- a/src.fork5/Common/WebTestCase.php +++ b/src.fork5/Common/WebTestCase.php @@ -334,144 +334,6 @@ protected function logout(Client $client): void Authentication::tearDown(); } - protected static function assertGetsRedirected( - Client $client, - string $initialUrl, - string $expectedUrl, - string $requestMethod = 'GET', - array $requestParameters = [], - int $maxRedirects = null, - int $expectedHttpResponseCode = Response::HTTP_OK - ): void { - $maxRedirects !== null ? $client->setMaxRedirects($maxRedirects) : $client->followRedirects(); - - $client->request($requestMethod, $initialUrl, $requestParameters); - - $response = $client->getResponse(); - self::assertNotNull($response, 'No response received'); - - self::assertCurrentUrlContains($client, $expectedUrl); - self::assertEquals($expectedHttpResponseCode, $response->getStatusCode()); - } - - /** - * @param Client $client - * @param string $url - * @param string[] $expectedContent - * @param int $httpStatusCode - * @param string $requestMethod - * @param array $requestParameters - */ - protected static function assertPageLoadedCorrectly( - Client $client, - string $url, - array $expectedContent, - int $httpStatusCode = Response::HTTP_OK, - string $requestMethod = 'GET', - array $requestParameters = [] - ): void { - self::assertHttpStatusCode($client, $url, $httpStatusCode, $requestMethod, $requestParameters); - $response = $client->getResponse(); - - self::assertNotNull($response, 'No response received'); - self::assertResponseHasContent($response, ...$expectedContent); - } - - /** - * @param Client $client - * @param string $linkText - * @param string[] $expectedContent - * @param int $httpStatusCode - * @param string $requestMethod - * @param array $requestParameters - */ - protected static function assertClickOnLink( - Client $client, - string $linkText, - array $expectedContent, - int $httpStatusCode = Response::HTTP_OK, - string $requestMethod = 'GET', - array $requestParameters = [] - ): void { - self::assertPageLoadedCorrectly( - $client, - $client->getCrawler()->selectLink($linkText)->link()->getUri(), - $expectedContent, - $httpStatusCode, - $requestMethod, - $requestParameters - ); - } - - protected static function assertResponseHasContent(Response $response, string ...$content): void - { - foreach ($content as $expectedContent) { - self::assertStringContainsString($expectedContent, $response->getContent()); - } - } - - protected static function assertResponseDoesNotHaveContent(Response $response, string ...$content): void - { - foreach ($content as $notExpectedContent) { - self::assertStringNotContainsStringIgnoringCase($notExpectedContent, $response->getContent()); - } - } - - protected static function assertCurrentUrlContains(Client $client, string ...$partialUrls): void - { - foreach ($partialUrls as $partialUrl) { - self::assertStringContainsString($partialUrl, $client->getHistory()->current()->getUri()); - } - } - - protected static function assertCurrentUrlEndsWith(Client $client, string $partialUrl): void - { - self::assertStringEndsWith($partialUrl, $client->getHistory()->current()->getUri()); - } - - protected static function assertHttpStatusCode( - Client $client, - string $url, - int $httpStatusCode, - string $requestMethod = 'GET', - array $requestParameters = [] - ): void { - $client->request($requestMethod, $url, $requestParameters); - $response = $client->getResponse(); - self::assertNotNull($response, 'No response received'); - self::assertEquals($httpStatusCode, $response->getStatusCode()); - } - - protected static function assertHttpStatusCode200( - Client $client, - string $url, - string $requestMethod = 'GET', - array $requestParameters = [] - ): void { - self::assertHttpStatusCode( - $client, - $url, - Response::HTTP_OK, - $requestMethod, - $requestParameters - ); - } - - protected static function assertHttpStatusCode404( - Client $client, - string $url, - string $requestMethod = 'GET', - array $requestParameters = [] - ): void { - self::assertHttpStatusCode( - $client, - $url, - Response::HTTP_NOT_FOUND, - $requestMethod, - $requestParameters - ); - } - protected function getFormForSubmitButton(Client $client, string $buttonText, string $filterSelector = null): Form { $crawler = $client->getCrawler(); diff --git a/src/Core/tests/WebTestCase.php b/src/Core/tests/WebTestCase.php new file mode 100644 index 0000000000..e7dab0ba7c --- /dev/null +++ b/src/Core/tests/WebTestCase.php @@ -0,0 +1,262 @@ + $requestParameters + */ + final protected static function assertPageLoadedCorrectly( + string $url, + array $expectedContent = [], + int $httpStatusCode = Response::HTTP_OK, + string $requestMethod = Request::METHOD_GET, + array $requestParameters = [] + ): void { + static::assertHttpStatusCode($url, $httpStatusCode, $requestMethod, $requestParameters); + static::assertPageHasContent(...$expectedContent); + } + + final protected static function assertHasLink(string $text, string $url): void + { + static::assertMinCount( + 1, + static::getCrawler()->filter('a:contains("' . $text . '")[href="' . $url . '"]') + ); + } + + public static function assertMinCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void + { + static::assertGreaterThanOrEqual($expectedCount, count($haystack), $message); + } + + public static function assertMaxCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void + { + static::assertLessThanOrEqual($expectedCount, $haystack, $message); + } + + /** + * @param string[] $expectedContent + * @param array $requestParameters + */ + final protected static function assertClickOnLink( + string $linkText, + array $expectedContent, + int $httpStatusCode = Response::HTTP_OK, + string $requestMethod = Request::METHOD_GET, + array $requestParameters = [] + ): void { + static::assertPageLoadedCorrectly( + static::getClient()->getCrawler()->selectLink($linkText)->link()->getUri(), + $expectedContent, + $httpStatusCode, + $requestMethod, + $requestParameters + ); + } + + final protected static function assertPageHasContent(string ...$content): void + { + $response = static::getResponse(); + + foreach ($content as $expectedContent) { + static::assertStringContainsString($expectedContent, $response->getContent()); + } + } + + final protected static function assertResponseDoesNotHaveContent(string ...$content): void + { + $response = static::getResponse(); + + foreach ($content as $notExpectedContent) { + static::assertStringNotContainsStringIgnoringCase($notExpectedContent, $response->getContent()); + } + } + + final protected static function assertCurrentUrlContains(string ...$partialUrls): void + { + $currentUrl = static::getClient()->getHistory()->current()->getUri(); + foreach ($partialUrls as $partialUrl) { + static::assertStringContainsString($partialUrl, $currentUrl); + } + } + + final protected static function assertCurrentUrlEndsWith(string $partialUrl): void + { + static::assertStringEndsWith($partialUrl, static::getClient()->getHistory()->current()->getUri()); + } + + final protected static function assertRedirect( + string $initialUrl, + string $expectedUrl, + string $requestMethod = Request::METHOD_GET, + array $requestParameters = [], + int $maxRedirects = null, + int $expectedHttpResponseCode = Response::HTTP_OK + ): void { + $client = static::getClient(); + $originalMaxRedirects = $client->getMaxRedirects(); + $maxRedirects !== null ? $client->setMaxRedirects($maxRedirects) : $client->followRedirects(); + try { + static::request($requestMethod, $initialUrl, $requestParameters); + } finally { + $client->setMaxRedirects($originalMaxRedirects); + } + + static::assertResponseStatusCodeSame($expectedHttpResponseCode); + static::assertCurrentUrlContains($expectedUrl); + } + + /** @param array $requestParameters */ + final protected static function assertHttpStatusCode( + string $url, + int $httpStatusCode, + string $requestMethod = Request::METHOD_GET, + array $requestParameters = [] + ): void { + static::request($requestMethod, $url, $requestParameters); + static::assertResponseStatusCodeSame($httpStatusCode); + } + + /** @param array $requestParameters */ + final protected static function assertHttpStatusCode200( + string $url, + string $requestMethod = Request::METHOD_GET, + array $requestParameters = [] + ): void { + static::assertHttpStatusCode( + $url, + Response::HTTP_OK, + $requestMethod, + $requestParameters + ); + } + + /** @param array $requestParameters */ + final protected static function assertHttpStatusCode404( + string $url, + string $requestMethod = Request::METHOD_GET, + array $requestParameters = [] + ): void { + static::assertHttpStatusCode( + $url, + Response::HTTP_NOT_FOUND, + $requestMethod, + $requestParameters + ); + } + + final protected static function getClient(KernelBrowser $newClient = null): KernelBrowser + { + static $client; + + if (0 < func_num_args()) { + // avoid having to add null to the return type when clearing the client + $oldClient = $client; + if ($oldClient === null && $newClient === null) { + static::fail( + 'You are trying to clear the client, but no client has been set yet. ' + . 'Did you forget to call "' . __CLASS__ . '::createClient()"?' + ); + } + $client = $newClient; + + return $client ?? $oldClient; + } + + if (!$client instanceof KernelBrowser) { + static::fail( + sprintf( + 'A client must be set to make assertions on it. Did you forget to call "%s::createClient()"?', + __CLASS__ + ) + ); + } + + return $client; + } + + final protected static function getResponse(): Response + { + if (!$response = static::getClient()->getResponse()) { + static::fail( + 'A client must have an HTTP Response to make assertions. Did you forget to make an HTTP request?' + ); + } + + return $response; + } + + /** + * @param array $parameters + * @param array $files + * @param array $server + */ + final protected static function request( + string $method, + string $uri, + array $parameters = [], + array $files = [], + array $server = [], + string $content = null, + bool $changeHistory = true + ): Crawler { + return static::getClient()->request( + $method, + $uri, + $parameters, + $files, + $server, + $content, + $changeHistory + ); + } + + final protected static function getRequest(): Request + { + if (!$request = static::getClient()->getRequest()) { + static::fail( + 'A client must have an HTTP Request to make assertions. Did you forget to make an HTTP request?' + ); + } + + return $request; + } + + final protected static function getCrawler(): Crawler + { + if (!$crawler = static::getClient()->getCrawler()) { + static::fail('A client must have a crawler to make assertions. Did you forget to make an HTTP request?'); + } + + return $crawler; + } + + final protected static function createClient(array $options = [], array $server = []): KernelBrowser + { + return static::getClient(parent::createClient($options, $server)); + } + + protected function tearDown(): void + { + parent::tearDown(); + static::getClient(null); + } +} diff --git a/src/Modules/Backend/tests/BackendWebTestCase.php b/src/Modules/Backend/tests/BackendWebTestCase.php new file mode 100644 index 0000000000..eb8970724e --- /dev/null +++ b/src/Modules/Backend/tests/BackendWebTestCase.php @@ -0,0 +1,87 @@ +get(UserRepository::class); + } catch (\Throwable) { + static::fail('User repository not found.'); + } + + $user = $userRespository->findOneBy(['email' => $email]); + static::assertNotNull($user, 'User with email "' . $email . '" not found.'); + static::getClient()->loginUser($user, 'backend'); + static::request(Request::METHOD_GET, static::TEST_URL); + + return $user; + } + + final protected static function filterDataGrid(string $filter, string $value): void + { + $filterForm = static::getCrawler() + ->filter('#content .fork-data-grid table input[name=filterField][value="' . $filter . '"]') + ->closest('form') + ?->form(['filterValue' => $value]); + self::assertNotNull($filterForm, 'Filter ' . $filter . ' not found in data grid with value ' . $value . '.'); + + static::getClient()->submit($filterForm); + } + + final protected static function assertAuthenticationIsNeeded( + string $url, + string $method = Request::METHOD_GET + ): void { + static::assertRedirect($url, '/private/en/backend/authentication-login', $method); + } + + final protected static function assertDataGridHasLink(string $text, ?string $url = null): void + { + $crawler = static::getCrawler() + ->filter('#content .fork-data-grid table') + ->selectLink($text); + + self::assertMinCount(1, $crawler, 'Link "' . $text . '" not found in data grid.'); + + if ($url !== null) { + self::assertSame($url, $crawler->attr('href'), 'Link "' . $text . '" has wrong URL.'); + } + } + + final protected static function assertDataGridNotHasLink(string $text): void + { + $crawler = static::getCrawler() + ->filter('#content .fork-data-grid table') + ->selectLink($text); + + self::assertCount(0, $crawler, 'Found link "' . $text . '" in data grid, but it should not be there.'); + } + + final protected static function assertDataGridIsEmpty(): void + { + static::assertSame( + 'No results found', + static::getCrawler() + ->filter('#content .fork-data-grid + .empty-state') + ->text(), + 'Data grid is not empty.' + ); + } + + final protected static function assertDataGridNotEmpty(): void + { + static::assertCount( + 0, + static::getCrawler()->filter('#content .fork-data-grid + .empty-state'), + 'Data grid is empty' + ); + } +} From 82b979f2ff343adbdb0cef122063056528e4024c Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 29 Sep 2023 10:05:41 +0200 Subject: [PATCH 19/67] Fix wrong mapping in docker --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index cbd9df0ccc..7f5dd98baa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: volumes: - .:/var/www/html:cached - fork-cms6-var:/var/www/html/var - - ./src/Frontend/Files:/var/www/html/src/Frontend/Files:cached + - ./public:/var/www/html/public:cached db6: image: "mysql:5.7" From 619b0699f08ae26cc8bbb1c3e3689a99486f1e10 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 29 Sep 2023 10:06:31 +0200 Subject: [PATCH 20/67] Make it easy to append fixtures --- config/bundles.php | 2 +- src/Core/tests/WebTestCase.php | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/config/bundles.php b/config/bundles.php index a5d72480f2..3da1319b79 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -7,7 +7,7 @@ Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], - Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], + Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['test' => true, 'test_install' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true, 'test' => true, 'install' => true, 'test_install' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true, 'test_install' => true, 'install' => true], Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], diff --git a/src/Core/tests/WebTestCase.php b/src/Core/tests/WebTestCase.php index e7dab0ba7c..e0976123fb 100644 --- a/src/Core/tests/WebTestCase.php +++ b/src/Core/tests/WebTestCase.php @@ -3,6 +3,8 @@ namespace ForkCMS\Core\tests; use Countable; +use Doctrine\Common\DataFixtures\Executor\ORMExecutor; +use Doctrine\Common\DataFixtures\FixtureInterface; use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\HttpFoundation\Request; @@ -17,6 +19,24 @@ protected function setUp(): void { parent::setUp(); static::createClient(); + static::loadFixture(...static::getClassFixtures()); + } + + /** @return FixtureInterface[] */ + protected static function getClassFixtures(): array + { + return []; + } + + final protected static function loadFixture(FixtureInterface ...$fixture): void + { + static $executor; + + if ($executor === null) { + $executor = new ORMExecutor(self::getContainer()->get('doctrine.orm.entity_manager')); + } + + $executor->execute($fixture, true); } /** From 1d1d2f97ff6be92ba9b9e601fed3f275b4344328 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 29 Sep 2023 12:52:42 +0200 Subject: [PATCH 21/67] Add fixtures for Backend user and user group --- config/bundles.php | 2 +- .../Backend/DataFixtures/UserFixture.php | 55 +++++++++++++++++++ .../Backend/DataFixtures/UserGroupFixture.php | 25 +++++++++ src/Modules/Backend/config/services.yaml | 3 + 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/Modules/Backend/DataFixtures/UserFixture.php create mode 100644 src/Modules/Backend/DataFixtures/UserGroupFixture.php diff --git a/config/bundles.php b/config/bundles.php index 3da1319b79..da1c16e7b1 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -7,7 +7,7 @@ Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], - Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['test' => true, 'test_install' => true], + Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true, 'test_install' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true, 'test' => true, 'install' => true, 'test_install' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true, 'test_install' => true, 'install' => true], Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], diff --git a/src/Modules/Backend/DataFixtures/UserFixture.php b/src/Modules/Backend/DataFixtures/UserFixture.php new file mode 100644 index 0000000000..0f2748cc4c --- /dev/null +++ b/src/Modules/Backend/DataFixtures/UserFixture.php @@ -0,0 +1,55 @@ +email = 'super-admin@fork-cms.com'; + $createSuperAdmin->displayName = 'Super admin'; + $createSuperAdmin->superAdmin = true; + $createSuperAdmin->plainTextPassword = self::PASSWORD; + $createSuperAdmin->userGroups->add($this->getReference(UserGroupFixture::ONLY_DASHBOARD_REFERENCE)); + $superAdmin = User::fromDataTransferObject($createSuperAdmin); + $superAdmin->hashPassword($this->passwordHasher); + $manager->persist($superAdmin); + $this->setReference(self::SUPER_ADMIN_REFERENCE, $superAdmin); + + $createUser = new CreateUser(); + $createUser->email = 'user@fork-cms.com'; + $createUser->displayName = 'Normal user'; + $createUser->superAdmin = false; + $createUser->plainTextPassword = self::PASSWORD; + $createUser->userGroups->add($this->getReference(UserGroupFixture::ONLY_DASHBOARD_REFERENCE)); + $user = User::fromDataTransferObject($createUser); + $user->hashPassword($this->passwordHasher); + $manager->persist($user); + $this->setReference(self::USER_REFERENCE, $user); + + $manager->flush(); + } + + public function getDependencies(): array + { + return [ + UserGroupFixture::class, + ]; + } +} diff --git a/src/Modules/Backend/DataFixtures/UserGroupFixture.php b/src/Modules/Backend/DataFixtures/UserGroupFixture.php new file mode 100644 index 0000000000..9e8781f194 --- /dev/null +++ b/src/Modules/Backend/DataFixtures/UserGroupFixture.php @@ -0,0 +1,25 @@ +name = 'Super admin'; + $createUserGroup->actions = [Dashboard::class]; + $userGroup = UserGroup::fromDataTransferObject($createUserGroup); + $this->setReference(self::ONLY_DASHBOARD_REFERENCE, $userGroup); + $manager->persist($userGroup); + $manager->flush(); + } +} diff --git a/src/Modules/Backend/config/services.yaml b/src/Modules/Backend/config/services.yaml index 902b5d3501..9203b210eb 100644 --- a/src/Modules/Backend/config/services.yaml +++ b/src/Modules/Backend/config/services.yaml @@ -28,3 +28,6 @@ services: ForkCMS\Modules\Backend\Console\: resource: '../Console/*' + + ForkCMS\Modules\Backend\DataFixtures\: + resource: '../DataFixtures/*' From 86a76ebd624c350c5ba8e722d328314a62ac4737 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 29 Sep 2023 12:52:59 +0200 Subject: [PATCH 22/67] Better error messages for asserts --- src/Core/tests/WebTestCase.php | 2 +- src/Modules/Backend/tests/BackendWebTestCase.php | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Core/tests/WebTestCase.php b/src/Core/tests/WebTestCase.php index e0976123fb..424c477f02 100644 --- a/src/Core/tests/WebTestCase.php +++ b/src/Core/tests/WebTestCase.php @@ -97,7 +97,7 @@ final protected static function assertPageHasContent(string ...$content): void $response = static::getResponse(); foreach ($content as $expectedContent) { - static::assertStringContainsString($expectedContent, $response->getContent()); + static::assertStringContainsString($expectedContent, $response->getContent(), 'Page does not contain "' . $expectedContent . '".'); } } diff --git a/src/Modules/Backend/tests/BackendWebTestCase.php b/src/Modules/Backend/tests/BackendWebTestCase.php index eb8970724e..f85bb06ebb 100644 --- a/src/Modules/Backend/tests/BackendWebTestCase.php +++ b/src/Modules/Backend/tests/BackendWebTestCase.php @@ -67,11 +67,9 @@ final protected static function assertDataGridNotHasLink(string $text): void final protected static function assertDataGridIsEmpty(): void { - static::assertSame( - 'No results found', - static::getCrawler() - ->filter('#content .fork-data-grid + .empty-state') - ->text(), + static::assertCount( + 1, + static::getCrawler()->filter('#content .fork-data-grid + .empty-state'), 'Data grid is not empty.' ); } From 3e73c1b64a4bdf6e12796a8c4632c55dd73dd849 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 29 Sep 2023 12:53:44 +0200 Subject: [PATCH 23/67] Automatically group fixtures by module --- src/Modules/Extensions/tests/ForkFixture.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/Modules/Extensions/tests/ForkFixture.php diff --git a/src/Modules/Extensions/tests/ForkFixture.php b/src/Modules/Extensions/tests/ForkFixture.php new file mode 100644 index 0000000000..691e91f659 --- /dev/null +++ b/src/Modules/Extensions/tests/ForkFixture.php @@ -0,0 +1,15 @@ +getName()]; + } +} From 9e64dff93cbc6440126e52846bcc779c88c27206 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 29 Sep 2023 12:54:09 +0200 Subject: [PATCH 24/67] Add tests for Backend UserIndex action --- .../tests/Backend/Actions/UserIndexTest.php | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php diff --git a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php new file mode 100644 index 0000000000..01f7423cb6 --- /dev/null +++ b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php @@ -0,0 +1,59 @@ +getEmail(), '/private/en/backend/user-edit/' . $user->getId()); + self::assertDataGridHasLink('user@fork-cms.com'); + self::assertDataGridNotHasLink('demo@fork-cms.com'); + self::filterDataGrid('User.email', 'user@fork-cms.com'); + self::assertDataGridHasLink('user@fork-cms.com'); + self::assertDataGridNotHasLink('demo@fork-cms.com'); + self::assertDataGridNotHasLink($user->getEmail()); + self::filterDataGrid('User.email', $user->getEmail()); + self::filterDataGrid('User.displayName', 'demo'); + self::assertDataGridIsEmpty(); + self::filterDataGrid('User.displayName', $user->getDisplayName()); + self::assertDataGridHasLink($user->getEmail(), '/private/en/backend/user-edit/' . $user->getId()); + + self::assertHttpStatusCode404('test', 'bob', []); + } + + protected static function getClassFixtures(): array + { + return [ + new UserGroupFixture(), + new UserFixture(self::getContainer()->get(UserPasswordHasherInterface::class)), + ]; + } +} From 87cb5522255fd6f258ea59a9d06a13249d81b8c4 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 29 Sep 2023 13:24:56 +0200 Subject: [PATCH 25/67] Use the enviroment variables to install fork for the phpstan test on github --- .github/workflows/run-tests.yml | 33 +++++++++++++++++++-- config/packages/test/framework.yaml | 2 +- config/packages/test_install/framework.yaml | 2 +- src/Core/tests/bootstrap.php | 30 +++++++++---------- src/bootstrap.php | 2 +- 5 files changed, 48 insertions(+), 21 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 2931789a7e..3b2a66ac17 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -69,22 +69,49 @@ jobs: - run: node_modules/.bin/webpack + - name: Install Fork CMS + run: cp src/Modules/Installer/tests/fork-cms-installation-configuration.yaml fork-cms-installation-configuration.yaml && bin/console forkcms:installer:install && sed -i 's/forkcms_test/forkcms/g' .env.local + env: + FORK_DATABASE_USER: root + FORK_DATABASE_PASSWORD: kingtriton + - name: Execute tests with coverage if: ${{ matrix.php == '8.1' }} run: bin/simple-phpunit --testsuite=${{ matrix.testsuite}} --coverage-clover=${{ matrix.testsuite}}.clover + env: + FORK_INSTALLATION_LOCALE_ENV_PATH: .env.local + APP_ENV: test + FORK_DATABASE_HOST: 127.0.0.1 + FORK_DATABASE_PORT: 3306 + FORK_DATABASE_NAME: forkcms_test + FORK_DATABASE_USER: root + FORK_DATABASE_PASSWORD: kingtriton - name: Execute tests without coverage if: ${{ matrix.php != '8.1' }} run: bin/simple-phpunit --testsuite=${{ matrix.testsuite}} + env: + FORK_INSTALLATION_LOCALE_ENV_PATH: .env.local + APP_ENV: test + FORK_DATABASE_HOST: 127.0.0.1 + FORK_DATABASE_PORT: 3306 + FORK_DATABASE_NAME: forkcms_test + FORK_DATABASE_USER: root + FORK_DATABASE_PASSWORD: kingtriton - name: Display error logs on failure if: ${{ failure() }} run: | - [ -r var/log/installer/debug.log ] && cat var/log/installer/debug.log + bin/console debug:dotenv + ls -alh + [ -r var/log/install/debug.log ] && cat var/log/install/debug.log [ -r var/log/test/debug.log ] && cat var/log/test/debug.log - [ -r var/log/test_instal/debug.log ] && cat var/log/test_instal/debug.log + [ -r var/log/test_install/debug.log ] && cat var/log/test_install/debug.log [ -r var/log/prod/debug.log ] && cat var/log/prod/debug.log [ -r var/log/dev/debug.log ] && cat var/log/dev/debug.log + env: + FORK_INSTALLATION_LOCALE_ENV_PATH: .env.local + APP_ENV: test - name: Upload Coverage report uses: codecov/codecov-action@v1 @@ -126,7 +153,7 @@ jobs: APP_ENV: dev - name: Install Fork CMS - run: mv src/Modules/Installer/tests/fork-cms-installation-configuration.yaml fork-cms-installation-configuration.yaml && bin/console forkcms:installer:install && bin/console cache:clear + run: cp src/Modules/Installer/tests/fork-cms-installation-configuration.yaml fork-cms-installation-configuration.yaml && bin/console forkcms:installer:install && bin/console cache:clear env: APP_ENV: dev APP_DEBUG: 1 diff --git a/config/packages/test/framework.yaml b/config/packages/test/framework.yaml index 9475d9f0d6..b1e2bb6d86 100644 --- a/config/packages/test/framework.yaml +++ b/config/packages/test/framework.yaml @@ -2,4 +2,4 @@ framework: test: true session: storage_factory_id: session.storage.factory.mock_file - save_path: '' + save_path: null diff --git a/config/packages/test_install/framework.yaml b/config/packages/test_install/framework.yaml index 9166293bd4..41805ca178 100644 --- a/config/packages/test_install/framework.yaml +++ b/config/packages/test_install/framework.yaml @@ -10,7 +10,7 @@ framework: # Remove or comment this section to explicitly disable session support. session: storage_factory_id: session.storage.factory.mock_file - save_path: '' + save_path: null #esi: true #fragments: true diff --git a/src/Core/tests/bootstrap.php b/src/Core/tests/bootstrap.php index 4d35f5f2a0..836a42b38a 100644 --- a/src/Core/tests/bootstrap.php +++ b/src/Core/tests/bootstrap.php @@ -10,7 +10,7 @@ $loader = require __DIR__ . '/../../../vendor/autoload.php'; AnnotationRegistry::registerLoader([$loader, 'loadClass']); -(new Dotenv())->loadEnv(__DIR__ . '/../../../.env', null, 'test', []); +(new Dotenv())->loadEnv(__DIR__ . '/../../../.env', null, 'test', ['test_install', 'test']); function installTest() { @@ -23,30 +23,25 @@ function installTest() $application->run( new ArrayInput([ 'command' => 'cache:clear', - '--no-warmup' => '1', - '--env' => 'test_install', - ]) - ); - $application->run( - new ArrayInput([ - 'command' => 'cache:clear', - '--no-warmup' => '1', - '--env' => 'test', + '--no-warmup' => true, ]) ); fwrite(STDERR, print_r('Create test database', true)); + $application->run( new ArrayInput([ - 'command' => 'doctrine:database:drop', - '--if-exists' => '1', - '--force' => '1', + 'command' => 'doctrine:database:create', + '--if-not-exists' => true, ]) ); + fwrite(STDERR, print_r('Clear test database', true)); $application->run( new ArrayInput([ - 'command' => 'doctrine:database:create', + 'command' => 'doctrine:schema:drop', + '--full-database' => true, + '--force' => true, ]) ); @@ -59,7 +54,12 @@ function installTest() $kernel->shutdown(); } -if ((($_ENV['TEST_DATABASE'] ?? 'cached') === 'fresh') || ForkConnection::get('test_instal')->exec('select 1') === false) { +try { + $freshInstall = (($_ENV['TEST_DATABASE'] ?? 'cached') === 'fresh') || ForkConnection::get('test_instal')->exec('select 1 from backend__user') === false; +} catch (Throwable $throwable) { + $freshInstall = true; +} +if ($freshInstall) { installTest(); } diff --git a/src/bootstrap.php b/src/bootstrap.php index 43e14c84ab..addada6571 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -7,6 +7,6 @@ $loader = require __DIR__ . '/../vendor/autoload.php'; AnnotationRegistry::registerLoader([$loader, 'loadClass']); -(new Dotenv())->loadEnv(__DIR__ . '/../.env', null, 'dev', []); +(new Dotenv())->loadEnv(__DIR__ . '/../.env', null, 'dev', ['test_install', 'test']); return $loader; From f06f2fcccc803ecf2131033a901daee6e2dd5c32 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sat, 30 Sep 2023 00:25:18 +0200 Subject: [PATCH 26/67] Always verity authentication when the TEST_URL constant is present for a backend action --- .../Backend/tests/Backend/Actions/UserIndexTest.php | 12 +++++------- src/Modules/Backend/tests/BackendWebTestCase.php | 7 +++++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php index 01f7423cb6..7f7cc7ac98 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php @@ -16,16 +16,14 @@ protected function setUp(): void self::request(Request::METHOD_GET, self::TEST_URL); } - public function testAuthenticationIsNeeded(): void - { - self::assertAuthenticationIsNeeded(self::TEST_URL); - } - public function testPageLoads(): void { self::loginBackendUser(); - self::assertPageLoadedCorrectly(self::TEST_URL, ['Display name', 'E-mail', 'Super admin', 'Normal user']); - self::assertPageTitleSame('Users | settings | Fork CMS | Fork CMS'); + self::assertPageLoadedCorrectly( + self::TEST_URL, + 'Users | settings | Fork CMS | Fork CMS', + ['Display name', 'E-mail', 'Super admin', 'Normal user'] + ); self::assertHasLink('Add', '/private/en/backend/user-add'); } diff --git a/src/Modules/Backend/tests/BackendWebTestCase.php b/src/Modules/Backend/tests/BackendWebTestCase.php index f85bb06ebb..6b514e4d81 100644 --- a/src/Modules/Backend/tests/BackendWebTestCase.php +++ b/src/Modules/Backend/tests/BackendWebTestCase.php @@ -9,6 +9,13 @@ abstract class BackendWebTestCase extends WebTestCase { + public function testAuthenticationIsNeeded(): void + { + if (defined(static::class . '::TEST_URL') === true) { + self::assertAuthenticationIsNeeded(static::TEST_URL); + } + } + final protected static function loginBackendUser(string $email = 'test@fork-cms.com'): User { try { From 98c584a942a019beffd13f792896a0f5ed2baf3e Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sat, 30 Sep 2023 00:25:53 +0200 Subject: [PATCH 27/67] Move the title verification to the page load test so we can reuse a proper error message --- src/Core/tests/WebTestCase.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Core/tests/WebTestCase.php b/src/Core/tests/WebTestCase.php index 424c477f02..768876f9f1 100644 --- a/src/Core/tests/WebTestCase.php +++ b/src/Core/tests/WebTestCase.php @@ -45,12 +45,16 @@ final protected static function loadFixture(FixtureInterface ...$fixture): void */ final protected static function assertPageLoadedCorrectly( string $url, + ?string $pageTitle = null, array $expectedContent = [], int $httpStatusCode = Response::HTTP_OK, string $requestMethod = Request::METHOD_GET, array $requestParameters = [] ): void { static::assertHttpStatusCode($url, $httpStatusCode, $requestMethod, $requestParameters); + if ($pageTitle !== null) { + self::assertPageTitleSame($pageTitle, 'Page title is not "' . $pageTitle . '".'); + } static::assertPageHasContent(...$expectedContent); } @@ -85,6 +89,7 @@ final protected static function assertClickOnLink( ): void { static::assertPageLoadedCorrectly( static::getClient()->getCrawler()->selectLink($linkText)->link()->getUri(), + null, $expectedContent, $httpStatusCode, $requestMethod, From ee5ca319bac59ef449d2e184839c8bb8173de6c7 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sat, 30 Sep 2023 01:57:03 +0200 Subject: [PATCH 28/67] Add automated test for missing translations --- config/packages/test/web_profiler.yaml | 4 +- .../Backend/Core/Installer/Data/locale.xml | 17 --------- .../Backend/assets/installer/translations.xml | 17 +++++++++ .../tests/Backend/Actions/UserIndexTest.php | 9 +---- .../Backend/tests/BackendWebTestCase.php | 38 ++++++++++++++++++- 5 files changed, 58 insertions(+), 27 deletions(-) diff --git a/config/packages/test/web_profiler.yaml b/config/packages/test/web_profiler.yaml index 03752de213..4a16e97840 100644 --- a/config/packages/test/web_profiler.yaml +++ b/config/packages/test/web_profiler.yaml @@ -3,4 +3,6 @@ web_profiler: intercept_redirects: false framework: - profiler: { collect: false } + profiler: + collect: false + collect_parameter: enable-framework-profiler diff --git a/src.fork5/Backend/Core/Installer/Data/locale.xml b/src.fork5/Backend/Core/Installer/Data/locale.xml index 722a3d35f2..d1e4a2b159 100644 --- a/src.fork5/Backend/Core/Installer/Data/locale.xml +++ b/src.fork5/Backend/Core/Installer/Data/locale.xml @@ -3934,23 +3934,6 @@ - - - - - - - - - - - - - - - - - diff --git a/src/Modules/Backend/assets/installer/translations.xml b/src/Modules/Backend/assets/installer/translations.xml index 748fba46b5..d778834a75 100644 --- a/src/Modules/Backend/assets/installer/translations.xml +++ b/src/Modules/Backend/assets/installer/translations.xml @@ -1,6 +1,23 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php index 7f7cc7ac98..ad13278287 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php @@ -10,12 +10,6 @@ final class UserIndexTest extends BackendWebTestCase { protected const TEST_URL = '/private/en/backend/user-index'; - protected function setUp(): void - { - parent::setUp(); - self::request(Request::METHOD_GET, self::TEST_URL); - } - public function testPageLoads(): void { self::loginBackendUser(); @@ -30,6 +24,7 @@ public function testPageLoads(): void public function testDataGrid(): void { $user = self::loginBackendUser(); + self::loadPage(); self::assertDataGridHasLink($user->getEmail(), '/private/en/backend/user-edit/' . $user->getId()); self::assertDataGridHasLink('user@fork-cms.com'); @@ -43,8 +38,6 @@ public function testDataGrid(): void self::assertDataGridIsEmpty(); self::filterDataGrid('User.displayName', $user->getDisplayName()); self::assertDataGridHasLink($user->getEmail(), '/private/en/backend/user-edit/' . $user->getId()); - - self::assertHttpStatusCode404('test', 'bob', []); } protected static function getClassFixtures(): array diff --git a/src/Modules/Backend/tests/BackendWebTestCase.php b/src/Modules/Backend/tests/BackendWebTestCase.php index 6b514e4d81..d3969d6868 100644 --- a/src/Modules/Backend/tests/BackendWebTestCase.php +++ b/src/Modules/Backend/tests/BackendWebTestCase.php @@ -6,21 +6,57 @@ use ForkCMS\Modules\Backend\Domain\User\User; use ForkCMS\Modules\Backend\Domain\User\UserRepository; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Translation\DataCollectorTranslator; +use Throwable; abstract class BackendWebTestCase extends WebTestCase { public function testAuthenticationIsNeeded(): void { if (defined(static::class . '::TEST_URL') === true) { + self::loadPage(); self::assertAuthenticationIsNeeded(static::TEST_URL); } } + public function testTranslations(): void + { + if (defined(static::class . '::TEST_URL') === true) { + self::loginBackendUser(); + self::loadPage(enableProfiler: true); + + $dataCollector = self::getContainer()->get('translator.data_collector'); + $missingTranslations = array_filter($dataCollector->getCollectedMessages(), static fn (array $message): bool => $message['state'] === DataCollectorTranslator::MESSAGE_MISSING); + self::assertSame([], [], 'Missing translations found.'); + } + } + + final protected static function loadPage(?string $url = null, bool $enableProfiler = false): void + { + if (defined(static::class . '::TEST_URL') === true) { + $url = $url ?? static::TEST_URL; + + if ($enableProfiler) { + $url .= str_contains($url, '?') ? '&enable-framework-profiler=1' : '?enable-framework-profiler=1'; + } + + self::request(Request::METHOD_GET, $url); + + return; + } + + if ($url === null) { + static::fail('No URL defined.'); + } + + self::request(Request::METHOD_GET, $url); + } + final protected static function loginBackendUser(string $email = 'test@fork-cms.com'): User { try { $userRespository = static::getContainer()->get(UserRepository::class); - } catch (\Throwable) { + } catch (Throwable) { static::fail('User repository not found.'); } From ea5dda71e3fd49da8da22cc27bcc30278d6bb9a8 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sat, 30 Sep 2023 02:45:25 +0200 Subject: [PATCH 29/67] Log in by default when loading a page during a functional test --- .../Backend/tests/Backend/Actions/UserIndexTest.php | 2 +- src/Modules/Backend/tests/BackendWebTestCase.php | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php index ad13278287..95a49853ba 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php @@ -24,7 +24,7 @@ public function testPageLoads(): void public function testDataGrid(): void { $user = self::loginBackendUser(); - self::loadPage(); + self::loadPage(loginBackendUser: false); self::assertDataGridHasLink($user->getEmail(), '/private/en/backend/user-edit/' . $user->getId()); self::assertDataGridHasLink('user@fork-cms.com'); diff --git a/src/Modules/Backend/tests/BackendWebTestCase.php b/src/Modules/Backend/tests/BackendWebTestCase.php index d3969d6868..c059f8fcb8 100644 --- a/src/Modules/Backend/tests/BackendWebTestCase.php +++ b/src/Modules/Backend/tests/BackendWebTestCase.php @@ -14,7 +14,7 @@ abstract class BackendWebTestCase extends WebTestCase public function testAuthenticationIsNeeded(): void { if (defined(static::class . '::TEST_URL') === true) { - self::loadPage(); + self::loadPage(loginBackendUser: false); self::assertAuthenticationIsNeeded(static::TEST_URL); } } @@ -22,17 +22,20 @@ public function testAuthenticationIsNeeded(): void public function testTranslations(): void { if (defined(static::class . '::TEST_URL') === true) { - self::loginBackendUser(); self::loadPage(enableProfiler: true); $dataCollector = self::getContainer()->get('translator.data_collector'); $missingTranslations = array_filter($dataCollector->getCollectedMessages(), static fn (array $message): bool => $message['state'] === DataCollectorTranslator::MESSAGE_MISSING); - self::assertSame([], [], 'Missing translations found.'); + self::assertSame([], $missingTranslations, 'Missing translations found.'); } } - final protected static function loadPage(?string $url = null, bool $enableProfiler = false): void + final protected static function loadPage(?string $url = null, bool $enableProfiler = false, bool $loginBackendUser = true): void { + if ($loginBackendUser) { + self::loginBackendUser(); + } + if (defined(static::class . '::TEST_URL') === true) { $url = $url ?? static::TEST_URL; From 5489e30b080ff2af646e419d91bcd56f4fa48c5b Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sat, 30 Sep 2023 02:46:00 +0200 Subject: [PATCH 30/67] Fix inconsistant assert name for page content check --- src/Core/tests/WebTestCase.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/tests/WebTestCase.php b/src/Core/tests/WebTestCase.php index 768876f9f1..f76268e524 100644 --- a/src/Core/tests/WebTestCase.php +++ b/src/Core/tests/WebTestCase.php @@ -55,7 +55,7 @@ final protected static function assertPageLoadedCorrectly( if ($pageTitle !== null) { self::assertPageTitleSame($pageTitle, 'Page title is not "' . $pageTitle . '".'); } - static::assertPageHasContent(...$expectedContent); + static::assertResponseHasContent(...$expectedContent); } final protected static function assertHasLink(string $text, string $url): void @@ -97,7 +97,7 @@ final protected static function assertClickOnLink( ); } - final protected static function assertPageHasContent(string ...$content): void + final protected static function assertResponseHasContent(string ...$content): void { $response = static::getResponse(); From a7fd1a720137b0b6511e56616a57a50e6f6c539e Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sat, 30 Sep 2023 02:46:28 +0200 Subject: [PATCH 31/67] Better error messages for user validation --- .../Backend/Core/Installer/Data/locale.xml | 100 ------------------ .../Profiles/Installer/Data/locale.xml | 39 ------- .../Domain/User/UserDataTransferObject.php | 6 +- .../Backend/assets/installer/translations.xml | 72 +++++++++++++ 4 files changed, 75 insertions(+), 142 deletions(-) diff --git a/src.fork5/Backend/Core/Installer/Data/locale.xml b/src.fork5/Backend/Core/Installer/Data/locale.xml index d1e4a2b159..b37d59be83 100644 --- a/src.fork5/Backend/Core/Installer/Data/locale.xml +++ b/src.fork5/Backend/Core/Installer/Data/locale.xml @@ -3574,74 +3574,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -12736,38 +12668,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src.fork5/Backend/Modules/Profiles/Installer/Data/locale.xml b/src.fork5/Backend/Modules/Profiles/Installer/Data/locale.xml index 7b774e7290..4485c1d8f2 100644 --- a/src.fork5/Backend/Modules/Profiles/Installer/Data/locale.xml +++ b/src.fork5/Backend/Modules/Profiles/Installer/Data/locale.xml @@ -890,45 +890,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Modules/Backend/Domain/User/UserDataTransferObject.php b/src/Modules/Backend/Domain/User/UserDataTransferObject.php index 98e33f8d60..9db671f44c 100644 --- a/src/Modules/Backend/Domain/User/UserDataTransferObject.php +++ b/src/Modules/Backend/Domain/User/UserDataTransferObject.php @@ -11,8 +11,8 @@ use Symfony\Component\Validator\Constraints as Assert; /** @implements UniqueDataTransferObjectInterface */ -#[UniqueDataTransferObject(['entityClass' => User::class, 'fields' => ['email']])] -#[UniqueDataTransferObject(['entityClass' => User::class, 'fields' => ['displayName']])] +#[UniqueDataTransferObject(['entityClass' => User::class, 'fields' => ['email'], 'message' => 'err.EmailExists'])] +#[UniqueDataTransferObject(['entityClass' => User::class, 'fields' => ['displayName'], 'message' => 'err.DisplayNameExists'])] abstract class UserDataTransferObject implements UniqueDataTransferObjectInterface { /** @@ -30,7 +30,7 @@ abstract class UserDataTransferObject implements UniqueDataTransferObjectInterfa public ?string $plainTextPassword = null; /** - * @Assert\NotBlank (message="err.FieldIsRequired") + * @Assert\NotBlank (message="err.DisplayNameIsRequired") */ public ?string $displayName = null; diff --git a/src/Modules/Backend/assets/installer/translations.xml b/src/Modules/Backend/assets/installer/translations.xml index d778834a75..9441ba2e6f 100644 --- a/src/Modules/Backend/assets/installer/translations.xml +++ b/src/Modules/Backend/assets/installer/translations.xml @@ -18,6 +18,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1606,6 +1674,10 @@ + + + + From e3789e9e07cdc197314c7a37342ea88c2495b1b3 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sat, 30 Sep 2023 02:46:47 +0200 Subject: [PATCH 32/67] Add tests for adding users --- .../tests/Backend/Actions/UserAddTest.php | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/Modules/Backend/tests/Backend/Actions/UserAddTest.php diff --git a/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php new file mode 100644 index 0000000000..9c6b0a5f07 --- /dev/null +++ b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php @@ -0,0 +1,100 @@ +selectButton('Add')->form(); + self::getClient()->submit($form); + self::assertMinCount( + 3, + self::getCrawler()->filter('#content form[name="user"] .form-control.is-invalid'), + 'Not all required fields are marked as invalid.' + ); + } + + public function testValidData(): void + { + self::loadPage(); + + $form = self::getCrawler()->selectButton('Add')->form(); + self::getClient()->submit( + $form, + [ + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => 'Jelmer Prins', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'jelmer.prins', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'I<3ForkCMS', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'I<3ForkCMS', + ] + ); + self::assertResponseHasContent('The password is too short.', 'Please provide a valid e-mail address.'); + } + + public function testUniqueness(): void + { + $user = self::loginBackendUser(); + self::loadPage(loginBackendUser: false); + + $form = self::getCrawler()->selectButton('Add')->form(); + self::getClient()->submit( + $form, + [ + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => $user->getDisplayName(), + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => $user->getEmail(), + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'IAbsolutely<3ForkCMS', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'IAbsolutely<3ForkCMS', + ] + ); + self::assertResponseHasContent('This e-mailaddress is in use.', 'This display name is in use.'); + } + + public function testSubmittedFormRedirectsToIndex(): void + { + self::loadPage(); + + $form = self::getCrawler()->selectButton('Add')->form(); + self::getClient()->submit( + $form, + [ + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => 'Jelmer Prins', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'jelmer.prins@fork-cms.com', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'IAbsolutely<3ForkCMS', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'IAbsolutely<3ForkCMS', + ] + ); + self::getClient()->followRedirect(); + self::assertCurrentUrlEndsWith('/private/en/backend/user-index'); + self::assertDataGridHasLink('jelmer.prins@fork-cms.com'); + self::assertResponseHasContent('The user "Jelmer Prins" was added.'); + } +} From 92cf2eed240d5fac99adc3a9da0520a8cad6e17b Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sat, 30 Sep 2023 03:31:30 +0200 Subject: [PATCH 33/67] Fix missing translation for filter button on pages with data grids --- .../Backend/Core/Installer/Data/locale.xml | 18 ------------ .../tests/Backend/Actions/UserIndexTest.php | 1 - .../dataGrid.html.twig | 28 +++++++++++++++++++ 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src.fork5/Backend/Core/Installer/Data/locale.xml b/src.fork5/Backend/Core/Installer/Data/locale.xml index b37d59be83..ab7248393b 100644 --- a/src.fork5/Backend/Core/Installer/Data/locale.xml +++ b/src.fork5/Backend/Core/Installer/Data/locale.xml @@ -1654,24 +1654,6 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php index 95a49853ba..065da4060d 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php @@ -3,7 +3,6 @@ use ForkCMS\Modules\Backend\DataFixtures\UserFixture; use ForkCMS\Modules\Backend\DataFixtures\UserGroupFixture; use ForkCMS\Modules\Backend\tests\BackendWebTestCase; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; final class UserIndexTest extends BackendWebTestCase diff --git a/templates/bundles/PageonDoctrineDataGridBundle/dataGrid.html.twig b/templates/bundles/PageonDoctrineDataGridBundle/dataGrid.html.twig index 99923493d1..d66e518a04 100644 --- a/templates/bundles/PageonDoctrineDataGridBundle/dataGrid.html.twig +++ b/templates/bundles/PageonDoctrineDataGridBundle/dataGrid.html.twig @@ -11,6 +11,34 @@ {% if dataGrid.paginator is not empty or dataGrid.paginator.params.filterValue is defined %}
{% embed '@!PageonDoctrineDataGrid/dataGrid.html.twig'%} + {% block thead %} + + + {% set hasFilters = false %} + {% for column in dataGrid.columns %} + + {% if column.showColumnLabel %} + {% if(column.sortable) %} + {{ knp_pagination_sortable(dataGrid.paginator, column.label|trans|ucfirst, column.fullName) }} + {% else %} + {{ column.label|trans|ucfirst }} + {% endif %} + {% if(column.filterable) %} + {% set hasFilters = true %} + {% endif %} + {% endif %} + + {% endfor %} + + {% if(hasFilters) %} + + {% for column in dataGrid.columns %} + {% if(column.filterable) %}{{ knp_pagination_filter(dataGrid.paginator, {(column.fullName): column.label}, {button: 'lbl.Filter'}) }}{% endif %} + {% endfor %} + + {% endif %} + + {% endblock %} {% block noResults %}{% endblock %} {% block columnValue %} {% set value = column.showColumnLabel or column.hasValueCallback? column.value(attribute(row, column.name), row, column.name) : column.label|trans|ucfirst %} From 6bdf267aad90a2150de7a61e1e272823febe0245 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sat, 30 Sep 2023 03:32:54 +0200 Subject: [PATCH 34/67] Speed up UserFixture --- src/Modules/Backend/DataFixtures/UserFixture.php | 13 +++++++------ .../Backend/tests/Backend/Actions/UserIndexTest.php | 3 +-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Modules/Backend/DataFixtures/UserFixture.php b/src/Modules/Backend/DataFixtures/UserFixture.php index 0f2748cc4c..89d9bcb984 100644 --- a/src/Modules/Backend/DataFixtures/UserFixture.php +++ b/src/Modules/Backend/DataFixtures/UserFixture.php @@ -11,11 +11,12 @@ final class UserFixture extends ForkFixture implements DependentFixtureInterface { - public const PASSWORD = 'password'; + public const PLAIN_TEXT_PASSWORD = 'test'; + private const PASSWORD = '$2y$13$YJGHIjorExsQSD9VCzMgBuwi5QS3Zr2tVBisWS4vrcKDin/9BdBQq'; public const SUPER_ADMIN_REFERENCE = 'user-super-admin'; public const USER_REFERENCE = 'user-super-admin'; - public function __construct(private readonly UserPasswordHasherInterface $passwordHasher) + public function __construct() { } @@ -25,10 +26,10 @@ public function load(ObjectManager $manager): void $createSuperAdmin->email = 'super-admin@fork-cms.com'; $createSuperAdmin->displayName = 'Super admin'; $createSuperAdmin->superAdmin = true; - $createSuperAdmin->plainTextPassword = self::PASSWORD; + $createSuperAdmin->plainTextPassword = self::PLAIN_TEXT_PASSWORD; $createSuperAdmin->userGroups->add($this->getReference(UserGroupFixture::ONLY_DASHBOARD_REFERENCE)); $superAdmin = User::fromDataTransferObject($createSuperAdmin); - $superAdmin->hashPassword($this->passwordHasher); + $superAdmin->setPassword(self::PASSWORD); $manager->persist($superAdmin); $this->setReference(self::SUPER_ADMIN_REFERENCE, $superAdmin); @@ -36,10 +37,10 @@ public function load(ObjectManager $manager): void $createUser->email = 'user@fork-cms.com'; $createUser->displayName = 'Normal user'; $createUser->superAdmin = false; - $createUser->plainTextPassword = self::PASSWORD; + $createUser->plainTextPassword = self::PLAIN_TEXT_PASSWORD; $createUser->userGroups->add($this->getReference(UserGroupFixture::ONLY_DASHBOARD_REFERENCE)); $user = User::fromDataTransferObject($createUser); - $user->hashPassword($this->passwordHasher); + $user->setPassword(self::PASSWORD); $manager->persist($user); $this->setReference(self::USER_REFERENCE, $user); diff --git a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php index 065da4060d..17024aed6c 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php @@ -3,7 +3,6 @@ use ForkCMS\Modules\Backend\DataFixtures\UserFixture; use ForkCMS\Modules\Backend\DataFixtures\UserGroupFixture; use ForkCMS\Modules\Backend\tests\BackendWebTestCase; -use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; final class UserIndexTest extends BackendWebTestCase { @@ -43,7 +42,7 @@ protected static function getClassFixtures(): array { return [ new UserGroupFixture(), - new UserFixture(self::getContainer()->get(UserPasswordHasherInterface::class)), + new UserFixture(), ]; } } From 374a60ba89129edb5a52d91646dc611a96812801 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sat, 30 Sep 2023 04:30:26 +0200 Subject: [PATCH 35/67] Allow symfomny flex --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a411b666cf..590ad04fb5 100644 --- a/composer.json +++ b/composer.json @@ -86,7 +86,10 @@ "symfony/css-selector": "6.*" }, "config": { - "bin-dir": "bin" + "bin-dir": "bin", + "allow-plugins": { + "symfony/flex": true + } }, "support": { "forum": "https://fork-cms.herokuapp.com", From da6623b0bc9fb39103d3bd1d0d350b5d534df635 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sat, 30 Sep 2023 12:00:50 +0200 Subject: [PATCH 36/67] Fix cache issues in tests --- src/Core/Domain/Header/PageTitle.php | 7 +-- src/Core/tests/WebTestCase.php | 8 +--- .../Backend/Domain/Navigation/Navigation.php | 12 +++-- .../tests/Backend/Actions/UserAddTest.php | 5 -- .../Domain/Module/ModuleSettings.php | 8 ++-- .../Domain/Translator/ForkTranslator.php | 10 ++-- .../Domain/Twig/ForkIntlExtension.php | 46 +++++++++++-------- .../Pages/Domain/Revision/Revision.php | 9 +--- 8 files changed, 48 insertions(+), 57 deletions(-) diff --git a/src/Core/Domain/Header/PageTitle.php b/src/Core/Domain/Header/PageTitle.php index 00ebbba199..47a5b9044e 100644 --- a/src/Core/Domain/Header/PageTitle.php +++ b/src/Core/Domain/Header/PageTitle.php @@ -14,11 +14,6 @@ public function __construct(private readonly BreadcrumbCollection $breadcrumbs) public function __toString(): string { - static $pageTitle; - if ($pageTitle === null) { - $pageTitle = $this->breadcrumbs->asPageTitle(); - } - - return $pageTitle; + return $this->breadcrumbs->asPageTitle(); } } diff --git a/src/Core/tests/WebTestCase.php b/src/Core/tests/WebTestCase.php index f76268e524..5a8555ad3e 100644 --- a/src/Core/tests/WebTestCase.php +++ b/src/Core/tests/WebTestCase.php @@ -30,13 +30,7 @@ protected static function getClassFixtures(): array final protected static function loadFixture(FixtureInterface ...$fixture): void { - static $executor; - - if ($executor === null) { - $executor = new ORMExecutor(self::getContainer()->get('doctrine.orm.entity_manager')); - } - - $executor->execute($fixture, true); + (new ORMExecutor(self::getContainer()->get('doctrine.orm.entity_manager')))->execute($fixture, true); } /** diff --git a/src/Modules/Backend/Domain/Navigation/Navigation.php b/src/Modules/Backend/Domain/Navigation/Navigation.php index 0f44cf1f8f..c0ca25b5c6 100644 --- a/src/Modules/Backend/Domain/Navigation/Navigation.php +++ b/src/Modules/Backend/Domain/Navigation/Navigation.php @@ -12,6 +12,9 @@ final class Navigation { + /** @var array> */ + private $navigation = []; + public function __construct( private readonly AuthorizationCheckerInterface $authorizationChecker, private readonly NavigationCache $navigationCache, @@ -33,16 +36,15 @@ public function parse(Environment $twig): void /** @return array> */ private function getNavigationForAllowedModulesAndActions(): array { - static $navigation; - if ($navigation !== null) { - return $navigation; + if ($this->navigation !== []) { + return $this->navigation; } - $navigation = $this->addActiveStateToNavigation( + $this->navigation = $this->addActiveStateToNavigation( array_filter(array_map($this->getPermissionCheckerFunction(), $this->navigationCache->get())) ); - return $navigation; + return $this->navigation; } private function getPermissionCheckerFunction(): callable diff --git a/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php index 9c6b0a5f07..9c2359ee2d 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php @@ -8,11 +8,6 @@ final class UserAddTest extends BackendWebTestCase { protected const TEST_URL = '/private/en/backend/user-add'; - protected function setUp(): void - { - parent::setUp(); - } - public function testPageLoads(): void { self::loginBackendUser(); diff --git a/src/Modules/Extensions/Domain/Module/ModuleSettings.php b/src/Modules/Extensions/Domain/Module/ModuleSettings.php index ceb3bdf360..c744e932ff 100644 --- a/src/Modules/Extensions/Domain/Module/ModuleSettings.php +++ b/src/Modules/Extensions/Domain/Module/ModuleSettings.php @@ -9,6 +9,9 @@ final class ModuleSettings implements EventSubscriberInterface { + /** @var array */ + private array $modules = []; + public function __construct( private readonly ModuleRepository $moduleRepository, private readonly CacheItemPoolInterface $cache @@ -123,14 +126,13 @@ private function getAllSettingsFromDatabase(): array public function getModule(ModuleName $moduleName): Module { - static $modules = []; $name = $moduleName->getName(); - $modules[$name] = $modules[$name] + $this->modules[$name] = $this->modules[$name] ?? $this->moduleRepository->find($moduleName) ?? throw new InvalidArgumentException('Module name not found: ' . $name); - return $modules[$name]; + return $this->modules[$name]; } /** diff --git a/src/Modules/Internationalisation/Domain/Translator/ForkTranslator.php b/src/Modules/Internationalisation/Domain/Translator/ForkTranslator.php index 822e15d32c..0745a02ad8 100644 --- a/src/Modules/Internationalisation/Domain/Translator/ForkTranslator.php +++ b/src/Modules/Internationalisation/Domain/Translator/ForkTranslator.php @@ -7,6 +7,7 @@ use ForkCMS\Modules\Backend\Domain\Action\ActionSlug; use ForkCMS\Modules\Backend\Domain\AjaxAction\AjaxActionSlug; use ForkCMS\Modules\Backend\Domain\User\User; +use ForkCMS\Modules\Internationalisation\Domain\Locale\Locale; use ForkCMS\Modules\Internationalisation\Domain\Translation\TranslationDomain; use InvalidArgumentException; use Psr\Container\ContainerInterface; @@ -25,6 +26,8 @@ final class ForkTranslator extends Translator /** @var ?string used for debug reasons */ private ?string $lastUsedDomain; + private ?string $fallbackLocale = null; + /** * @param array>$loaderIds * @param array$options @@ -51,12 +54,11 @@ public function trans(?string $id, array $parameters = [], string $domain = null $domain = null; } - static $fallbackLocale = null; - if ($fallbackLocale === null) { + if ($this->fallbackLocale === null) { $user = $this->security?->getUser(); - $fallbackLocale = ($user instanceof User ? $user->getSetting('locale') : null) ?? $this->getLocale(); + $this->fallbackLocale = ($user instanceof User ? $user->getSetting('locale') : null) ?? $this->getLocale(); } - $locale = $locale ?? $fallbackLocale; + $locale = $locale ?? $this->fallbackLocale; if (!$this->requestStack instanceof RequestStack) { return $this->getTranslationAndStoreDomain($id, $parameters, $domain, $locale); diff --git a/src/Modules/Internationalisation/Domain/Twig/ForkIntlExtension.php b/src/Modules/Internationalisation/Domain/Twig/ForkIntlExtension.php index 692a68ea9e..d7e3f27516 100644 --- a/src/Modules/Internationalisation/Domain/Twig/ForkIntlExtension.php +++ b/src/Modules/Internationalisation/Domain/Twig/ForkIntlExtension.php @@ -49,6 +49,18 @@ */ final class ForkIntlExtension extends AbstractExtension implements EventSubscriberInterface { + /** @var array */ + private array $extensions = []; + + /** @var array */ + private array $numberFormatterCache = []; + + /** @var array */ + private array $dateFormatter = []; + + /** @var array */ + private array $installedLocales = []; + public function __construct( private readonly InstalledLocaleRepository $installedLocaleRepository, private readonly ModuleSettings $moduleSettings, @@ -148,10 +160,9 @@ public function __call(string $name, array $arguments): mixed private function getIntlExtension(string $function, Locale $locale): IntlExtension { $cacheKey = $function . $locale->value; - static $extensions = []; - if (array_key_exists($cacheKey, $extensions)) { - return $extensions[$cacheKey]; + if (array_key_exists($cacheKey, $this->extensions)) { + return $this->extensions[$cacheKey]; } /** @var User|null $user */ @@ -171,21 +182,20 @@ private function getIntlExtension(string $function, Locale $locale): IntlExtensi } } - $extensions[$cacheKey] = new IntlExtension( + $this->extensions[$cacheKey] = new IntlExtension( $this->createIntlDateFormatter($installedLocale, $order, $dateFormatType, $user), $this->createNumberFormatter($installedLocale, $user) ); - return $extensions[$cacheKey]; + return $this->extensions[$cacheKey]; } private function createNumberFormatter(InstalledLocale $locale, ?User $user): NumberFormatter { $cacheKey = $locale->getLocale()->value . ($user ? 'user' : ''); - static $numberFormatterCache = []; - if (array_key_exists($cacheKey, $numberFormatterCache)) { - return $numberFormatterCache[$cacheKey]; + if (array_key_exists($cacheKey, $this->numberFormatterCache)) { + return $this->numberFormatterCache[$cacheKey]; } $numberFormat = $locale->getSetting('number_format'); @@ -205,7 +215,7 @@ private function createNumberFormatter(InstalledLocale $locale, ?User $user): Nu $numberFormatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $separatorSymbols[1]); $numberFormatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $separatorSymbols[0]); - $numberFormatterCache[$cacheKey] = $numberFormatter; + $this->numberFormatterCache[$cacheKey] = $numberFormatter; return $numberFormatter; } @@ -216,10 +226,9 @@ private function createIntlDateFormatter( string $dateFormatType = 'short', ?User $user = null ): IntlDateFormatter { - static $dateFormatter = []; $cacheKey = $locale->getLocale()->value . $order . $dateFormatType . ($user ? 'user' : ''); - if (array_key_exists($cacheKey, $dateFormatter)) { - return $dateFormatter[$cacheKey]; + if (array_key_exists($cacheKey, $this->dateFormatter)) { + return $this->dateFormatter[$cacheKey]; } $timeFormatKey = $locale->getSetting('time_format'); $dateFormatKey = $locale->getSetting('date_format_' . $dateFormatType); @@ -233,7 +242,7 @@ private function createIntlDateFormatter( $dateFormat = $this->moduleSettings->get($coreModule, 'date_formats_' . $dateFormatType)[$dateFormatKey]; $timeFormat = $this->moduleSettings->get($coreModule, 'time_formats')[$timeFormatKey]; - $dateFormatter[$cacheKey] = new IntlDateFormatter( + $this->dateFormatter[$cacheKey] = new IntlDateFormatter( null, IntlDateFormatter::SHORT, IntlDateFormatter::SHORT, @@ -242,7 +251,7 @@ private function createIntlDateFormatter( sprintf($order, $dateFormat, $timeFormat) ); - return $dateFormatter[$cacheKey]; + return $this->dateFormatter[$cacheKey]; } /** @@ -359,14 +368,13 @@ public function formatUserLongDate( public function getInstalledLocale(Locale $locale): InstalledLocale { - static $cache = []; - if (array_key_exists($locale->value, $cache)) { - return $cache[$locale->value]; + if (array_key_exists($locale->value, $this->installedLocales)) { + return $this->installedLocales[$locale->value]; } - $cache[$locale->value] = $this->installedLocaleRepository->find($locale->value); + $this->installedLocales[$locale->value] = $this->installedLocaleRepository->find($locale->value); - return $cache[$locale->value]; + return $this->installedLocales[$locale->value]; } public static function getSubscribedEvents(): array diff --git a/src/Modules/Pages/Domain/Revision/Revision.php b/src/Modules/Pages/Domain/Revision/Revision.php index 51bce15037..3197cbd898 100644 --- a/src/Modules/Pages/Domain/Revision/Revision.php +++ b/src/Modules/Pages/Domain/Revision/Revision.php @@ -182,11 +182,6 @@ public function getPage(): Page public function getRel(): string { - static $rel = null; - if ($rel !== null) { - return $rel; - } - $relParts = []; $follow = $this->meta->getSEOFollow(); if ($follow === SEOFollow::noFollow) { @@ -197,9 +192,7 @@ public function getRel(): string $relParts[] = $index->value; } - $rel = implode(' ', $relParts); - - return $rel; + return implode(' ', $relParts); } public function getTitle(): string From 38f2101da766967f8f98df853158fe3d6169ccbe Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sun, 1 Oct 2023 02:14:05 +0200 Subject: [PATCH 37/67] Make testing forms more easy --- src/Core/tests/WebTestCase.php | 7 ++++ .../tests/Backend/Actions/UserAddTest.php | 38 ++++++++----------- .../Backend/tests/BackendWebTestCase.php | 10 +++++ 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/Core/tests/WebTestCase.php b/src/Core/tests/WebTestCase.php index 5a8555ad3e..e1b3e4a8cf 100644 --- a/src/Core/tests/WebTestCase.php +++ b/src/Core/tests/WebTestCase.php @@ -248,6 +248,13 @@ final protected static function request( ); } + /** @param array $formData */ + protected static function submitForm(string $submitButtonLabel, array $formData, string ...$expectedContent): void + { + self::getClient()->submit(self::getCrawler()->selectButton($submitButtonLabel)->form(), $formData); + self::assertResponseHasContent(...$expectedContent); + } + final protected static function getRequest(): Request { if (!$request = static::getClient()->getRequest()) { diff --git a/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php index 9c2359ee2d..4de923948a 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php @@ -28,31 +28,24 @@ public function testPageLoads(): void public function testEmptyFormShowsValidationErrors(): void { self::loadPage(); - - $form = self::getCrawler()->selectButton('Add')->form(); - self::getClient()->submit($form); - self::assertMinCount( - 3, - self::getCrawler()->filter('#content form[name="user"] .form-control.is-invalid'), - 'Not all required fields are marked as invalid.' - ); + self::assertEmptyFormSubmission('user', 3); } public function testValidData(): void { self::loadPage(); - $form = self::getCrawler()->selectButton('Add')->form(); - self::getClient()->submit( - $form, + self::submitForm( + 'Add', [ 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => 'Jelmer Prins', 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'jelmer.prins', 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'I<3ForkCMS', 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'I<3ForkCMS', - ] + ], + 'The password is too short.', + 'Please provide a valid e-mail address.', ); - self::assertResponseHasContent('The password is too short.', 'Please provide a valid e-mail address.'); } public function testUniqueness(): void @@ -60,32 +53,31 @@ public function testUniqueness(): void $user = self::loginBackendUser(); self::loadPage(loginBackendUser: false); - $form = self::getCrawler()->selectButton('Add')->form(); - self::getClient()->submit( - $form, + self::submitForm( + 'Add', [ 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => $user->getDisplayName(), - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => $user->getEmail(), + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => $user->getEmail(), 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'IAbsolutely<3ForkCMS', 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'IAbsolutely<3ForkCMS', - ] + ], + 'This e-mailaddress is in use.', + 'This display name is in use.', ); - self::assertResponseHasContent('This e-mailaddress is in use.', 'This display name is in use.'); } public function testSubmittedFormRedirectsToIndex(): void { self::loadPage(); - $form = self::getCrawler()->selectButton('Add')->form(); - self::getClient()->submit( - $form, + self::submitForm( + 'Add', [ 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => 'Jelmer Prins', 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'jelmer.prins@fork-cms.com', 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'IAbsolutely<3ForkCMS', 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'IAbsolutely<3ForkCMS', - ] + ], ); self::getClient()->followRedirect(); self::assertCurrentUrlEndsWith('/private/en/backend/user-index'); diff --git a/src/Modules/Backend/tests/BackendWebTestCase.php b/src/Modules/Backend/tests/BackendWebTestCase.php index c059f8fcb8..24c65f911f 100644 --- a/src/Modules/Backend/tests/BackendWebTestCase.php +++ b/src/Modules/Backend/tests/BackendWebTestCase.php @@ -128,4 +128,14 @@ final protected static function assertDataGridNotEmpty(): void 'Data grid is empty' ); } + + protected static function assertEmptyFormSubmission(string $formName, int $expectedErrorCount, string $submitButtonLabel = 'Add'): void + { + self::submitForm($submitButtonLabel, []); + self::assertMinCount( + $expectedErrorCount, + self::getCrawler()->filter('#content form[name="' . $formName . '"] .form-control.is-invalid'), + 'Not all required fields are marked as invalid.' + ); + } } From 1ce4c8ab562dbfd5611f94a382d6a24f5df7436d Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Wed, 4 Oct 2023 01:40:14 +0300 Subject: [PATCH 38/67] Implement functional testing feedback --- src/Modules/Backend/DataFixtures/UserFixture.php | 4 ++-- .../Backend/tests/Backend/Actions/UserAddTest.php | 6 +++--- .../Backend/tests/Backend/Actions/UserIndexTest.php | 10 +++++----- src/Modules/Backend/tests/BackendWebTestCase.php | 2 +- .../tests/fork-cms-installation-configuration.yaml | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Modules/Backend/DataFixtures/UserFixture.php b/src/Modules/Backend/DataFixtures/UserFixture.php index 89d9bcb984..eecebc7122 100644 --- a/src/Modules/Backend/DataFixtures/UserFixture.php +++ b/src/Modules/Backend/DataFixtures/UserFixture.php @@ -23,7 +23,7 @@ public function __construct() public function load(ObjectManager $manager): void { $createSuperAdmin = new CreateUser(); - $createSuperAdmin->email = 'super-admin@fork-cms.com'; + $createSuperAdmin->email = 'super-admin@example.com'; $createSuperAdmin->displayName = 'Super admin'; $createSuperAdmin->superAdmin = true; $createSuperAdmin->plainTextPassword = self::PLAIN_TEXT_PASSWORD; @@ -34,7 +34,7 @@ public function load(ObjectManager $manager): void $this->setReference(self::SUPER_ADMIN_REFERENCE, $superAdmin); $createUser = new CreateUser(); - $createUser->email = 'user@fork-cms.com'; + $createUser->email = 'user@example.com'; $createUser->displayName = 'Normal user'; $createUser->superAdmin = false; $createUser->plainTextPassword = self::PLAIN_TEXT_PASSWORD; diff --git a/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php index 4de923948a..276a31a305 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php @@ -31,7 +31,7 @@ public function testEmptyFormShowsValidationErrors(): void self::assertEmptyFormSubmission('user', 3); } - public function testValidData(): void + public function testWithInvalidData(): void { self::loadPage(); @@ -74,14 +74,14 @@ public function testSubmittedFormRedirectsToIndex(): void 'Add', [ 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => 'Jelmer Prins', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'jelmer.prins@fork-cms.com', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'jelmer.prins@example.com', 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'IAbsolutely<3ForkCMS', 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'IAbsolutely<3ForkCMS', ], ); self::getClient()->followRedirect(); self::assertCurrentUrlEndsWith('/private/en/backend/user-index'); - self::assertDataGridHasLink('jelmer.prins@fork-cms.com'); + self::assertDataGridHasLink('jelmer.prins@example.com'); self::assertResponseHasContent('The user "Jelmer Prins" was added.'); } } diff --git a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php index 17024aed6c..f0400ef0af 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php @@ -25,11 +25,11 @@ public function testDataGrid(): void self::loadPage(loginBackendUser: false); self::assertDataGridHasLink($user->getEmail(), '/private/en/backend/user-edit/' . $user->getId()); - self::assertDataGridHasLink('user@fork-cms.com'); - self::assertDataGridNotHasLink('demo@fork-cms.com'); - self::filterDataGrid('User.email', 'user@fork-cms.com'); - self::assertDataGridHasLink('user@fork-cms.com'); - self::assertDataGridNotHasLink('demo@fork-cms.com'); + self::assertDataGridHasLink('user@example.com'); + self::assertDataGridNotHasLink('demo@example.com'); + self::filterDataGrid('User.email', 'user@example.com'); + self::assertDataGridHasLink('user@example.com'); + self::assertDataGridNotHasLink('demo@example.com'); self::assertDataGridNotHasLink($user->getEmail()); self::filterDataGrid('User.email', $user->getEmail()); self::filterDataGrid('User.displayName', 'demo'); diff --git a/src/Modules/Backend/tests/BackendWebTestCase.php b/src/Modules/Backend/tests/BackendWebTestCase.php index 24c65f911f..fe577d0dc8 100644 --- a/src/Modules/Backend/tests/BackendWebTestCase.php +++ b/src/Modules/Backend/tests/BackendWebTestCase.php @@ -55,7 +55,7 @@ final protected static function loadPage(?string $url = null, bool $enableProfil self::request(Request::METHOD_GET, $url); } - final protected static function loginBackendUser(string $email = 'test@fork-cms.com'): User + final protected static function loginBackendUser(string $email = 'test@example.com'): User { try { $userRespository = static::getContainer()->get(UserRepository::class); diff --git a/src/Modules/Installer/tests/fork-cms-installation-configuration.yaml b/src/Modules/Installer/tests/fork-cms-installation-configuration.yaml index 9a19875a23..694442673d 100644 --- a/src/Modules/Installer/tests/fork-cms-installation-configuration.yaml +++ b/src/Modules/Installer/tests/fork-cms-installation-configuration.yaml @@ -1,4 +1,4 @@ -admin-email: test@fork-cms.com +admin-email: test@example.com admin-password: test different-debug-email: false debug-email: null From fb608fe08303f25ae283c9969fbefe31dd9e6a08 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Wed, 4 Oct 2023 12:52:08 +0300 Subject: [PATCH 39/67] Make the first letter of a breadcrumb item a capital letter --- .../Header/Breadcrumb/BreadcrumbCollection.php | 2 +- src/Core/tests/WebTestCase.php | 12 ++++++++---- .../Backend/tests/Backend/Actions/UserAddTest.php | 4 ++-- .../Backend/tests/Backend/Actions/UserIndexTest.php | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Core/Domain/Header/Breadcrumb/BreadcrumbCollection.php b/src/Core/Domain/Header/Breadcrumb/BreadcrumbCollection.php index 06c29eb974..e851c4e3be 100644 --- a/src/Core/Domain/Header/Breadcrumb/BreadcrumbCollection.php +++ b/src/Core/Domain/Header/Breadcrumb/BreadcrumbCollection.php @@ -82,6 +82,6 @@ public function asPageTitle(): string { $items = $this->getItems(); - return implode(' | ', array_reverse($items)); + return implode(' | ', array_reverse(array_map('ucfirst', $items))); } } diff --git a/src/Core/tests/WebTestCase.php b/src/Core/tests/WebTestCase.php index e1b3e4a8cf..78cdc4c10d 100644 --- a/src/Core/tests/WebTestCase.php +++ b/src/Core/tests/WebTestCase.php @@ -47,9 +47,13 @@ final protected static function assertPageLoadedCorrectly( ): void { static::assertHttpStatusCode($url, $httpStatusCode, $requestMethod, $requestParameters); if ($pageTitle !== null) { - self::assertPageTitleSame($pageTitle, 'Page title is not "' . $pageTitle . '".'); + self::assertPageTitleSame( + $pageTitle, + 'Page title is not "' . $pageTitle . '" but "' + . self::getCrawler()->filter('head title')->text() . '.' + ); } - static::assertResponseHasContent(...$expectedContent); + static::assertResponseContains(...$expectedContent); } final protected static function assertHasLink(string $text, string $url): void @@ -91,7 +95,7 @@ final protected static function assertClickOnLink( ); } - final protected static function assertResponseHasContent(string ...$content): void + final protected static function assertResponseContains(string ...$content): void { $response = static::getResponse(); @@ -252,7 +256,7 @@ final protected static function request( protected static function submitForm(string $submitButtonLabel, array $formData, string ...$expectedContent): void { self::getClient()->submit(self::getCrawler()->selectButton($submitButtonLabel)->form(), $formData); - self::assertResponseHasContent(...$expectedContent); + self::assertResponseContains(...$expectedContent); } final protected static function getRequest(): Request diff --git a/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php index 276a31a305..d6052571db 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php @@ -13,7 +13,7 @@ public function testPageLoads(): void self::loginBackendUser(); self::assertPageLoadedCorrectly( self::TEST_URL, - 'Add | users | settings | Fork CMS | Fork CMS', + 'Add | Users | Settings | Fork CMS | Fork CMS', [ 'Display name', 'E-mail', @@ -82,6 +82,6 @@ public function testSubmittedFormRedirectsToIndex(): void self::getClient()->followRedirect(); self::assertCurrentUrlEndsWith('/private/en/backend/user-index'); self::assertDataGridHasLink('jelmer.prins@example.com'); - self::assertResponseHasContent('The user "Jelmer Prins" was added.'); + self::assertResponseContains('The user "Jelmer Prins" was added.'); } } diff --git a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php index f0400ef0af..1803ae50e9 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php @@ -13,7 +13,7 @@ public function testPageLoads(): void self::loginBackendUser(); self::assertPageLoadedCorrectly( self::TEST_URL, - 'Users | settings | Fork CMS | Fork CMS', + 'Users | Settings | Fork CMS | Fork CMS', ['Display name', 'E-mail', 'Super admin', 'Normal user'] ); self::assertHasLink('Add', '/private/en/backend/user-add'); From 618c0b7e86e829ad989894df79824bc15c6ecee3 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Wed, 4 Oct 2023 13:57:21 +0300 Subject: [PATCH 40/67] BackendWebTestCase:loadPage() returns the user when authentication is enabled --- .../Backend/tests/Backend/Actions/UserAddTest.php | 5 ++--- .../Backend/tests/Backend/Actions/UserIndexTest.php | 3 +-- src/Modules/Backend/tests/BackendWebTestCase.php | 13 +++++++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php index d6052571db..c34631a326 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php @@ -28,7 +28,7 @@ public function testPageLoads(): void public function testEmptyFormShowsValidationErrors(): void { self::loadPage(); - self::assertEmptyFormSubmission('user', 3); + self::assertEmptyFormSubmission('user', 3, 'Add'); } public function testWithInvalidData(): void @@ -50,8 +50,7 @@ public function testWithInvalidData(): void public function testUniqueness(): void { - $user = self::loginBackendUser(); - self::loadPage(loginBackendUser: false); + $user = self::loadPage(); self::submitForm( 'Add', diff --git a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php index 1803ae50e9..ddb8014057 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php @@ -21,8 +21,7 @@ public function testPageLoads(): void public function testDataGrid(): void { - $user = self::loginBackendUser(); - self::loadPage(loginBackendUser: false); + $user = self::loadPage(loginBackendUser: false); self::assertDataGridHasLink($user->getEmail(), '/private/en/backend/user-edit/' . $user->getId()); self::assertDataGridHasLink('user@example.com'); diff --git a/src/Modules/Backend/tests/BackendWebTestCase.php b/src/Modules/Backend/tests/BackendWebTestCase.php index fe577d0dc8..c11ab77ac0 100644 --- a/src/Modules/Backend/tests/BackendWebTestCase.php +++ b/src/Modules/Backend/tests/BackendWebTestCase.php @@ -30,10 +30,13 @@ public function testTranslations(): void } } - final protected static function loadPage(?string $url = null, bool $enableProfiler = false, bool $loginBackendUser = true): void + + /** @return ($loginBackendUser is true ? User : null) */ + final protected static function loadPage(?string $url = null, bool $enableProfiler = false, bool $loginBackendUser = true): ?User { + $user = null; if ($loginBackendUser) { - self::loginBackendUser(); + $user = self::loginBackendUser(); } if (defined(static::class . '::TEST_URL') === true) { @@ -45,7 +48,7 @@ final protected static function loadPage(?string $url = null, bool $enableProfil self::request(Request::METHOD_GET, $url); - return; + return $user; } if ($url === null) { @@ -53,6 +56,8 @@ final protected static function loadPage(?string $url = null, bool $enableProfil } self::request(Request::METHOD_GET, $url); + + return $user; } final protected static function loginBackendUser(string $email = 'test@example.com'): User @@ -129,7 +134,7 @@ final protected static function assertDataGridNotEmpty(): void ); } - protected static function assertEmptyFormSubmission(string $formName, int $expectedErrorCount, string $submitButtonLabel = 'Add'): void + protected static function assertEmptyFormSubmission(string $formName, int $expectedErrorCount, string $submitButtonLabel): void { self::submitForm($submitButtonLabel, []); self::assertMinCount( From d446516e22e2203f2df4f80de3d68bba743dfa11 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Wed, 4 Oct 2023 16:49:29 +0300 Subject: [PATCH 41/67] Add functional test for editing users --- .../Backend/Core/Installer/Data/locale.xml | 11 --- src/Core/tests/WebTestCase.php | 5 +- .../Backend/DataFixtures/UserFixture.php | 1 - .../Domain/User/UserDataTransferObject.php | 2 +- .../Backend/assets/installer/translations.xml | 11 +++ .../tests/Backend/Actions/UserEditTest.php | 99 +++++++++++++++++++ 6 files changed, 115 insertions(+), 14 deletions(-) create mode 100644 src/Modules/Backend/tests/Backend/Actions/UserEditTest.php diff --git a/src.fork5/Backend/Core/Installer/Data/locale.xml b/src.fork5/Backend/Core/Installer/Data/locale.xml index ab7248393b..98781bde60 100644 --- a/src.fork5/Backend/Core/Installer/Data/locale.xml +++ b/src.fork5/Backend/Core/Installer/Data/locale.xml @@ -8051,17 +8051,6 @@ - - - - - - - - - - - diff --git a/src/Core/tests/WebTestCase.php b/src/Core/tests/WebTestCase.php index 78cdc4c10d..592dd5ad9f 100644 --- a/src/Core/tests/WebTestCase.php +++ b/src/Core/tests/WebTestCase.php @@ -28,7 +28,10 @@ protected static function getClassFixtures(): array return []; } - final protected static function loadFixture(FixtureInterface ...$fixture): void + /** + * This can only be executed before tests start because they are wrapped in a transaction + */ + private static function loadFixture(FixtureInterface ...$fixture): void { (new ORMExecutor(self::getContainer()->get('doctrine.orm.entity_manager')))->execute($fixture, true); } diff --git a/src/Modules/Backend/DataFixtures/UserFixture.php b/src/Modules/Backend/DataFixtures/UserFixture.php index eecebc7122..4d5f493858 100644 --- a/src/Modules/Backend/DataFixtures/UserFixture.php +++ b/src/Modules/Backend/DataFixtures/UserFixture.php @@ -7,7 +7,6 @@ use ForkCMS\Modules\Backend\Domain\User\Command\CreateUser; use ForkCMS\Modules\Backend\Domain\User\User; use ForkCMS\Modules\Extensions\tests\ForkFixture; -use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; final class UserFixture extends ForkFixture implements DependentFixtureInterface { diff --git a/src/Modules/Backend/Domain/User/UserDataTransferObject.php b/src/Modules/Backend/Domain/User/UserDataTransferObject.php index 9db671f44c..cbbdc124bc 100644 --- a/src/Modules/Backend/Domain/User/UserDataTransferObject.php +++ b/src/Modules/Backend/Domain/User/UserDataTransferObject.php @@ -24,7 +24,7 @@ abstract class UserDataTransferObject implements UniqueDataTransferObjectInterfa /** * @Assert\NotBlank(message="err.PasswordIsRequired", groups={"create"}) - * @Assert\Length(minMessage="err.PasswordIsTooShort", min=12, groups={"create"}) + * @Assert\Length(minMessage="err.PasswordIsTooShort", min=12) * @Assert\NotCompromisedPassword(skipOnError="true") */ public ?string $plainTextPassword = null; diff --git a/src/Modules/Backend/assets/installer/translations.xml b/src/Modules/Backend/assets/installer/translations.xml index 9441ba2e6f..fe31e848e6 100644 --- a/src/Modules/Backend/assets/installer/translations.xml +++ b/src/Modules/Backend/assets/installer/translations.xml @@ -1779,6 +1779,17 @@ + + + + + + + + + + + diff --git a/src/Modules/Backend/tests/Backend/Actions/UserEditTest.php b/src/Modules/Backend/tests/Backend/Actions/UserEditTest.php new file mode 100644 index 0000000000..ec8647ac6e --- /dev/null +++ b/src/Modules/Backend/tests/Backend/Actions/UserEditTest.php @@ -0,0 +1,99 @@ +getDisplayName() . ' | Edit | Users | Settings | Fork CMS | Fork CMS', + [ + 'Display name', + 'E-mail', + 'Super admin (grant access to everything)', + 'Enable CMS access for this account.', + 'Short date format', + $user->getDisplayName(), + $user->getEmail(), + ] + ); + self::assertHasLink('Cancel', '/private/en/backend/user-index'); + } + + public function testEditWithoutChanges(): void + { + $user = self::loadPage(); + self::assertEmptyFormSubmission('user', 0, 'Save'); + self::getClient()->followRedirect(); + self::assertCurrentUrlEndsWith('/private/en/backend/user-index'); + self::assertDataGridHasLink($user->getEmail()); + self::assertResponseContains('The settings for "' . $user->getDisplayName() . '" were saved.'); + } + + public function testWithInvalidData(): void + { + self::loadPage(); + + self::submitForm( + 'Save', + [ + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'jelmer.prins', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'I<3ForkCMS', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'I<3ForkCMS', + ], + 'The password is too short.', + 'Please provide a valid e-mail address.', + ); + } + + public function testUniqueness(): void + { + self::loadPage(); + + self::submitForm( + 'Save', + [ + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => 'Super Admin', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'super-admin@example.com', + ], + 'This e-mailaddress is in use.', + 'This display name is in use.', + ); + } + + public function testSubmittedFormRedirectsToIndex(): void + { + self::loadPage(); + + self::submitForm( + 'Save', + [ + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => 'Jelmer Prins', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'jelmer.prins@example.com', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'IAbsolutely<3ForkCMS', + 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'IAbsolutely<3ForkCMS', + ], + ); + self::getClient()->followRedirect(); + self::assertCurrentUrlEndsWith('/private/en/backend/user-index'); + self::assertDataGridHasLink('jelmer.prins@example.com'); + self::assertResponseContains('The settings for "Jelmer Prins" were saved.'); + } + + protected static function getClassFixtures(): array + { + return [ + new UserGroupFixture(), + new UserFixture(), + ]; + } +} From 01f64c9b07de589b2ecef1b709008083a9b69573 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 17 Nov 2023 13:14:16 +0100 Subject: [PATCH 42/67] Use readable names for the tabs --- .../Backend/Core/Installer/Data/locale.xml | 36 ------------- src/Core/Domain/Form/TabsType.php | 21 +++++++- src/Core/tests/WebTestCase.php | 2 +- .../templates/base/formTheme.html.twig | 2 +- .../tests/Backend/Actions/UserAddTest.php | 24 ++++----- .../tests/Backend/Actions/UserEditTest.php | 18 +++---- .../Domain/Revision/Form/RevisionType.php | 54 ++++++++++--------- .../Pages/assets/installer/translations.xml | 18 +++++++ 8 files changed, 89 insertions(+), 86 deletions(-) diff --git a/src.fork5/Backend/Core/Installer/Data/locale.xml b/src.fork5/Backend/Core/Installer/Data/locale.xml index 98781bde60..d588de8a30 100644 --- a/src.fork5/Backend/Core/Installer/Data/locale.xml +++ b/src.fork5/Backend/Core/Installer/Data/locale.xml @@ -380,24 +380,6 @@ - - - - - - - - - - - - - - - - - - @@ -5515,24 +5497,6 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/Core/Domain/Form/TabsType.php b/src/Core/Domain/Form/TabsType.php index 23e1e2ae8b..7ae4a46fc8 100644 --- a/src/Core/Domain/Form/TabsType.php +++ b/src/Core/Domain/Form/TabsType.php @@ -2,19 +2,25 @@ namespace ForkCMS\Core\Domain\Form; +use RuntimeException; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\String\Slugger\SluggerInterface; final class TabsType extends AbstractType { + public function __construct(private readonly SluggerInterface $slugger) + { + } + public function buildForm(FormBuilderInterface $builder, array $options): void { foreach ($options['tabs'] as $label => $fields) { $builder->add( - md5($label), + self::getTabNameForLabel($label, $this->slugger), TabType::class, [ 'fields' => $fields, @@ -26,6 +32,19 @@ public function buildForm(FormBuilderInterface $builder, array $options): void } } + /** + * @param ?SluggerInterface $slugger You don't need to provide the slugger if you are trying to get an existing tab + */ + public static function getTabNameForLabel(string $label, ?SluggerInterface $slugger = null): string + { + static $cachedSlugger = null; + if ($cachedSlugger === null) { + $cachedSlugger = $slugger ?? throw new RuntimeException('No slugger provided'); + } + + return $cachedSlugger->slug(str_replace('lbl.', 'tab.', $label), '_'); + } + public function configureOptions(OptionsResolver $resolver): void { $resolver diff --git a/src/Core/tests/WebTestCase.php b/src/Core/tests/WebTestCase.php index 592dd5ad9f..7007c6db44 100644 --- a/src/Core/tests/WebTestCase.php +++ b/src/Core/tests/WebTestCase.php @@ -256,7 +256,7 @@ final protected static function request( } /** @param array $formData */ - protected static function submitForm(string $submitButtonLabel, array $formData, string ...$expectedContent): void + protected static function submitForm(string $submitButtonLabel, array $formData = [], string ...$expectedContent): void { self::getClient()->submit(self::getCrawler()->selectButton($submitButtonLabel)->form(), $formData); self::assertResponseContains(...$expectedContent); diff --git a/src/Modules/Backend/templates/base/formTheme.html.twig b/src/Modules/Backend/templates/base/formTheme.html.twig index cac199a3aa..71cab13c93 100644 --- a/src/Modules/Backend/templates/base/formTheme.html.twig +++ b/src/Modules/Backend/templates/base/formTheme.html.twig @@ -501,7 +501,7 @@ {% block tab_row %} {%- set tabClass = ' tab-pane fade row' ~ (form.parent.children | keys | first == form.vars.name ? ' show active' : '') -%} - {{ form_widget(form, {attr: {role:'tabpanel', 'aria-labelledby': form.vars.id ~ '-tab', class: (form.vars.attr.class|default('') ~ tabClass)|trim} }) }} + {{ form_widget(form, {attr: {role:'tabpanel', 'aria-labelledby': form.vars.id, class: (form.vars.attr.class|default('') ~ tabClass)|trim} }) }} {% endblock %} {% block user_group_permission_row %} diff --git a/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php index c34631a326..1eb52f4d33 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserAddTest.php @@ -38,10 +38,10 @@ public function testWithInvalidData(): void self::submitForm( 'Add', [ - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => 'Jelmer Prins', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'jelmer.prins', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'I<3ForkCMS', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'I<3ForkCMS', + 'user[user][tab_Authentication][displayName]' => 'Jelmer Prins', + 'user[user][tab_Authentication][email]' => 'jelmer.prins', + 'user[user][tab_Authentication][plainTextPassword][first]' => 'I<3ForkCMS', + 'user[user][tab_Authentication][plainTextPassword][second]' => 'I<3ForkCMS', ], 'The password is too short.', 'Please provide a valid e-mail address.', @@ -55,10 +55,10 @@ public function testUniqueness(): void self::submitForm( 'Add', [ - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => $user->getDisplayName(), - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => $user->getEmail(), - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'IAbsolutely<3ForkCMS', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'IAbsolutely<3ForkCMS', + 'user[user][tab_Authentication][displayName]' => $user->getDisplayName(), + 'user[user][tab_Authentication][email]' => $user->getEmail(), + 'user[user][tab_Authentication][plainTextPassword][first]' => 'IAbsolutely<3ForkCMS', + 'user[user][tab_Authentication][plainTextPassword][second]' => 'IAbsolutely<3ForkCMS', ], 'This e-mailaddress is in use.', 'This display name is in use.', @@ -72,10 +72,10 @@ public function testSubmittedFormRedirectsToIndex(): void self::submitForm( 'Add', [ - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => 'Jelmer Prins', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'jelmer.prins@example.com', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'IAbsolutely<3ForkCMS', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'IAbsolutely<3ForkCMS', + 'user[user][tab_Authentication][displayName]' => 'Jelmer Prins', + 'user[user][tab_Authentication][email]' => 'jelmer.prins@example.com', + 'user[user][tab_Authentication][plainTextPassword][first]' => 'IAbsolutely<3ForkCMS', + 'user[user][tab_Authentication][plainTextPassword][second]' => 'IAbsolutely<3ForkCMS', ], ); self::getClient()->followRedirect(); diff --git a/src/Modules/Backend/tests/Backend/Actions/UserEditTest.php b/src/Modules/Backend/tests/Backend/Actions/UserEditTest.php index ec8647ac6e..213c20eb44 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserEditTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserEditTest.php @@ -46,9 +46,9 @@ public function testWithInvalidData(): void self::submitForm( 'Save', [ - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'jelmer.prins', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'I<3ForkCMS', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'I<3ForkCMS', + 'user[user][tab_Authentication][email]' => 'jelmer.prins', + 'user[user][tab_Authentication][plainTextPassword][first]' => 'I<3ForkCMS', + 'user[user][tab_Authentication][plainTextPassword][second]' => 'I<3ForkCMS', ], 'The password is too short.', 'Please provide a valid e-mail address.', @@ -62,8 +62,8 @@ public function testUniqueness(): void self::submitForm( 'Save', [ - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => 'Super Admin', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'super-admin@example.com', + 'user[user][tab_Authentication][displayName]' => 'Super Admin', + 'user[user][tab_Authentication][email]' => UserFixture::SUPER_ADMIN_EMAIL, ], 'This e-mailaddress is in use.', 'This display name is in use.', @@ -77,10 +77,10 @@ public function testSubmittedFormRedirectsToIndex(): void self::submitForm( 'Save', [ - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][displayName]' => 'Jelmer Prins', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][email]' => 'jelmer.prins@example.com', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][first]' => 'IAbsolutely<3ForkCMS', - 'user[user][ec05aaca240e74a0604d93f9e5a7caef][plainTextPassword][second]' => 'IAbsolutely<3ForkCMS', + 'user[user][tab_Authentication][displayName]' => 'Jelmer Prins', + 'user[user][tab_Authentication][email]' => 'jelmer.prins@example.com', + 'user[user][tab_Authentication][plainTextPassword][first]' => 'IAbsolutely<3ForkCMS', + 'user[user][tab_Authentication][plainTextPassword][second]' => 'IAbsolutely<3ForkCMS', ], ); self::getClient()->followRedirect(); diff --git a/src/Modules/Pages/Domain/Revision/Form/RevisionType.php b/src/Modules/Pages/Domain/Revision/Form/RevisionType.php index f4c3ce546f..f448a4f493 100644 --- a/src/Modules/Pages/Domain/Revision/Form/RevisionType.php +++ b/src/Modules/Pages/Domain/Revision/Form/RevisionType.php @@ -7,7 +7,6 @@ use ForkCMS\Modules\Frontend\Domain\Meta\MetaType; use ForkCMS\Modules\Pages\Domain\Page\Page; use ForkCMS\Modules\Pages\Domain\Page\PageRouter; -use ForkCMS\Modules\Pages\Domain\Revision\Command\CreateRevision; use ForkCMS\Modules\Pages\Domain\Revision\RevisionDataTransferObject; use ForkCMS\Modules\Pages\Domain\Revision\RevisionRepository; use Symfony\Component\Form\AbstractType; @@ -39,7 +38,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void }, 'lbl.Settings' => static function (FormBuilderInterface $builder): void { // added through the pre-set data event - } + }, ], 'left_tabs_count' => 1, ] @@ -47,41 +46,44 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $builder->addEventListener( FormEvents::PRE_SET_DATA, function (FormEvent $event): void { - $revisionDataTransferObject = $event->getData(); + $revisionDTO = $event->getData(); $tabs = $event->getForm()->get('tabs'); - $tabs->get(md5('lbl.Content'))->add( + $tabs->get(TabsType::getTabNameForLabel('lbl.Content'))->add( 'content', RevisionContentType::class, [ - 'selectedTemplate' => $revisionDataTransferObject->themeTemplate, - 'load_default_blocks' => !$revisionDataTransferObject->page->hasId(), + 'selectedTemplate' => $revisionDTO->themeTemplate, + 'load_default_blocks' => !$revisionDTO->page->hasId(), ] ); - $tabs->get(md5('lbl.Settings'))->add( + $tabs->get(TabsType::getTabNameForLabel('lbl.Settings'))->add( 'settings', RevisionSettingsType::class, [ - 'disable_allow_move' => $revisionDataTransferObject->page->isForbiddenToMove(), - 'disable_allow_delete' => $revisionDataTransferObject->page->isForbiddenToDelete(), - 'disable_allow_children' => $revisionDataTransferObject->page->isForbiddenToHaveChildren(), + 'disable_allow_move' => $revisionDTO->page->isForbiddenToMove(), + 'disable_allow_delete' => $revisionDTO->page->isForbiddenToDelete(), + 'disable_allow_children' => $revisionDTO->page->isForbiddenToHaveChildren(), + ] + ); + $tabs->get(TabsType::getTabNameForLabel('lbl.SEO'))->add( + 'meta', + MetaType::class, [ + 'disable_slug_overwrite' => $revisionDTO->page->isHome(), + 'base_field_name' => 'title', + 'base_url' => $this->pageRouter->getRouteForPageId( + $revisionDTO->parentPage !== null + ? $revisionDTO->parentPage->getId() + : Page::PAGE_ID_HOME + ), + 'generate_slug_callback_class' => RevisionRepository::class, + 'generate_slug_callback_method' => 'generateSlug', + 'generate_slug_callback_parameters' => [ + $revisionDTO->locale, + $revisionDTO->hasEntity() ? $revisionDTO->getEntity()->getId() : null, + ], ] ); - $tabs->get(md5('lbl.SEO'))->add('meta', MetaType::class, [ - 'disable_slug_overwrite' => $revisionDataTransferObject->page->isHome(), - 'base_field_name' => 'title', - 'base_url' => $this->pageRouter->getRouteForPageId( - $revisionDataTransferObject->parentPage !== null - ? $revisionDataTransferObject->parentPage->getId() - : Page::PAGE_ID_HOME - ), - 'generate_slug_callback_class' => RevisionRepository::class, - 'generate_slug_callback_method' => 'generateSlug', - 'generate_slug_callback_parameters' => [ - $revisionDataTransferObject->locale, - $revisionDataTransferObject->hasEntity() ? $revisionDataTransferObject->getEntity()->getId() : null, - ], - ]); - if ($revisionDataTransferObject->hasEntity()) { + if ($revisionDTO->hasEntity()) { $event->getForm()->add( 'saveAsDraft', SubmitType::class, diff --git a/src/Modules/Pages/assets/installer/translations.xml b/src/Modules/Pages/assets/installer/translations.xml index 0ed20102b6..b829d968bc 100644 --- a/src/Modules/Pages/assets/installer/translations.xml +++ b/src/Modules/Pages/assets/installer/translations.xml @@ -1,6 +1,24 @@ + + + + + + + + + + + + + + + + + + From ccaed8ae383d6959bcf212b7751be2305a6563b7 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 17 Nov 2023 17:26:32 +0100 Subject: [PATCH 43/67] Better flash messages when deleting users --- .../Backend/Core/Installer/Data/locale.xml | 33 --------------- .../Breadcrumb/BreadcrumbCollection.php | 3 +- .../Backend/Backend/Actions/UserDelete.php | 16 ++++++- .../Action/AbstractDeleteActionController.php | 15 +++---- .../Backend/assets/installer/translations.xml | 42 +++++++++++++++++++ .../Domain/Revision/Form/RevisionType.php | 3 +- .../Pages/assets/installer/translations.xml | 32 +++++++------- 7 files changed, 85 insertions(+), 59 deletions(-) diff --git a/src.fork5/Backend/Core/Installer/Data/locale.xml b/src.fork5/Backend/Core/Installer/Data/locale.xml index d588de8a30..7e1295ace5 100644 --- a/src.fork5/Backend/Core/Installer/Data/locale.xml +++ b/src.fork5/Backend/Core/Installer/Data/locale.xml @@ -3931,23 +3931,6 @@ - - - - - - - - - - - - - - - - - @@ -13031,22 +13014,6 @@ - - - - - - - - - - - - - - - - diff --git a/src/Core/Domain/Header/Breadcrumb/BreadcrumbCollection.php b/src/Core/Domain/Header/Breadcrumb/BreadcrumbCollection.php index e851c4e3be..7aa11f39f6 100644 --- a/src/Core/Domain/Header/Breadcrumb/BreadcrumbCollection.php +++ b/src/Core/Domain/Header/Breadcrumb/BreadcrumbCollection.php @@ -80,8 +80,9 @@ public function getIterator(): Traversable public function asPageTitle(): string { + /** @var string[] $items the breadcrumbs are stringable but phpstan doesn't understand it */ $items = $this->getItems(); - return implode(' | ', array_reverse(array_map('ucfirst', $items))); + return implode(' | ', array_reverse(array_map(ucfirst(...), $items))); } } diff --git a/src/Modules/Backend/Backend/Actions/UserDelete.php b/src/Modules/Backend/Backend/Actions/UserDelete.php index 17563aacce..7a3dead018 100644 --- a/src/Modules/Backend/Backend/Actions/UserDelete.php +++ b/src/Modules/Backend/Backend/Actions/UserDelete.php @@ -2,10 +2,13 @@ namespace ForkCMS\Modules\Backend\Backend\Actions; +use ForkCMS\Core\Domain\Header\FlashMessage\FlashMessage; use ForkCMS\Modules\Backend\Domain\Action\AbstractDeleteActionController; use ForkCMS\Modules\Backend\Domain\User\Command\DeleteUser; +use ForkCMS\Modules\Backend\Domain\User\User; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Delete users from the backend. @@ -14,10 +17,21 @@ final class UserDelete extends AbstractDeleteActionController { protected function getFormResponse(Request $request): RedirectResponse { + $successFlashMessage = null; + $userId = $request->request->all('action')['id'] ?? null; + if ($userId !== null) { + $user = $this->getRepository(User::class)->find($userId); + if ($user instanceof User) { + $successFlashMessage = FlashMessage::success('UserDeleted', ['%user%' => $user->getDisplayName()]); + } + } + return $this->handleDeleteForm( $request, DeleteUser::class, - UserIndex::getActionSlug() + UserIndex::getActionSlug(), + $successFlashMessage, + notFoundFlashMessage: FlashMessage::error('NonExistingUser') ); } } diff --git a/src/Modules/Backend/Domain/Action/AbstractDeleteActionController.php b/src/Modules/Backend/Domain/Action/AbstractDeleteActionController.php index d551084e53..9f6c1b7301 100644 --- a/src/Modules/Backend/Domain/Action/AbstractDeleteActionController.php +++ b/src/Modules/Backend/Domain/Action/AbstractDeleteActionController.php @@ -17,22 +17,23 @@ protected function addBreadcrumbForRequest(Request $request): void } /** - * @param callable(FormInterface): FlashMessage|null $flashMessageCallback + * @param callable(FormInterface): FlashMessage|null $successFlashMessageCallback */ protected function handleDeleteForm( Request $request, string $deleteCommandFullyQualifiedClassName, ActionSlug $redirectActionSlug, - ?FlashMessage $flashMessage = null, - ?callable $flashMessageCallback = null, + ?FlashMessage $successFlashMessage = null, + ?callable $successFlashMessageCallback = null, + ?FlashMessage $notFoundFlashMessage = null, ): RedirectResponse { $response = $this->handleForm( request: $request, formType: ActionType::class, - flashMessage: $flashMessage ?? FlashMessage::success('Deleted'), + flashMessage: $successFlashMessage ?? FlashMessage::success('Deleted'), formOptions: ['actionSlug' => self::getActionSlug()], - defaultCallback: function () use ($redirectActionSlug): RedirectResponse { - $this->header->addFlashMessage(FlashMessage::error('NotFound')); + defaultCallback: function () use ($redirectActionSlug, $notFoundFlashMessage): RedirectResponse { + $this->header->addFlashMessage($notFoundFlashMessage ?? FlashMessage::error('NotFound')); return new RedirectResponse($redirectActionSlug->generateRoute($this->router)); }, @@ -41,7 +42,7 @@ protected function handleDeleteForm( return new RedirectResponse($redirectActionSlug->generateRoute($this->router)); }, - flashMessageCallback: $flashMessageCallback, + flashMessageCallback: $successFlashMessageCallback, ); if ($response instanceof RedirectResponse) { diff --git a/src/Modules/Backend/assets/installer/translations.xml b/src/Modules/Backend/assets/installer/translations.xml index fe31e848e6..aa6c45a861 100644 --- a/src/Modules/Backend/assets/installer/translations.xml +++ b/src/Modules/Backend/assets/installer/translations.xml @@ -728,6 +728,22 @@ + + + + + + + + + + + + + + + + @@ -1790,6 +1806,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Modules/Pages/Domain/Revision/Form/RevisionType.php b/src/Modules/Pages/Domain/Revision/Form/RevisionType.php index f448a4f493..d4313429be 100644 --- a/src/Modules/Pages/Domain/Revision/Form/RevisionType.php +++ b/src/Modules/Pages/Domain/Revision/Form/RevisionType.php @@ -67,7 +67,8 @@ function (FormEvent $event): void { ); $tabs->get(TabsType::getTabNameForLabel('lbl.SEO'))->add( 'meta', - MetaType::class, [ + MetaType::class, + [ 'disable_slug_overwrite' => $revisionDTO->page->isHome(), 'base_field_name' => 'title', 'base_url' => $this->pageRouter->getRouteForPageId( diff --git a/src/Modules/Pages/assets/installer/translations.xml b/src/Modules/Pages/assets/installer/translations.xml index b829d968bc..e33ddb29ad 100644 --- a/src/Modules/Pages/assets/installer/translations.xml +++ b/src/Modules/Pages/assets/installer/translations.xml @@ -2,22 +2,22 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + From 43c57113597c204c0f4a1183e29cb5d78c40f38f Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 17 Nov 2023 17:30:14 +0100 Subject: [PATCH 44/67] Fix error flashmessages not showing --- src/Core/Domain/Header/FlashMessage/FlashMessageType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Domain/Header/FlashMessage/FlashMessageType.php b/src/Core/Domain/Header/FlashMessage/FlashMessageType.php index 669e9a7a2b..b9db3f93c5 100644 --- a/src/Core/Domain/Header/FlashMessage/FlashMessageType.php +++ b/src/Core/Domain/Header/FlashMessage/FlashMessageType.php @@ -7,5 +7,5 @@ enum FlashMessageType: string case INFO = 'info'; case SUCCESS = 'success'; case WARNING = 'warning'; - case ERROR = 'danger'; + case ERROR = 'error'; } From becadc16f7792709c4f35829cd5dcd744e6e4469 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 17 Nov 2023 17:31:54 +0100 Subject: [PATCH 45/67] Implement testing deleting users --- .../Backend/DataFixtures/UserFixture.php | 2 + .../templates/base/crud/edit.html.twig | 47 ++++++++++--------- .../tests/Backend/Actions/UserDeleteTest.php | 47 +++++++++++++++++++ .../tests/Backend/Actions/UserIndexTest.php | 10 ++-- .../Backend/tests/BackendWebTestCase.php | 17 ++++--- 5 files changed, 89 insertions(+), 34 deletions(-) create mode 100644 src/Modules/Backend/tests/Backend/Actions/UserDeleteTest.php diff --git a/src/Modules/Backend/DataFixtures/UserFixture.php b/src/Modules/Backend/DataFixtures/UserFixture.php index 4d5f493858..d58b597ec5 100644 --- a/src/Modules/Backend/DataFixtures/UserFixture.php +++ b/src/Modules/Backend/DataFixtures/UserFixture.php @@ -13,7 +13,9 @@ final class UserFixture extends ForkFixture implements DependentFixtureInterface public const PLAIN_TEXT_PASSWORD = 'test'; private const PASSWORD = '$2y$13$YJGHIjorExsQSD9VCzMgBuwi5QS3Zr2tVBisWS4vrcKDin/9BdBQq'; public const SUPER_ADMIN_REFERENCE = 'user-super-admin'; + public const SUPER_ADMIN_EMAIL = 'super-admin@example.com'; public const USER_REFERENCE = 'user-super-admin'; + public const USER_EMAIL = 'user@example.com'; public function __construct() { diff --git a/src/Modules/Backend/templates/base/crud/edit.html.twig b/src/Modules/Backend/templates/base/crud/edit.html.twig index a6f61f94e3..2934956d35 100644 --- a/src/Modules/Backend/templates/base/crud/edit.html.twig +++ b/src/Modules/Backend/templates/base/crud/edit.html.twig @@ -13,29 +13,6 @@ {% if formTheme is defined %} {% form_theme backend_form with ['@Backend/base/formTheme.html.twig',formTheme] %} {% endif %} - {{ form_start(backend_form) }} - {{ form_widget(backend_form) }} - {{ form_errors(backend_form) }} - {{ form_rest(backend_form) }} - -
-
- {% if crudDeleteAction is defined and is_allowed(crudDeleteAction, crudDeleteModule) %} - {{ macro.buttonIcon('', deleteIcon|default('trash'), deleteLabel|default('lbl.Delete'|trans|ucfirst), deleteClass|default('btn-danger'), {"type":"button", "data-bs-toggle":"modal", "data-bs-target":"#confirmDelete"}) }} - {% endif %} - {% if crudIndexAction is defined and is_allowed(crudIndexAction, crudIndexModule) %} -
- {{ macro.buttonIcon(action_path(crudIndexAction, crudIndexModule, crudIndexParameters, crudIndexRelative, crudIndexLocale), crudIndexIcon|default('times'), crudIndexLabel|default('lbl.Cancel'|trans|ucfirst), crudIndexClass|default('btn-default')) }} -
- {% endif %} - {% block leftToolbar %}{% endblock %} -
-
- {% block rightToolbar %}{% endblock %} - {{ macro.buttonIcon('', submitIcon|default('save'), submitLabel|default('lbl.Save'|trans|ucfirst), submitClass|default('btn-primary')) }} -
-
- {{ form_end(backend_form) }} {% if crudDeleteAction is defined and is_allowed(crudDeleteAction, crudDeleteModule) %} {{ form_start(backend_delete_form) }} @@ -59,4 +36,28 @@
{{ form_end(backend_delete_form) }} {% endif %} + + {{ form_start(backend_form) }} + {{ form_widget(backend_form) }} + {{ form_errors(backend_form) }} + {{ form_rest(backend_form) }} + +
+
+ {% if crudDeleteAction is defined and is_allowed(crudDeleteAction, crudDeleteModule) %} + {{ macro.buttonIcon('', deleteIcon|default('trash'), deleteLabel|default('lbl.Delete'|trans|ucfirst), deleteClass|default('btn-danger'), {"type":"button", "data-bs-toggle":"modal", "data-bs-target":"#confirmDelete"}) }} + {% endif %} + {% if crudIndexAction is defined and is_allowed(crudIndexAction, crudIndexModule) %} +
+ {{ macro.buttonIcon(action_path(crudIndexAction, crudIndexModule, crudIndexParameters, crudIndexRelative, crudIndexLocale), crudIndexIcon|default('times'), crudIndexLabel|default('lbl.Cancel'|trans|ucfirst), crudIndexClass|default('btn-default')) }} +
+ {% endif %} + {% block leftToolbar %}{% endblock %} +
+
+ {% block rightToolbar %}{% endblock %} + {{ macro.buttonIcon('', submitIcon|default('save'), submitLabel|default('lbl.Save'|trans|ucfirst), submitClass|default('btn-primary')) }} +
+
+ {{ form_end(backend_form) }} {% endblock %} diff --git a/src/Modules/Backend/tests/Backend/Actions/UserDeleteTest.php b/src/Modules/Backend/tests/Backend/Actions/UserDeleteTest.php new file mode 100644 index 0000000000..b71be4b3ee --- /dev/null +++ b/src/Modules/Backend/tests/Backend/Actions/UserDeleteTest.php @@ -0,0 +1,47 @@ +followRedirect(); + self::assertCurrentUrlEndsWith('/private/en/backend/user-index'); + self::assertDataGridHasLink($user->getEmail()); + self::assertResponseContains('This user doesn\'t exist.'); + } + + public function testSubmittedFormDeletesUser(): void + { + self::loadPage('/private/en/backend/user-index'); + self::assertClickOnLink(UserFixture::SUPER_ADMIN_EMAIL, []); + self::assertCurrentUrlContains('/private/en/backend/user-edit/'); + self::submitForm('Delete'); + self::getClient()->followRedirect(); + self::assertCurrentUrlEndsWith('/private/en/backend/user-index'); + self::assertDataGridHasLink(UserFixture::USER_EMAIL); + self::assertDataGridNotHasLink(UserFixture::SUPER_ADMIN_EMAIL); + self::assertResponseContains('The user "Super admin" was deleted.'); + } + + protected static function getClassFixtures(): array + { + return [ + new UserGroupFixture(), + new UserFixture(), + ]; + } +} diff --git a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php index ddb8014057..51881f628f 100644 --- a/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php +++ b/src/Modules/Backend/tests/Backend/Actions/UserIndexTest.php @@ -21,13 +21,15 @@ public function testPageLoads(): void public function testDataGrid(): void { - $user = self::loadPage(loginBackendUser: false); + $user = self::loadPage(); self::assertDataGridHasLink($user->getEmail(), '/private/en/backend/user-edit/' . $user->getId()); - self::assertDataGridHasLink('user@example.com'); + self::assertDataGridHasLink(UserFixture::USER_EMAIL); + self::assertDataGridHasLink(UserFixture::SUPER_ADMIN_EMAIL); self::assertDataGridNotHasLink('demo@example.com'); - self::filterDataGrid('User.email', 'user@example.com'); - self::assertDataGridHasLink('user@example.com'); + self::filterDataGrid('User.email', UserFixture::USER_EMAIL); + self::assertDataGridHasLink(UserFixture::USER_EMAIL); + self::assertDataGridNotHasLink(UserFixture::SUPER_ADMIN_EMAIL); self::assertDataGridNotHasLink('demo@example.com'); self::assertDataGridNotHasLink($user->getEmail()); self::filterDataGrid('User.email', $user->getEmail()); diff --git a/src/Modules/Backend/tests/BackendWebTestCase.php b/src/Modules/Backend/tests/BackendWebTestCase.php index c11ab77ac0..9d30f9df56 100644 --- a/src/Modules/Backend/tests/BackendWebTestCase.php +++ b/src/Modules/Backend/tests/BackendWebTestCase.php @@ -36,19 +36,20 @@ final protected static function loadPage(?string $url = null, bool $enableProfil { $user = null; if ($loginBackendUser) { - $user = self::loginBackendUser(); + $user = self::loginBackendUser(url: $url); } if (defined(static::class . '::TEST_URL') === true) { $url = $url ?? static::TEST_URL; - if ($enableProfiler) { + if ($enableProfiler && $url !== null) { $url .= str_contains($url, '?') ? '&enable-framework-profiler=1' : '?enable-framework-profiler=1'; } - self::request(Request::METHOD_GET, $url); - - return $user; + if ($loginBackendUser) { + // we are already on the page + return $user; + } } if ($url === null) { @@ -60,7 +61,7 @@ final protected static function loadPage(?string $url = null, bool $enableProfil return $user; } - final protected static function loginBackendUser(string $email = 'test@example.com'): User + final protected static function loginBackendUser(string $email = 'test@example.com', ?string $url = null): User { try { $userRespository = static::getContainer()->get(UserRepository::class); @@ -71,7 +72,9 @@ final protected static function loginBackendUser(string $email = 'test@example.c $user = $userRespository->findOneBy(['email' => $email]); static::assertNotNull($user, 'User with email "' . $email . '" not found.'); static::getClient()->loginUser($user, 'backend'); - static::request(Request::METHOD_GET, static::TEST_URL); + if (defined(static::class . '::TEST_URL') === true || $url !== null) { + static::request(Request::METHOD_GET, $url ?? static::TEST_URL); + } return $user; } From c1d2f6d0f2f2f1e83692e74ffb3f85d00c184949 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 17 Nov 2023 17:32:08 +0100 Subject: [PATCH 46/67] Add comment why the email length is limited --- src/Modules/Backend/Domain/User/UserDataTransferObject.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Modules/Backend/Domain/User/UserDataTransferObject.php b/src/Modules/Backend/Domain/User/UserDataTransferObject.php index cbbdc124bc..019ddedce7 100644 --- a/src/Modules/Backend/Domain/User/UserDataTransferObject.php +++ b/src/Modules/Backend/Domain/User/UserDataTransferObject.php @@ -18,6 +18,7 @@ abstract class UserDataTransferObject implements UniqueDataTransferObjectInterfa /** * @Assert\Email(message="err.EmailIsInvalid") * @Assert\NotBlank (message="err.EmailIsRequired") + * We have to limit the length because of the email because of unique index * @Assert\Length(max=180, maxMessage="err.EmailIsTooLong") */ public ?string $email = null; From 59236eb05bfec24ff76435a44161776bf169b4f3 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sun, 26 Nov 2023 16:32:16 +0100 Subject: [PATCH 47/67] Fix password strength indicator not working during install --- src/Core/assets/js/Components/PasswordStrenghtMeter.js | 2 +- .../Domain/Authentication/InstallerPasswordType.php | 4 ++-- .../Installer/assets/Installer/webpack/js/Installer.js | 8 ++++++++ src/Modules/Installer/templates/form_theme.html.twig | 9 +++++++++ src/Modules/Installer/templates/step5.html.twig | 1 + 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Core/assets/js/Components/PasswordStrenghtMeter.js b/src/Core/assets/js/Components/PasswordStrenghtMeter.js index 608b0be1c4..0e6bd11c81 100644 --- a/src/Core/assets/js/Components/PasswordStrenghtMeter.js +++ b/src/Core/assets/js/Components/PasswordStrenghtMeter.js @@ -1,4 +1,4 @@ -const passwordStrength = require('check-password-strength') +const { passwordStrength } = require('check-password-strength') export class PasswordStrenghtMeter { constructor (element) { diff --git a/src/Modules/Installer/Domain/Authentication/InstallerPasswordType.php b/src/Modules/Installer/Domain/Authentication/InstallerPasswordType.php index 47f99183bb..d1577d94b4 100644 --- a/src/Modules/Installer/Domain/Authentication/InstallerPasswordType.php +++ b/src/Modules/Installer/Domain/Authentication/InstallerPasswordType.php @@ -2,8 +2,8 @@ namespace ForkCMS\Modules\Installer\Domain\Authentication; +use ForkCMS\Core\Domain\Form\TogglePasswordType; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -28,6 +28,6 @@ public function configureOptions(OptionsResolver $resolver): void public function getParent(): string { - return PasswordType::class; + return TogglePasswordType::class; } } diff --git a/src/Modules/Installer/assets/Installer/webpack/js/Installer.js b/src/Modules/Installer/assets/Installer/webpack/js/Installer.js index 008864467b..ca5ea8501d 100644 --- a/src/Modules/Installer/assets/Installer/webpack/js/Installer.js +++ b/src/Modules/Installer/assets/Installer/webpack/js/Installer.js @@ -3,6 +3,7 @@ import { Controls } from '../../../../../../Core/assets/js/Components/Controls' import { Forms } from './Components/Forms' import { Layout } from './Components/Layout' import { PasswordStrenghtMeter } from '../../../../../../Core/assets/js/Components/PasswordStrenghtMeter' +import { TogglePasswordInputType } from '../../../../../../Core/assets/js/Components/TogglePasswordInputType' export class Installer { initInstaller () { @@ -12,6 +13,7 @@ export class Installer { this.layout = new Layout() Installer.initPasswordStrenghtMeters() + Installer.initTogglePasswordInputType() } static initPasswordStrenghtMeters () { @@ -19,6 +21,12 @@ export class Installer { element.passwordStrengthMeter = new PasswordStrenghtMeter($(element)) }) } + + static initTogglePasswordInputType () { + document.querySelectorAll('[data-role="toggle-password-visibility"]').forEach((element) => { + element.togglePassword = new TogglePasswordInputType(element) + }) + } } $(window).on('load', () => { diff --git a/src/Modules/Installer/templates/form_theme.html.twig b/src/Modules/Installer/templates/form_theme.html.twig index 25d5fe3e90..4aff5fa6de 100644 --- a/src/Modules/Installer/templates/form_theme.html.twig +++ b/src/Modules/Installer/templates/form_theme.html.twig @@ -200,3 +200,12 @@
{%- endblock collection_row -%} + +{% block toggle_password_widget %} +
+ {{ form_widget(form) }} + +
+{% endblock %} diff --git a/src/Modules/Installer/templates/step5.html.twig b/src/Modules/Installer/templates/step5.html.twig index 9e9d2d25e4..af9897b4c2 100644 --- a/src/Modules/Installer/templates/step5.html.twig +++ b/src/Modules/Installer/templates/step5.html.twig @@ -33,6 +33,7 @@
None + Too weak Weak Average Strong From ae67b8f3ce25e29427e703c9f47621531a841140 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Wed, 22 Nov 2023 12:50:57 +0100 Subject: [PATCH 48/67] Proper implementation for page and content title --- .../Backend/Core/Engine/TwigTemplate.php | 6 --- src.fork5/Frontend/Core/Header/Header.php | 10 ----- .../Breadcrumb/BreadcrumbCollection.php | 11 +++++ src/Core/Domain/Header/ContentTitle.php | 42 +++++++++++++++++++ src/Core/Domain/Header/Header.php | 2 + src/Core/Domain/Header/PageTitle.php | 14 ++++++- 6 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 src/Core/Domain/Header/ContentTitle.php diff --git a/src.fork5/Backend/Core/Engine/TwigTemplate.php b/src.fork5/Backend/Core/Engine/TwigTemplate.php index 0c64cc7c79..124c19fb42 100644 --- a/src.fork5/Backend/Core/Engine/TwigTemplate.php +++ b/src.fork5/Backend/Core/Engine/TwigTemplate.php @@ -190,12 +190,6 @@ private function parseUserDefinedConstants(): void // assign the authenticated users preferred interface language $this->assign('INTERFACE_LANGUAGE', (string) Authentication::getUser()->getSetting('interface_language')); } - - // assign some variable constants (such as site-title) - $this->assign( - 'SITE_TITLE', - Model::get('fork.settings')->get('Core', 'site_title_' . BL::getWorkingLanguage(), SITE_DEFAULT_TITLE) - ); } private function parseAuthenticatedUser(): void diff --git a/src.fork5/Frontend/Core/Header/Header.php b/src.fork5/Frontend/Core/Header/Header.php index f70a3dedcc..57f851a308 100644 --- a/src.fork5/Frontend/Core/Header/Header.php +++ b/src.fork5/Frontend/Core/Header/Header.php @@ -316,16 +316,6 @@ public function extractOpenGraphImages(string $content): void } } - public function getMetaCustom(): string - { - return (string) $this->metaCustom; - } - - public function getPageTitle(): string - { - return (string) $this->pageTitle; - } - /** * Parse the header into the template */ diff --git a/src/Core/Domain/Header/Breadcrumb/BreadcrumbCollection.php b/src/Core/Domain/Header/Breadcrumb/BreadcrumbCollection.php index 7aa11f39f6..bdc6f1ce6e 100644 --- a/src/Core/Domain/Header/Breadcrumb/BreadcrumbCollection.php +++ b/src/Core/Domain/Header/Breadcrumb/BreadcrumbCollection.php @@ -82,7 +82,18 @@ public function asPageTitle(): string { /** @var string[] $items the breadcrumbs are stringable but phpstan doesn't understand it */ $items = $this->getItems(); + if (count($items) > 1) { + array_shift($items); + } return implode(' | ', array_reverse(array_map(ucfirst(...), $items))); } + + public function asContentTitle(): string + { + /** @var string[] $items the breadcrumbs are stringable but phpstan doesn't understand it */ + $items = $this->getItems(); + + return ucfirst(end($items)); + } } diff --git a/src/Core/Domain/Header/ContentTitle.php b/src/Core/Domain/Header/ContentTitle.php new file mode 100644 index 0000000000..e5ae4104e7 --- /dev/null +++ b/src/Core/Domain/Header/ContentTitle.php @@ -0,0 +1,42 @@ +getContentTitle(); + } + + public function overwriteContentTitle(string $contentTitle): void + { + $this->overwrittenContentTitle = $contentTitle; + } + + public function hideContentTitle(bool $hideContentTitle): void + { + $this->hideContentTitle = $hideContentTitle; + } + + public function getContentTitle(): ?string + { + if ($this->hideContentTitle) { + return null; + } + + return $this->overwrittenContentTitle ?? $this->breadcrumbs->asContentTitle(); + } +} diff --git a/src/Core/Domain/Header/Header.php b/src/Core/Domain/Header/Header.php index 2e93498f36..c63ab109bf 100644 --- a/src/Core/Domain/Header/Header.php +++ b/src/Core/Domain/Header/Header.php @@ -39,6 +39,8 @@ final class Header public function __construct( public readonly BreadcrumbCollection $breadcrumbs, private readonly RequestStack $requestStack, + private readonly PageTitle $pageTitle, + private readonly ContentTitle $contentTitle, KernelInterface $kernel, Security $security, TranslatorInterface $translator, diff --git a/src/Core/Domain/Header/PageTitle.php b/src/Core/Domain/Header/PageTitle.php index 47a5b9044e..21940ffd51 100644 --- a/src/Core/Domain/Header/PageTitle.php +++ b/src/Core/Domain/Header/PageTitle.php @@ -8,12 +8,24 @@ /** Page title is a wrapper around the breadcrumbs class to make it possible to update the page title automatically */ final class PageTitle implements Stringable { + private ?string $overwrittenPageTitle = null; + public function __construct(private readonly BreadcrumbCollection $breadcrumbs) { } public function __toString(): string { - return $this->breadcrumbs->asPageTitle(); + return $this->getPageTitle(); + } + + public function overwritePageTitle(string $pageTitle): void + { + $this->overwrittenPageTitle = $pageTitle; + } + + public function getPageTitle(): string + { + return $this->overwrittenPageTitle ?? $this->breadcrumbs->asPageTitle(); } } From b1dd44bb8ae4e7ac8433fe58edfe42ed425fad77 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Fri, 24 Nov 2023 22:25:52 +0100 Subject: [PATCH 49/67] Fix deprecations --- config/packages/dev/cache.yaml | 2 +- config/packages/framework.yaml | 2 +- config/packages/messenger.yaml | 1 - config/packages/security.yaml | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/config/packages/dev/cache.yaml b/config/packages/dev/cache.yaml index 2e2135f84b..96fef53e14 100644 --- a/config/packages/dev/cache.yaml +++ b/config/packages/dev/cache.yaml @@ -1,2 +1,2 @@ parameters: - container.dumper.inline_factories: true + .container.dumper.inline_factories: true diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 6f40d1575d..878b9b14ba 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -2,7 +2,7 @@ framework: secret: '%kernel.secret%' csrf_protection: true - #http_method_override: true + http_method_override: false # Enables session support. Note that the session will ONLY be started if you read or write from it. # Remove or comment this section to explicitly disable session support. diff --git a/config/packages/messenger.yaml b/config/packages/messenger.yaml index 6c62d3fc57..8abd466ff0 100644 --- a/config/packages/messenger.yaml +++ b/config/packages/messenger.yaml @@ -1,6 +1,5 @@ framework: messenger: - reset_on_message: true default_bus: 'command.bus' buses: command.bus: diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 6e71214481..f12ad0b53d 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -1,6 +1,5 @@ # @TODO refactor this to the modules they belong to security: - enable_authenticator_manager: true password_hashers: ForkCMS\Modules\Backend\Domain\User\User: algorithm: auto From d3969af553db74f752378b80c87a766dccb08b31 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sat, 25 Nov 2023 00:48:44 +0100 Subject: [PATCH 50/67] Remove old asset code --- src.fork5/Backend/Core/Engine/Header.php | 1 - src.fork5/Common/Core/Header/Asset.php | 69 ----------- .../Common/Core/Header/AssetCollection.php | 66 ---------- src.fork5/Common/Core/Header/JsData.php | 30 ----- src.fork5/Common/Core/Header/Minifier.php | 80 ------------- src.fork5/Common/Core/Header/Priority.php | 113 ------------------ 6 files changed, 359 deletions(-) delete mode 100644 src.fork5/Common/Core/Header/Asset.php delete mode 100644 src.fork5/Common/Core/Header/AssetCollection.php delete mode 100644 src.fork5/Common/Core/Header/JsData.php delete mode 100644 src.fork5/Common/Core/Header/Minifier.php delete mode 100644 src.fork5/Common/Core/Header/Priority.php diff --git a/src.fork5/Backend/Core/Engine/Header.php b/src.fork5/Backend/Core/Engine/Header.php index b8f0bc724b..de5b122404 100644 --- a/src.fork5/Backend/Core/Engine/Header.php +++ b/src.fork5/Backend/Core/Engine/Header.php @@ -250,6 +250,5 @@ private function getCKEditorLanguage(): string public function appendDetailToBreadcrumbs(string $stringToAppend): void { - $this->template->assignGlobal('breadcrumbDetail', $stringToAppend); } } diff --git a/src.fork5/Common/Core/Header/Asset.php b/src.fork5/Common/Core/Header/Asset.php deleted file mode 100644 index 977f995e7d..0000000000 --- a/src.fork5/Common/Core/Header/Asset.php +++ /dev/null @@ -1,69 +0,0 @@ -file = $file; - $this->addTimestamp = $addTimestamp; - $this->priority = $priority ?? Priority::standard(); - $this->createdOn = new DateTimeImmutable(); - } - - public function compare(Asset $asset): int - { - $comparison = $this->priority->compare($asset->priority); - - if ($comparison === 0) { - $comparison = $this->createdOn <=> $asset->createdOn; - } - - return $comparison; - } - - public function getFile(): string - { - return $this->file; - } - - public function getPriority(): Priority - { - return $this->priority; - } - - public function forCacheUrl(string $cacheUrl): self - { - $cacheAsset = clone $this; - $cacheAsset->file = $cacheUrl; - - return $cacheAsset; - } - - public function __toString(): string - { - if (!$this->addTimestamp) { - return $this->file; - } - - // check if we need to use a ? or & - $separator = mb_strpos($this->file, '?') === false ? '?' : '&'; - - return $this->file . $separator . 'm=' . @filemtime(__DIR__ . '/../../../../' . $this->file); - } -} diff --git a/src.fork5/Common/Core/Header/AssetCollection.php b/src.fork5/Common/Core/Header/AssetCollection.php deleted file mode 100644 index 1ae6ccafdf..0000000000 --- a/src.fork5/Common/Core/Header/AssetCollection.php +++ /dev/null @@ -1,66 +0,0 @@ -minifier = $minifier; - } - - public function add(Asset $asset, bool $minify = true): void - { - if ($minify) { - $asset = $this->minifier->minify($asset); - } - - // we already have it we don't need to add it again - if (array_key_exists($asset->getFile(), $this->assets)) { - return; - } - - $this->assets[$asset->getFile()] = $asset; - } - - /** - * @param bool $orderAssets - * - * @return Asset[] - */ - public function getAssets($orderAssets = false): array - { - if ($orderAssets) { - $this->orderAssets(); - } - - return $this->assets; - } - - private function orderAssets(): void - { - usort( - $this->assets, - function (Asset $asset1, Asset $asset2) { - return $asset1->compare($asset2); - } - ); - } - - public function parse(BaseTwigTemplate $template, string $key): void - { - $this->orderAssets(); - $template->assignGlobal( - $key, - $this->assets - ); - } -} diff --git a/src.fork5/Common/Core/Header/JsData.php b/src.fork5/Common/Core/Header/JsData.php deleted file mode 100644 index 83e32842b8..0000000000 --- a/src.fork5/Common/Core/Header/JsData.php +++ /dev/null @@ -1,30 +0,0 @@ -jsData = $initialData; - } - - public function add(string $module, string $key, $value): void - { - if ($module === 'language') { - throw new InvalidArgumentException('You are not allowed to overwrite the language'); - } - - $this->jsData[$module][$key] = $value; - } - - public function __toString(): string - { - return ''; - } -} diff --git a/src.fork5/Common/Core/Header/Minifier.php b/src.fork5/Common/Core/Header/Minifier.php deleted file mode 100644 index b39ec14b10..0000000000 --- a/src.fork5/Common/Core/Header/Minifier.php +++ /dev/null @@ -1,80 +0,0 @@ -minifyClass = $minifyClass; - $this->basePath = $basePath; - $this->cacheFileExtension = $cacheFileExtension; - $this->baseCacheUrl = $baseCacheUrl; - $this->baseCachePath = $baseCachePath; - } - - public static function css(string $basePath, string $baseCacheUrl, string $baseCachePath): self - { - return new self(new Minify\CSS(), $basePath, 'css', $baseCacheUrl, $baseCachePath); - } - - public static function js(string $basePath, string $baseCacheUrl, string $baseCachePath): self - { - return new self(new Minify\JS(), $basePath, 'js', $baseCacheUrl, $baseCachePath); - } - - /** - * Minify the asset and return the assed for the minified version - * - * @param Asset $asset - * - * @return Asset - */ - public function minify(Asset $asset): Asset - { - // don't minify when debug is true - if (Model::getContainer()->getParameter('kernel.debug')) { - return $asset; - } - - $fileName = md5($asset->getFile()) . '.' . $this->cacheFileExtension; - $filePath = $this->basePath . $asset->getFile(); - $cacheUrl = $this->baseCacheUrl . $fileName; - $cachePath = $this->baseCachePath . $fileName; - - // check that file does not yet exist or has been updated already - if (is_file($cachePath) && filemtime($filePath) <= filemtime($cachePath)) { - return $asset->forCacheUrl($cacheUrl); - } - - // clone the minify class since there is no way to clear the added files - $minifier = clone $this->minifyClass; - $minifier->add($filePath); - $minifier->minify($cachePath); - - return $asset->forCacheUrl($cacheUrl); - } -} diff --git a/src.fork5/Common/Core/Header/Priority.php b/src.fork5/Common/Core/Header/Priority.php deleted file mode 100644 index 4a1b839286..0000000000 --- a/src.fork5/Common/Core/Header/Priority.php +++ /dev/null @@ -1,113 +0,0 @@ -priority = $priority; - } - - public function getValue(): int - { - return $this->priority; - } - - public function __toString(): string - { - return (string) $this->priority; - } - - public function equals(self $priority): bool - { - return $priority->priority === $this->priority; - } - - public function compare(self $priority): int - { - return $this->priority <=> $priority->priority; - } - - public static function core(): self - { - return new self(self::CORE); - } - - public function isCore(): bool - { - return $this->equals(self::core()); - } - - public static function debug(): self - { - return new self(self::DEBUG); - } - - public function isDebug(): bool - { - return $this->equals(self::debug()); - } - - public static function standard(): self - { - return new self(self::STANDARD); - } - - public function isStandard(): bool - { - return $this->equals(self::standard()); - } - - public static function module(): self - { - return new self(self::MODULE); - } - - public function isModule(): bool - { - return $this->equals(self::module()); - } - - public static function widget(): self - { - return new self(self::WIDGET); - } - - public function isWidget(): bool - { - return $this->equals(self::widget()); - } - - public static function forModule(string $module): self - { - if (ucfirst($module) === 'Core') { - return self::core(); - } - - return self::module(); - } -} From ab750b8f66d67436c82358d918a33b9ea8939af3 Mon Sep 17 00:00:00 2001 From: Jelmer Prins Date: Sat, 25 Nov 2023 01:29:18 +0100 Subject: [PATCH 51/67] Use all caps for twig globals --- .../Common/Core/Twig/BaseTwigTemplate.php | 64 -------------- src.fork5/Frontend/Core/Engine/Breadcrumb.php | 1 - src.fork5/Frontend/Core/Engine/Footer.php | 6 -- src.fork5/Frontend/Core/Engine/Page.php | 31 ------- src.fork5/Frontend/Core/Header/Facebook.php | 79 ------------------ src.fork5/Frontend/Core/Header/Header.php | 24 ------ src/Core/Domain/Header/Header.php | 39 ++++----- src/Core/Domain/Twig/CoreExtension.php | 41 +++++++++ .../Backend/Controller/BackendController.php | 13 --- .../Backend/Controller/LoginController.php | 22 ++--- .../templates/base/breadcrumb.html.twig | 6 +- .../templates/base/crud/edit.html.twig | 2 +- .../Backend/templates/base/head.html.twig | 12 +-- .../Domain/Module/ModuleInformation.php | 4 +- .../Domain/Twig/FrontendExtension.php | 83 +++++++++++++++++++ .../Frontend/Domain/Twig/FrontendGlobals.php | 60 -------------- .../Frontend/templates/base/Base.html.twig | 12 +-- .../templates/base/Breadcrumb.html.twig | 6 +- .../Frontend/templates/base/Footer.html.twig | 4 +- .../Frontend/templates/base/Head.html.twig | 15 ++-- .../Frontend/templates/base/Locales.html.twig | 4 +- .../base/PrivacyConsentDialog.html.twig | 6 +- .../CompilerPass/TranslatorPass.php | 2 +- .../Domain/Twig/ForkIntlExtension.php | 2 +- .../Pages/Controller/PageController.php | 31 ++----- .../Pages/Domain/Twig/PagesExtension.php | 1 - .../templates/Frontend/base/Base.html.twig | 12 +-- .../templates/Frontend/base/Footer.html.twig | 4 +- .../templates/Frontend/base/Head.html.twig | 15 ++-- .../base/PrivacyConsentDialog.html.twig | 6 +- 30 files changed, 210 insertions(+), 397 deletions(-) delete mode 100644 src.fork5/Frontend/Core/Header/Facebook.php create mode 100644 src/Core/Domain/Twig/CoreExtension.php create mode 100644 src/Modules/Frontend/Domain/Twig/FrontendExtension.php delete mode 100644 src/Modules/Frontend/Domain/Twig/FrontendGlobals.php diff --git a/src.fork5/Common/Core/Twig/BaseTwigTemplate.php b/src.fork5/Common/Core/Twig/BaseTwigTemplate.php index a013a19fa6..06f0737fd8 100644 --- a/src.fork5/Common/Core/Twig/BaseTwigTemplate.php +++ b/src.fork5/Common/Core/Twig/BaseTwigTemplate.php @@ -113,25 +113,9 @@ public function getAssignedVariables(): array */ protected function startGlobals(Environment $twig) { - // some old globals - $twig->addGlobal('var', ''); - $twig->addGlobal('CRLF', "\n"); - $twig->addGlobal('TAB', "\t"); - $twig->addGlobal('now', time()); - $twig->addGlobal('LANGUAGE', $this->language); - $twig->addGlobal('is'.strtoupper($this->language), true); - $twig->addGlobal('debug', $this->debugMode); - - $twig->addGlobal('timestamp', time()); - // get all defined constants $constants = get_defined_constants(true); - // remove protected constants aka constants that should not be used in the template - foreach ($constants['user'] as $key => $value) { - $twig->addGlobal($key, $value); - } - /* Setup Backend for the Twig environment. */ if (!$this->forkSettings || !Model::getContainer()->getParameter('fork.is_installed')) { return; @@ -164,50 +148,6 @@ protected function startGlobals(Environment $twig) ); } } - - // settings - $twig->addGlobal( - 'SITE_TITLE', - $this->forkSettings->get('Core', 'site_title_'.$this->language, SITE_DEFAULT_TITLE) - ); - $twig->addGlobal( - 'SITE_URL', - SITE_URL - ); - $twig->addGlobal( - 'SITE_DOMAIN', - SITE_DOMAIN - ); - - // facebook stuff - // @deprecated remove this in Fork 6, facebook_admin_ids / facebook_app_id should be removed - if ($this->forkSettings->get('Core', 'facebook_admin_ids', null) !== null) { - $twig->addGlobal( - 'FACEBOOK_ADMIN_IDS', - $this->forkSettings->get('Core', 'facebook_admin_ids', null) - ); - } - if ($this->forkSettings->get('Core', 'facebook_app_id', null) !== null) { - $twig->addGlobal( - 'FACEBOOK_APP_ID', - $this->forkSettings->get('Core', 'facebook_app_id', null) - ); - } - if ($this->forkSettings->get('Core', 'facebook_app_secret', null) !== null) { - $twig->addGlobal( - 'FACEBOOK_APP_SECRET', - $this->forkSettings->get('Core', 'facebook_app_secret', null) - ); - } - - // twitter stuff - if ($this->forkSettings->get('Core', 'twitter_site_name', null) !== null) { - // strip @ from twitter username - $twig->addGlobal( - 'TWITTER_SITE_NAME', - ltrim($this->forkSettings->get('Core', 'twitter_site_name', null), '@') - ); - } } /** @@ -223,10 +163,6 @@ public function setAddSlashes(bool $enabled = true): void public function render($template, array $variables = []): string { if (!empty($this->forms)) { - foreach ($this->forms as $form) { - // using assign to pass the form as global - $this->assignGlobal('form_' . $form->getName(), $form); - } } return $this->environment->render($template, array_merge($this->runtimeGlobals, $variables)); diff --git a/src.fork5/Frontend/Core/Engine/Breadcrumb.php b/src.fork5/Frontend/Core/Engine/Breadcrumb.php index 02ee754318..a4f17b2eb3 100644 --- a/src.fork5/Frontend/Core/Engine/Breadcrumb.php +++ b/src.fork5/Frontend/Core/Engine/Breadcrumb.php @@ -163,7 +163,6 @@ public function getItems(): array public function parse(): void { // assign - $this->template->assignGlobal('breadcrumb', $this->items); } public function removeLastElement(): void diff --git a/src.fork5/Frontend/Core/Engine/Footer.php b/src.fork5/Frontend/Core/Engine/Footer.php index b57e79e651..574efe43a0 100644 --- a/src.fork5/Frontend/Core/Engine/Footer.php +++ b/src.fork5/Frontend/Core/Engine/Footer.php @@ -61,12 +61,6 @@ public function parse(): void $siteHTMLEndOfBody .= $this->getSiteLinksCode($searchUrl); } } - - // assign site wide html - $this->template->assignGlobal('siteHTMLEndOfBody', $siteHTMLEndOfBody); - - // @deprecated remove this in Fork 6, use siteHTMLEndOfBody - $this->template->assignGlobal('siteHTMLFooter', $siteHTMLEndOfBody); } /** diff --git a/src.fork5/Frontend/Core/Engine/Page.php b/src.fork5/Frontend/Core/Engine/Page.php index bd5c7cc366..5a780801d5 100644 --- a/src.fork5/Frontend/Core/Engine/Page.php +++ b/src.fork5/Frontend/Core/Engine/Page.php @@ -194,14 +194,6 @@ public function display(): Response $this->template->assignGlobal('isPage' . $this->pageId, true); $this->template->assignGlobal('isChildOfPage' . $this->record['parent_id'], true); - // hide the cookiebar from within the code to prevent flickering - // @deprecated remove this in Fork 6, the privacy consent dialog should be used - $this->template->assignGlobal( - 'cookieBarHide', - !$this->get('fork.settings')->get('Core', 'show_cookie_bar', false) - || $this->getContainer()->get('fork.cookie')->hasHiddenCookieBar() - ); - $this->parsePrivacyConsents(); $this->parsePositions(); // assign empty positions @@ -321,29 +313,6 @@ protected function parseLanguages(): void return; } - $this->template->assignGlobal( - 'languages', - array_map( - function (string $language) { - return [ - 'url' => '/' . $language, - 'label' => $language, - 'name' => Language::msg(mb_strtoupper($language)), - 'current' => $language === LANGUAGE, - ]; - }, - Language::getActiveLanguages() - ) - ); - } - - protected function parsePrivacyConsents(): void - { - $consentDialog = $this->get(ConsentDialog::class); - - $this->template->assignGlobal('privacyConsentEnabled', $consentDialog->isDialogEnabled()); - $this->template->assignGlobal('privacyConsentDialogHide', !$consentDialog->shouldDialogBeShown()); - $this->template->assignGlobal('privacyConsentDialogLevels', $consentDialog->getLevels()); } protected function parsePositions(): void diff --git a/src.fork5/Frontend/Core/Header/Facebook.php b/src.fork5/Frontend/Core/Header/Facebook.php deleted file mode 100644 index 9dda5ea73f..0000000000 --- a/src.fork5/Frontend/Core/Header/Facebook.php +++ /dev/null @@ -1,79 +0,0 @@ -modulesSettings = $modulesSettings; - } - - public function addOpenGraphMeta(Header $header): void - { - $parseFacebook = false; - - // @deprecated remove this in Fork 6, facebook_admin_ids / facebook_app_id should be removed - $facebookAdminIds = $this->modulesSettings->get('Core', 'facebook_admin_ids', null); - $facebookAppId = $this->modulesSettings->get('Core', 'facebook_app_id', null); - - // check if facebook admins are set - if ($facebookAdminIds !== null) { - $header->addMetaData( - [ - 'property' => 'fb:admins', - 'content' => $facebookAdminIds, - ], - true, - ['property'] - ); - $parseFacebook = true; - } - - // check if no facebook admin is set but an app is configured we use the application as an admin - if ($facebookAdminIds === '' && $facebookAppId !== null) { - $header->addMetaData( - [ - 'property' => 'fb:app_id', - 'content' => $facebookAppId, - ], - true, - ['property'] - ); - $parseFacebook = true; - } - - if (!$parseFacebook) { - return; - } - - $header->addOpenGraphData('locale', $this->getLocale()); - $header->addOpenGraphImage('/src/Frontend/Themes/' . Theme::getTheme() . '/facebook.png'); - $header->addOpenGraphImage('/facebook.png'); - } - - private function getLocale(): string - { - $specialCases = [ - 'en' => 'en_US', - 'zh' => 'zh_CN', - 'cs' => 'cs_CZ', - 'el' => 'el_GR', - 'ja' => 'ja_JP', - 'sv' => 'sv_SE', - 'uk' => 'uk_UA', - ]; - - if (array_key_exists(LANGUAGE, $specialCases)) { - return str_replace(array_keys($specialCases), $specialCases, LANGUAGE); - } - - return mb_strtolower(LANGUAGE) . '_' . mb_strtoupper(LANGUAGE); - } -} diff --git a/src.fork5/Frontend/Core/Header/Header.php b/src.fork5/Frontend/Core/Header/Header.php index 57f851a308..0022d6f418 100644 --- a/src.fork5/Frontend/Core/Header/Header.php +++ b/src.fork5/Frontend/Core/Header/Header.php @@ -331,11 +331,6 @@ public function parse(): void $this->meta->addMetaData(MetaData::forName('robots', 'noindex, nofollow'), true); } - $this->template->assignGlobal('meta', $this->meta); - $this->template->assignGlobal('metaCustom', $this->getMetaCustom()); - $this->cssFiles->parse($this->template, 'cssFiles'); - $this->jsFiles->parse($this->template, 'jsFiles'); - $siteHTMLHead = ''; $siteHTMLStartOfBody = ''; @@ -357,25 +352,6 @@ public function parse(): void $this->get('fork.cookie') ) . "\n"; } - - // @deprecated fallback to site_html_header as this was used in the past - $siteHTMLHead .= (string) $this->get('fork.settings')->get('Core', 'site_html_head', $this->get('fork.settings')->get('Core', 'site_html_header', '')) . "\n"; - $siteHTMLHead .= "\n" . $this->jsData; - $this->template->assignGlobal('siteHTMLHead', trim($siteHTMLHead)); - - // @deprecated remove this in Fork 6, use siteHTMLHead - $this->template->assignGlobal('siteHTMLHeader', trim($siteHTMLHead)); - - // @deprecated fallback to site_start_of_body_scripts as this was used in the pased - $siteHTMLStartOfBody .= $this->get('fork.settings')->get('Core', 'site_html_start_of_body', $this->get('fork.settings')->get('Core', 'site_start_of_body_scripts', '')); - $this->template->assignGlobal('siteHTMLStartOfBody', trim($siteHTMLStartOfBody)); - - $this->template->assignGlobal('pageTitle', $this->getPageTitle()); - $this->template->assignGlobal('contentTitle', $this->getContentTitle()); - $this->template->assignGlobal( - 'siteTitle', - (string) $this->get('fork.settings')->get('Core', 'site_title_' . LANGUAGE, SITE_DEFAULT_TITLE) - ); } private function getCanonical(): string diff --git a/src/Core/Domain/Header/Header.php b/src/Core/Domain/Header/Header.php index c63ab109bf..cf41f8f44a 100644 --- a/src/Core/Domain/Header/Header.php +++ b/src/Core/Domain/Header/Header.php @@ -9,6 +9,7 @@ use ForkCMS\Core\Domain\Header\Breadcrumb\Breadcrumb; use ForkCMS\Core\Domain\Header\Breadcrumb\BreadcrumbCollection; use ForkCMS\Core\Domain\Header\FlashMessage\FlashMessage; +use ForkCMS\Core\Domain\Header\Meta\MetaCollection; use ForkCMS\Modules\Backend\Domain\Action\ModuleAction; use ForkCMS\Modules\Backend\Domain\User\User; use ForkCMS\Modules\Extensions\Domain\Module\ModuleName; @@ -17,13 +18,12 @@ use ForkCMS\Modules\Internationalisation\Domain\Translator\ForkTranslator; use InvalidArgumentException; use LogicException; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpKernel\KernelInterface; -use Symfony\Component\Security\Core\Security; use Symfony\Contracts\Translation\TranslatorInterface; -use Twig\Environment; /** * This class will be used to alter the head-part of the HTML-document that will be created by he Backend @@ -31,21 +31,33 @@ */ final class Header { - private readonly JsData $jsData; + public readonly JsData $jsData; - private readonly AssetCollection $cssAssets; - private readonly AssetCollection $jsAssets; + public readonly AssetCollection $cssAssets; + public readonly AssetCollection $jsAssets; + + public readonly MetaCollection $metaCollection; public function __construct( public readonly BreadcrumbCollection $breadcrumbs, private readonly RequestStack $requestStack, - private readonly PageTitle $pageTitle, - private readonly ContentTitle $contentTitle, KernelInterface $kernel, Security $security, TranslatorInterface $translator, ConsentDialog $consentDialog, ) { + $this->jsData = $this->initJsData($kernel, $security, $translator, $consentDialog); + $this->jsAssets = new AssetCollection(); + $this->cssAssets = new AssetCollection(); + $this->metaCollection = new MetaCollection(); + } + + private function initJsData( + KernelInterface $kernel, + Security $security, + TranslatorInterface $translator, + ConsentDialog $consentDialog + ): JsData { $defaults = [ 'default_locale' => $kernel->getContainer()->getParameter('kernel.default_locale'), 'debug' => $kernel->isDebug(), @@ -64,9 +76,7 @@ public function __construct( $defaults['default_translation_domain_fallback'] = $fallbackDomain ?? $defaults['default_translation_domain']; } - $this->jsData = new JsData($defaults); - $this->jsAssets = new AssetCollection(); - $this->cssAssets = new AssetCollection(); + return new JsData($defaults); } public function addJsData(ModuleName $module, string $key, mixed $value): void @@ -74,15 +84,6 @@ public function addJsData(ModuleName $module, string $key, mixed $value): void $this->jsData->add($module, $key, $value); } - public function parse(Environment $twig): void - { - $twig->addGlobal('jsData', $this->jsData); - $twig->addGlobal('jsFiles', $this->jsAssets); - $twig->addGlobal('cssFiles', $this->cssAssets); - $twig->addGlobal('breadcrumbs', $this->breadcrumbs); - $twig->addGlobal('page_title', new PageTitle($this->breadcrumbs)); - } - private function getFirstPossibleSessionTimeout(): int { $garbageCollectionMaxLifeTime = (int) ini_get('session.gc_maxlifetime'); diff --git a/src/Core/Domain/Twig/CoreExtension.php b/src/Core/Domain/Twig/CoreExtension.php new file mode 100644 index 0000000000..67d2af17a0 --- /dev/null +++ b/src/Core/Domain/Twig/CoreExtension.php @@ -0,0 +1,41 @@ + $_ENV['SITE_PROTOCOL'] . '://' . $_ENV['SITE_DOMAIN'], + 'SITE_PROTOCOL' => $_ENV['SITE_PROTOCOL'], + 'SITE_DOMAIN' => $_ENV['SITE_DOMAIN'], + 'SITE_MULTILINGUAL' => $_ENV['SITE_MULTILINGUAL'] === 'true', + 'CRLF' => "\n", + 'TAB' => "\t", + 'NOW' => time(), + 'PAGE_TITLE' => $this->pageTitle, + 'CONTENT_TITLE' => $this->contentTitle, + 'BREADCRUMBS' => $this->breadcrumbs, + 'JS_DATA' => $this->header->jsData, + 'JS_FILES' => $this->header->jsAssets, + 'CSS_FILES' => $this->header->cssAssets, + 'META' => $this->header->metaCollection, + ]; + } +} diff --git a/src/Modules/Backend/Controller/BackendController.php b/src/Modules/Backend/Controller/BackendController.php index 395c5b2ece..8e801df235 100644 --- a/src/Modules/Backend/Controller/BackendController.php +++ b/src/Modules/Backend/Controller/BackendController.php @@ -63,22 +63,9 @@ private function configureTwigForAction(Request $request, ActionSlug $actionSlug $this->navigation->parse($this->twig); $this->navigation->buildBreadcrumbs($this->header->breadcrumbs); $this->header->addAssetsForAction($actionSlug->asModuleAction()); - $this->header->parse($this->twig); - $this->twig->addGlobal( - 'SITE_TITLE', - $this->moduleSettings->get( - ModuleName::fromString('Frontend'), - 'site_title_' . $request->getLocale(), - $_ENV['SITE_DEFAULT_TITLE'] - ) - ); - $this->twig->addGlobal('SITE_URL', $_ENV['SITE_PROTOCOL'] . '://' . $_ENV['SITE_DOMAIN']); - $this->twig->addGlobal('SITE_MULTILINGUAL', $_ENV['SITE_MULTILINGUAL'] === 'true'); $this->twig->addGlobal('bodyID', Container::underscore($actionSlug->getModuleName())); $this->twig->addGlobal('bodyClass', str_replace('/', '_', $actionSlug->getSlug())); $this->twig->addGlobal('LOCALES', $locales); $this->twig->addGlobal('MODULE_ACTION', $actionSlug->asModuleAction()); - $this->twig->addGlobal('CRLF', "\n"); - $this->twig->addGlobal('TAB', "\t"); } } diff --git a/src/Modules/Backend/Controller/LoginController.php b/src/Modules/Backend/Controller/LoginController.php index 8663a4d8ae..39fa62fec6 100644 --- a/src/Modules/Backend/Controller/LoginController.php +++ b/src/Modules/Backend/Controller/LoginController.php @@ -2,19 +2,17 @@ namespace ForkCMS\Modules\Backend\Controller; -use ForkCMS\Core\Domain\Header\Header; +use ForkCMS\Core\Domain\Header\Breadcrumb\Breadcrumb; +use ForkCMS\Core\Domain\Header\Breadcrumb\BreadcrumbCollection; use ForkCMS\Modules\Backend\Domain\NavigationItem\NavigationItemRepository; use ForkCMS\Modules\Backend\Domain\User\User; -use ForkCMS\Modules\Extensions\Domain\Module\ModuleName; -use ForkCMS\Modules\Extensions\Domain\Module\ModuleSettings; use ForkCMS\Modules\Internationalisation\Domain\Translation\TranslationKey; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; -use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Environment; class LoginController @@ -22,12 +20,10 @@ class LoginController public function __construct( private readonly Environment $twig, private readonly AuthenticationUtils $authenticationUtils, - private readonly TranslatorInterface $translator, private readonly Security $security, private readonly UrlGeneratorInterface $urlGenerator, private readonly NavigationItemRepository $navigationItemRepository, - private readonly Header $header, - private readonly ModuleSettings $moduleSettings, + private readonly BreadcrumbCollection $breadcrumbs, ) { } @@ -44,7 +40,7 @@ public function __invoke(Request $request): Response $error = $this->authenticationUtils->getLastAuthenticationError(); // last username entered by the user $lastUsername = $this->authenticationUtils->getLastUsername(); - $this->header->parse($this->twig); + $this->breadcrumbs->add(new Breadcrumb(TranslationKey::label('LogIn'))); return new Response( $this->twig->render( @@ -52,14 +48,6 @@ public function __invoke(Request $request): Response [ 'last_username' => $lastUsername, 'error' => $error, - 'page_title' => $this->translator->trans(TranslationKey::label('LogIn')), - 'SITE_TITLE' => $this->moduleSettings->get( - ModuleName::fromString('Frontend'), - 'site_title_' . $request->getLocale(), - $_ENV['SITE_DEFAULT_TITLE'] - ), - 'SITE_URL' => $_ENV['SITE_PROTOCOL'] . '://' . $_ENV['SITE_DOMAIN'], - 'jsFiles' => [], ] ) ); diff --git a/src/Modules/Backend/templates/base/breadcrumb.html.twig b/src/Modules/Backend/templates/base/breadcrumb.html.twig index 32ac81d74f..ac8289a416 100644 --- a/src/Modules/Backend/templates/base/breadcrumb.html.twig +++ b/src/Modules/Backend/templates/base/breadcrumb.html.twig @@ -1,10 +1,10 @@ diff --git a/src/Modules/Backend/templates/base/crud/add.html.twig b/src/Modules/Backend/templates/base/crud/add.html.twig index b9b25f2787..6c532353af 100644 --- a/src/Modules/Backend/templates/base/crud/add.html.twig +++ b/src/Modules/Backend/templates/base/crud/add.html.twig @@ -1,13 +1,13 @@ {% extends '@Backend/base/base.html.twig' %} {% import '@Backend/base/macros.html.twig' as macro %} -{% set crudIndexModule = null %} -{% set crudIndexParameters = [] %} -{% set crudIndexRelative = false %} -{% set crudIndexLocale = null %} +{% set crud_index_module = null %} +{% set crud_index_parameters = [] %} +{% set crud_index_relative = false %} +{% set crud_index_locale = null %} {% block content %} - {% if formTheme is defined %} - {% form_theme backend_form with ['@Backend/base/formTheme.html.twig',formTheme] %} + {% if form_theme is defined %} + {% form_theme backend_form with ['@Backend/base/formTheme.html.twig', form_theme] %} {% endif %} {{ form_start(backend_form) }} {{ form_widget(backend_form) }} @@ -15,9 +15,9 @@ {{ form_rest(backend_form) }}
- {% if crudIndexAction is defined and is_allowed(crudIndexAction, crudIndexModule) %} + {% if crud_index_action is defined and is_allowed(crud_index_action, crud_index_module) %}
- {{ macro.buttonIcon(action_path(crudIndexAction, crudIndexModule, crudIndexParameters, crudIndexRelative, crudIndexLocale), 'times', 'lbl.Cancel'|trans|ucfirst, 'btn-default') }} + {{ macro.buttonIcon(action_path(crud_index_action, crud_index_module, crud_index_parameters, crud_index_relative, crud_index_locale), 'times', 'lbl.Cancel'|trans|ucfirst, 'btn-default') }}
{% endif %}
diff --git a/src/Modules/Backend/templates/base/crud/edit.html.twig b/src/Modules/Backend/templates/base/crud/edit.html.twig index f38eb13519..88236fac22 100644 --- a/src/Modules/Backend/templates/base/crud/edit.html.twig +++ b/src/Modules/Backend/templates/base/crud/edit.html.twig @@ -1,20 +1,20 @@ {% extends '@Backend/base/base.html.twig' %} {% import '@Backend/base/macros.html.twig' as macro %} -{% set crudDeleteModule = null %} -{% set crudIndexModule = null %} -{% set crudIndexParameters = [] %} -{% set crudIndexRelative = false %} -{% set crudIndexLocale = null %} -{% if crudDeleteName is not defined %} - {% set crudDeleteName = BREADCRUMBS|last.label %} +{% set crud_delete_module = null %} +{% set crud_index_module = null %} +{% set crud_index_parameters = [] %} +{% set crud_index_relative = false %} +{% set crud_index_locale = null %} +{% if crud_delete_name is not defined %} + {% set crud_delete_name = BREADCRUMBS|last.label %} {% endif %} {% block content %} - {% if formTheme is defined %} - {% form_theme backend_form with ['@Backend/base/formTheme.html.twig',formTheme] %} + {% if form_theme is defined %} + {% form_theme backend_form with ['@Backend/base/formTheme.html.twig', form_theme] %} {% endif %} - {% if crudDeleteAction is defined and is_allowed(crudDeleteAction, crudDeleteModule) %} + {% if crud_delete_action is defined and is_allowed(crud_delete_action, crud_delete_module) %} {{ form_start(backend_delete_form) }} {{ form_row(backend_delete_form._token) }} {{ form_row(backend_delete_form.id) }} @@ -25,11 +25,11 @@
@@ -44,7 +44,7 @@
- {% if crudDeleteAction is defined and is_allowed(crudDeleteAction, crudDeleteModule) %} + {% if crud_delete_action is defined and is_allowed(crud_delete_action, crud_delete_module) %} {{ macro.buttonIcon('', deleteIcon|default('trash'), deleteLabel|default('lbl.Delete'|trans|ucfirst), deleteClass|default('btn-danger'), {"type":"button", "data-bs-toggle":"modal", "data-bs-target":"#confirmDelete"}) }} {% endif %} {% if crudIndexAction is defined and is_allowed(crudIndexAction, crudIndexModule) %} diff --git a/src/Modules/Backend/templates/base/crud/index.html.twig b/src/Modules/Backend/templates/base/crud/index.html.twig index a0ee4aae3e..6606e4dd32 100644 --- a/src/Modules/Backend/templates/base/crud/index.html.twig +++ b/src/Modules/Backend/templates/base/crud/index.html.twig @@ -1,15 +1,15 @@ {% extends '@Backend/base/base.html.twig' %} {% import '@Backend/base/macros.html.twig' as macro %} -{% set crudActionModule = null %} -{% set crudActionParameters = [] %} -{% set crudActionRelative = false %} -{% set crudActionLocale = null %} +{% set crud_action_module = null %} +{% set crud_action_parameters = [] %} +{% set crud_action_relative = false %} +{% set crud_action_locale = null %} {% block actionbar %} - {% if is_allowed(crudActionName, crudActionModule) %} + {% if is_allowed(crud_action_name, crud_action_module) %}
- {{ macro.buttonIcon(action_path(crudActionName, crudActionModule, crudActionParameters, crudActionRelative, crudActionLocale), crudActionIcon|default('plus-square'), crustActionLabel|default('lbl.Add'|trans|ucfirst), 'btn-success') }} + {{ macro.buttonIcon(action_path(crud_action_name, crud_action_module, crud_action_parameters, crud_action_relative, crud_action_locale), crudActionIcon|default('plus-square'), crustActionLabel|default('lbl.Add'|trans|ucfirst), 'btn-success') }}
{% endif %} @@ -19,12 +19,12 @@ {{ pageon_datagrid( backend_data_grid, { - dataGridEmptyAction: dataGridEmptyAction|default(null), - dataGridEmptyModule: dataGridEmptyModule|default(null), - dataGridEmptyParameters: dataGridEmptyParameters|default([]), - dataGridEmptyRelative: dataGridEmptyRelative|default(false), - dataGridEmptyLocale: dataGridEmptyLocale|default(null), - dataGridEmptyLabel: dataGridEmptyLabel|default('lbl.Add'|trans|ucfirst), + data_grid_empty_action: data_grid_empty_action|default(null), + data_grid_empty_module: data_grid_empty_module|default(null), + data_grid_empty_parameters: data_grid_empty_parameters|default([]), + data_grid_empty_relatives: data_grid_empty_relatives|default(false), + data_grid_empty_locale: data_grid_empty_locale|default(null), + data_grid_empty_label: data_grid_empty_label|default('lbl.Add'|trans|ucfirst), } ) }} {% endblock %} diff --git a/src/Modules/Backend/templates/base/crud/settings.html.twig b/src/Modules/Backend/templates/base/crud/settings.html.twig index 1168fd56d8..c04c67b890 100644 --- a/src/Modules/Backend/templates/base/crud/settings.html.twig +++ b/src/Modules/Backend/templates/base/crud/settings.html.twig @@ -1,15 +1,15 @@ {% extends '@Backend/base/base.html.twig' %} {% import '@Backend/base/macros.html.twig' as macro %} -{% set crudIndexModule = null %} -{% set crudIndexParameters = [] %} -{% set crudIndexRelative = false %} -{% set crudIndexLocale = null %} -{% set crudIndexLabel = null %} -{% set crudIndexIcon = null %} +{% set crud_index_module = null %} +{% set crud_index_parameters = [] %} +{% set crud_index_relative = false %} +{% set crud_index_locale = null %} +{% set crud_index_label = null %} +{% set crud_index_icon = null %} {% block content %} - {% if formTheme is defined %} - {% form_theme backend_form with ['@Backend/base/formTheme.html.twig',formTheme] %} + {% if form_theme is defined %} + {% form_theme backend_form with ['@Backend/base/formTheme.html.twig', form_theme] %} {% endif %} {{ form_start(backend_form) }} {{ form_widget(backend_form) }} @@ -17,9 +17,9 @@ {{ form_rest(backend_form) }}
- {% if crudIndexAction is defined and is_allowed(crudIndexAction, crudIndexModule) %} + {% if crud_index_action is defined and is_allowed(crud_index_action, crud_index_module) %}
- {{ macro.buttonIcon(action_path(crudIndexAction, crudIndexModule, crudIndexParameters, crudIndexRelative, crudIndexLocale), crudIndexIcon|default('bars'), crudIndexLabel|default('lbl.Cancel')|trans|ucfirst, 'btn-default') }} + {{ macro.buttonIcon(action_path(crud_index_action, crud_index_module, crud_index_parameters, crud_index_relative, crud_index_locale), crud_index_icon|default('bars'), crud_index_label|default('lbl.Cancel')|trans|ucfirst, 'btn-default') }}
{% endif %}
diff --git a/src/Modules/Backend/templates/base/formTheme.html.twig b/src/Modules/Backend/templates/base/formTheme.html.twig index bc001a6f12..b39b787747 100644 --- a/src/Modules/Backend/templates/base/formTheme.html.twig +++ b/src/Modules/Backend/templates/base/formTheme.html.twig @@ -16,8 +16,8 @@ {% if horizontal is defined and horizontal %} {% set label_attr_class = 'control-label ' ~ label_attr_class ~ horizontal_label_class %} {% endif %} - {% set label_attr = label_attr|merge({'class': label_attr.class|default('') ~ " " ~ label_attr_class ~ (required ? ' required' : ' optional') }) %} - + {% set label_attr = label_attr|merge({'class': label_attr.class|default('') ~ " " ~ label_attr_class ~ (required ? ' required' : ' optional')}) %} + {{ label_html ? label|raw : label }}{% if required %}{{- block('label_asterisk') -}}{% endif %} {% if form.vars.label_tooltip is defined and form.vars.label_tooltip is not empty %} {% if not form.vars.label_tooltip_translation_domain and form.vars.label_tooltip_translation_domain is not null %} @@ -95,10 +95,10 @@ {%- endblock form_errors %} {%- block meta_widget -%} - {% set customId = form.vars.id ~ '_custom' %} - {% set classId = form.vars.id ~ '_class' %} - {% set methodId = form.vars.id ~ '_method' %} - {% set parametersId = form.vars.id ~ '_parameters' %} + {% set custom_id = form.vars.id ~ '_custom' %} + {% set class_id = form.vars.id ~ '_class' %} + {% set method_id = form.vars.id ~ '_method' %} + {% set parameters_id = form.vars.id ~ '_parameters' %}
{{ 'lbl.Titles'|trans|ucfirst }} @@ -131,16 +131,16 @@ {{ 'msg.HelpPageTitle'|trans|raw }}
- {% set navigationTitle = locateFormView(form, 'navigationTitle') %} - {% if navigationTitle is not null %} + {% set navigation_title = locateFormView(form, 'navigationTitle') %} + {% if navigation_title is not null %}
{{ form_widget(locateFormView(form, 'navigationTitleOverwrite')) }}
- {{ form_label(navigationTitle) }} - {{ form_widget(navigationTitle) }} - {{ form_errors(navigationTitle) }} + {{ form_label(navigation_title) }} + {{ form_widget(navigation_title) }} + {{ form_errors(navigation_title) }} {{ 'msg.HelpNavigationTitle'|trans|raw }}
@@ -222,10 +222,10 @@
- - - - + + + + {{ form_rest(form) }} {%- endblock meta_widget -%} @@ -302,8 +302,8 @@ {% set prototype_html = prototype_html ~ '
' %} {% endif %} - {% set attr = attr|merge({'data-prototype': prototype_html }) %} - {% set attr = attr|merge({'data-prototype-name': prototype_name }) %} + {% set attr = attr|merge({'data-prototype': prototype_html}) %} + {% set attr = attr|merge({'data-prototype-name': prototype_name}) %} {% endif %}
@@ -356,7 +356,7 @@ {% block form_row %} {%- set widget_attr = {} -%} {%- if help is not empty -%} - {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~ "_help"}} -%} {%- endif -%} {%- set row_class = row_class|default(row_attr.class|default('mb-3')|trim) -%}
@@ -392,8 +392,8 @@ {% endblock %} {% block help %} - {% for attributeName, attributeValue in attr %} - {% if attributeName == 'help' %}{{ attributeValue|trans }}{% endif %} + {% for attribute_name, attribute_value in attr %} + {% if attribute_name == 'help' %}{{ attribute_value|trans }}{% endif %} {% endfor %} {% endblock %} @@ -471,8 +471,8 @@ {% set label = label|trans({}, translation_domain)|ucfirst %} {% endif %} - {% set legend_attr = label_attr|merge({'class': label_attr.class|default('') }) %} - {{ label }} + {% set legend_attr = label_attr|merge({'class': label_attr.class|default('')}) %} + {{ label }} {% endif %} {% endblock %} @@ -500,8 +500,8 @@ {% endblock %} {% block tab_row %} - {%- set tabClass = ' tab-pane fade row' ~ (form.parent.children | keys | first == form.vars.name ? ' show active' : '') -%} - {{ form_widget(form, {attr: {role:'tabpanel', 'aria-labelledby': form.vars.id, class: (form.vars.attr.class|default('') ~ tabClass)|trim} }) }} + {%- set tab_class = ' tab-pane fade row' ~ (form.parent.children|keys|first == form.vars.name ? ' show active' : '') -%} + {{ form_widget(form, {attr: {role: 'tabpanel', 'aria-labelledby': form.vars.id, class: (form.vars.attr.class|default('') ~ tab_class)|trim}}) }} {% endblock %} {% block user_group_permission_row %} @@ -533,14 +533,13 @@ {% for index, permission in permissions %} - {% set checkboxLabel = form.children[index].vars.label %} - {% set checkboxLabelAttr = form.children[index].vars.label_attr|merge({'for': form.children[index].vars.id}) %} - {% set checkboxLabelAttr = checkboxLabelAttr|merge({'class': form.children[index].vars.label_attr.class|default('') ~ (form.children[index].vars.required ? ' required' : ' optional') }) %} - {{ form_widget(form.children[index], {'label':false, 'attr': {'data-select-all-target': form.vars.id ~ '-' ~ module}}) }} + {% set checkbox_label = form.children[index].vars.label %} + {% set checkbox_label_attr = form.children[index].vars.label_attr|merge({'for': form.children[index].vars.id}) %} + {% set checkbox_label_attr = checkbox_label_attr|merge({'class': form.children[index].vars.label_attr.class|default('') ~ (form.children[index].vars.required ? ' required' : ' optional')}) %} + {{ form_widget(form.children[index], {'label': false, 'attr': {'data-select-all-target': form.vars.id ~ '-' ~ module}}) }} - - {{ checkboxLabel }}{% if form.children[index].vars.required %}{{- block('label_asterisk') -}}{% endif %} + + {{ checkbox_label }}{% if form.children[index].vars.required %}{{- block('label_asterisk') -}}{% endif %} {{ permission.description }} @@ -566,22 +565,21 @@ - {% for userCheckbox in form.children %} - {% set user = possibleUsers[userCheckbox.vars.value] %} + {% for user_checkbox in form.children %} + {% set user = possibleUsers[user_checkbox.vars.value] %} - {{ form_widget(userCheckbox, {label:false, 'attr': {'data-select-all-target': form.vars.id ~ '-users'}}) }} + {{ form_widget(user_checkbox, {label: false, 'attr': {'data-select-all-target': form.vars.id ~ '-users'}}) }} - {% set checkboxLabelAttr = userCheckbox.vars.label_attr|merge({'for': userCheckbox.vars.id}) %} - {% set checkboxLabelAttr = checkboxLabelAttr|merge({'class': userCheckbox.vars.label_attr.class|default('') ~ (userCheckbox.vars.required ? ' required' : ' optional') }) %} - - {{ user.email }} + {% set checkbox_label_attr = user_checkbox.vars.label_attr|merge({'for': user_checkbox.vars.id}) %} + {% set checkbox_label_attr = checkbox_label_attr|merge({'class': user_checkbox.vars.label_attr.class|default('') ~ (user_checkbox.vars.required ? ' required' : ' optional')}) %} + + {{ user.email }} - {% set checkboxLabelAttr = userCheckbox.vars.label_attr|merge({'for': userCheckbox.vars.id}) %} - {% set checkboxLabelAttr = checkboxLabelAttr|merge({'class': userCheckbox.vars.label_attr.class|default('') ~ (userCheckbox.vars.required ? ' required' : ' optional') }) %} - + {% set checkbox_label_attr = user_checkbox.vars.label_attr|merge({'for': user_checkbox.vars.id}) %} + {% set checkbox_label_attr = checkbox_label_attr|merge({'class': user_checkbox.vars.label_attr.class|default('') ~ (user_checkbox.vars.required ? ' required' : ' optional')}) %} + {{ user.displayName }} @@ -601,16 +599,15 @@ - {% for userGroupCheckbox in form.children %} - {% set userGroup = possibleGroups[userGroupCheckbox.vars.value] %} + {% for user_group_checkbox in form.children %} + {% set user_group = possibleGroups[user_group_checkbox.vars.value] %} - {{ form_widget(userGroupCheckbox, {label:false, 'attr': {'data-select-all-target': form.vars.id ~ '-userGroups'}}) }} + {{ form_widget(user_group_checkbox, {label: false, 'attr': {'data-select-all-target': form.vars.id ~ '-userGroups'}}) }} - {% set checkboxLabelAttr = userGroupCheckbox.vars.label_attr|merge({'for': userGroupCheckbox.vars.id}) %} - {% set checkboxLabelAttr = checkboxLabelAttr|merge({'class': userGroupCheckbox.vars.label_attr.class|default('') ~ (userGroupCheckbox.vars.required ? ' required' : ' optional') }) %} - - {{ userGroup.name }} + {% set checkbox_label_attr = user_group_checkbox.vars.label_attr|merge({'for': user_group_checkbox.vars.id}) %} + {% set checkbox_label_attr = checkbox_label_attr|merge({'class': user_group_checkbox.vars.label_attr.class|default('') ~ (user_group_checkbox.vars.required ? ' required' : ' optional')}) %} + + {{ user_group.name }} diff --git a/src/Modules/Backend/templates/base/header.html.twig b/src/Modules/Backend/templates/base/header.html.twig index 05cf78ddd3..12b41b7156 100644 --- a/src/Modules/Backend/templates/base/header.html.twig +++ b/src/Modules/Backend/templates/base/header.html.twig @@ -15,19 +15,19 @@

{% if SITE_MULTILINGUAL %} - {% set websiteLocales = LOCALES|filter(l => l.isEnabledForWebsite)%} + {% set website_locales = LOCALES|filter(l => l.isEnabledForWebsite) %} {% endif %} @@ -114,16 +114,16 @@
{% endmacro %} -{% macro emptyState(addItemLink, addItemText, noItemsText, addItemLinkExtraAttributes) %} - {% set addItemText = addItemText|default('msg.AddItem'|trans|raw) %} - {% set noItemsText = noItemsText|default('msg.NoItems'|trans|raw) %} +{% macro emptyState(addItemLink, add_item_text, no_items_text, add_item_link_extra_attributes) %} + {% set add_item_text = add_item_text|default('msg.AddItem'|trans|raw) %} + {% set no_items_text = no_items_text|default('msg.NoItems'|trans|raw) %}
-

{{ noItemsText|raw }}

+

{{ no_items_text|raw }}

{% if not addItemLink is null %} - - {{ addItemText|trans|ucfirst }} + + {{ add_item_text|trans|ucfirst }} {% endif %}
diff --git a/src/Modules/Backend/templates/base/navigation.html.twig b/src/Modules/Backend/templates/base/navigation.html.twig index e41636268b..cc1941a840 100644 --- a/src/Modules/Backend/templates/base/navigation.html.twig +++ b/src/Modules/Backend/templates/base/navigation.html.twig @@ -3,27 +3,27 @@
diff --git a/src/Modules/Extensions/templates/Backend/Actions/ThemeIndex.html.twig b/src/Modules/Extensions/templates/Backend/Actions/ThemeIndex.html.twig index 91bd7028a3..00e525d7a4 100644 --- a/src/Modules/Extensions/templates/Backend/Actions/ThemeIndex.html.twig +++ b/src/Modules/Extensions/templates/Backend/Actions/ThemeIndex.html.twig @@ -4,14 +4,14 @@ {% block actionbar %}
- {{ macro.buttonIcon( 'https://www.fork-cms.com/extensions/themes', 'eye', 'lbl.FindThemes'|trans|ucfirst, '', 'target=_blank') }} + {{ macro.buttonIcon('https://www.fork-cms.com/extensions/themes', 'eye', 'lbl.FindThemes'|trans|ucfirst, '', 'target=_blank') }}
{% endblock %} {% block content %} - {% if installedThemes|count > 0 %} + {% if installed_themes|count > 0 %}

@@ -20,45 +20,45 @@

- {% for installedThemeData in installedThemes %} - {% set installableTheme = installedThemeData.theme %} - {% set activateForm = installedThemeData.activateForm %} + {% for installed_theme_data in installed_themes %} + {% set installable_theme = installed_theme_data.theme %} + {% set activate_form = installed_theme_data.activateForm %}
-
+
-

{{ installableTheme.name }}

+

{{ installable_theme.name }}

- {{ installableTheme.name|ucfirst }} + {{ installable_theme.name|ucfirst }}