diff --git a/.docksal/docksal-local.env b/.docksal/docksal-local.env index 8ecd27dba..c49944789 100644 --- a/.docksal/docksal-local.env +++ b/.docksal/docksal-local.env @@ -1 +1,3 @@ MYSQL_PORT_MAPPING='3380:3306' +XDEBUG_ENABLED=1 +XDEBUG_MODE=coverage diff --git a/.docksal/docksal.env b/.docksal/docksal.env index 7da5888bd..8923009e6 100644 --- a/.docksal/docksal.env +++ b/.docksal/docksal.env @@ -1,4 +1,4 @@ DOCKSAL_STACK=default DOCROOT=html DB_IMAGE="docksal/mariadb:10.6" -CLI_IMAGE='docksal/cli:php8.2-build' +CLI_IMAGE='docksal/cli:php8.3-build' diff --git a/.docksal/etc/mysql/my.cnf b/.docksal/etc/mysql/my.cnf new file mode 100644 index 000000000..5a9ce8345 --- /dev/null +++ b/.docksal/etc/mysql/my.cnf @@ -0,0 +1,2 @@ +[mysqld] +innodb_force_recovery=0 diff --git a/.docksal/etc/php/php.ini b/.docksal/etc/php/php.ini index 5e25967e8..cea362e77 100644 --- a/.docksal/etc/php/php.ini +++ b/.docksal/etc/php/php.ini @@ -1,2 +1 @@ -; Mail settings -sendmail_path = '/usr/bin/msmtp -t --host=mail --port=1025 --read-envelope-from' +xdebug.mode=coverage diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index c54b86212..51a8cab4d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -137,7 +137,7 @@ jobs: docker compose -f tests/docker-compose.yml exec -T drupal chmod -R 777 /srv/www/html/sites/default/files /srv/www/html/sites/default/private docker compose -f tests/docker-compose.yml exec -T drupal mkdir -p /srv/www/html/build/logs docker compose -f tests/docker-compose.yml exec -T drupal chmod -R 777 /srv/www/html/build/logs - docker compose -f tests/docker-compose.yml exec -T -w /srv/www -e XDEBUG_MODE=coverage -e BROWSERTEST_OUTPUT_DIRECTORY=/srv/www/html/sites/default/files/browser_output -e DTT_BASE_URL=http://127.0.0.1 drupal ./vendor/bin/phpunit --coverage-clover /srv/www/html/build/logs/clover.xml --debug + docker compose -f tests/docker-compose.yml exec -T -w /srv/www -e XDEBUG_MODE=coverage -e BROWSERTEST_OUTPUT_DIRECTORY=/srv/www/html/sites/default/files/browser_output -e SIMPLETEST_BASE_URL=http://127.0.0.1 -e DTT_BASE_URL=http://127.0.0.1 drupal ./vendor/bin/phpunit --coverage-clover /srv/www/html/build/logs/clover.xml --debug docker cp "$(docker compose -f tests/docker-compose.yml ps -q drupal)":/srv/www/html/build/logs/clover.xml . env: fail-fast: true diff --git a/PATCHES/openid_connect_windows_aad-3346603-5.patch b/PATCHES/openid_connect_windows_aad-3346603-5.patch index 56a481852..35356fef8 100644 --- a/PATCHES/openid_connect_windows_aad-3346603-5.patch +++ b/PATCHES/openid_connect_windows_aad-3346603-5.patch @@ -1,8 +1,8 @@ diff --git a/src/Plugin/OpenIDConnectClient/WindowsAad.php b/src/Plugin/OpenIDConnectClient/WindowsAad.php -index 8845843..6431581 100644 +index 88d4853..ac36baf 100644 --- a/src/Plugin/OpenIDConnectClient/WindowsAad.php +++ b/src/Plugin/OpenIDConnectClient/WindowsAad.php -@@ -318,7 +318,7 @@ as the mapping between Azure AD accounts and Drupal users.
+@@ -329,7 +329,7 @@ as the mapping between Azure AD accounts and Drupal users.
case 2: $v2 = str_contains($endpoints['token'], '/oauth2/v2.0/'); if (!$v2) { diff --git a/composer.json b/composer.json index 3fd73e5d3..f99ba1342 100644 --- a/composer.json +++ b/composer.json @@ -85,6 +85,7 @@ "unocha/gtm_barebones": "^1.1", "unocha/ocha_ai": "^1.7", "unocha/ocha_content_classification": "^1.0", + "unocha/ocha_entraid": "^1.0", "unocha/ocha_monitoring": "^1.0", "webflo/drupal-finder": "^1.2.2" }, @@ -109,6 +110,7 @@ "prefer-stable": true, "config": { "bin-dir": "vendor/bin/", + "discard-changes": true, "sort-packages": true, "allow-plugins": { "composer/installers": true, diff --git a/composer.lock b/composer.lock index 6a9f617bd..dd60ec2c6 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": "b8b3d79cb86b6b8956b17662a2b2dba7", + "content-hash": "44aba325c78bc76813d54e96d5698924", "packages": [ { "name": "asm89/stack-cors", @@ -118,16 +118,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.326.0", + "version": "3.334.4", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "5420284de9aad84e375fa8012cefd834bebfd623" + "reference": "3261e515cfc1bae024bce72be3ea28708461c0a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5420284de9aad84e375fa8012cefd834bebfd623", - "reference": "5420284de9aad84e375fa8012cefd834bebfd623", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3261e515cfc1bae024bce72be3ea28708461c0a3", + "reference": "3261e515cfc1bae024bce72be3ea28708461c0a3", "shasum": "" }, "require": { @@ -156,8 +156,8 @@ "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", - "psr/cache": "^1.0", - "psr/simple-cache": "^1.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "sebastian/comparator": "^1.2.3 || ^4.0", "yoast/phpunit-polyfills": "^1.0" }, @@ -210,9 +210,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.326.0" + "source": "https://github.com/aws/aws-sdk-php/tree/3.334.4" }, - "time": "2024-11-13T19:07:44+00:00" + "time": "2024-12-11T19:41:47+00:00" }, { "name": "behat/mink", @@ -549,16 +549,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.3", + "version": "1.5.4", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2" + "reference": "bc0593537a463e55cadf45fd938d23b75095b7e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3b1fc3f0be055baa7c6258b1467849c3e8204eb2", - "reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/bc0593537a463e55cadf45fd938d23b75095b7e1", + "reference": "bc0593537a463e55cadf45fd938d23b75095b7e1", "shasum": "" }, "require": { @@ -605,7 +605,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.3" + "source": "https://github.com/composer/ca-bundle/tree/1.5.4" }, "funding": [ { @@ -621,20 +621,20 @@ "type": "tidelift" } ], - "time": "2024-11-04T10:15:26+00:00" + "time": "2024-11-27T15:35:25+00:00" }, { "name": "composer/class-map-generator", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "98bbf6780e56e0fd2404fe4b82eb665a0f93b783" + "reference": "4b0a223cf5be7c9ee7e0ef1bc7db42b4a97c9915" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/98bbf6780e56e0fd2404fe4b82eb665a0f93b783", - "reference": "98bbf6780e56e0fd2404fe4b82eb665a0f93b783", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/4b0a223cf5be7c9ee7e0ef1bc7db42b4a97c9915", + "reference": "4b0a223cf5be7c9ee7e0ef1bc7db42b4a97c9915", "shasum": "" }, "require": { @@ -643,10 +643,10 @@ "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7" }, "require-dev": { - "phpstan/phpstan": "^1.6", - "phpstan/phpstan-deprecation-rules": "^1", - "phpstan/phpstan-phpunit": "^1", - "phpstan/phpstan-strict-rules": "^1.1", + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", "phpunit/phpunit": "^8", "symfony/filesystem": "^5.4 || ^6" }, @@ -678,7 +678,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.4.0" + "source": "https://github.com/composer/class-map-generator/tree/1.5.0" }, "funding": [ { @@ -694,20 +694,20 @@ "type": "tidelift" } ], - "time": "2024-10-03T18:14:00+00:00" + "time": "2024-11-25T16:11:06+00:00" }, { "name": "composer/composer", - "version": "2.8.3", + "version": "2.8.4", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "2a7c71266b2545a3bed9f4860734081963f6e688" + "reference": "112e37d1dca22b3fdb81cf3524ab4994f47fdb8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/2a7c71266b2545a3bed9f4860734081963f6e688", - "reference": "2a7c71266b2545a3bed9f4860734081963f6e688", + "url": "https://api.github.com/repos/composer/composer/zipball/112e37d1dca22b3fdb81cf3524ab4994f47fdb8c", + "reference": "112e37d1dca22b3fdb81cf3524ab4994f47fdb8c", "shasum": "" }, "require": { @@ -751,13 +751,13 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-main": "2.8-dev" - }, "phpstan": { "includes": [ "phpstan/rules.neon" ] + }, + "branch-alias": { + "dev-main": "2.8-dev" } }, "autoload": { @@ -792,7 +792,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.8.3" + "source": "https://github.com/composer/composer/tree/2.8.4" }, "funding": [ { @@ -808,7 +808,7 @@ "type": "tidelift" } ], - "time": "2024-11-17T12:13:04+00:00" + "time": "2024-12-11T10:57:47+00:00" }, { "name": "composer/installers", @@ -2126,29 +2126,27 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", + "doctrine/coding-standard": "^9 || ^12", + "phpstan/phpstan": "1.4.10 || 2.0.3", + "phpstan/phpstan-phpunit": "^1.0 || ^2", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" + "psr/log": "^1 || ^2 || ^3" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -2156,7 +2154,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + "Doctrine\\Deprecations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2167,9 +2165,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + "source": "https://github.com/doctrine/deprecations/tree/1.1.4" }, - "time": "2024-01-30T19:34:25+00:00" + "time": "2024-12-07T21:18:45+00:00" }, { "name": "doctrine/instantiator", @@ -2321,16 +2319,16 @@ }, { "name": "dompdf/php-font-lib", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/dompdf/php-font-lib.git", - "reference": "991d6a954f6bbd7e41022198f00586b230731441" + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/991d6a954f6bbd7e41022198f00586b230731441", - "reference": "991d6a954f6bbd7e41022198f00586b230731441", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", "shasum": "" }, "require": { @@ -2360,9 +2358,9 @@ "homepage": "https://github.com/dompdf/php-font-lib", "support": { "issues": "https://github.com/dompdf/php-font-lib/issues", - "source": "https://github.com/dompdf/php-font-lib/tree/1.0.0" + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.1" }, - "time": "2024-04-29T13:40:38+00:00" + "time": "2024-12-02T14:37:59+00:00" }, { "name": "drupal/admin_denied", @@ -2644,16 +2642,16 @@ }, { "name": "drupal/coder", - "version": "8.3.25", + "version": "8.3.26", "source": { "type": "git", "url": "https://github.com/pfrenssen/coder.git", - "reference": "c58e5a0c44c0010bbc8a91fc468f4667e177b976" + "reference": "fd98546ce3373aa7767240901eda47963ce64c82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pfrenssen/coder/zipball/c58e5a0c44c0010bbc8a91fc468f4667e177b976", - "reference": "c58e5a0c44c0010bbc8a91fc468f4667e177b976", + "url": "https://api.github.com/repos/pfrenssen/coder/zipball/fd98546ce3373aa7767240901eda47963ce64c82", + "reference": "fd98546ce3373aa7767240901eda47963ce64c82", "shasum": "" }, "require": { @@ -2691,7 +2689,7 @@ "issues": "https://www.drupal.org/project/issues/coder", "source": "https://www.drupal.org/project/coder" }, - "time": "2024-09-22T19:02:16+00:00" + "time": "2024-11-28T23:14:29+00:00" }, { "name": "drupal/components", @@ -2940,16 +2938,16 @@ }, { "name": "drupal/core", - "version": "10.3.9", + "version": "10.3.10", "source": { "type": "git", "url": "https://github.com/drupal/core.git", - "reference": "42a6516491b4793158542a2326dc6ad1fe2aa5bd" + "reference": "3ebb71e9c4ef0c13f683353547551fca49f9a144" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core/zipball/42a6516491b4793158542a2326dc6ad1fe2aa5bd", - "reference": "42a6516491b4793158542a2326dc6ad1fe2aa5bd", + "url": "https://api.github.com/repos/drupal/core/zipball/3ebb71e9c4ef0c13f683353547551fca49f9a144", + "reference": "3ebb71e9c4ef0c13f683353547551fca49f9a144", "shasum": "" }, "require": { @@ -3033,29 +3031,29 @@ "extra": { "drupal-scaffold": { "file-mapping": { - "[project-root]/.editorconfig": "assets/scaffold/files/editorconfig", - "[project-root]/.gitattributes": "assets/scaffold/files/gitattributes", - "[web-root]/.csslintrc": "assets/scaffold/files/csslintrc", - "[web-root]/.eslintignore": "assets/scaffold/files/eslintignore", - "[web-root]/.eslintrc.json": "assets/scaffold/files/eslintrc.json", - "[web-root]/.ht.router.php": "assets/scaffold/files/ht.router.php", "[web-root]/.htaccess": "assets/scaffold/files/htaccess", - "[web-root]/example.gitignore": "assets/scaffold/files/example.gitignore", - "[web-root]/index.php": "assets/scaffold/files/index.php", - "[web-root]/INSTALL.txt": "assets/scaffold/files/drupal.INSTALL.txt", "[web-root]/README.md": "assets/scaffold/files/drupal.README.md", + "[web-root]/index.php": "assets/scaffold/files/index.php", + "[web-root]/.csslintrc": "assets/scaffold/files/csslintrc", "[web-root]/robots.txt": "assets/scaffold/files/robots.txt", "[web-root]/update.php": "assets/scaffold/files/update.php", "[web-root]/web.config": "assets/scaffold/files/web.config", + "[web-root]/INSTALL.txt": "assets/scaffold/files/drupal.INSTALL.txt", + "[web-root]/.eslintignore": "assets/scaffold/files/eslintignore", + "[web-root]/.eslintrc.json": "assets/scaffold/files/eslintrc.json", + "[web-root]/.ht.router.php": "assets/scaffold/files/ht.router.php", "[web-root]/sites/README.txt": "assets/scaffold/files/sites.README.txt", + "[project-root]/.editorconfig": "assets/scaffold/files/editorconfig", + "[web-root]/example.gitignore": "assets/scaffold/files/example.gitignore", + "[web-root]/themes/README.txt": "assets/scaffold/files/themes.README.txt", + "[project-root]/.gitattributes": "assets/scaffold/files/gitattributes", + "[web-root]/modules/README.txt": "assets/scaffold/files/modules.README.txt", + "[web-root]/profiles/README.txt": "assets/scaffold/files/profiles.README.txt", + "[web-root]/sites/example.sites.php": "assets/scaffold/files/example.sites.php", "[web-root]/sites/development.services.yml": "assets/scaffold/files/development.services.yml", "[web-root]/sites/example.settings.local.php": "assets/scaffold/files/example.settings.local.php", - "[web-root]/sites/example.sites.php": "assets/scaffold/files/example.sites.php", "[web-root]/sites/default/default.services.yml": "assets/scaffold/files/default.services.yml", - "[web-root]/sites/default/default.settings.php": "assets/scaffold/files/default.settings.php", - "[web-root]/modules/README.txt": "assets/scaffold/files/modules.README.txt", - "[web-root]/profiles/README.txt": "assets/scaffold/files/profiles.README.txt", - "[web-root]/themes/README.txt": "assets/scaffold/files/themes.README.txt" + "[web-root]/sites/default/default.settings.php": "assets/scaffold/files/default.settings.php" } } }, @@ -3098,13 +3096,13 @@ ], "description": "Drupal is an open source content management platform powering millions of websites and applications.", "support": { - "source": "https://github.com/drupal/core/tree/10.3.9" + "source": "https://github.com/drupal/core/tree/10.3.10" }, - "time": "2024-11-20T17:59:45+00:00" + "time": "2024-11-22T12:51:33+00:00" }, { "name": "drupal/core-composer-scaffold", - "version": "10.3.9", + "version": "10.3.10", "source": { "type": "git", "url": "https://github.com/drupal/core-composer-scaffold.git", @@ -3148,13 +3146,13 @@ "drupal" ], "support": { - "source": "https://github.com/drupal/core-composer-scaffold/tree/10.3.9" + "source": "https://github.com/drupal/core-composer-scaffold/tree/10.3.10" }, "time": "2024-08-22T14:31:34+00:00" }, { "name": "drupal/core-dev", - "version": "10.3.9", + "version": "10.3.10", "source": { "type": "git", "url": "https://github.com/drupal/core-dev.git", @@ -3204,22 +3202,22 @@ ], "description": "require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.", "support": { - "source": "https://github.com/drupal/core-dev/tree/10.3.9" + "source": "https://github.com/drupal/core-dev/tree/10.3.10" }, "time": "2024-07-04T10:19:29+00:00" }, { "name": "drupal/core-recommended", - "version": "10.3.9", + "version": "10.3.10", "source": { "type": "git", "url": "https://github.com/drupal/core-recommended.git", - "reference": "03da2860a10c12b86714e778178433b620bb890d" + "reference": "1d739e569c9324bcac1ecc7be600d414386a399b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core-recommended/zipball/03da2860a10c12b86714e778178433b620bb890d", - "reference": "03da2860a10c12b86714e778178433b620bb890d", + "url": "https://api.github.com/repos/drupal/core-recommended/zipball/1d739e569c9324bcac1ecc7be600d414386a399b", + "reference": "1d739e569c9324bcac1ecc7be600d414386a399b", "shasum": "" }, "require": { @@ -3228,7 +3226,7 @@ "doctrine/annotations": "~1.14.3", "doctrine/deprecations": "~1.1.3", "doctrine/lexer": "~2.1.1", - "drupal/core": "10.3.9", + "drupal/core": "10.3.10", "egulias/email-validator": "~4.0.2", "guzzlehttp/guzzle": "~7.8.1", "guzzlehttp/promises": "~2.0.2", @@ -3289,9 +3287,9 @@ ], "description": "Core and its dependencies with known-compatible minor versions. Require this project INSTEAD OF drupal/core.", "support": { - "source": "https://github.com/drupal/core-recommended/tree/10.3.9" + "source": "https://github.com/drupal/core-recommended/tree/10.3.10" }, - "time": "2024-11-20T17:59:45+00:00" + "time": "2024-11-22T12:51:33+00:00" }, { "name": "drupal/ctools", @@ -4328,7 +4326,7 @@ "shasum": "fc8ea60619b6b4682bade340e13fb4565d3a7e0c" }, "require": { - "drupal/core": "^10" + "drupal/core": "^9.1 || ^10" }, "type": "drupal-module", "extra": { @@ -4447,17 +4445,17 @@ }, { "name": "drupal/metatag", - "version": "2.0.2", + "version": "2.1.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/metatag.git", - "reference": "2.0.2" + "reference": "2.1.0" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/metatag-2.0.2.zip", - "reference": "2.0.2", - "shasum": "748013c50a0ed5e10359413bb3481392a0bf0d3f" + "url": "https://ftp.drupal.org/files/projects/metatag-2.1.0.zip", + "reference": "2.1.0", + "shasum": "c28fe2fdac68a9370a6af6cbafff4425dd5148f3" }, "require": { "drupal/core": "^9.4 || ^10 || ^11", @@ -4465,6 +4463,7 @@ "php": ">=8.0" }, "require-dev": { + "drupal/forum": "*", "drupal/hal": "^1 || ^2 || ^9", "drupal/metatag_dc": "*", "drupal/metatag_open_graph": "*", @@ -4476,8 +4475,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "2.0.2", - "datestamp": "1722869772", + "version": "2.1.0", + "datestamp": "1731004042", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -4530,7 +4529,7 @@ "shasum": "c25246747dac4372c7d5a5a5fd0f276d9e468eff" }, "require": { - "drupal/core": "^10", + "drupal/core": "^9.3 || ^10", "drupal/mailsystem": "^4" }, "require-dev": { @@ -4638,7 +4637,7 @@ "source": { "type": "git", "url": "https://git.drupalcode.org/project/openid_connect.git", - "reference": "1c1f25f39570a77ee41ceab85f774b3425b67ea3" + "reference": "593afceafc3499f80b76afa591ccaa51b8ee466b" }, "require": { "drupal/core": "^9.5 || ^10.2 || ^11", @@ -4652,8 +4651,8 @@ "dev-3.x": "3.x-dev" }, "drupal": { - "version": "3.0.0-alpha3+3-dev", - "datestamp": "1730974732", + "version": "3.0.0-alpha3+4-dev", + "datestamp": "1731686912", "security-coverage": { "status": "not-covered", "message": "Dev releases are not covered by Drupal security advisories." @@ -5327,17 +5326,17 @@ }, { "name": "drupal/stage_file_proxy", - "version": "3.1.3", + "version": "3.1.4", "source": { "type": "git", "url": "https://git.drupalcode.org/project/stage_file_proxy.git", - "reference": "3.1.3" + "reference": "3.1.4" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/stage_file_proxy-3.1.3.zip", - "reference": "3.1.3", - "shasum": "33fe8bf9cf0978a9e370627302b80340c972abd5" + "url": "https://ftp.drupal.org/files/projects/stage_file_proxy-3.1.4.zip", + "reference": "3.1.4", + "shasum": "57be6bfd1a429d41852e049e047b3ac0e277d277" }, "require": { "drupal/core": "^10.3 || ^11", @@ -5353,8 +5352,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "3.1.3", - "datestamp": "1728406824", + "version": "3.1.4", + "datestamp": "1733151924", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -5422,27 +5421,27 @@ }, { "name": "drupal/svg_image", - "version": "3.1.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/svg_image.git", - "reference": "3.1.0" + "reference": "3.2.0" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/svg_image-3.1.0.zip", - "reference": "3.1.0", - "shasum": "edb3038bf21c327706793cd1328a1734a7db48e6" + "url": "https://ftp.drupal.org/files/projects/svg_image-3.2.0.zip", + "reference": "3.2.0", + "shasum": "d55ef2bc3b751b8ce3f33485216c38cfbfb076b2" }, "require": { - "drupal/core": "^10", + "drupal/core": "^10.3 || ^11", "enshrined/svg-sanitize": ">=0.15 <1.0" }, "type": "drupal-module", "extra": { "drupal": { - "version": "3.1.0", - "datestamp": "1727351323", + "version": "3.2.0", + "datestamp": "1733298643", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -5530,33 +5529,33 @@ }, { "name": "drupal/taxonomy_term_revision", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/taxonomy_term_revision.git", - "reference": "8.x-1.1" + "reference": "8.x-1.2" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/taxonomy_term_revision-8.x-1.1.zip", - "reference": "8.x-1.1", - "shasum": "72a3c67ff7fff4a8247f59a2ba95c25c914f5359" + "url": "https://ftp.drupal.org/files/projects/taxonomy_term_revision-8.x-1.2.zip", + "reference": "8.x-1.2", + "shasum": "2720c1e9468cd19cb4ccf2a9141a834c46de2fd3" }, "require": { - "drupal/core": "^8 || ^9 || ^10" + "drupal/core": "^8.8 || ^9 || ^10 || ^11" }, "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-1.1", - "datestamp": "1660910720", + "version": "8.x-1.2", + "datestamp": "1733746201", "security-coverage": { "status": "not-covered", "message": "Project has not opted into security advisory coverage!" } }, "branch-alias": { - "dev-8.x-2.x": "2.x-dev" + "dev-8.x-1.x": "1.x-dev" }, "drush": { "services": { @@ -5582,26 +5581,26 @@ }, { "name": "drupal/theme_switcher", - "version": "2.0.1", + "version": "2.1.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/theme_switcher.git", - "reference": "2.0.1" + "reference": "2.1.0" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/theme_switcher-2.0.1.zip", - "reference": "2.0.1", - "shasum": "9ea9eee91cf75f21fcc939704baa6a7ec10d7748" + "url": "https://ftp.drupal.org/files/projects/theme_switcher-2.1.0.zip", + "reference": "2.1.0", + "shasum": "4aa9b119c9c992fd9930fa50f97cd89959c3bafb" }, "require": { - "drupal/core": "^10" + "drupal/core": "^8.9 || ^9 || ^10 || ^11" }, "type": "drupal-module", "extra": { "drupal": { - "version": "2.0.1", - "datestamp": "1687963275", + "version": "2.1.0", + "datestamp": "1732010759", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -5618,11 +5617,11 @@ "homepage": "https://www.drupal.org/user/3529039" }, { - "name": "Anybody", + "name": "anybody", "homepage": "https://www.drupal.org/user/291091" }, { - "name": "Carlos Romero", + "name": "carlos romero", "homepage": "https://www.drupal.org/user/3722452" }, { @@ -5865,23 +5864,23 @@ "sut/libraries/{$name}": [ "type:drupal-library" ], + "sut/themes/unish/{$name}": [ + "drupal/empty_theme" + ], + "sut/drush/contrib/{$name}": [ + "type:drupal-drush" + ], "sut/modules/unish/{$name}": [ "drupal/devel" ], - "sut/themes/unish/{$name}": [ - "drupal/empty_theme" + "sut/themes/contrib/{$name}": [ + "type:drupal-theme" ], "sut/modules/contrib/{$name}": [ "type:drupal-module" ], "sut/profiles/contrib/{$name}": [ "type:drupal-profile" - ], - "sut/themes/contrib/{$name}": [ - "type:drupal-theme" - ], - "sut/drush/contrib/{$name}": [ - "type:drupal-drush" ] } }, @@ -6787,16 +6786,16 @@ }, { "name": "google/protobuf", - "version": "v4.28.3", + "version": "v4.29.1", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "c5c311e0f3d89928251ac5a2f0e3db283612c100" + "reference": "6042b5483f8029e42473faeb8ef75ba266278381" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/c5c311e0f3d89928251ac5a2f0e3db283612c100", - "reference": "c5c311e0f3d89928251ac5a2f0e3db283612c100", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/6042b5483f8029e42473faeb8ef75ba266278381", + "reference": "6042b5483f8029e42473faeb8ef75ba266278381", "shasum": "" }, "require": { @@ -6825,22 +6824,22 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.28.3" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.29.1" }, - "time": "2024-10-22T22:27:17+00:00" + "time": "2024-12-03T22:07:45+00:00" }, { "name": "grasmash/expander", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/grasmash/expander.git", - "reference": "bb1c1a2430957945cf08c5a62f5d72a6aa6a2c82" + "reference": "eea11b9afb0c32483b18b9009f4ca07b770e39f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/grasmash/expander/zipball/bb1c1a2430957945cf08c5a62f5d72a6aa6a2c82", - "reference": "bb1c1a2430957945cf08c5a62f5d72a6aa6a2c82", + "url": "https://api.github.com/repos/grasmash/expander/zipball/eea11b9afb0c32483b18b9009f4ca07b770e39f4", + "reference": "eea11b9afb0c32483b18b9009f4ca07b770e39f4", "shasum": "" }, "require": { @@ -6877,9 +6876,9 @@ "description": "Expands internal property references in PHP arrays file.", "support": { "issues": "https://github.com/grasmash/expander/issues", - "source": "https://github.com/grasmash/expander/tree/3.0.0" + "source": "https://github.com/grasmash/expander/tree/3.0.1" }, - "time": "2022-05-10T13:14:49+00:00" + "time": "2024-11-25T23:28:05+00:00" }, { "name": "grasmash/yaml-cli", @@ -7492,16 +7491,16 @@ }, { "name": "league/commonmark", - "version": "2.5.3", + "version": "2.6.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "b650144166dfa7703e62a22e493b853b58d874b0" + "reference": "d150f911e0079e90ae3c106734c93137c184f932" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0", - "reference": "b650144166dfa7703e62a22e493b853b58d874b0", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d150f911e0079e90ae3c106734c93137c184f932", + "reference": "d150f911e0079e90ae3c106734c93137c184f932", "shasum": "" }, "require": { @@ -7526,8 +7525,9 @@ "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0 || ^7.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 || ^7.0", + "symfony/finder": "^5.3 | ^6.0 | ^7.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0" }, @@ -7537,7 +7537,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.6-dev" + "dev-main": "2.7-dev" } }, "autoload": { @@ -7594,7 +7594,7 @@ "type": "tidelift" } ], - "time": "2024-08-16T11:46:16+00:00" + "time": "2024-12-07T15:34:16+00:00" }, { "name": "league/config", @@ -7680,16 +7680,16 @@ }, { "name": "league/container", - "version": "4.2.3", + "version": "4.2.4", "source": { "type": "git", "url": "https://github.com/thephpleague/container.git", - "reference": "72f9bebe7bd623007782a40f5ec305661ab706d8" + "reference": "7ea728b013b9a156c409c6f0fc3624071b742dec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/container/zipball/72f9bebe7bd623007782a40f5ec305661ab706d8", - "reference": "72f9bebe7bd623007782a40f5ec305661ab706d8", + "url": "https://api.github.com/repos/thephpleague/container/zipball/7ea728b013b9a156c409c6f0fc3624071b742dec", + "reference": "7ea728b013b9a156c409c6f0fc3624071b742dec", "shasum": "" }, "require": { @@ -7714,11 +7714,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev", - "dev-4.x": "4.x-dev", - "dev-3.x": "3.x-dev", + "dev-1.x": "1.x-dev", "dev-2.x": "2.x-dev", - "dev-1.x": "1.x-dev" + "dev-3.x": "3.x-dev", + "dev-4.x": "4.x-dev", + "dev-master": "4.x-dev" } }, "autoload": { @@ -7750,7 +7750,7 @@ ], "support": { "issues": "https://github.com/thephpleague/container/issues", - "source": "https://github.com/thephpleague/container/tree/4.2.3" + "source": "https://github.com/thephpleague/container/tree/4.2.4" }, "funding": [ { @@ -7758,7 +7758,7 @@ "type": "github" } ], - "time": "2024-10-23T12:06:58+00:00" + "time": "2024-11-10T12:42:13+00:00" }, { "name": "league/html-to-markdown", @@ -7851,35 +7851,30 @@ }, { "name": "league/oauth2-client", - "version": "2.7.0", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth2-client.git", - "reference": "160d6274b03562ebeb55ed18399281d8118b76c8" + "reference": "3d5cf8d0543731dfb725ab30e4d7289891991e13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/160d6274b03562ebeb55ed18399281d8118b76c8", - "reference": "160d6274b03562ebeb55ed18399281d8118b76c8", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/3d5cf8d0543731dfb725ab30e4d7289891991e13", + "reference": "3d5cf8d0543731dfb725ab30e4d7289891991e13", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "^6.0 || ^7.0", - "paragonie/random_compat": "^1 || ^2 || ^9.99", - "php": "^5.6 || ^7.0 || ^8.0" + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "php": "^7.1 || >=8.0.0 <8.5.0" }, "require-dev": { "mockery/mockery": "^1.3.5", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", - "squizlabs/php_codesniffer": "^2.3 || ^3.0" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.11" }, "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.0.x-dev" - } - }, "autoload": { "psr-4": { "League\\OAuth2\\Client\\": "src/" @@ -7915,9 +7910,9 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth2-client/issues", - "source": "https://github.com/thephpleague/oauth2-client/tree/2.7.0" + "source": "https://github.com/thephpleague/oauth2-client/tree/2.8.0" }, - "time": "2023-04-16T18:19:15+00:00" + "time": "2024-12-11T05:05:52+00:00" }, { "name": "lolli42/finediff", @@ -8222,16 +8217,16 @@ }, { "name": "mglaman/phpstan-drupal", - "version": "1.3.1", + "version": "1.3.2", "source": { "type": "git", "url": "https://github.com/mglaman/phpstan-drupal.git", - "reference": "2bc25a59b53c8f3990f168efd71241d9c25ea0c3" + "reference": "bbb92dee546da3988da851122cb2925f72c149f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mglaman/phpstan-drupal/zipball/2bc25a59b53c8f3990f168efd71241d9c25ea0c3", - "reference": "2bc25a59b53c8f3990f168efd71241d9c25ea0c3", + "url": "https://api.github.com/repos/mglaman/phpstan-drupal/zipball/bbb92dee546da3988da851122cb2925f72c149f3", + "reference": "bbb92dee546da3988da851122cb2925f72c149f3", "shasum": "" }, "require": { @@ -8261,6 +8256,12 @@ }, "type": "phpstan-extension", "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + }, "branch-alias": { "dev-main": "1.0-dev" }, @@ -8271,20 +8272,14 @@ "tests/fixtures/drupal/libraries/{$name}": [ "type:drupal-library" ], + "tests/fixtures/drupal/themes/contrib/{$name}": [ + "type:drupal-theme" + ], "tests/fixtures/drupal/modules/contrib/{$name}": [ "type:drupal-module" ], "tests/fixtures/drupal/profiles/contrib/{$name}": [ "type:drupal-profile" - ], - "tests/fixtures/drupal/themes/contrib/{$name}": [ - "type:drupal-theme" - ] - }, - "phpstan": { - "includes": [ - "extension.neon", - "rules.neon" ] } }, @@ -8306,7 +8301,7 @@ "description": "Drupal extension and rules for PHPStan", "support": { "issues": "https://github.com/mglaman/phpstan-drupal/issues", - "source": "https://github.com/mglaman/phpstan-drupal/tree/1.3.1" + "source": "https://github.com/mglaman/phpstan-drupal/tree/1.3.2" }, "funding": [ { @@ -8322,7 +8317,7 @@ "type": "tidelift" } ], - "time": "2024-09-27T08:54:16+00:00" + "time": "2024-11-19T15:26:05+00:00" }, { "name": "micheh/phpcs-gitlab", @@ -9705,16 +9700,16 @@ }, { "name": "pear/pear-core-minimal", - "version": "v1.10.15", + "version": "v1.10.16", "source": { "type": "git", "url": "https://github.com/pear/pear-core-minimal.git", - "reference": "ce0adade8b97561656ace07cdaac4751c271ea8c" + "reference": "c0f51b45f50683bf5bbf558036854ebc9b54d033" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/ce0adade8b97561656ace07cdaac4751c271ea8c", - "reference": "ce0adade8b97561656ace07cdaac4751c271ea8c", + "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/c0f51b45f50683bf5bbf558036854ebc9b54d033", + "reference": "c0f51b45f50683bf5bbf558036854ebc9b54d033", "shasum": "" }, "require": { @@ -9750,7 +9745,7 @@ "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR", "source": "https://github.com/pear/pear-core-minimal" }, - "time": "2024-03-16T18:41:45+00:00" + "time": "2024-11-24T22:27:58+00:00" }, { "name": "pear/pear_exception", @@ -10240,21 +10235,21 @@ }, { "name": "php-http/guzzle7-adapter", - "version": "1.0.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-http/guzzle7-adapter.git", - "reference": "fb075a71dbfa4847cf0c2938c4e5a9c478ef8b01" + "reference": "03a415fde709c2f25539790fecf4d9a31bc3d0eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/guzzle7-adapter/zipball/fb075a71dbfa4847cf0c2938c4e5a9c478ef8b01", - "reference": "fb075a71dbfa4847cf0c2938c4e5a9c478ef8b01", + "url": "https://api.github.com/repos/php-http/guzzle7-adapter/zipball/03a415fde709c2f25539790fecf4d9a31bc3d0eb", + "reference": "03a415fde709c2f25539790fecf4d9a31bc3d0eb", "shasum": "" }, "require": { "guzzlehttp/guzzle": "^7.0", - "php": "^7.2 | ^8.0", + "php": "^7.3 | ^8.0", "php-http/httplug": "^2.0", "psr/http-client": "^1.0" }, @@ -10265,14 +10260,11 @@ }, "require-dev": { "php-http/client-integration-tests": "^3.0", + "php-http/message-factory": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^8.0|^9.3" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.2.x-dev" - } - }, "autoload": { "psr-4": { "Http\\Adapter\\Guzzle7\\": "src/" @@ -10296,9 +10288,9 @@ ], "support": { "issues": "https://github.com/php-http/guzzle7-adapter/issues", - "source": "https://github.com/php-http/guzzle7-adapter/tree/1.0.0" + "source": "https://github.com/php-http/guzzle7-adapter/tree/1.1.0" }, - "time": "2021-03-09T07:35:15+00:00" + "time": "2024-11-26T11:14:36+00:00" }, { "name": "php-http/httplug", @@ -10520,16 +10512,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.0", + "version": "5.6.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c" + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/f3558a4c23426d12bffeaab463f8a8d8b681193c", - "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", "shasum": "" }, "require": { @@ -10578,9 +10570,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.1" }, - "time": "2024-11-12T11:25:25+00:00" + "time": "2024-12-07T09:39:29+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -11024,16 +11016,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.11", + "version": "1.12.12", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "0d1fc20a962a91be578bcfe7cf939e6e1a2ff733" + "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d1fc20a962a91be578bcfe7cf939e6e1a2ff733", - "reference": "0d1fc20a962a91be578bcfe7cf939e6e1a2ff733", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", + "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", "shasum": "" }, "require": { @@ -11078,7 +11070,7 @@ "type": "github" } ], - "time": "2024-11-17T14:08:01+00:00" + "time": "2024-11-28T22:13:23+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -11500,16 +11492,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.21", + "version": "9.6.22", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa" + "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", - "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", + "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", "shasum": "" }, "require": { @@ -11520,7 +11512,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.0", + "myclabs/deep-copy": "^1.12.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", @@ -11583,7 +11575,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.21" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22" }, "funding": [ { @@ -11599,7 +11591,7 @@ "type": "tidelift" } ], - "time": "2024-09-19T10:50:18+00:00" + "time": "2024-12-05T13:48:26+00:00" }, { "name": "psr/cache", @@ -12013,16 +12005,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.4", + "version": "v0.12.7", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818" + "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/2fd717afa05341b4f8152547f142cd2f130f6818", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", + "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", "shasum": "" }, "require": { @@ -12049,12 +12041,12 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-main": "0.12.x-dev" - }, "bamarni-bin": { "bin-links": false, "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.x-dev" } }, "autoload": { @@ -12086,9 +12078,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.4" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.7" }, - "time": "2024-06-10T01:18:23+00:00" + "time": "2024-12-10T01:58:33+00:00" }, { "name": "ralouphie/getallheaders", @@ -13700,16 +13692,16 @@ }, { "name": "sirbrillig/phpcs-variable-analysis", - "version": "v2.11.19", + "version": "v2.11.21", "source": { "type": "git", "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", - "reference": "bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1" + "reference": "eb2b351927098c24860daa7484e290d3eed693be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1", - "reference": "bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1", + "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/eb2b351927098c24860daa7484e290d3eed693be", + "reference": "eb2b351927098c24860daa7484e290d3eed693be", "shasum": "" }, "require": { @@ -13720,9 +13712,9 @@ "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", "phpcsstandards/phpcsdevcs": "^1.1", "phpstan/phpstan": "^1.7", - "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0 || ^10.5.32 || ^11.3.3", "sirbrillig/phpcs-import-detection": "^1.1", - "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0@beta" + "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0" }, "type": "phpcodesniffer-standard", "autoload": { @@ -13754,7 +13746,7 @@ "source": "https://github.com/sirbrillig/phpcs-variable-analysis", "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" }, - "time": "2024-06-26T20:08:34+00:00" + "time": "2024-12-02T16:37:49+00:00" }, { "name": "slevomat/coding-standard", @@ -13880,16 +13872,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.11.1", + "version": "3.11.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87" + "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/19473c30efe4f7b3cd42522d0b2e6e7f243c6f87", - "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1368f4a58c3c52114b86b1abe8f4098869cb0079", + "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079", "shasum": "" }, "require": { @@ -13956,7 +13948,7 @@ "type": "open_collective" } ], - "time": "2024-11-16T12:02:36+00:00" + "time": "2024-12-11T16:04:26+00:00" }, { "name": "symfony/browser-kit", @@ -14187,16 +14179,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v6.4.15", + "version": "v6.4.16", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "70ab1f65a4516ef741e519ea938e6aa465e6aa36" + "reference": "7a379d8871f6a36f01559c14e11141cc02eb8dc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/70ab1f65a4516ef741e519ea938e6aa465e6aa36", - "reference": "70ab1f65a4516ef741e519ea938e6aa465e6aa36", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/7a379d8871f6a36f01559c14e11141cc02eb8dc8", + "reference": "7a379d8871f6a36f01559c14e11141cc02eb8dc8", "shasum": "" }, "require": { @@ -14248,7 +14240,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.4.15" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.16" }, "funding": [ { @@ -14264,20 +14256,20 @@ "type": "tidelift" } ], - "time": "2024-11-09T06:56:25+00:00" + "time": "2024-11-25T14:52:46+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "shasum": "" }, "require": { @@ -14315,7 +14307,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" }, "funding": [ { @@ -14331,20 +14323,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/dom-crawler", - "version": "v6.4.13", + "version": "v6.4.16", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "ae074dffb018c37a57071990d16e6152728dd972" + "reference": "4304e6ad5c894a9c72831ad459f627bfd35d766d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/ae074dffb018c37a57071990d16e6152728dd972", - "reference": "ae074dffb018c37a57071990d16e6152728dd972", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4304e6ad5c894a9c72831ad459f627bfd35d766d", + "reference": "4304e6ad5c894a9c72831ad459f627bfd35d766d", "shasum": "" }, "require": { @@ -14382,7 +14374,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v6.4.13" + "source": "https://github.com/symfony/dom-crawler/tree/v6.4.16" }, "funding": [ { @@ -14398,7 +14390,7 @@ "type": "tidelift" } ], - "time": "2024-10-25T15:07:50+00:00" + "time": "2024-11-13T15:06:22+00:00" }, { "name": "symfony/error-handler", @@ -14557,16 +14549,16 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", "shasum": "" }, "require": { @@ -14613,7 +14605,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" }, "funding": [ { @@ -14629,7 +14621,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/filesystem", @@ -14763,16 +14755,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.4.15", + "version": "v6.4.16", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6" + "reference": "431771b7a6f662f1575b3cfc8fd7617aa9864d57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6", - "reference": "9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/431771b7a6f662f1575b3cfc8fd7617aa9864d57", + "reference": "431771b7a6f662f1575b3cfc8fd7617aa9864d57", "shasum": "" }, "require": { @@ -14820,7 +14812,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.15" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.16" }, "funding": [ { @@ -14836,20 +14828,20 @@ "type": "tidelift" } ], - "time": "2024-11-08T16:09:24+00:00" + "time": "2024-11-13T18:58:10+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.15", + "version": "v6.4.16", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b002a5b3947653c5aee3adac2a024ea615fd3ff5" + "reference": "8838b5b21d807923b893ccbfc2cbeda0f1bc00f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b002a5b3947653c5aee3adac2a024ea615fd3ff5", - "reference": "b002a5b3947653c5aee3adac2a024ea615fd3ff5", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/8838b5b21d807923b893ccbfc2cbeda0f1bc00f0", + "reference": "8838b5b21d807923b893ccbfc2cbeda0f1bc00f0", "shasum": "" }, "require": { @@ -14934,7 +14926,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.15" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.16" }, "funding": [ { @@ -14950,7 +14942,7 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:57:37+00:00" + "time": "2024-11-27T12:49:36+00:00" }, { "name": "symfony/lock", @@ -15198,16 +15190,16 @@ }, { "name": "symfony/phpunit-bridge", - "version": "v6.4.13", + "version": "v6.4.16", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "e6377bea5b114f70de6332fe935e160da5014ce8" + "reference": "cebafe2f1ad2d1e745c1015b7c2519592341e4e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/e6377bea5b114f70de6332fe935e160da5014ce8", - "reference": "e6377bea5b114f70de6332fe935e160da5014ce8", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/cebafe2f1ad2d1e745c1015b7c2519592341e4e6", + "reference": "cebafe2f1ad2d1e745c1015b7c2519592341e4e6", "shasum": "" }, "require": { @@ -15227,8 +15219,8 @@ "type": "symfony-bridge", "extra": { "thanks": { - "name": "phpunit/phpunit", - "url": "https://github.com/sebastianbergmann/phpunit" + "url": "https://github.com/sebastianbergmann/phpunit", + "name": "phpunit/phpunit" } }, "autoload": { @@ -15260,7 +15252,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v6.4.13" + "source": "https://github.com/symfony/phpunit-bridge/tree/v6.4.16" }, "funding": [ { @@ -15276,7 +15268,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-11-13T15:06:22+00:00" }, { "name": "symfony/polyfill-ctype", @@ -15304,8 +15296,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -15460,8 +15452,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -15540,8 +15532,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -15622,8 +15614,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -15706,8 +15698,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -15780,8 +15772,8 @@ "type": "metapackage", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "notification-url": "https://packagist.org/downloads/", @@ -15845,8 +15837,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -15921,8 +15913,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -16001,8 +15993,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -16077,8 +16069,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -16154,8 +16146,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -16236,8 +16228,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -16435,16 +16427,16 @@ }, { "name": "symfony/routing", - "version": "v6.4.13", + "version": "v6.4.16", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278" + "reference": "91e02e606b4b705c2f4fb42f7e7708b7923a3220" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/640a74250d13f9c30d5ca045b6aaaabcc8215278", - "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278", + "url": "https://api.github.com/repos/symfony/routing/zipball/91e02e606b4b705c2f4fb42f7e7708b7923a3220", + "reference": "91e02e606b4b705c2f4fb42f7e7708b7923a3220", "shasum": "" }, "require": { @@ -16498,7 +16490,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.13" + "source": "https://github.com/symfony/routing/tree/v6.4.16" }, "funding": [ { @@ -16514,7 +16506,7 @@ "type": "tidelift" } ], - "time": "2024-10-01T08:30:56+00:00" + "time": "2024-11-13T15:31:34+00:00" }, { "name": "symfony/serializer", @@ -16616,16 +16608,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", "shasum": "" }, "require": { @@ -16679,7 +16671,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" }, "funding": [ { @@ -16695,7 +16687,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/string", @@ -16785,16 +16777,16 @@ }, { "name": "symfony/translation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", "shasum": "" }, "require": { @@ -16843,7 +16835,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" }, "funding": [ { @@ -16859,7 +16851,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/uid", @@ -16937,16 +16929,16 @@ }, { "name": "symfony/validator", - "version": "v6.4.15", + "version": "v6.4.16", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "7541055cdaf54ff95f0735bf703d313374e8b20b" + "reference": "9b0d1988b56511706bc91d96ead39acd77aaf34d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/7541055cdaf54ff95f0735bf703d313374e8b20b", - "reference": "7541055cdaf54ff95f0735bf703d313374e8b20b", + "url": "https://api.github.com/repos/symfony/validator/zipball/9b0d1988b56511706bc91d96ead39acd77aaf34d", + "reference": "9b0d1988b56511706bc91d96ead39acd77aaf34d", "shasum": "" }, "require": { @@ -17014,7 +17006,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v6.4.15" + "source": "https://github.com/symfony/validator/tree/v6.4.16" }, "funding": [ { @@ -17030,7 +17022,7 @@ "type": "tidelift" } ], - "time": "2024-11-08T15:28:48+00:00" + "time": "2024-11-27T09:48:51+00:00" }, { "name": "symfony/var-dumper", @@ -17293,10 +17285,10 @@ }, "type": "composer-plugin", "extra": { + "class": "Nevay\\SPI\\Composer\\Plugin", "branch-alias": { "dev-main": "0.2.x-dev" }, - "class": "Nevay\\SPI\\Composer\\Plugin", "plugin-optional": true }, "autoload": { @@ -17579,16 +17571,16 @@ }, { "name": "unocha/common_design", - "version": "v9.4.3", + "version": "v9.4.4", "source": { "type": "git", "url": "https://github.com/UN-OCHA/common_design.git", - "reference": "15592bea6f10db7c6bb4b58abd2b4a1962979c7d" + "reference": "f11b39f2faf9eabcd88ec10a120e465897116e7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/UN-OCHA/common_design/zipball/15592bea6f10db7c6bb4b58abd2b4a1962979c7d", - "reference": "15592bea6f10db7c6bb4b58abd2b4a1962979c7d", + "url": "https://api.github.com/repos/UN-OCHA/common_design/zipball/f11b39f2faf9eabcd88ec10a120e465897116e7d", + "reference": "f11b39f2faf9eabcd88ec10a120e465897116e7d", "shasum": "" }, "require": { @@ -17603,9 +17595,9 @@ "description": "OCHA Common Design base theme for Drupal 9+", "support": { "issues": "https://github.com/UN-OCHA/common_design/issues", - "source": "https://github.com/UN-OCHA/common_design/tree/v9.4.3" + "source": "https://github.com/UN-OCHA/common_design/tree/v9.4.4" }, - "time": "2024-08-08T09:37:46+00:00" + "time": "2024-11-18T09:15:05+00:00" }, { "name": "unocha/gtm_barebones", @@ -17636,16 +17628,16 @@ }, { "name": "unocha/ocha_ai", - "version": "v1.7.1", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/UN-OCHA/ocha_ai.git", - "reference": "2ff9e6a19c7587a36311347f66ab1b373717c95c" + "reference": "4ac8a4b0527bafc563c802ed6158e81408b17136" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/UN-OCHA/ocha_ai/zipball/2ff9e6a19c7587a36311347f66ab1b373717c95c", - "reference": "2ff9e6a19c7587a36311347f66ab1b373717c95c", + "url": "https://api.github.com/repos/UN-OCHA/ocha_ai/zipball/4ac8a4b0527bafc563c802ed6158e81408b17136", + "reference": "4ac8a4b0527bafc563c802ed6158e81408b17136", "shasum": "" }, "require": { @@ -17677,9 +17669,9 @@ "description": "OCHA AI module", "support": { "issues": "https://github.com/UN-OCHA/ocha_ai/issues", - "source": "https://github.com/UN-OCHA/ocha_ai/tree/v1.7.1" + "source": "https://github.com/UN-OCHA/ocha_ai/tree/v1.8.0" }, - "time": "2024-11-13T07:46:57+00:00" + "time": "2024-11-26T23:31:48+00:00" }, { "name": "unocha/ocha_content_classification", @@ -17722,6 +17714,49 @@ }, "time": "2024-11-15T07:26:19+00:00" }, + { + "name": "unocha/ocha_entraid", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/UN-OCHA/ocha_entraid.git", + "reference": "bc788a50b42971f4dbbfb101c619cf033cdceb4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/UN-OCHA/ocha_entraid/zipball/bc788a50b42971f4dbbfb101c619cf033cdceb4d", + "reference": "bc788a50b42971f4dbbfb101c619cf033cdceb4d", + "shasum": "" + }, + "require": { + "drupal/core": "^10", + "drupal/honeypot": "^2", + "drupal/openid_connect": "dev-3.x", + "drupal/openid_connect_windows_aad": "^2.0@beta", + "php": ">=8.3" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "drupal/coder": "^8.3", + "phpcompatibility/php-compatibility": "^9.3" + }, + "type": "drupal-module", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "UNOCHA" + } + ], + "description": "OCHA Entra ID module", + "support": { + "issues": "https://github.com/UN-OCHA/ocha_entraid/issues", + "source": "https://github.com/UN-OCHA/ocha_entraid/tree/v1.0.0" + }, + "time": "2024-11-12T06:36:39+00:00" + }, { "name": "unocha/ocha_monitoring", "version": "1.0.18", diff --git a/config/core.entity_form_display.node.job.default.yml b/config/core.entity_form_display.node.job.default.yml index 28533403e..bcca5d6df 100644 --- a/config/core.entity_form_display.node.job.default.yml +++ b/config/core.entity_form_display.node.job.default.yml @@ -12,12 +12,9 @@ dependencies: - field.field.node.job.field_import_hash - field.field.node.job.field_job_closing_date - field.field.node.job.field_job_experience - - field.field.node.job.field_job_tagger_queue_count - field.field.node.job.field_job_type - field.field.node.job.field_source - field.field.node.job.field_theme - - field.field.node.job.reliefweb_job_tagger_info - - field.field.node.job.reliefweb_job_tagger_status - node.type.job module: - allowed_formats @@ -190,11 +187,8 @@ hidden: created: true field_import_guid: true field_import_hash: true - field_job_tagger_queue_count: true langcode: true promote: true - reliefweb_job_tagger_info: true - reliefweb_job_tagger_status: true status: true sticky: true uid: true diff --git a/config/core.entity_view_display.node.job.default.yml b/config/core.entity_view_display.node.job.default.yml index 4735853c3..3af4adf88 100644 --- a/config/core.entity_view_display.node.job.default.yml +++ b/config/core.entity_view_display.node.job.default.yml @@ -12,12 +12,9 @@ dependencies: - field.field.node.job.field_import_hash - field.field.node.job.field_job_closing_date - field.field.node.job.field_job_experience - - field.field.node.job.field_job_tagger_queue_count - field.field.node.job.field_job_type - field.field.node.job.field_source - field.field.node.job.field_theme - - field.field.node.job.reliefweb_job_tagger_info - - field.field.node.job.reliefweb_job_tagger_status - node.type.job module: - datetime @@ -115,8 +112,5 @@ content: hidden: field_import_guid: true field_import_hash: true - field_job_tagger_queue_count: true langcode: true links: true - reliefweb_job_tagger_info: true - reliefweb_job_tagger_status: true diff --git a/config/core.entity_view_display.node.job.teaser.yml b/config/core.entity_view_display.node.job.teaser.yml index 9d88c6d86..764868069 100644 --- a/config/core.entity_view_display.node.job.teaser.yml +++ b/config/core.entity_view_display.node.job.teaser.yml @@ -13,12 +13,9 @@ dependencies: - field.field.node.job.field_import_hash - field.field.node.job.field_job_closing_date - field.field.node.job.field_job_experience - - field.field.node.job.field_job_tagger_queue_count - field.field.node.job.field_job_type - field.field.node.job.field_source - field.field.node.job.field_theme - - field.field.node.job.reliefweb_job_tagger_info - - field.field.node.job.reliefweb_job_tagger_status - node.type.job module: - datetime @@ -59,10 +56,7 @@ hidden: field_import_guid: true field_import_hash: true field_job_experience: true - field_job_tagger_queue_count: true field_job_type: true field_theme: true langcode: true links: true - reliefweb_job_tagger_info: true - reliefweb_job_tagger_status: true diff --git a/config/core.extension.yml b/config/core.extension.yml index d7db6a7ba..17ff1b3af 100644 --- a/config/core.extension.yml +++ b/config/core.extension.yml @@ -60,8 +60,8 @@ module: node: 0 ocha_ai: 0 ocha_ai_chat: 0 - ocha_ai_tag: 0 ocha_content_classification: 0 + ocha_entraid: 0 ocha_monitoring: 0 openid_connect: 0 openid_connect_windows_aad: 0 @@ -70,21 +70,19 @@ module: path: 0 path_alias: 0 redirect: 0 + reliefweb_ai: 0 reliefweb_analytics: 0 reliefweb_api: 0 reliefweb_bookmarks: 0 reliefweb_contact: 0 reliefweb_disaster_map: 0 - reliefweb_dsr: 0 reliefweb_entities: 0 - reliefweb_entraid: 0 reliefweb_fields: 0 reliefweb_files: 0 reliefweb_form: 0 reliefweb_guidelines: 0 reliefweb_homepage: 0 reliefweb_import: 0 - reliefweb_job_tagger: 0 reliefweb_meta: 0 reliefweb_moderation: 0 reliefweb_reporting: 0 diff --git a/config/field.field.node.job.field_job_tagger_queue_count.yml b/config/field.field.node.job.field_job_tagger_queue_count.yml deleted file mode 100644 index 987da0ae1..000000000 --- a/config/field.field.node.job.field_job_tagger_queue_count.yml +++ /dev/null @@ -1,25 +0,0 @@ -uuid: ac87ff1a-b49d-47f9-8b81-6a3d30212913 -langcode: en -status: true -dependencies: - config: - - field.storage.node.field_job_tagger_queue_count - - node.type.job -id: node.job.field_job_tagger_queue_count -field_name: field_job_tagger_queue_count -entity_type: node -bundle: job -label: 'Job tagger queue count' -description: '' -required: false -translatable: false -default_value: - - - value: 0 -default_value_callback: '' -settings: - min: 0 - max: null - prefix: '' - suffix: '' -field_type: integer diff --git a/config/field.field.node.job.reliefweb_job_tagger_info.yml b/config/field.field.node.job.reliefweb_job_tagger_info.yml deleted file mode 100644 index 4b73a7226..000000000 --- a/config/field.field.node.job.reliefweb_job_tagger_info.yml +++ /dev/null @@ -1,26 +0,0 @@ -uuid: bf072428-5463-444c-aa3b-5867c18e5820 -langcode: en -status: true -dependencies: - config: - - field.storage.node.reliefweb_job_tagger_info - - filter.format.markdown - - node.type.job - module: - - text -_core: - default_config_hash: c9jZ6pByZz5O4j5rz8-PTwZpIhgpD5B9vRSD6y2KaL4 -id: node.job.reliefweb_job_tagger_info -field_name: reliefweb_job_tagger_info -entity_type: node -bundle: job -label: 'Job tagger info' -description: '' -required: false -translatable: false -default_value: { } -default_value_callback: '' -settings: - allowed_formats: - - markdown -field_type: text_long diff --git a/config/field.field.node.job.reliefweb_job_tagger_status.yml b/config/field.field.node.job.reliefweb_job_tagger_status.yml deleted file mode 100644 index baf6fd85c..000000000 --- a/config/field.field.node.job.reliefweb_job_tagger_status.yml +++ /dev/null @@ -1,23 +0,0 @@ -uuid: 1e084105-9ae6-435a-8c64-428e9957508e -langcode: en -status: true -dependencies: - config: - - field.storage.node.reliefweb_job_tagger_status - - node.type.job - module: - - options -_core: - default_config_hash: b39BL7pzxGe8ARXfSfgWQ56VdVlrzDeu5QPN0V6FB6U -id: node.job.reliefweb_job_tagger_status -field_name: reliefweb_job_tagger_status -entity_type: node -bundle: job -label: 'Job tagger state' -description: '' -required: false -translatable: false -default_value: { } -default_value_callback: '' -settings: { } -field_type: list_string diff --git a/config/field.storage.node.field_job_tagger_queue_count.yml b/config/field.storage.node.field_job_tagger_queue_count.yml deleted file mode 100644 index c8445f719..000000000 --- a/config/field.storage.node.field_job_tagger_queue_count.yml +++ /dev/null @@ -1,20 +0,0 @@ -uuid: a8443d1d-5ac8-4d42-b340-a7dc9726ef51 -langcode: en -status: true -dependencies: - module: - - node -id: node.field_job_tagger_queue_count -field_name: field_job_tagger_queue_count -entity_type: node -type: integer -settings: - unsigned: false - size: normal -module: core -locked: false -cardinality: 1 -translatable: true -indexes: { } -persist_with_no_fields: false -custom_storage: false diff --git a/config/field.storage.node.reliefweb_job_tagger_info.yml b/config/field.storage.node.reliefweb_job_tagger_info.yml deleted file mode 100644 index b8b542637..000000000 --- a/config/field.storage.node.reliefweb_job_tagger_info.yml +++ /dev/null @@ -1,21 +0,0 @@ -uuid: c9d57c46-67fe-4662-a647-cbc139b8b5ab -langcode: en -status: true -dependencies: - module: - - node - - text -_core: - default_config_hash: unTujV1MU9BLJ6YJTAJP79UzayOJZqe3ENIunYL_eoU -id: node.reliefweb_job_tagger_info -field_name: reliefweb_job_tagger_info -entity_type: node -type: text_long -settings: { } -module: text -locked: true -cardinality: 1 -translatable: true -indexes: { } -persist_with_no_fields: false -custom_storage: false diff --git a/config/field.storage.node.reliefweb_job_tagger_status.yml b/config/field.storage.node.reliefweb_job_tagger_status.yml deleted file mode 100644 index b9a931117..000000000 --- a/config/field.storage.node.reliefweb_job_tagger_status.yml +++ /dev/null @@ -1,35 +0,0 @@ -uuid: eff4e934-a8c8-4c26-87dd-3e86f9311a78 -langcode: en -status: true -dependencies: - module: - - node - - options -_core: - default_config_hash: bGdwPWYjAlDGCrUl508wL_rTVtBV2UWW1UJtbe5PFB8 -id: node.reliefweb_job_tagger_status -field_name: reliefweb_job_tagger_status -entity_type: node -type: list_string -settings: - allowed_values: - - - value: queue - label: queue - - - value: queued - label: queued - - - value: processed - label: processed - - - value: skipped - label: skipped - allowed_values_function: '' -module: options -locked: true -cardinality: 1 -translatable: true -indexes: { } -persist_with_no_fields: false -custom_storage: false diff --git a/config/filter.format.markdown.yml b/config/filter.format.markdown.yml index ae94b2e9e..63c51a4c0 100644 --- a/config/filter.format.markdown.yml +++ b/config/filter.format.markdown.yml @@ -82,4 +82,4 @@ filters: status: false weight: 0 settings: - replace_empty: '0' + replace_empty: 0 diff --git a/config/ocha_ai_tag.settings.yml b/config/ocha_ai_tag.settings.yml deleted file mode 100644 index a746a18fc..000000000 --- a/config/ocha_ai_tag.settings.yml +++ /dev/null @@ -1,21 +0,0 @@ -_core: - default_config_hash: r1hk7e31MjYWgy4AuBRXxwuhornPFBp0LDrncwSG38Q -defaults: - form: - instructions: - value: 'The process of posting jobs via ReliefWeb is now even smoother. The form now includes an automated function for the "career" and "themes" categories; fewer fields for users to fill and higher efficiency overall.' - format: markdown_editor - plugins: - embedding: - plugin_id: aws_bedrock_titan_embed_text_v2 - text_extractor: - application/pdf: - plugin_id: mupdf - text_splitter: - plugin_id: token - vector_store: - plugin_id: elasticsearch_job - completion: - plugin_id: aws_bedrock - source: - plugin_id: reliefweb diff --git a/config/ocha_content_classification.ocha_classification_workflow.node_job.yml b/config/ocha_content_classification.ocha_classification_workflow.node_job.yml index 0c2eb32b4..084ba9e59 100644 --- a/config/ocha_content_classification.ocha_classification_workflow.node_job.yml +++ b/config/ocha_content_classification.ocha_classification_workflow.node_job.yml @@ -16,8 +16,6 @@ fields: enabled: true field_how_to_apply: enabled: false - reliefweb_job_tagger_info: - enabled: false classifiable: field_career_categories: enabled: true diff --git a/config/ocha_entraid.settings.yml b/config/ocha_entraid.settings.yml new file mode 100644 index 000000000..f4ab34550 --- /dev/null +++ b/config/ocha_entraid.settings.yml @@ -0,0 +1,30 @@ +_core: + default_config_hash: NAmQvdKWBfPlHEZzOU3fgGjxcYBQTfz7xn9goDv0r80 +uimc_api: + token_url: '' + registration_url: '' + user_details_url: '' + group_management_url: '' + username: '' + password: '' + consumer_key: '' + consumer_secret: '' + send_email: false + verify_ssl: true + request_timeout: 10 + default_group: '' + encryption_key: '' +messages: + invalid_email: 'Invalid valid email address.' + login_explanation: '

Please enter your email address to access your account.

' + login_account_blocked: 'We encountered an issue with your account. Please reach out for assistance.' + login_account_not_found: 'We were unable to find your account. Please check your information and try again.' + login_account_verification_error: 'We encountered an issue while verifying your account. Please try again later or reach out for assistance if the problem persists.' + login_redirection_error: 'We are experiencing difficulties with the login process. Please try again later or reach out for assistance if the issue continues.' + registration_explanation: '

Create an account to easily access our services.
If you already possess a UN email address, please log in here directly.

' + registration_invalid_first_name: 'First name must contain only letters, spaces, hyphens, or apostrophes and be no longer than 30 characters.' + registration_invalid_last_name: 'Last name must contain only letters, spaces, hyphens, or apostrophes and be no longer than 30 characters.' + registration_invalid_email: 'Email must contain only letters, numbers, hyphens, or periods and be no longer than 100 characters.' + registration_success: 'Thank you for signing up. Please wait a moment and then log in to finalize your registration.' + registration_success_with_email: 'Thank you for signing up! A confirmation email has been sent to your mailbox. Please check your email and follow the instructions to finalize your registration.' + registration_failure: 'We encountered an issue while processing your registration. Please try again later or reach out for assistance.' diff --git a/config/openid_connect.client.entraid.yml b/config/openid_connect.client.entraid.yml index c85519eeb..fd17c7125 100644 --- a/config/openid_connect.client.entraid.yml +++ b/config/openid_connect.client.entraid.yml @@ -10,8 +10,10 @@ plugin: windows_aad settings: client_id: entraid_client_id client_secret: entraid_client_secret + iss_allowed_domains: '' authorization_endpoint_wa: '' token_endpoint_wa: '' + end_session_endpoint: '' userinfo_endpoint_wa: '' map_ad_groups_to_roles: false group_mapping: @@ -23,6 +25,4 @@ settings: userinfo_update_email: true hide_email_address_warning: false subject_key: sub - end_session_endpoint: '' - iss_allowed_domains: '' front_channel_logout_url: '' diff --git a/config/reliefweb_ai.settings.yml b/config/reliefweb_ai.settings.yml new file mode 100644 index 000000000..6d460a57f --- /dev/null +++ b/config/reliefweb_ai.settings.yml @@ -0,0 +1,8 @@ +_core: + default_config_hash: fU8Wp0OCsudl5x0QSV_mUWWt2KbQuzhVwEB-o9A8peg +ocha_ai_chat: + allow_for_anonymous: false + instructions_replace: false + login_instructions: | +

The use of Ask ReliefWeb requires an account on ReliefWeb.

+

Please login or create a new account.

diff --git a/config/reliefweb_dsr.settings.yml b/config/reliefweb_dsr.settings.yml deleted file mode 100644 index c6f0a6802..000000000 --- a/config/reliefweb_dsr.settings.yml +++ /dev/null @@ -1,6 +0,0 @@ -ctf_cda_access_token: null -ctf_dsr_url: null -ocha_dsr_url: 'https://reports.unocha.org' -cache_lifetime: 300 -last_update_skip: 30 -show_illustration: true diff --git a/config/reliefweb_job_tagger.settings.yml b/config/reliefweb_job_tagger.settings.yml deleted file mode 100644 index befeaa19f..000000000 --- a/config/reliefweb_job_tagger.settings.yml +++ /dev/null @@ -1 +0,0 @@ -use_es: true diff --git a/config/system.action.user_add_role_action.contributor.yml b/config/system.action.user_add_role_action.contributor.yml new file mode 100644 index 000000000..40e9e3b35 --- /dev/null +++ b/config/system.action.user_add_role_action.contributor.yml @@ -0,0 +1,14 @@ +uuid: 8a1c6d2a-cf03-48d7-9dc8-ba12b7239159 +langcode: en +status: true +dependencies: + config: + - user.role.contributor + module: + - user +id: user_add_role_action.contributor +label: 'Add the Contributor role role to the selected user(s)' +type: user +plugin: user_add_role_action +configuration: + rid: contributor diff --git a/config/system.action.user_remove_role_action.contributor.yml b/config/system.action.user_remove_role_action.contributor.yml new file mode 100644 index 000000000..efe9281cb --- /dev/null +++ b/config/system.action.user_remove_role_action.contributor.yml @@ -0,0 +1,14 @@ +uuid: e0c6f28b-74a1-41a4-a75f-b9c12fe98676 +langcode: en +status: true +dependencies: + config: + - user.role.contributor + module: + - user +id: user_remove_role_action.contributor +label: 'Remove the Contributor role role from the selected user(s)' +type: user +plugin: user_remove_role_action +configuration: + rid: contributor diff --git a/config/user.role.anonymous.yml b/config/user.role.anonymous.yml index fce92c9f5..6ec936568 100644 --- a/config/user.role.anonymous.yml +++ b/config/user.role.anonymous.yml @@ -4,6 +4,7 @@ status: true dependencies: module: - media + - ocha_ai_chat - system _core: default_config_hash: j5zLMOdJBqC0bMvSdth5UebkprJB8g_2FXHqhfpJzow @@ -13,4 +14,5 @@ weight: 0 is_admin: false permissions: - 'access content' + - 'access ocha ai chat' - 'view media' diff --git a/config/user.role.authenticated.yml b/config/user.role.authenticated.yml index 4d6f02efa..5b47d3617 100644 --- a/config/user.role.authenticated.yml +++ b/config/user.role.authenticated.yml @@ -3,7 +3,6 @@ langcode: en status: true dependencies: config: - - filter.format.markdown - filter.format.markdown_editor - filter.format.token_markdown - node.type.job @@ -49,7 +48,6 @@ permissions: - 'subscribe to jobs' - 'subscribe to training' - 'use enhanced input forms' - - 'use text format markdown' - 'use text format markdown_editor' - 'use text format token_markdown' - 'view media' diff --git a/config/user.role.contributor.yml b/config/user.role.contributor.yml new file mode 100644 index 000000000..5f260b723 --- /dev/null +++ b/config/user.role.contributor.yml @@ -0,0 +1,29 @@ +uuid: 42dacb5f-f71a-4e37-858e-d4cf46729e39 +langcode: en +status: true +dependencies: + config: + - media.type.image_report + - node.type.report + module: + - media + - node + - reliefweb_files + - reliefweb_form + - reliefweb_revisions +id: contributor +label: Contributor +weight: 9 +is_admin: null +permissions: + - 'access reliefweb private files' + - 'create image_report media' + - 'create report content' + - 'delete media' + - 'delete own image_report media' + - 'display source attention messages' + - 'edit own image_report media' + - 'edit own report content' + - 'update media' + - 'view entity history' + - 'view own unpublished media' diff --git a/config/user.role.user_manager.yml b/config/user.role.user_manager.yml index 247d3393a..3b20e9a6a 100644 --- a/config/user.role.user_manager.yml +++ b/config/user.role.user_manager.yml @@ -16,6 +16,7 @@ permissions: - 'administer subscriptions' - 'administer users' - 'assign beta_tester role' + - 'assign contributor role' - 'assign editor role' - 'assign external_disaster_manager role' - 'assign job_importer role' diff --git a/config/views.view.stalled_jobs.yml b/config/views.view.stalled_jobs.yml deleted file mode 100644 index fab542b44..000000000 --- a/config/views.view.stalled_jobs.yml +++ /dev/null @@ -1,425 +0,0 @@ -uuid: 0de05c1e-1423-47cd-ba0f-2e447f3117b8 -langcode: en -status: true -dependencies: - config: - - node.type.job - module: - - node - - options - - user -id: stalled_jobs -label: 'Stalled jobs' -module: views -description: '' -tag: '' -base_table: node_field_data -base_field: nid -display: - default: - id: default - display_title: Default - display_plugin: default - position: 0 - display_options: - title: 'Stalled jobs' - fields: - title: - id: title - table: node_field_data - field: title - relationship: none - group_type: group - admin_label: '' - entity_type: node - entity_field: title - plugin_id: field - label: Title - exclude: false - alter: - alter_text: false - make_link: false - absolute: false - word_boundary: false - ellipsis: false - strip_tags: false - trim: false - html: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - click_sort_column: value - type: string - settings: - link_to_entity: true - group_column: value - group_columns: { } - group_rows: true - delta_limit: 0 - delta_offset: 0 - delta_reversed: false - delta_first_last: false - multi_type: separator - separator: ', ' - field_api_classes: false - created: - id: created - table: node_field_data - field: created - relationship: none - group_type: group - admin_label: '' - entity_type: node - entity_field: created - plugin_id: field - label: 'Authored on' - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - click_sort_column: value - type: timestamp - settings: - date_format: medium - custom_date_format: '' - timezone: '' - tooltip: - date_format: long - custom_date_format: '' - time_diff: - enabled: false - future_format: '@interval hence' - past_format: '@interval ago' - granularity: 2 - refresh: 60 - description: '' - group_column: value - group_columns: { } - group_rows: true - delta_limit: 0 - delta_offset: 0 - delta_reversed: false - delta_first_last: false - multi_type: separator - separator: ', ' - field_api_classes: false - status: - id: status - table: node_field_data - field: status - relationship: none - group_type: group - admin_label: '' - entity_type: node - entity_field: status - plugin_id: field - label: Published - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - click_sort_column: value - type: boolean - settings: - format: default - format_custom_false: '' - format_custom_true: '' - group_column: value - group_columns: { } - group_rows: true - delta_limit: 0 - delta_offset: 0 - delta_reversed: false - delta_first_last: false - multi_type: separator - separator: ', ' - field_api_classes: false - operations: - id: operations - table: node - field: operations - relationship: none - group_type: group - admin_label: '' - entity_type: node - plugin_id: entity_operations - label: Operations - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - destination: false - pager: - type: mini - options: - offset: 0 - pagination_heading_level: h4 - items_per_page: 10 - total_pages: null - id: 0 - tags: - next: ›› - previous: ‹‹ - expose: - items_per_page: false - items_per_page_label: 'Items per page' - items_per_page_options: '5, 10, 25, 50' - items_per_page_options_all: false - items_per_page_options_all_label: '- All -' - offset: false - offset_label: Offset - exposed_form: - type: basic - options: - submit_button: Apply - reset_button: false - reset_button_label: Reset - exposed_sorts_label: 'Sort by' - expose_sort_order: true - sort_asc_label: Asc - sort_desc_label: Desc - access: - type: perm - options: - perm: 'edit any job content' - cache: - type: tag - options: { } - empty: { } - sorts: - created: - id: created - table: node_field_data - field: created - relationship: none - group_type: group - admin_label: '' - entity_type: node - entity_field: created - plugin_id: date - order: DESC - expose: - label: '' - field_identifier: '' - exposed: false - granularity: second - arguments: { } - filters: - type: - id: type - table: node_field_data - field: type - entity_type: node - entity_field: type - plugin_id: bundle - value: - job: job - reliefweb_job_tagger_status_value: - id: reliefweb_job_tagger_status_value - table: node__reliefweb_job_tagger_status - field: reliefweb_job_tagger_status_value - relationship: none - group_type: group - admin_label: '' - plugin_id: list_field - operator: or - value: - skipped: skipped - group: 1 - exposed: false - expose: - operator_id: '' - label: '' - description: '' - use_operator: false - operator: '' - operator_limit_selection: false - operator_list: { } - identifier: '' - required: false - remember: false - multiple: false - remember_roles: - authenticated: authenticated - reduce: false - is_grouped: false - group_info: - label: '' - description: '' - identifier: '' - optional: true - widget: select - multiple: false - remember: false - default_group: All - default_group_multiple: { } - group_items: { } - reduce_duplicates: false - style: - type: table - row: - type: fields - query: - type: views_query - options: - query_comment: '' - disable_sql_rewrite: false - distinct: false - replica: false - query_tags: { } - relationships: { } - header: { } - footer: { } - display_extenders: { } - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url.query_args - - 'user.node_grants:view' - - user.permissions - tags: { } - page_1: - id: page_1 - display_title: Page - display_plugin: page - position: 1 - display_options: - display_extenders: { } - path: admin/content/stalled-jobs - menu: - type: tab - title: 'Stalled jobs' - description: '' - weight: 50 - expanded: false - menu_name: admin - parent: system.admin_content - context: '0' - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url.query_args - - 'user.node_grants:view' - - user.permissions - tags: { } diff --git a/html/modules/custom/reliefweb_ai/README.md b/html/modules/custom/reliefweb_ai/README.md new file mode 100644 index 000000000..bc84b3483 --- /dev/null +++ b/html/modules/custom/reliefweb_ai/README.md @@ -0,0 +1,8 @@ +ReliefWeb AI +============ + +AI usage for ReliefWeb. + +## AI chat + +Currently this module simply alters the chat form to display the chat popup to anonymous users but asking them to log in or register an account to use the chat. This behavior can be controller via the configuration of this module. diff --git a/html/modules/custom/reliefweb_ai/config/install/reliefweb_ai.settings.yml b/html/modules/custom/reliefweb_ai/config/install/reliefweb_ai.settings.yml new file mode 100644 index 000000000..acee1fe5c --- /dev/null +++ b/html/modules/custom/reliefweb_ai/config/install/reliefweb_ai.settings.yml @@ -0,0 +1,7 @@ +ocha_ai_chat: + allow_for_anonymous: false + instructions_replace: false + login_instructions: | +

The use of Ask ReliefWeb requires an account on ReliefWeb.

+

Please login or create a new account.

+ diff --git a/html/modules/custom/reliefweb_ai/config/schema/reliefweb_ai.schema.yml b/html/modules/custom/reliefweb_ai/config/schema/reliefweb_ai.schema.yml new file mode 100644 index 000000000..6bacc5ebc --- /dev/null +++ b/html/modules/custom/reliefweb_ai/config/schema/reliefweb_ai.schema.yml @@ -0,0 +1,18 @@ +reliefweb_ai.settings: + type: config_object + label: 'ReliefWeb AI settings.' + mapping: + ocha_ai_chat: + type: mapping + label: 'Settings for the OCHA AI chat.' + mapping: + allow_for_anonymous: + type: boolean + label: 'Allow anonymous user to access the chat.' + instructions_replace: + type: boolean + label: 'If TRUE, replace the chat instructions when the chat is disabled (error, anonymous access etc.), otherwise append the extra instructions.' + login_instructions: + type: text + label: 'Login or register instructions for anonymous users.' + diff --git a/html/modules/custom/reliefweb_ai/reliefweb_ai.info.yml b/html/modules/custom/reliefweb_ai/reliefweb_ai.info.yml new file mode 100644 index 000000000..6badfc6b5 --- /dev/null +++ b/html/modules/custom/reliefweb_ai/reliefweb_ai.info.yml @@ -0,0 +1,7 @@ +type: module +name: ReliefWeb AI +description: 'AI usage for ReliefWeb' +package: reliefweb +core_version_requirement: ^10 +dependencies: + - drupal:ocha_ai diff --git a/html/modules/custom/reliefweb_ai/reliefweb_ai.module b/html/modules/custom/reliefweb_ai/reliefweb_ai.module new file mode 100644 index 000000000..0518f7316 --- /dev/null +++ b/html/modules/custom/reliefweb_ai/reliefweb_ai.module @@ -0,0 +1,154 @@ +query?->get('url'); + + // Add some caching context and tags. + $form['#cache']['contexts'] = array_merge($form['#cache']['contexts'] ?? [], [ + 'user.roles', 'url.query_args', + ]); + $form['#cache']['tags'] = array_merge($form['#cache']['tags'] ?? [], [ + 'config:reliefweb_ai.settings', + ]); + + // Add a more unique class to the chat submit button. + if (isset($form['actions']['submit'])) { + $form['actions']['submit']['#attributes']['class'][] = 'ocha-ai-chat-ask'; + } + + // Message to display if the form is disabled for any reason. + $disabled = ''; + + // Check if the user is anonymous and not allowed to access the chat. + if ($current_user->isAnonymous() && !$config->get('ocha_ai_chat.allow_for_anonymous')) { + $disabled = $config->get('ocha_ai_chat.login_instructions') ?? ''; + + // Redirect to the current page if possible. + if (!empty($url)) { + $disabled = strtr($disabled, [ + '@destination' => UrlHelper::encodePath(parse_url($url, \PHP_URL_PATH)), + ]); + } + } + + if (empty($disabled)) { + // Check if we have a URL to allow the chat. + if (empty($url)) { + $instructions = t('

Something went wrong.

'); + } + // Otherwise check if the language or type of the document. + else { + $router = \Drupal::service('router.no_access_checks'); + $parameters = $router->match($url); + $node = $parameters['node'] ?? NULL; + + // Disable the form if it's not a report. + if (!isset($node) || $node->bundle() !== 'report') { + $disabled = t('

Something went wrong.

'); + } + else { + // No need to show the source when chatting with a single report. + if (isset($form['source'])) { + $form['source']['#access'] = FALSE; + } + + // Only English documents are supported due to LLM limitations. + $is_english_report = FALSE; + foreach ($node->field_language as $item) { + if ($item->target_id == 267) { + $is_english_report = TRUE; + break; + } + } + if (!$is_english_report) { + $disabled = t('

Sorry, only English reports are supported.

'); + } + + // Non supported content formats. + foreach ($node->field_content_format as $item) { + if ($item->target_id == 12) { + $disabled = t('

Sorry, maps are not supported.

'); + break; + } + elseif ($item->target_id == 12570) { + $disabled = t('

Sorry, infographics are not supported.

'); + break; + } + elseif ($item->target_id == 38974) { + $disabled = t('

Sorry, interactive reports are not supported.

'); + break; + } + } + } + } + } + + if (!empty($disabled)) { + // Check whether we are requested to replace the instructions or append the + // disabled instructions. + $replace = $config->get('ocha_ai_chat.instructions_replace') === TRUE || !isset($form['chat']['content']); + + if (!$replace) { + // We cannot just append the instructions to the current ones because of + // the text format may include sanitation that removes the target + // attributes. We indeed need to preserve those attributes in the login + // instructions so the login and register links open in parent window + // and not in the chat iframe. + // So first we format the current instructions and then append the extra + // instructions. + $text = $form['chat']['content']['#text'] ?? ''; + $format = $form['chat']['content']['#format'] ?? 'markdown_editor'; + $instructions = (string) check_markup($text, $format); + $disabled = $instructions . $disabled; + } + + // Replace the instructions with a simple markup render element. + $form['chat']['content'] = [ + '#type' => 'markup', + '#markup' => $disabled, + '#prefix' => '
', + '#suffix' => '
', + ]; + + // Disable or hide the reset of the form. + foreach (Element::children($form['chat']) as $key) { + if ($key !== 'content') { + $form['chat'][$key]['#access'] = FALSE; + } + } + foreach (Element::children($form) as $key) { + if ($key !== 'chat') { + $form[$key]['#disabled'] = TRUE; + } + } + + $form['#cache']['max-age'] = 3600; + } +} + +/** + * Implements hook_block_view_alter(). + */ +function reliefweb_ai_block_view_alter(array &$build, BlockPluginInterface $block): void { + // Alter the chat popup block, notably to adjust the caching. + if ($block->getPluginId() === 'ocha_ai_chat_chat_popup') { + $build['#pre_render'][] = [OchaAiChatPopupBlockHandler::class, 'alterBuild']; + return; + } +} diff --git a/html/modules/custom/reliefweb_ai/reliefweb_ai.services.yml b/html/modules/custom/reliefweb_ai/reliefweb_ai.services.yml new file mode 100644 index 000000000..2d25372c2 --- /dev/null +++ b/html/modules/custom/reliefweb_ai/reliefweb_ai.services.yml @@ -0,0 +1,6 @@ +services: + reliefweb_ai.ocha_ai_chat_cache_subscriber: + class: Drupal\reliefweb_ai\EventSubscriber\OchaAiChatCacheSubscriber + arguments: ['@config.factory', '@current_user'] + tags: + - { name: event_subscriber } diff --git a/html/modules/custom/reliefweb_ai/src/EventSubscriber/OchaAiChatCacheSubscriber.php b/html/modules/custom/reliefweb_ai/src/EventSubscriber/OchaAiChatCacheSubscriber.php new file mode 100644 index 000000000..f33ae48c5 --- /dev/null +++ b/html/modules/custom/reliefweb_ai/src/EventSubscriber/OchaAiChatCacheSubscriber.php @@ -0,0 +1,73 @@ + ['onResponse', -1000], + ]; + } + + /** + * {@inheritdoc} + */ + public function onResponse(ResponseEvent $event): void { + $request = $event->getRequest(); + $route = $request->attributes->get('_route'); + + if ($route === 'ocha_ai_chat.chat_form' || $route === 'ocha_ai_chat.chat_form.popup') { + $response = $event->getResponse(); + + // Ajax response are not cacheable so only handle normal form response. + if ($response instanceof CacheableResponseInterface) { + $config = $this->configFactory->get('reliefweb_ai.settings'); + + $cache_metadata = $response->getCacheableMetadata(); + + // Vary the cache by role, url parameters and config since they control + // what is displayed to the user. + $cache_metadata->addCacheContexts(['user.roles', 'url.query_args']); + $cache_metadata->addCacheTags(['config:reliefweb_ai.settings']); + + // Cache the response for 1 hour for anonymous user when we just show + // a disabled form asking to log in or register. + if ($this->currentUser->isAnonymous() && !$config->get('ocha_ai_chat.allow_for_anonymous')) { + // Cache for 1 hour. + $cache_metadata->setCacheMaxAge(3600); + // Ensure varnish for example can cache the page. + $response->headers->set('Cache-Control', 'public, max-age=3600'); + } + } + } + } + +} diff --git a/html/modules/custom/reliefweb_ai/src/OchaAiChatPopupBlockHandler.php b/html/modules/custom/reliefweb_ai/src/OchaAiChatPopupBlockHandler.php new file mode 100644 index 000000000..04ed68071 --- /dev/null +++ b/html/modules/custom/reliefweb_ai/src/OchaAiChatPopupBlockHandler.php @@ -0,0 +1,37 @@ + $items) { foreach ($items as $bundle => $info) { @@ -96,6 +92,8 @@ function reliefweb_entities_entity_bundle_info_alter(&$bundles) { } if ($class !== FALSE && is_subclass_of($class, BundleEntityInterface::class)) { $label = ucwords(str_replace(['_', '-'], ' ', $bundle)); + // No leading \ otherwise EntityTypeRepository::getEntityTypeFromClass + // fails. $bundles[$entity_type_id][$bundle]['class'] = $class; $bundles[$entity_type_id][$bundle]['label'] = $label; } @@ -828,90 +826,3 @@ function reliefweb_entities_node_delete(EntityInterface $entity) { } } } - -/** - * Implements hook_form_FORM_ID_alter() for `ocha_ai_chat_chat_form`. - */ -function reliefweb_entities_form_ocha_ai_chat_chat_form_alter(array &$form, FormStateInterface $form_state, string $form_id) { - $url = \Drupal::request()?->query?->get('url'); - if (isset($url)) { - $router = \Drupal::service('router.no_access_checks'); - $parameters = $router->match($url); - $node = $parameters['node'] ?? NULL; - - if (isset($node) && $node instanceof Report) { - // No need to show the source when chatting with a single report. - if (isset($form['source'])) { - $form['source']['#access'] = FALSE; - } - - // Only English documents are supported due to LLM limitations. - $is_english_report = FALSE; - foreach ($node->field_language as $item) { - if ($item->target_id == 267) { - $is_english_report = TRUE; - break; - } - } - if (!$is_english_report) { - $reason = t('Sorry, only English reports are supported.'); - } - - // Non supported content formats. - foreach ($node->field_content_format as $item) { - if ($item->target_id == 12) { - $reason = t('Sorry, maps are not supported.'); - break; - } - elseif ($item->target_id == 12570) { - $reason = t('Sorry, infographics are not supported.'); - break; - } - elseif ($item->target_id == 38974) { - $reason = t('Sorry, interactive reports are not supported.'); - break; - } - } - - if (!empty($reason)) { - foreach (Element::children($form) as $key) { - $form[$key]['#access'] = FALSE; - } - $form['unsupported'] = [ - '#type' => 'inline_template', - '#template' => '

{{ reason }}

', - '#context' => [ - 'reason' => $reason, - ], - ]; - } - } - } -} - -/** - * Implements hook_block_access(). - */ -function reliefweb_entities_block_access(Block $block, $operation, AccountInterface $account) { - // Disable the AI chat on map, infographic and interactive content since they - // don't have much content that can be used by the AI. - if ($operation === 'view' && $block->getPluginId() === 'ocha_ai_chat_chat_popup') { - $router = \Drupal::service('router.no_access_checks'); - try { - $parameters = $router->matchRequest(\Drupal::request()); - } - catch (\Exception $exception) { - return AccessResult::forbidden(); - } - - $node = $parameters['node'] ?? NULL; - - if (isset($node) && $node instanceof Report) { - if (in_array($node->field_content_format->target_id, [12, 12570, 38974])) { - return AccessResult::forbidden(); - } - } - } - // No opinion. - return AccessResult::neutral(); -} diff --git a/html/modules/custom/reliefweb_entities/src/DocumentTrait.php b/html/modules/custom/reliefweb_entities/src/DocumentTrait.php index 8d2098a4d..dee23b8db 100644 --- a/html/modules/custom/reliefweb_entities/src/DocumentTrait.php +++ b/html/modules/custom/reliefweb_entities/src/DocumentTrait.php @@ -4,6 +4,7 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Entity\EntityPublishedInterface; +use Drupal\Core\Entity\RevisionLogInterface; use Drupal\Core\Field\EntityReferenceFieldItemList; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Url; @@ -394,4 +395,42 @@ protected function updateSourceModerationStatus() { } } + /** + * Update the status to refused if any of the sources is blocked. + */ + protected function updateModerationStatusFromSourceStatus() { + if (!$this->hasField('field_source') || $this->field_source->isEmpty()) { + return; + } + + $blocked = []; + foreach ($this->field_source as $item) { + $source = $item->entity; + if (empty($source) || !($source instanceof Source)) { + continue; + } + + if ($source->getModerationStatus() === 'blocked') { + $blocked[] = $source->label(); + } + } + + if (!empty($blocked)) { + $this->setModerationStatus('refused'); + + // Add a message to the revision log. + if ($this instanceof RevisionLogInterface) { + $message = 'Submissions from "' . implode('", "', $blocked) . '" are no longer allowed.'; + + $log = $this->getRevisionLogMessage(); + if (empty($log)) { + $this->setRevisionLogMessage($message); + } + else { + $this->setRevisionLogMessage($message . ' ' . $log); + } + } + } + } + } diff --git a/html/modules/custom/reliefweb_entities/src/Entity/Country.php b/html/modules/custom/reliefweb_entities/src/Entity/Country.php index 69a96cf7a..1500b308c 100644 --- a/html/modules/custom/reliefweb_entities/src/Entity/Country.php +++ b/html/modules/custom/reliefweb_entities/src/Entity/Country.php @@ -57,7 +57,6 @@ public function getPageContent() { */ public function getPageSections() { $sections = []; - $sections['digital-sitrep'] = $this->getDigitalSitrepSection(); $queries = []; @@ -121,20 +120,6 @@ public function getPageTableOfContents() { ]; } - /** - * Get the Digital Situation Report for the country. - * - * @return array - * Render array for the Digital Situation Report section. - */ - protected function getDigitalSitrepSection() { - $client = \Drupal::service('reliefweb_dsr.client'); - $iso3 = $this->field_iso3->value; - $ongoing = $this->getModerationStatus() === 'ongoing'; - - return $client->getDigitalSitrepBuild($iso3, $ongoing); - } - /** * {@inheritdoc} */ diff --git a/html/modules/custom/reliefweb_entities/src/Entity/Report.php b/html/modules/custom/reliefweb_entities/src/Entity/Report.php index fdea3da90..8d22935fb 100644 --- a/html/modules/custom/reliefweb_entities/src/Entity/Report.php +++ b/html/modules/custom/reliefweb_entities/src/Entity/Report.php @@ -13,11 +13,14 @@ use Drupal\reliefweb_entities\DocumentTrait; use Drupal\reliefweb_moderation\EntityModeratedInterface; use Drupal\reliefweb_moderation\EntityModeratedTrait; +use Drupal\reliefweb_moderation\Helpers\UserPostingRightsHelper; use Drupal\reliefweb_revisions\EntityRevisionedInterface; use Drupal\reliefweb_revisions\EntityRevisionedTrait; use Drupal\reliefweb_utility\Helpers\DateHelper; use Drupal\reliefweb_utility\Helpers\ReliefWebStateHelper; +use Drupal\reliefweb_utility\Helpers\TaxonomyHelper; use Drupal\reliefweb_utility\Helpers\UrlHelper; +use Drupal\reliefweb_utility\Helpers\UserHelper; /** * Bundle class for report nodes. @@ -210,8 +213,6 @@ public function getAttachments(?array $build = NULL) { * {@inheritdoc} */ public function preSave(EntityStorageInterface $storage) { - parent::preSave($storage); - // Change the publication date if bury is selected, to the original // publication date. if (!empty($this->field_bury->value) && !$this->field_original_publication_date->isEmpty()) { @@ -241,21 +242,33 @@ public function preSave(EntityStorageInterface $storage) { } } + // Update the entity status based on the user posting rights. + $this->updateModerationStatusFromPostingRights(); + // Change the status to `embargoed` if there is an embargo date. - if (!empty($this->field_embargo_date->value) && $this->getModerationStatus() !== 'draft') { + if (!empty($this->field_embargo_date->value) && in_array($this->getModerationStatus(), [ + 'embargoed', + 'to-review', + 'published', + ])) { $this->setModerationStatus('embargoed'); $message = strtr('Embargoed (to be automatically published on @date).', [ '@date' => DateHelper::format($this->field_embargo_date->value, 'custom', 'd M Y H:i e'), ]); - $log = trim($this->getRevisionLogMessage()); + $log = !empty($this->getRevisionLogMessage()) ? trim($this->getRevisionLogMessage()) : ''; $log = $message . (!empty($log) ? "\n" . $log : ''); $this->setRevisionLogMessage($log); } // Prepare notifications. $this->preparePublicationNotification(); + + // Update the entity status based on the source(s) moderation status. + $this->updateModerationStatusFromSourceStatus(); + + parent::preSave($storage); } /** @@ -353,6 +366,102 @@ protected function sendPublicationNotification() { ->mail('reliefweb_entities', 'report_publication_notification', $to, $langcode, $parameters, $from, TRUE); } + /** + * Update the status for the entity based on the user posting rights. + */ + protected function updateModerationStatusFromPostingRights() { + // In theory the revision user here, is the current user saving the entity. + /** @var \Drupal\user\UserInterface|null $user */ + $user = $this->getRevisionUser(); + $status = $this->getModerationStatus(); + + // Skip if there is no revision user. That should normally not happen with + // new content but some old revisions may reference users that don't exist + // anymore (which should not happen either but...). + if (empty($user)) { + return; + } + + // For non editors, we determine the real status based on the user + // posting rights for the selected sources. + if (!UserHelper::userHasRoles(['editor'], $user) && in_array($status, ['to-review'])) { + // Retrieve the list of sources and check the user rights. + if (!$this->field_source->isEmpty()) { + // Extract source ids. + $sources = []; + foreach ($this->field_source as $item) { + if (!empty($item->target_id)) { + $sources[] = $item->target_id; + } + } + + // Get the user's posting right for the document. + $rights = []; + foreach (UserPostingRightsHelper::getUserPostingRights($user, $sources) as $tid => $data) { + $rights[$data[$this->bundle()] ?? 0][] = $tid; + } + + // Blocked for some sources. + if (!empty($rights[1])) { + $status = 'refused'; + } + // Trusted for all the sources. + elseif (isset($rights[3]) && count($rights[3]) === count($sources)) { + $status = 'published'; + } + // Trusted for at least 1. + elseif (isset($rights[3]) && count($rights[3]) > 0) { + $status = 'to-review'; + } + // Allowed for all the sources. + elseif (isset($rights[2]) && count($rights[2]) === count($sources)) { + $status = 'to-review'; + } + // Unverified for some sources. + else { + $status = 'pending'; + } + + $this->setModerationStatus($status); + + // Add messages indicating the posting rights for easier review. + $message = ''; + if (!empty($rights[1])) { + $message = trim($message . strtr(' Blocked user for @sources.', [ + '@sources' => implode(', ', TaxonomyHelper::getSourceShortnames($rights[1])), + ])); + } + if (!empty($rights[0])) { + $message = trim($message . strtr(' Unverified user for @sources.', [ + '@sources' => implode(', ', TaxonomyHelper::getSourceShortnames($rights[0])), + ])); + } + if (!empty($rights[2])) { + $message = trim($message . strtr(' Allowed user for @sources.', [ + '@sources' => implode(', ', TaxonomyHelper::getSourceShortnames($rights[2])), + ])); + } + if (!empty($rights[3])) { + $message = trim($message . strtr(' Trusted user for @sources.', [ + '@sources' => implode(', ', TaxonomyHelper::getSourceShortnames($rights[3])), + ])); + } + + // Update the log message. + if (!empty($message)) { + $revision_log_field = $this->getEntityType() + ->getRevisionMetadataKey('revision_log_message'); + + if (!empty($revision_log_field)) { + $log = trim($this->{$revision_log_field}->value ?? ''); + $log = $message . (!empty($log) ? ' ' . $log : ''); + $this->{$revision_log_field}->value = $log; + } + } + } + } + } + /** * Temporarily store the email address to notify after publication. * diff --git a/html/modules/custom/reliefweb_entities/src/Entity/Source.php b/html/modules/custom/reliefweb_entities/src/Entity/Source.php index df559c8e5..e2c008350 100644 --- a/html/modules/custom/reliefweb_entities/src/Entity/Source.php +++ b/html/modules/custom/reliefweb_entities/src/Entity/Source.php @@ -146,18 +146,20 @@ protected function getOrganizationDetails() { $meta = []; // Organization type. - $type = $this->field_organization_type->entity->label(); - $meta['type'] = [ - 'type' => 'link', - 'label' => $this->t('Organization type'), - 'value' => [ - 'url' => RiverServiceBase::getRiverUrl('source', [ - 'search' => 'type.exact:"' . $type . '"', - ]), - 'title' => $type, - 'external' => FALSE, - ], - ]; + if ($this->hasField('field_organization_type') && !$this->get('field_organization_type')->isEmpty()) { + $type = $this->field_organization_type->entity->label(); + $meta['type'] = [ + 'type' => 'link', + 'label' => $this->t('Organization type'), + 'value' => [ + 'url' => RiverServiceBase::getRiverUrl('source', [ + 'search' => 'type.exact:"' . $type . '"', + ]), + 'title' => $type, + 'external' => FALSE, + ], + ]; + } // Headquarters. $countries = []; diff --git a/html/modules/custom/reliefweb_entities/src/EntityFormAlterServiceBase.php b/html/modules/custom/reliefweb_entities/src/EntityFormAlterServiceBase.php index f006e7ffd..985b28fed 100644 --- a/html/modules/custom/reliefweb_entities/src/EntityFormAlterServiceBase.php +++ b/html/modules/custom/reliefweb_entities/src/EntityFormAlterServiceBase.php @@ -582,7 +582,7 @@ public static function retrievePotentialNewSourceInformation(EntityModeratedInte } /** - * Add the user information to a job/training node form. + * Add the user information to a job/training/report node form. * * @param array $form * The entity form. @@ -594,7 +594,7 @@ protected function addUserInformation(array &$form, FormStateInterface $form_sta $entity_id = $entity->id(); $bundle = $entity->bundle(); - // It's only for jobs and training. + // It's only for jobs and trainings. if (!in_array($bundle, ['job', 'training'])) { return; } diff --git a/html/modules/custom/reliefweb_entities/src/OpportunityDocumentTrait.php b/html/modules/custom/reliefweb_entities/src/OpportunityDocumentTrait.php index 44ed15aa7..03c4126df 100644 --- a/html/modules/custom/reliefweb_entities/src/OpportunityDocumentTrait.php +++ b/html/modules/custom/reliefweb_entities/src/OpportunityDocumentTrait.php @@ -2,14 +2,12 @@ namespace Drupal\reliefweb_entities; -use Drupal\Core\Entity\RevisionLogInterface; -use Drupal\reliefweb_entities\Entity\Source; use Drupal\reliefweb_moderation\Helpers\UserPostingRightsHelper; use Drupal\reliefweb_utility\Helpers\TaxonomyHelper; use Drupal\reliefweb_utility\Helpers\UserHelper; /** - * Trait for "opportunity" documents like jobs and training. + * Trait for "opportunity" documents like jobs and trainings. * * @see Drupal\reliefweb_entities\DocuemntInterface */ @@ -103,44 +101,6 @@ protected function updateModerationStatusFromExpirationDate() { } } - /** - * Update the status to refused if any of the sources is blocked. - */ - protected function updateModerationStatusFromSourceStatus() { - if (!$this->hasField('field_source') || $this->field_source->isEmpty()) { - return; - } - - $blocked = []; - foreach ($this->field_source as $item) { - $source = $item->entity; - if (empty($source) || !($source instanceof Source)) { - continue; - } - - if ($source->getModerationStatus() === 'blocked') { - $blocked[] = $source->label(); - } - } - - if (!empty($blocked)) { - $this->setModerationStatus('refused'); - - // Add a message to the revision log. - if ($this instanceof RevisionLogInterface) { - $message = 'Submissions from "' . implode('", "', $blocked) . '" are no longer allowed.'; - - $log = $this->getRevisionLogMessage(); - if (empty($log)) { - $this->setRevisionLogMessage($message); - } - else { - $this->setRevisionLogMessage($message . ' ' . $log); - } - } - } - } - /** * Update creation date when the opportunity is published for the first time. */ diff --git a/html/modules/custom/reliefweb_entities/src/Services/ReportFormAlter.php b/html/modules/custom/reliefweb_entities/src/Services/ReportFormAlter.php index ee73f401e..a9708690d 100644 --- a/html/modules/custom/reliefweb_entities/src/Services/ReportFormAlter.php +++ b/html/modules/custom/reliefweb_entities/src/Services/ReportFormAlter.php @@ -9,6 +9,7 @@ use Drupal\reliefweb_entities\Entity\Report; use Drupal\reliefweb_entities\EntityFormAlterServiceBase; use Drupal\reliefweb_form\Helpers\FormHelper; +use Drupal\reliefweb_moderation\Helpers\UserPostingRightsHelper; use Drupal\reliefweb_utility\Helpers\UrlHelper; /** @@ -102,6 +103,11 @@ protected function addBundleFormAlterations(array &$form, FormStateInterface $fo ); } + // Special tweaks for contributors. + if ($this->currentUser->hasRole('contributor')) { + $this->alterFieldsForContributors($form, $form_state); + } + // Validate the attachments. $form['#validate'][] = [$this, 'validateAttachment']; @@ -110,6 +116,43 @@ protected function addBundleFormAlterations(array &$form, FormStateInterface $fo // Prevent saving from a blocked source. $form['#validate'][] = [$this, 'validateBlockedSource']; + + // Prevent saving if user is blocked for a source. + $form['#validate'][] = [$this, 'validatePostingRightsBlockedSource']; + } + + /** + * Prevent saving if user is blocked for a source. + * + * @param array $form + * Form to alter. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * Form state. + */ + public function validatePostingRightsBlockedSource(array $form, FormStateInterface &$form_state) { + $ids = []; + foreach ($form_state->getValue('field_source', []) as $item) { + if (!empty($item['target_id'])) { + $ids[] = $item['target_id']; + } + } + + $rights = UserPostingRightsHelper::getUserConsolidatedPostingRight($this->currentUser, 'report', $ids); + // Blocked for at least one source. + if (!empty($rights) && isset($rights['code']) && $rights['code'] == 1) { + $sources = $this->getEntityTypeManager() + ->getStorage('taxonomy_term') + ->loadMultiple($rights['sources']); + + /** @var \Drupal\taxonomy\Entity\Term $term */ + array_walk($sources, function (&$term) { + $term = $term->label(); + }); + + $form_state->setErrorByName('field_source', $this->t('Publications from "@sources" are not allowed.', [ + '@sources' => implode('", "', $sources), + ])); + } } /** @@ -400,4 +443,33 @@ public function validateEmbargoDate(array $form, FormStateInterface &$form_state } } + /** + * Make alterations for Contributor role. + * + * @param array $form + * Form to alter. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * Form state. + */ + protected function alterFieldsForContributors(array &$form, FormStateInterface $form_state) { + // Default to submit for new documents otherwise preserve the value, for + // example when editing a report created by an editor. + if ($form_state->getFormObject()?->getEntity()?->isNew() === TRUE) { + $form['field_origin']['widget']['#default_value'] = '1'; + } + // Change the field to 'hidden' to hide it while perserving its value so + // that the alteration and validation of the origin notes field still work. + // @see ::alterOriginFields() + $form['field_origin']['widget']['#type'] = 'hidden'; + + // Hide fields. + $form['field_bury']['#access'] = FALSE; + $form['field_feature']['#access'] = FALSE; + + $form['field_headline']['#access'] = FALSE; + $form['field_headline_title']['#access'] = FALSE; + $form['field_headline_summary']['#access'] = FALSE; + $form['field_headline_image']['#access'] = FALSE; + } + } diff --git a/html/modules/custom/reliefweb_entities/tests/src/ExistingSite/RwReportAddMultipleSourcesTest.php b/html/modules/custom/reliefweb_entities/tests/src/ExistingSite/RwReportAddMultipleSourcesTest.php new file mode 100644 index 000000000..c2bf3ea10 --- /dev/null +++ b/html/modules/custom/reliefweb_entities/tests/src/ExistingSite/RwReportAddMultipleSourcesTest.php @@ -0,0 +1,388 @@ +contributor = $this->createUserIfNeeded(2884910, 'report unverified'); + if (!$this->contributor->hasRole('contributor')) { + $this->contributor->addRole('contributor'); + $this->contributor->save(); + } + + $rights = [ + 0 => 'unverified', + 1 => 'blocked', + 2 => 'allowed', + 3 => 'trusted', + ]; + + foreach ($rights as $id => $right) { + $label = 'Src ' . $right; + $field_name = 'source_' . $right; + + // Create term and assign rights. + $this->{$field_name} = $this->createTermIfNeeded('source', 9999900 + $id, $label, [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + // Set posting right to + $this->{$field_name}->set('field_user_posting_rights', [ + [ + 'id' => $this->contributor->id(), + 'job' => '0', + 'training' => '0', + 'report' => $id, + 'notes' => '', + ], + ]); + $this->{$field_name}->save(); + } + + // Create term and assign rights. + $this->source_random = $this->createTermIfNeeded('source', 9999999, 'Src random', [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + } + + /** + * Test adding a report - blocked. + */ + public function testAddReportAsContributorBlockedWithoutRandom() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $this->drupalLogin($this->contributor); + + $edit = $this->getEditFields($title); + $edit['field_source[]'] = [ + $this->source_unverified->id(), + $this->source_blocked->id(), + $this->source_allowed->id(), + $this->source_trusted->id(), + ]; + + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Submit'); + + // Check that the report has thrown an error. + $this->assertSession()->pageTextContains('Publications from "Src blocked" are not allowed.'); + } + + /** + * Test adding a report - unverified. + */ + public function testAddReportAsContributorUnverifiedWithoutRandom() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $this->drupalLogin($this->contributor); + + $edit = $this->getEditFields($title); + $edit['field_source[]'] = [ + $this->source_unverified->id(), + $this->source_allowed->id(), + $this->source_trusted->id(), + ]; + + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Submit'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(200); + + // Check moderation status. + $this->assertEquals($node->moderation_status->value, 'to-review'); + } + + /** + * Test adding a report - allowed. + */ + public function testAddReportAsContributorAllowedWithoutRandom() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $this->drupalLogin($this->contributor); + + $edit = $this->getEditFields($title); + $edit['field_source[]'] = [ + $this->source_allowed->id(), + $this->source_trusted->id(), + ]; + + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Submit'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(200); + + // Check moderation status. + $this->assertEquals($node->moderation_status->value, 'to-review'); + } + + /** + * Test adding a report - trusted. + */ + public function testAddReportAsContributorTrustedWithoutRandom() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $this->drupalLogin($this->contributor); + + $edit = $this->getEditFields($title); + $edit['field_source[]'] = [ + $this->source_trusted->id(), + ]; + + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Submit'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(200); + + // Check moderation status. + $this->assertEquals($node->moderation_status->value, 'published'); + } + + /** + * Test adding a report - blocked. + */ + public function testAddReportAsContributorBlockedWithRandom() { + $title = $this->randomMachineName(32); + + $this->drupalLogin($this->contributor); + + $edit = $this->getEditFields($title); + $edit['field_source[]'] = [ + $this->source_unverified->id(), + $this->source_blocked->id(), + $this->source_allowed->id(), + $this->source_trusted->id(), + $this->source_random->id(), + ]; + + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Submit'); + + // Check that the report has thrown an error. + $this->assertSession()->pageTextContains('Publications from "Src blocked" are not allowed.'); + } + + /** + * Test adding a report - unverified. + */ + public function testAddReportAsContributorUnverifiedWithRandom() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $this->drupalLogin($this->contributor); + + $edit = $this->getEditFields($title); + $edit['field_source[]'] = [ + $this->source_unverified->id(), + $this->source_allowed->id(), + $this->source_trusted->id(), + $this->source_random->id(), + ]; + + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Submit'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(200); + + // Check moderation status. + $this->assertEquals($node->moderation_status->value, 'to-review'); + } + + /** + * Test adding a report - allowed. + */ + public function testAddReportAsContributorAllowedWithRandom() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $this->drupalLogin($this->contributor); + + $edit = $this->getEditFields($title); + $edit['field_source[]'] = [ + $this->source_allowed->id(), + $this->source_trusted->id(), + $this->source_random->id(), + ]; + + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Submit'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(200); + + // Check moderation status. + $this->assertEquals($node->moderation_status->value, 'to-review'); + } + + /** + * Test adding a report - trusted. + */ + public function testAddReportAsContributorTrustedWithRandom() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $this->drupalLogin($this->contributor); + + $edit = $this->getEditFields($title); + $edit['field_source[]'] = [ + $this->source_trusted->id(), + $this->source_random->id(), + ]; + + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Submit'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(200); + + // Check moderation status. + $this->assertEquals($node->moderation_status->value, 'to-review'); + } + + protected function getEditFields($title) { + $term_language = $this->createTermIfNeeded('language', 267, 'English'); + $term_country = $this->createTermIfNeeded('country', 34, 'Belgium'); + $term_format = $this->createTermIfNeeded('content_format', 11, 'UN Document'); + $term_source = $this->createTermIfNeeded('source', 43679, 'ABC Color', [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + // Create a node. + $edit = []; + $edit['title[0][value]'] = $title; + $edit['field_language[' . $term_language->id() . ']'] = $term_language->id(); + $edit['field_country[]'] = [$term_country->id()]; + $edit['field_primary_country'] = $term_country->id(); + $edit['field_content_format'] = $term_format->id(); + $edit['field_source[]'] = [$term_source->id()]; + + return $edit; + } +} diff --git a/html/modules/custom/reliefweb_entities/tests/src/ExistingSite/RwReportAddTest.php b/html/modules/custom/reliefweb_entities/tests/src/ExistingSite/RwReportAddTest.php new file mode 100644 index 000000000..d393c0f69 --- /dev/null +++ b/html/modules/custom/reliefweb_entities/tests/src/ExistingSite/RwReportAddTest.php @@ -0,0 +1,509 @@ +get('name'); + $title = $this->randomMachineName(32); + + $admin = User::load(1); + $this->drupalLogin($admin); + + $edit = $this->getEditFields($title); + $edit['field_origin_notes[0][value]'] = 'https://example.com/' . $title; + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Publish'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('ABC Color'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + $this->assertSession()->elementTextEquals('css', '.rw-moderation-information__status.rw-moderation-status', 'Published'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + } + + /** + * Test adding a report as admin, draft. + */ + public function testAddReportAsAdminDraft() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $admin = User::load(1); + $this->drupalLogin($admin); + + $edit = $this->getEditFields($title); + $edit['field_origin_notes[0][value]'] = 'https://example.com/' . $title; + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Save as draft'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('ABC Color'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + $this->assertSession()->elementTextEquals('css', '.rw-moderation-information__status.rw-moderation-status', 'Draft'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(404); + } + + /** + * Test adding a report as contributor, draft, unverified. + */ + public function testAddReportAsContributorDraftUnverified() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $user = $this->createUserIfNeeded(2884910, 'report unverified'); + if (!$user->hasRole('contributor')) { + $user->addRole('contributor'); + $user->save(); + } + $this->drupalLogin($user); + + // Create term first so we can assign posting rights. + $term_source = $this->createTermIfNeeded('source', 43679, 'ABC Color', [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + // Set posting right to + $term_source->set('field_user_posting_rights', [ + [ + 'id' => $user->id(), + 'job' => '0', + 'training' => '0', + 'report' => '0', // Unverified. + 'notes' => '', + ], + ]); + $term_source->save(); + + $edit = $this->getEditFields($title); + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Save as draft'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('ABC Color'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(404); + + // Check moderation status. + $this->assertEquals($node->moderation_status->value, 'draft'); + } + + /** + * Test adding a report as contributor, submit, unverified. + */ + public function testAddReportAsContributorSubmitUnverified() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $user = $this->createUserIfNeeded(2884910, 'report unverified'); + if (!$user->hasRole('contributor')) { + $user->addRole('contributor'); + $user->save(); + } + $this->drupalLogin($user); + + // Create term first so we can assign posting rights. + $term_source = $this->createTermIfNeeded('source', 43679, 'ABC Color', [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + // Set posting right to + $term_source->set('field_user_posting_rights', [ + [ + 'id' => $user->id(), + 'job' => '0', + 'training' => '0', + 'report' => '0', // Unverified. + 'notes' => '', + ], + ]); + $term_source->save(); + + $edit = $this->getEditFields($title); + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Submit'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('ABC Color'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(404); + + // Check moderation status. + $this->assertEquals($node->moderation_status->value, 'pending'); + } + + /** + * Test adding a report as contributor, draft, blocked. + */ + public function testAddReportAsContributorDraftBlocked() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $user = $this->createUserIfNeeded(2884910, 'report blocked'); + if (!$user->hasRole('contributor')) { + $user->addRole('contributor'); + $user->save(); + } + $this->drupalLogin($user); + + // Create term first so we can assign posting rights. + $term_source = $this->createTermIfNeeded('source', 43679, 'ABC Color', [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + // Set posting right to + $term_source->set('field_user_posting_rights', [ + [ + 'id' => $user->id(), + 'job' => '0', + 'training' => '0', + 'report' => '1', // Blocked. + 'notes' => '', + ], + ]); + $term_source->save(); + + $edit = $this->getEditFields($title); + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Save as draft'); + + // Check that the report has thrown an error. + $this->assertSession()->pageTextContains('Publications from "ABC Color" are not allowed.'); + } + + /** + * Test adding a report as contributor, submit, blocked. + */ + public function testAddReportAsContributorSubmitBlocked() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $user = $this->createUserIfNeeded(2884910, 'report blocked'); + if (!$user->hasRole('contributor')) { + $user->addRole('contributor'); + $user->save(); + } + $this->drupalLogin($user); + + // Create term first so we can assign posting rights. + $term_source = $this->createTermIfNeeded('source', 43679, 'ABC Color', [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + // Set posting right to + $term_source->set('field_user_posting_rights', [ + [ + 'id' => $user->id(), + 'job' => '0', + 'training' => '0', + 'report' => '1', // Blocked. + 'notes' => '', + ], + ]); + $term_source->save(); + + $edit = $this->getEditFields($title); + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Submit'); + + // Check that the report has thrown an error. + $this->assertSession()->pageTextContains('Publications from "ABC Color" are not allowed.'); + } + + /** + * Test adding a report as contributor, draft, allowed. + */ + public function testAddReportAsContributorDraftAllowed() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $user = $this->createUserIfNeeded(2884910, 'report allowed'); + if (!$user->hasRole('contributor')) { + $user->addRole('contributor'); + $user->save(); + } + $this->drupalLogin($user); + + // Create term first so we can assign posting rights. + $term_source = $this->createTermIfNeeded('source', 43679, 'ABC Color', [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + // Set posting right to + $term_source->set('field_user_posting_rights', [ + [ + 'id' => $user->id(), + 'job' => '0', + 'training' => '0', + 'report' => '2', // Allowed. + 'notes' => '', + ], + ]); + $term_source->save(); + + $edit = $this->getEditFields($title); + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Save as draft'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('ABC Color'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(404); + + // Check moderation status. + $this->assertEquals($node->moderation_status->value, 'draft'); + } + + /** + * Test adding a report as contributor, submit, allowed. + */ + public function testAddReportAsContributorSubmitAllowed() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $user = $this->createUserIfNeeded(2884910, 'report allowed'); + if (!$user->hasRole('contributor')) { + $user->addRole('contributor'); + $user->save(); + } + $this->drupalLogin($user); + + // Create term first so we can assign posting rights. + $term_source = $this->createTermIfNeeded('source', 43679, 'ABC Color', [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + // Set posting right to + $term_source->set('field_user_posting_rights', [ + [ + 'id' => $user->id(), + 'job' => '0', + 'training' => '0', + 'report' => '2', // Allowed. + 'notes' => '', + ], + ]); + $term_source->save(); + + $edit = $this->getEditFields($title); + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Submit'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('ABC Color'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(200); + + // Check moderation status. + $this->assertEquals($node->moderation_status->value, 'to-review'); + } + + /** + * Test adding a report as contributor, draft, trusted. + */ + public function testAddReportAsContributorDraftTrusted() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $user = $this->createUserIfNeeded(2884910, 'report trusted'); + if (!$user->hasRole('contributor')) { + $user->addRole('contributor'); + $user->save(); + } + $this->drupalLogin($user); + + // Create term first so we can assign posting rights. + $term_source = $this->createTermIfNeeded('source', 43679, 'ABC Color', [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + // Set posting right to + $term_source->set('field_user_posting_rights', [ + [ + 'id' => $user->id(), + 'job' => '0', + 'training' => '0', + 'report' => '3', // Trusted. + 'notes' => '', + ], + ]); + $term_source->save(); + + $edit = $this->getEditFields($title); + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Save as draft'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('ABC Color'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(404); + + // Check moderation status. + $this->assertEquals($node->moderation_status->value, 'draft'); + } + + /** + * Test adding a report as contributor, submit, trusted. + */ + public function testAddReportAsContributorSubmitTrusted() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = $this->randomMachineName(32); + + $user = $this->createUserIfNeeded(2884910, 'report trusted'); + if (!$user->hasRole('contributor')) { + $user->addRole('contributor'); + $user->save(); + } + $this->drupalLogin($user); + + // Create term first so we can assign posting rights. + $term_source = $this->createTermIfNeeded('source', 43679, 'ABC Color', [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + // Set posting right to + $term_source->set('field_user_posting_rights', [ + [ + 'id' => $user->id(), + 'job' => '0', + 'training' => '0', + 'report' => '3', // Trusted. + 'notes' => '', + ], + ]); + $term_source->save(); + + $edit = $this->getEditFields($title); + $this->drupalGet('node/add/report'); + $this->submitForm($edit, 'Submit'); + + // Check that the report has been created. + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->pageTextContains('Report ' . $edit['title[0][value]'] . ' has been created.'); + $this->assertSession()->pageTextContains('Belgium'); + $this->assertSession()->pageTextContains('ABC Color'); + $this->assertSession()->pageTextContains('UN Document'); + $this->assertSession()->pageTextContains('English'); + + // Check as anonymous. + $this->drupalGet('user/logout'); + $node = $this->getNodeByTitle($title); + $this->drupalGet($node->toUrl()); + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + + // Check moderation status. + $this->assertEquals($node->moderation_status->value, 'published'); + } + + protected function getEditFields($title) { + $term_language = $this->createTermIfNeeded('language', 267, 'English'); + $term_country = $this->createTermIfNeeded('country', 34, 'Belgium'); + $term_format = $this->createTermIfNeeded('content_format', 11, 'UN Document'); + $term_source = $this->createTermIfNeeded('source', 43679, 'ABC Color', [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + // Create a node. + $edit = []; + $edit['title[0][value]'] = $title; + $edit['field_language[' . $term_language->id() . ']'] = $term_language->id(); + $edit['field_country[]'] = [$term_country->id()]; + $edit['field_primary_country'] = $term_country->id(); + $edit['field_content_format'] = $term_format->id(); + $edit['field_source[]'] = [$term_source->id()]; + + return $edit; + } +} diff --git a/html/modules/custom/reliefweb_entities/tests/src/ExistingSite/RwReportBase.php b/html/modules/custom/reliefweb_entities/tests/src/ExistingSite/RwReportBase.php new file mode 100644 index 000000000..905bdb5e7 --- /dev/null +++ b/html/modules/custom/reliefweb_entities/tests/src/ExistingSite/RwReportBase.php @@ -0,0 +1,54 @@ + $vocabulary, + 'tid' => $id, + 'name' => $title, + ] + $extra); + $term->save(); + + return $term; + } + + /** + * Create user if needed. + */ + protected function createUserIfNeeded($id, $name, array $extra = []) : User { + if ($user = User::load($id)) { + return $user; + } + + $user = User::create([ + 'uid' => $id, + 'name' => $name, + 'mail' => $this->randomMachineName(32) . '@localhost.localdomain', + 'status' => 1, + ] + $extra); + $user->save(); + + return $user; + } + +} diff --git a/html/modules/custom/reliefweb_entities/tests/src/ExistingSite/RwReportCreateTest.php b/html/modules/custom/reliefweb_entities/tests/src/ExistingSite/RwReportCreateTest.php new file mode 100644 index 000000000..a80081322 --- /dev/null +++ b/html/modules/custom/reliefweb_entities/tests/src/ExistingSite/RwReportCreateTest.php @@ -0,0 +1,322 @@ +get('name'); + $title = 'My report'; + $user = User::load(1); + $this->drupalLogin($user); + + $term_language = $this->createTermIfNeeded('language', 267, 'English'); + $term_country = $this->createTermIfNeeded('country', 34, 'Belgium'); + $term_format = $this->createTermIfNeeded('content_format', 11, 'UN Document'); + $term_source = $this->createTermIfNeeded('source', 43679, 'ABC Color', [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + $report = Report::create([ + 'uid' => $user->id(), + 'type' => 'report', + 'title' => $title, + 'moderation_status' => 'draft', + 'field_origin' => 0, + 'field_origin_notes' => 'https://www.example.com/my-report', + 'field_language' => $term_language->id(), + 'field_country' => [ + $term_country->id(), + ], + 'field_primary_country' => $term_country->id(), + 'field_content_format' => $term_format->id(), + 'field_source' => [ + $term_source->id(), + ], + ]); + + // Report will be saved as draft. + $report->save(); + + // OK for user. + $this->drupalGet($report->toUrl()); + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->elementTextEquals('css', '.rw-article__title.rw-page-title', $title); + + // 404 for anonymous. + $this->drupalGet('user/logout'); + $this->drupalGet($report->toUrl()); + $this->assertSession()->statusCodeEquals(404); + } + + /** + * Test report. + */ + public function testCreateReportAsAdminPublished() { + $site_name = \Drupal::config('system.site')->get('name'); + $title = 'My report'; + $user = User::load(1); + $this->drupalLogin($user); + + $term_language = $this->createTermIfNeeded('language', 267, 'English'); + $term_country = $this->createTermIfNeeded('country', 34, 'Belgium'); + $term_format = $this->createTermIfNeeded('content_format', 11, 'UN Document'); + $term_source = $this->createTermIfNeeded('source', 43679, 'ABC Color', [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + $report = Report::create([ + 'uid' => $user->id(), + 'type' => 'report', + 'title' => $title, + 'moderation_status' => 'published', + 'field_origin' => 0, + 'field_origin_notes' => 'https://www.example.com/my-report', + 'field_language' => $term_language->id(), + 'field_country' => [ + $term_country->id(), + ], + 'field_primary_country' => $term_country->id(), + 'field_content_format' => $term_format->id(), + 'field_source' => [ + $term_source->id(), + ], + ]); + + // Report will be saved as published. + $report->save(); + + // OK for user. + $this->drupalGet($report->toUrl()); + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->elementTextEquals('css', '.rw-article__title.rw-page-title', $title); + + // 404 for anonymous. + $this->drupalGet('user/logout'); + $this->drupalGet($report->toUrl()); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->elementTextEquals('css', '.rw-article__title.rw-page-title', $title); + } + + /** + * Test report as contributor unverified, draft. + */ + public function testCreateReportAsContributorUnverifiedDraft() { + $title = 'My report - unverified'; + $this->setUserPostingRightsGetSourceTerm(0, 'Unverified'); + $moderation_status = 'draft'; + $expected_moderation_status = 'draft'; + + $this->runTestCreateReportAsContributor($title, $moderation_status, $expected_moderation_status, FALSE); + } + + /** + * Test report as contributor unverified, pending. + */ + public function testCreateReportAsContributorUnverifiedToReview() { + $title = 'My report - unverified'; + $this->setUserPostingRightsGetSourceTerm(0, 'Unverified'); + $moderation_status = 'pending'; + $expected_moderation_status = 'pending'; + + $this->runTestCreateReportAsContributor($title, $moderation_status, $expected_moderation_status, FALSE); + } + + /** + * Test report as contributor blocked, draft. + */ + public function testCreateReportAsContributorBlockedDraft() { + $title = 'My report - blocked'; + $this->setUserPostingRightsGetSourceTerm(1, 'blocked'); + $moderation_status = 'draft'; + $expected_moderation_status = 'draft'; + + $this->runTestCreateReportAsContributor($title, $moderation_status, $expected_moderation_status, FALSE); + } + + /** + * Test report as contributor blocked, to-review. + */ + public function testCreateReportAsContributorBlockedToReview() { + $title = 'My report - blocked'; + $this->setUserPostingRightsGetSourceTerm(1, 'blocked'); + $moderation_status = 'to-review'; + $expected_moderation_status = 'refused'; + + $this->runTestCreateReportAsContributor($title, $moderation_status, $expected_moderation_status, FALSE); + } + + /** + * Test report as contributor allowed, draft. + */ + public function testCreateReportAsContributorAllowedDraft() { + $title = 'My report - allowed'; + $this->setUserPostingRightsGetSourceTerm(2, 'allowed'); + $moderation_status = 'draft'; + $expected_moderation_status = 'draft'; + + $this->runTestCreateReportAsContributor($title, $moderation_status, $expected_moderation_status, FALSE); + } + + /** + * Test report as contributor allowed, to-review. + */ + public function testCreateReportAsContributorAllowedToReview() { + $title = 'My report - allowed'; + $this->setUserPostingRightsGetSourceTerm(2, 'allowed'); + $moderation_status = 'to-review'; + $expected_moderation_status = 'to-review'; + + $this->runTestCreateReportAsContributor($title, $moderation_status, $expected_moderation_status, TRUE); + } + + /** + * Test report as contributor trusted, draft. + */ + public function testCreateReportAsContributorTrustedDraft() { + $title = 'My report - trusted'; + $this->setUserPostingRightsGetSourceTerm(3, 'trusted'); + $moderation_status = 'draft'; + $expected_moderation_status = 'draft'; + + $this->runTestCreateReportAsContributor($title, $moderation_status, $expected_moderation_status, FALSE); + } + + /** + * Test report as contributor trusted, to-review. + */ + public function testCreateReportAsContributorTrustedToReview() { + $title = 'My report - trusted'; + $this->setUserPostingRightsGetSourceTerm(3, 'trusted'); + $moderation_status = 'to-review'; + $expected_moderation_status = 'published'; + + $this->runTestCreateReportAsContributor($title, $moderation_status, $expected_moderation_status, TRUE); + } + + /** + * Test report as contributor trusted, to-review but blocked source. + */ + public function testCreateReportAsContributorTrustedToReviewBlockedSource() { + $title = 'My report - trusted'; + $term_source = $this->setUserPostingRightsGetSourceTerm(3, 'trusted', 2884910, 999999, 'Blocked source'); + $term_source + ->set('moderation_status', 'blocked') + ->save(); + + $moderation_status = 'to-review'; + $expected_moderation_status = 'refused'; + + $this->runTestCreateReportAsContributor($title, $moderation_status, $expected_moderation_status, FALSE, [ + 'field_source' => [ + $term_source->id(), + ], + ]); + } + + /** + * Test report as contributor. + */ + protected function runTestCreateReportAsContributor($title, $moderation_status, $expected_moderation_status, $will_be_public, array $overrides = []) { + $site_name = \Drupal::config('system.site')->get('name'); + + $user = User::load(2884910); + $this->drupalLogin($user); + + $term_language = $this->createTermIfNeeded('language', 267, 'English'); + $term_country = $this->createTermIfNeeded('country', 34, 'Belgium'); + $term_format = $this->createTermIfNeeded('content_format', 11, 'UN Document'); + $term_source = Term::load(43679); + + $report = Report::create($overrides + [ + 'uid' => $user->id(), + 'revision_uid' => $user->id(), + 'type' => 'report', + 'title' => $title, + 'moderation_status' => $moderation_status, + 'field_origin' => 0, + 'field_origin_notes' => 'https://www.example.com/my-report', + 'field_language' => $term_language->id(), + 'field_country' => [ + $term_country->id(), + ], + 'field_primary_country' => $term_country->id(), + 'field_content_format' => $term_format->id(), + 'field_source' => [ + $term_source->id(), + ], + ]); + + // Report will be saved as draft. + $report->save(); + + // OK for user. + $this->drupalGet($report->toUrl()); + $this->assertSession()->titleEquals($title . ' - Belgium | ' . $site_name); + $this->assertSession()->elementTextEquals('css', '.rw-article__title.rw-page-title', $title); + + // Test for anonymous. + $this->drupalGet('user/logout'); + $this->drupalGet($report->toUrl()); + if ($will_be_public) { + $this->assertSession()->statusCodeEquals(200); + } + else { + $this->assertSession()->statusCodeEquals(404); + } + + // Check moderation status. + $this->assertEquals($report->moderation_status->value, $expected_moderation_status); + } + + /** + * Set user posting rights. + */ + protected function setUserPostingRightsGetSourceTerm($right, $label, $uid = 2884910, $tid = 43679, $term_label = 'ABC Color') : Term { + $user = $this->createUserIfNeeded($uid, $label); + if (!$user->hasRole('contributor')) { + $user->addRole('contributor'); + $user->save(); + } + + // Create term first so we can assign posting rights. + $term_source = $this->createTermIfNeeded('source', $tid, $term_label, [ + 'field_allowed_content_types' => [ + 1, + ], + ]); + + // Set posting right to + $term_source->set('field_user_posting_rights', [ + [ + 'id' => $user->id(), + 'job' => '0', + 'training' => '0', + 'report' => $right, + 'notes' => '', + ], + ]); + $term_source->save(); + + drupal_static_reset('reliefweb_moderation_getUserPostingRights'); + + return $term_source; + } +} diff --git a/html/modules/custom/reliefweb_entraid/README.md b/html/modules/custom/reliefweb_entraid/README.md index 80749267f..606980f96 100644 --- a/html/modules/custom/reliefweb_entraid/README.md +++ b/html/modules/custom/reliefweb_entraid/README.md @@ -3,4 +3,4 @@ Reliefweb Entra ID This module provides user authentication tweaks for Entra ID -* Provide a `/user/login/entraid` callback to redirect to the Entra ID login workflow. +* Provide a `/user/login/reliefweb-entraid-direct` callback to redirect to the Entra ID login workflow. diff --git a/html/modules/custom/reliefweb_entraid/reliefweb_entraid.routing.yml b/html/modules/custom/reliefweb_entraid/reliefweb_entraid.routing.yml index 93872e877..3d624198d 100644 --- a/html/modules/custom/reliefweb_entraid/reliefweb_entraid.routing.yml +++ b/html/modules/custom/reliefweb_entraid/reliefweb_entraid.routing.yml @@ -1,5 +1,5 @@ reliefweb_entraid.login: - path: '/user/login/entraid' + path: '/user/login/reliefweb-entraid-direct' defaults: _controller: '\Drupal\reliefweb_entraid\Controller\AuthController::redirectLogin' _title: 'Login with Unite ID' diff --git a/html/modules/custom/reliefweb_entraid/tests/src/ExistingSite/ReliefwebEntraidLoginTest.php b/html/modules/custom/reliefweb_entraid/tests/src/ExistingSite/ReliefwebEntraidLoginTest.php index 4ba5e1e9c..6edc0d14d 100644 --- a/html/modules/custom/reliefweb_entraid/tests/src/ExistingSite/ReliefwebEntraidLoginTest.php +++ b/html/modules/custom/reliefweb_entraid/tests/src/ExistingSite/ReliefwebEntraidLoginTest.php @@ -49,6 +49,15 @@ protected function tearDown(): void { * @covers ::redirectLogin() */ public function testRedirectLogin() { + + global $base_url; + + // Skip if the module is not installed. + if (!$this->container->get('module_handler')->moduleExists('reliefweb_entraid')) { + $this->assertTrue(TRUE); + return; + } + // Get the EntraID configuration. $entraid_config = $this->container ->get('config.factory') @@ -63,19 +72,19 @@ public function testRedirectLogin() { // The incomplete config will results in an exception and 404 response // will be returned. - $this->drupalGet('/user/login/entraid'); + $this->drupalGet('/user/login/reliefweb-entraid-direct'); $this->assertSession()->statusCodeEquals(404); // Set the endpoints. We just point at the robots.txt as we know it exists // and so, if the reponse status code in 200, then the redirection worked. $data = $entraid_config->get(); - $data['settings']['authorization_endpoint_wa'] = 'http://localhost/robots.txt'; - $data['settings']['token_endpoint_wa'] = 'http://localhost/robots.txt'; - $data['settings']['iss_allowed_domains'] = 'http://localhost/robots.txt'; + $data['settings']['authorization_endpoint_wa'] = $base_url . '/robots.txt'; + $data['settings']['token_endpoint_wa'] = $base_url . '/robots.txt'; + $data['settings']['iss_allowed_domains'] = $base_url . '/robots.txt'; $entraid_config->setData($data)->save(); // If the redirection works, a 200 will be returned. - $this->drupalGet('/user/login/entraid'); + $this->drupalGet('/user/login/reliefweb-entraid-direct'); $this->assertSession()->statusCodeEquals(200); $this->assertStringContainsString('Disallow:', $this->getSession()->getPage()->getContent()); } diff --git a/html/modules/custom/reliefweb_fields/components/reliefweb-user-posting-rights/reliefweb-user-posting-rights.css b/html/modules/custom/reliefweb_fields/components/reliefweb-user-posting-rights/reliefweb-user-posting-rights.css index 2f1bd063e..45b4de6f8 100644 --- a/html/modules/custom/reliefweb_fields/components/reliefweb-user-posting-rights/reliefweb-user-posting-rights.css +++ b/html/modules/custom/reliefweb_fields/components/reliefweb-user-posting-rights/reliefweb-user-posting-rights.css @@ -28,12 +28,14 @@ margin: 0 8px; } .field--type-reliefweb-user-posting-rights label span, -.field--type-reliefweb-user-posting-rights label select { +.field--type-reliefweb-user-posting-rights label select, +.field--type-reliefweb-user-posting-rights label input[type="text"] { display: inline-block; margin: 0 8px 0 0; vertical-align: middle; } -.field--type-reliefweb-user-posting-rights label select { +.field--type-reliefweb-user-posting-rights label select, +.field--type-reliefweb-user-posting-rights [data-filters] label input[type="text"] { width: auto; } .field--type-reliefweb-user-posting-rights div[data-filters] ~ ul li { @@ -41,9 +43,18 @@ flex-wrap: wrap; align-items: center; } -.field--type-reliefweb-user-posting-rights div[data-filters][data-job="all"][data-training="all"] ~ ul li { + +.field--type-reliefweb-user-posting-rights div[data-filters][data-job="all"][data-training="all"][data-report="all"] ~ ul li { display: flex; } + +.field--type-reliefweb-user-posting-rights div[data-filters]:not([data-name=""]) ~ ul li { + display: none !important; +} +.field--type-reliefweb-user-posting-rights div[data-filters]:not([data-name=""]) ~ ul li[data-user-filtered] { + display: flex !important; +} + .field--type-reliefweb-user-posting-rights div[data-filters][data-job="0"] ~ ul li[data-job="0"] { display: flex; } @@ -68,8 +79,22 @@ .field--type-reliefweb-user-posting-rights div[data-filters][data-training="3"] ~ ul li[data-training="3"] { display: flex; } +.field--type-reliefweb-user-posting-rights div[data-filters][data-report="0"] ~ ul li[data-report="0"] { + display: flex; +} +.field--type-reliefweb-user-posting-rights div[data-filters][data-report="1"] ~ ul li[data-report="1"] { + display: flex; +} +.field--type-reliefweb-user-posting-rights div[data-filters][data-report="2"] ~ ul li[data-report="2"] { + display: flex; +} +.field--type-reliefweb-user-posting-rights div[data-filters][data-report="3"] ~ ul li[data-report="3"] { + display: flex; +} + .field--type-reliefweb-user-posting-rights [data-job] label.job span:after, -.field--type-reliefweb-user-posting-rights [data-training] label.training span:after { +.field--type-reliefweb-user-posting-rights [data-training] label.training span:after, +.field--type-reliefweb-user-posting-rights [data-report] label.report span:after { display: inline-block; width: 12px; height: 12px; @@ -79,21 +104,26 @@ border-radius: 4px; } .field--type-reliefweb-user-posting-rights [data-job="0"] label.job span:after, -.field--type-reliefweb-user-posting-rights [data-training="0"] label.training span:after { +.field--type-reliefweb-user-posting-rights [data-training="0"] label.training span:after, +.field--type-reliefweb-user-posting-rights [data-report="0"] label.report span:after { background: #f49e2c; } .field--type-reliefweb-user-posting-rights [data-job="1"] label.job span:after, -.field--type-reliefweb-user-posting-rights [data-training="1"] label.training span:after { +.field--type-reliefweb-user-posting-rights [data-training="1"] label.training span:after, +.field--type-reliefweb-user-posting-rights [data-report="1"] label.report span:after { background: #da190b; } .field--type-reliefweb-user-posting-rights [data-job="2"] label.job span:after, -.field--type-reliefweb-user-posting-rights [data-training="2"] label.training span:after { +.field--type-reliefweb-user-posting-rights [data-training="2"] label.training span:after, +.field--type-reliefweb-user-posting-rights [data-report="2"] label.report span:after { background: #076d96; } .field--type-reliefweb-user-posting-rights [data-job="3"] label.job span:after, -.field--type-reliefweb-user-posting-rights [data-training="3"] label.training span:after { +.field--type-reliefweb-user-posting-rights [data-training="3"] label.training span:after, +.field--type-reliefweb-user-posting-rights [data-report="3"] label.report span:after { background: #88bb09; } + .field--type-reliefweb-user-posting-rights ul { margin: 0; padding: 0; diff --git a/html/modules/custom/reliefweb_fields/components/reliefweb-user-posting-rights/reliefweb-user-posting-rights.js b/html/modules/custom/reliefweb_fields/components/reliefweb-user-posting-rights/reliefweb-user-posting-rights.js index 13bf15e58..d2510a703 100644 --- a/html/modules/custom/reliefweb_fields/components/reliefweb-user-posting-rights/reliefweb-user-posting-rights.js +++ b/html/modules/custom/reliefweb_fields/components/reliefweb-user-posting-rights/reliefweb-user-posting-rights.js @@ -84,6 +84,27 @@ return label; }, + /** + * Create a filter for users. + */ + createUserFilter: function () { + let name = 'name'; + + var select = document.createElement('input'); + select.setAttribute('type', 'text'); + select.setAttribute('data-name', name); + + var span = document.createElement('span'); + span.appendChild(document.createTextNode(t('user id, name, email'))); + + var label = document.createElement('label'); + label.appendChild(span); + label.appendChild(select); + label.className = name; + + return label; + }, + /** * Create the user notes field. */ @@ -117,6 +138,8 @@ container.setAttribute('data-status', data.status ? 'active' : 'blocked'); container.setAttribute('data-job', data.job); container.setAttribute('data-training', data.training); + container.setAttribute('data-report', data.report); + container.setAttribute('data-name', [data.id, data.name, data.mail].join(', ')); // User info. var info = document.createElement('div'); @@ -147,6 +170,7 @@ // Rights. actions.appendChild(this.createSelect('job', data.job, disabled)); actions.appendChild(this.createSelect('training', data.training, disabled)); + actions.appendChild(this.createSelect('report', data.report, disabled)); // Remove. actions.appendChild(this.createButton('remove', t('Remove'), true, '', disabled)); @@ -205,6 +229,8 @@ container.setAttribute('data-filters', ''); container.setAttribute('data-job', 'all'); container.setAttribute('data-training', 'all'); + container.setAttribute('data-report', 'all'); + container.setAttribute('data-name', ''); var title = document.createElement('span'); title.appendChild(document.createTextNode(t('Filter: '))); @@ -213,6 +239,10 @@ // Rights filters. container.appendChild(this.createSelect('job', '', false, true)); container.appendChild(this.createSelect('training', '', false, true)); + container.appendChild(this.createSelect('report', '', false, true)); + + // User filter. + container.appendChild(this.createUserFilter()); return container; }, @@ -255,6 +285,7 @@ // Handle change events on the different select elements in the form. container.addEventListener('change', this.handleChange.bind(this)); + container.addEventListener('keyup', this.handleChange.bind(this)); // Handle focus out events from notes fields. container.addEventListener('focusout', this.handleFocusOut.bind(this)); @@ -418,6 +449,7 @@ id: element.getAttribute('data-id'), job: Math.max(element.querySelector('select[data-name="job"]').selectedIndex, 0), training: Math.max(element.querySelector('select[data-name="training"]').selectedIndex, 0), + report: Math.max(element.querySelector('select[data-name="report"]').selectedIndex, 0), notes: element.querySelector('textarea').value.trim() }; }, @@ -511,7 +543,7 @@ var name = target.getAttribute('data-name'); // Update the rights attributes of the user row. - if (name === 'job' || name === 'training') { + if (name === 'job' || name === 'training' || name === 'report') { var parent = target.parentNode.parentNode; // If the parent is not the filter container, then it's a select @@ -520,6 +552,29 @@ parent = this.getParentElement(target, 'LI'); } + // Set the attribute to the value of the select element. + parent.setAttribute('data-' + name, target.value); + parent.setAttribute('data-modified', ''); + this.updateData(); + } + } + else if (target && target.tagName === 'INPUT') { + var name = target.getAttribute('data-name'); + var parent = target.parentNode.parentNode; + + // Filter on user name. + if (parent.hasAttribute('data-filters') && name === 'name') { + let grandParent = parent.parentNode; + if (grandParent.querySelectorAll('li[data-user-filtered]').length > 0) { + Array.from(grandParent.querySelectorAll('li[data-user-filtered]')) + .forEach(e => e.removeAttribute('data-user-filtered')); + } + + if (target.value !== '') { + Array.from(grandParent.querySelectorAll('li[data-name*="' + target.value + '"]')) + .forEach(e => e.setAttribute('data-user-filtered', '')); + } + // Set the attribute to the value of the select element. parent.setAttribute('data-' + name, target.value); parent.setAttribute('data-modified', ''); diff --git a/html/modules/custom/reliefweb_fields/reliefweb_fields.install b/html/modules/custom/reliefweb_fields/reliefweb_fields.install new file mode 100644 index 000000000..3d2d88028 --- /dev/null +++ b/html/modules/custom/reliefweb_fields/reliefweb_fields.install @@ -0,0 +1,65 @@ +get('taxonomy_term.field_schema_data.field_user_posting_rights'); + foreach ($kv_schema as $table => &$info) { + $schema = Database::getConnection()->schema(); + if (!$schema->fieldExists($table, 'field_user_posting_rights_report')) { + $spec = [ + 'description' => 'Report posting rights: 0 = unverified; 1 = blocked; 2 = allowed; 3 = trusted.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => '', + 'default' => 0, + ]; + $schema->addField($table, 'field_user_posting_rights_report', $spec); + + $spec = [ + 'fields' => [ + 'field_user_posting_rights_report' => [ + 'description' => 'Report posting rights: 0 = unverified; 1 = blocked; 2 = allowed; 3 = trusted.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => '', + 'default' => 0, + ], + ], + 'indexes' => [ + 'field_user_posting_rights_report' => ['field_user_posting_rights_report'], + ], + ]; + + $schema->addIndex($table, 'field_user_posting_rights_report', [ + 'field_user_posting_rights_report', + ], $spec); + } + + $info['fields']['field_user_posting_rights_report'] = [ + 'description' => 'Report posting rights: 0 = unverified; 1 = blocked; 2 = allowed; 3 = trusted.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => '', + 'default' => 0, + ]; + $info['indexes']['field_user_posting_rights_report'] = [ + 'field_user_posting_rights_report', + ]; + } + + \Drupal::keyValue('entity.storage_schema.sql')->set('taxonomy_term.field_schema_data.field_user_posting_rights', $kv_schema); + + $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); + $field_storage_definition = $entity_definition_update_manager->getFieldStorageDefinition('field_user_posting_rights', 'taxonomy_term'); + $field_storage_definition->setSetting('column_changes_handled', TRUE); + $entity_definition_update_manager->updateFieldStorageDefinition($field_storage_definition); +} diff --git a/html/modules/custom/reliefweb_fields/src/Plugin/Field/FieldType/ReliefWebUserPostingRights.php b/html/modules/custom/reliefweb_fields/src/Plugin/Field/FieldType/ReliefWebUserPostingRights.php index 39ecbedea..c356821b2 100644 --- a/html/modules/custom/reliefweb_fields/src/Plugin/Field/FieldType/ReliefWebUserPostingRights.php +++ b/html/modules/custom/reliefweb_fields/src/Plugin/Field/FieldType/ReliefWebUserPostingRights.php @@ -50,6 +50,13 @@ public static function schema(FieldStorageDefinitionInterface $field_definition) 'not null' => TRUE, 'default' => 0, ], + 'report' => [ + 'description' => 'Report posting rights: 0 = unverified; 1 = blocked; 2 = allowed; 3 = trusted.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ], 'notes' => [ 'description' => 'Notes', 'type' => 'text', @@ -60,6 +67,7 @@ public static function schema(FieldStorageDefinitionInterface $field_definition) 'id' => ['id'], 'job' => ['job'], 'training' => ['training'], + 'report' => ['report'], ], ]; } @@ -80,6 +88,10 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel ->setLabel(new TranslatableMarkup('Training')) ->setRequired(FALSE); + $properties['report'] = DataDefinition::create('integer') + ->setLabel(new TranslatableMarkup('Report')) + ->setRequired(FALSE); + $properties['notes'] = DataDefinition::create('string') ->setLabel(new TranslatableMarkup('Notes')) ->setRequired(FALSE); @@ -136,6 +148,17 @@ public function getConstraints() { ], ], ]); + $constraints[] = $constraint_manager->create('ComplexData', [ + 'report' => [ + 'AllowedValues' => [ + 'choices' => [0, 1, 2, 3], + 'strict' => TRUE, + 'message' => $this->t('%name: the Report rights must be one of 0, 1, 2 or 3.', [ + '%name' => $this->getFieldDefinition()->getLabel(), + ]), + ], + ], + ]); return $constraints; } @@ -147,6 +170,7 @@ public static function generateSampleValue(FieldDefinitionInterface $field_defin $values['url'] = mt_rand(3, 1000000); $values['job'] = mt_rand(0, 3); $values['training'] = mt_rand(0, 3); + $values['report'] = mt_rand(0, 3); return $values; } diff --git a/html/modules/custom/reliefweb_fields/src/Plugin/Field/FieldWidget/ReliefWebUserPostingRights.php b/html/modules/custom/reliefweb_fields/src/Plugin/Field/FieldWidget/ReliefWebUserPostingRights.php index 227ece21c..55c795ec8 100644 --- a/html/modules/custom/reliefweb_fields/src/Plugin/Field/FieldWidget/ReliefWebUserPostingRights.php +++ b/html/modules/custom/reliefweb_fields/src/Plugin/Field/FieldWidget/ReliefWebUserPostingRights.php @@ -174,6 +174,7 @@ public function massageFormValues(array $values, array $form, FormStateInterface 'id' => intval($item['id'], 10), 'job' => intval($item['job'], 10), 'training' => intval($item['training'], 10), + 'report' => intval($item['report'], 10), 'notes' => $item['notes'], ]; } @@ -200,6 +201,7 @@ public static function normalizeData(array $data) { $data['id'] = intval($data['id'] ?? $data['uid'], 10); $data['job'] = isset($data['job']) ? intval($data['job'], 10) : 0; $data['training'] = isset($data['training']) ? intval($data['training'], 10) : 0; + $data['report'] = isset($data['report']) ? intval($data['report'], 10) : 0; $data['notes'] = isset($data['notes']) ? trim($data['notes']) : ''; $data['name'] = trim($data['name']); @@ -210,6 +212,7 @@ public static function normalizeData(array $data) { if ($data['status'] === 0) { $data['job'] = 1; $data['training'] = 1; + $data['report'] = 1; } return $data; @@ -410,7 +413,8 @@ public static function updateFields($op, UserInterface $user) { $query->condition($query ->orConditionGroup() ->condition($field_name . '.job', 1, '<>') - ->condition($field_name . '.training', 1, '<>')); + ->condition($field_name . '.training', 1, '<>') + ->condition($field_name . '.report', 1, '<>')); } $ids = $query?->execute(); @@ -435,9 +439,10 @@ public static function updateFields($op, UserInterface $user) { } // Set the rights to 'blocked' if the account is blocked. elseif ($blocked) { - if ($item['job'] != 1 || $item['training'] != 1) { + if ($item['job'] != 1 || $item['training'] != 1 || $item['report'] != 1) { $items[$delta]['job'] = 1; $items[$delta]['training'] = 1; + $items[$delta]['report'] = 1; if (!empty($item['notes'])) { $items[$delta]['notes'] .= ' ' . $message; } @@ -450,9 +455,10 @@ public static function updateFields($op, UserInterface $user) { // Reset the rights to 'unverified' if the email changed but // preserve the 'blocked' rights. elseif ($email_changed) { - if ($item['job'] > 1 || $item['training'] > 1) { + if ($item['job'] > 1 || $item['training'] > 1 || $item['report'] > 1) { $items[$delta]['job'] = $item['job'] == 1 ? 1 : 0; $items[$delta]['training'] = $item['training'] == 1 ? 1 : 0; + $items[$delta]['report'] = $item['report'] == 1 ? 1 : 0; if (!empty($item['notes'])) { $items[$delta]['notes'] .= ' ' . $message; } diff --git a/html/modules/custom/reliefweb_files/src/Controller/ImageStyleDownloadController.php b/html/modules/custom/reliefweb_files/src/Controller/ImageStyleDownloadController.php index 6af910a20..504b0ced0 100644 --- a/html/modules/custom/reliefweb_files/src/Controller/ImageStyleDownloadController.php +++ b/html/modules/custom/reliefweb_files/src/Controller/ImageStyleDownloadController.php @@ -122,7 +122,7 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st // Let other modules handle the file if it's not a file matching the pattern // used for the reliefweb files. if (preg_match($pattern, $uri) !== 1) { - return parent::deliver($request, $scheme, $image_style); + return parent::deliver($request, $scheme, $image_style, $required_derivative_scheme); } // Check the image token. We return a 404 as it's more likely to be cached diff --git a/html/modules/custom/reliefweb_form/src/Controller/NodeForm.php b/html/modules/custom/reliefweb_form/src/Controller/NodeForm.php index 59c210194..c91a898ba 100644 --- a/html/modules/custom/reliefweb_form/src/Controller/NodeForm.php +++ b/html/modules/custom/reliefweb_form/src/Controller/NodeForm.php @@ -51,7 +51,10 @@ public function getSourceAttentionMessages($bundle) { // For reports, we simply load the messages from the report attention field. if ($bundle === 'report') { - $messages = $this->loadSourceAttentionMessages($bundle, $ids); + // No messages for contributors. + if (!$this->currentUser()->hasRole('contributor')) { + $messages = $this->loadSourceAttentionMessages($bundle, $ids); + } } // For jobs or training, we combine the job and training attention messages // as the information is useful for both teams. diff --git a/html/modules/custom/reliefweb_moderation/src/Helpers/UserPostingRightsHelper.php b/html/modules/custom/reliefweb_moderation/src/Helpers/UserPostingRightsHelper.php index f059d1862..cb259bd76 100644 --- a/html/modules/custom/reliefweb_moderation/src/Helpers/UserPostingRightsHelper.php +++ b/html/modules/custom/reliefweb_moderation/src/Helpers/UserPostingRightsHelper.php @@ -98,7 +98,7 @@ public static function getEntityAuthorPostingRights(EntityInterface $entity) { * Posting rights as an associative array keyed by source id. */ public static function getUserPostingRights(?AccountInterface $account = NULL, array $sources = []) { - static $users; + $users = &drupal_static('reliefweb_moderation_getUserPostingRights'); $helper = new UserPostingRightsHelper(); $account = $account ?: \Drupal::currentUser(); @@ -117,12 +117,14 @@ public static function getUserPostingRights(?AccountInterface $account = NULL, a $id_field = $helper->getFieldColumnName('taxonomy_term', 'field_user_posting_rights', 'id'); $job_field = $helper->getFieldColumnName('taxonomy_term', 'field_user_posting_rights', 'job'); $training_field = $helper->getFieldColumnName('taxonomy_term', 'field_user_posting_rights', 'training'); + $report_field = $helper->getFieldColumnName('taxonomy_term', 'field_user_posting_rights', 'report'); // Get the rights associated with the user id. $query = $helper->getDatabase()->select($table, $table); $query->addField($table, 'entity_id', 'tid'); $query->addField($table, $job_field, 'job'); $query->addField($table, $training_field, 'training'); + $query->addField($table, $report_field, 'report'); $query->condition($table . '.bundle', 'source', '='); $query->condition($table . '.' . $id_field, $account->id(), '='); if (!empty($sources)) { @@ -142,6 +144,7 @@ public static function getUserPostingRights(?AccountInterface $account = NULL, a 'tid' => $tid, 'job' => 0, 'training' => 0, + 'report' => 0, ]; } } @@ -158,7 +161,7 @@ public static function getUserPostingRights(?AccountInterface $account = NULL, a * Compute the "final" posting right for a document based on an account's * rights for the given sources. * - * Currently only for jobs and training. + * Currently only for jobs, trainings and reports. * * @param \Drupal\Core\Session\AccountInterface $account * A user's account object or the current user if NULL. @@ -172,8 +175,8 @@ public static function getUserPostingRights(?AccountInterface $account = NULL, a * the right applies. */ public static function getUserConsolidatedPostingRight(AccountInterface $account, $bundle, array $sources) { - // Not a job nor training or no sources, consider the user 'unverified'. - if (empty($account->uid) || ($bundle !== 'job' && $bundle !== 'training') || empty($sources)) { + // Not a job, training nor report or no sources, 'unverified'. + if (empty($account->id()) || ($bundle !== 'job' && $bundle !== 'training' && $bundle !== 'report') || empty($sources)) { return [ 'code' => 0, 'name' => 'unverified', @@ -251,8 +254,8 @@ public static function userHasPostingRights(AccountInterface $account, EntityInt $owner = $entity->getOwnerId() === $account->id() && $account->id() > 0; } - // Only applies to job and training. - if ($bundle === 'job' || $bundle === 'training') { + // Only applies to job, training and report. + if ($bundle === 'job' || $bundle === 'training' || $bundle === 'report') { // Check for sources for which the user is blocked, allowed or trusted. // // Note: if there is no source or the user in unverified for the sources diff --git a/html/modules/custom/reliefweb_moderation/src/ModerationServiceBase.php b/html/modules/custom/reliefweb_moderation/src/ModerationServiceBase.php index f056f74b4..0023ca00c 100644 --- a/html/modules/custom/reliefweb_moderation/src/ModerationServiceBase.php +++ b/html/modules/custom/reliefweb_moderation/src/ModerationServiceBase.php @@ -1913,8 +1913,8 @@ protected function joinPostingRights(Select $query, array $definition, $entity_t return ''; } - // This is only valid for jobs and training. Skip it otherwise. - if ($bundle !== 'job' && $bundle !== 'training') { + // This is only valid for jobs, trainings and reports. Skip it otherwise. + if ($bundle !== 'job' && $bundle !== 'training' && $bundle !== 'report') { return ''; } diff --git a/html/modules/custom/reliefweb_moderation/src/Services/ReportModeration.php b/html/modules/custom/reliefweb_moderation/src/Services/ReportModeration.php index 50d214e32..fa7d19e2e 100644 --- a/html/modules/custom/reliefweb_moderation/src/Services/ReportModeration.php +++ b/html/modules/custom/reliefweb_moderation/src/Services/ReportModeration.php @@ -6,7 +6,9 @@ use Drupal\Core\Link; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; +use Drupal\node\NodeInterface; use Drupal\reliefweb_moderation\EntityModeratedInterface; +use Drupal\reliefweb_moderation\Helpers\UserPostingRightsHelper; use Drupal\reliefweb_moderation\ModerationServiceBase; use Drupal\reliefweb_utility\Helpers\UserHelper; @@ -109,6 +111,12 @@ public function getRows(array $results) { // Country and source info. $info = []; + + // User posting rights. + if ($entity instanceof NodeInterface && $entity->getOwner()->hasRole('contributor')) { + $info['posting_rights'] = UserPostingRightsHelper::renderRight(UserPostingRightsHelper::getEntityAuthorPostingRights($entity)); + } + // Country. $country_link = $this->getTaxonomyTermLink($entity->field_primary_country->first()); if (!empty($country_link)) { @@ -201,8 +209,10 @@ public function getStatuses() { 'draft' => $this->t('Draft'), 'on-hold' => $this->t('On-hold'), 'to-review' => $this->t('To review'), + 'pending' => $this->t('Pending'), 'published' => $this->t('Published'), 'embargoed' => $this->t('Embargoed'), + 'refused' => $this->t('Refused'), 'archive' => $this->t('Archived'), 'reference' => $this->t('Reference'), ]; @@ -221,29 +231,90 @@ public function getFilterDefaultStatuses() { * {@inheritdoc} */ public function getEntityFormSubmitButtons($status, EntityModeratedInterface $entity) { - $buttons = [ - 'draft' => [ + $buttons = []; + $new = empty($status) || $status === 'draft' || $entity->isNew(); + + // Only show save as draft for non-published but editable documents. + if ($new || in_array($status, ['draft', 'on-hold'])) { + $buttons['draft'] = [ '#value' => $this->t('Save as draft'), - ], - 'to-review' => [ - '#value' => $this->t('To review'), - ], - 'published' => [ - '#value' => $this->t('Publish'), - ], - 'on-hold' => [ - '#value' => $this->t('On-hold'), - ], - 'reference' => [ - '#value' => $this->t('Reference'), - ], - ]; + ]; + } - // @todo replace with permission. - if (UserHelper::userHasRoles(['administrator', 'webmaster'])) { - $buttons['archive'] = [ - '#value' => $this->t('Archive'), + // Editors can publish, put on hold or refuse a document. + // @todo use permission. + if (UserHelper::userHasRoles(['editor'])) { + $buttons = [ + 'draft' => [ + '#value' => $this->t('Save as draft'), + ], + 'to-review' => [ + '#value' => $this->t('To review'), + ], + 'published' => [ + '#value' => $this->t('Publish'), + ], + 'on-hold' => [ + '#value' => $this->t('On-hold'), + ], + 'reference' => [ + '#value' => $this->t('Reference'), + ], + ]; + } + elseif (UserHelper::userHasRoles(['administrator', 'webmaster'])) { + $buttons = [ + 'draft' => [ + '#value' => $this->t('Save as draft'), + ], + 'to-review' => [ + '#value' => $this->t('To review'), + ], + 'published' => [ + '#value' => $this->t('Publish'), + ], + 'on-hold' => [ + '#value' => $this->t('On-hold'), + ], + 'reference' => [ + '#value' => $this->t('Reference'), + ], + 'archive' => [ + '#value' => $this->t('Archive'), + ], + ]; + } + // Other users can submit for review, on-hold or published if trusted. + else { + $buttons = [ + 'draft' => [ + '#value' => $this->t('Save as draft'), + ], + 'to-review' => [ + '#value' => $new ? $this->t('Submit') : $this->t('Submit changes'), + ], + 'on-hold' => [ + '#value' => $this->t('On-hold'), + ], ]; + + // Add confirmation when attempting to change published document. + if ($status === 'published') { + $message = $this->t('Press OK to submit the changes for review by the ReliefWeb editors. The report may become unpublished while being reviewed.'); + $buttons['to-review']['#attributes']['onclick'] = 'return confirm("' . $message . '")'; + } + } + + if (UserHelper::userHasRoles(['contributor'])) { + // Warning message when saving as a draft. + if (isset($buttons['draft'])) { + $message = $this->t('You are saving this document as a draft. It will not be visible to visitors. If you wish to proceed with the publication kindly click on @buttons instead.', [ + '@buttons' => implode(' or ', array_map(function ($item) { + return $item['#value']; + }, array_slice($buttons, 1))), + ]); + $buttons['draft']['#attributes']['onclick'] = 'return confirm("' . $message . '")'; + } } return $buttons; @@ -260,7 +331,7 @@ public function isPublishedStatus($status) { * {@inheritdoc} */ public function isEditableStatus($status, ?AccountInterface $account = NULL) { - if ($status === 'archive') { + if ($status === 'archive' || $status === 'refused') { return UserHelper::userHasRoles(['administrator', 'webmaster'], $account); } return TRUE; @@ -316,6 +387,7 @@ protected function initFilterDefinitions(array $filters = []) { 'original_publication_date', 'author', 'user_role', + 'posting_rights', 'reviewer', 'reviewed', 'comments', diff --git a/html/modules/custom/reliefweb_moderation/src/Services/SourceModeration.php b/html/modules/custom/reliefweb_moderation/src/Services/SourceModeration.php index d8b83b87b..5be9d3d39 100644 --- a/html/modules/custom/reliefweb_moderation/src/Services/SourceModeration.php +++ b/html/modules/custom/reliefweb_moderation/src/Services/SourceModeration.php @@ -225,9 +225,10 @@ public function entityPresave(EntityModeratedInterface $entity) { // Update the posting rights field, setting everything as blocked. if (!$entity->get('field_user_posting_rights')->isEmpty()) { foreach ($entity->get('field_user_posting_rights') as $item) { - if ($item->get('job')->getValue() != 1 || $item->get('training')->getValue() != 1) { + if ($item->get('job')->getValue() != 1 || $item->get('training')->getValue() != 1 || $item->get('report')->getValue() != 1) { $item->get('job')->setValue(1); $item->get('training')->setValue(1); + $item->get('report')->setValue(1); $changed = TRUE; } } diff --git a/html/modules/custom/reliefweb_reporting/reliefweb_reporting.module b/html/modules/custom/reliefweb_reporting/reliefweb_reporting.module index 5b6befcb9..95609dbf2 100644 --- a/html/modules/custom/reliefweb_reporting/reliefweb_reporting.module +++ b/html/modules/custom/reliefweb_reporting/reliefweb_reporting.module @@ -47,43 +47,6 @@ function reliefweb_reporting_get_weekly_ai_tagging_stats() { $schema = \Drupal::database()->schema(); $records = []; - // To avoid losing data when transitioning, we fetch the information from the - // previous tagging system and concatenate it with the new one. - if ($schema->tableExists('node_revision__reliefweb_job_tagger_status')) { - $old_records = $connection->query(" - SELECT - nr.nid AS nid, - nr.vid As vid, - IFNULL(GROUP_CONCAT(DISTINCT ur.roles_target_id ORDER BY ur.roles_target_id SEPARATOR ','), '') AS roles, - GROUP_CONCAT(DISTINCT nfcc.field_career_categories_target_id ORDER BY nfcc.field_career_categories_target_id SEPARATOR ',') AS career_categories, - GROUP_CONCAT(DISTINCT nft.field_theme_target_id ORDER BY nft.field_theme_target_id SEPARATOR ',') AS themes - FROM node_revision AS nr - INNER JOIN node_field_data AS n - ON n.nid = nr.nid - INNER JOIN node_revision__reliefweb_job_tagger_status AS njts - ON njts.entity_id = n.nid - AND njts.revision_id = nr.vid - AND njts.reliefweb_job_tagger_status_value = 'processed' - LEFT JOIN user__roles AS ur - ON ur.entity_id = nr.revision_uid - LEFT JOIN node_revision__field_career_categories AS nfcc - ON nfcc.entity_id = nr.nid - AND nfcc.revision_id = nr.vid - LEFT JOIN node_revision__field_theme AS nft - ON nft.entity_id = nr.nid - AND nft.revision_id = nr.vid - WHERE n.type = 'job' - AND n.created >= UNIX_TIMESTAMP(DATE_SUB(DATE(NOW()), INTERVAL DAYOFWEEK(NOW()) + 6 DAY)) - AND n.created < UNIX_TIMESTAMP(DATE_SUB(DATE(NOW()), INTERVAL DAYOFWEEK(NOW()) - 1 DAY)) - GROUP BY nr.vid - ORDER BY nr.vid - ")->fetchAll(\PDO::FETCH_ASSOC); - - foreach ($old_records as $record) { - $records[$record['nid']][$record['vid']] = $record; - } - } - if ($schema->tableExists('ocha_content_classification_progress')) { $new_records = $connection->query(" SELECT diff --git a/html/modules/custom/reliefweb_revisions/src/Services/EntityHistory.php b/html/modules/custom/reliefweb_revisions/src/Services/EntityHistory.php index 44ef43965..4884ec104 100644 --- a/html/modules/custom/reliefweb_revisions/src/Services/EntityHistory.php +++ b/html/modules/custom/reliefweb_revisions/src/Services/EntityHistory.php @@ -927,6 +927,7 @@ protected function formatReliefWebUserPostingRightsFieldDiff(FieldDefinitionInte 'removed' => array_diff_key($previous, $current), 'modified-training' => [], 'modified-job' => [], + 'modified-report' => [], 'modified-notes' => [], ]; @@ -935,6 +936,7 @@ protected function formatReliefWebUserPostingRightsFieldDiff(FieldDefinitionInte 'removed' => $this->t('Removed'), 'modified-training' => $this->t('Modified Training'), 'modified-job' => $this->t('Modified Job'), + 'modified-report' => $this->t('Modified Report'), 'modified-notes' => $this->t('Modified Notes'), ]; @@ -951,7 +953,7 @@ protected function formatReliefWebUserPostingRightsFieldDiff(FieldDefinitionInte $previous_item = $previous[$key]; $current_item = $current[$key]; // Rights change. - foreach (['job', 'training'] as $type) { + foreach (['job', 'training', 'report'] as $type) { if ($previous_item[$type] !== $current_item[$type]) { $item['change'] = new FormattableMarkup('@before → @after', [ '@before' => UserPostingRightsHelper::renderRight($rights[$previous_item[$type]]), @@ -995,9 +997,10 @@ protected function formatReliefWebUserPostingRightsFieldDiff(FieldDefinitionInte // Add the rights when a user is added. if ($category === 'added') { - $markup[] = '(job: @job, training: @training)'; + $markup[] = '(job: @job, training: @training, report: @report)'; $replacements['@job'] = UserPostingRightsHelper::renderRight($rights[$item['job']]); $replacements['@training'] = UserPostingRightsHelper::renderRight($rights[$item['training']]); + $replacements['@report'] = UserPostingRightsHelper::renderRight($rights[$item['report']]); } // Add the rights changes. diff --git a/html/modules/custom/reliefweb_subscriptions/src/ReliefwebSubscriptionsMailer.php b/html/modules/custom/reliefweb_subscriptions/src/ReliefwebSubscriptionsMailer.php index 37773001a..8e276232f 100644 --- a/html/modules/custom/reliefweb_subscriptions/src/ReliefwebSubscriptionsMailer.php +++ b/html/modules/custom/reliefweb_subscriptions/src/ReliefwebSubscriptionsMailer.php @@ -1156,14 +1156,6 @@ protected function generateEmailContentOchaSitrep(array $subscription, array $da ], ], ]; - // Add link to Digital sitrep if there is one. - $dsr_url = $this->config('reliefweb_dsr')->get('ocha_dsr_url'); - if (!empty($dsr_url) && isset($data['origin']) && strpos($data['origin'], $dsr_url) === 0) { - array_unshift($prefooter_parts, [ - 'text' => 'Digital Situation Report for ' . $country_name, - 'link' => $data['origin'], - ]); - } $variables['#prefooter'] = $this->prepareFooterLinks($prefooter_parts, $subscription); return $variables; diff --git a/html/modules/custom/reliefweb_user_posts/src/Form/UserPostsPageFilterForm.php b/html/modules/custom/reliefweb_user_posts/src/Form/UserPostsPageFilterForm.php index 6805d7d02..cece24eab 100644 --- a/html/modules/custom/reliefweb_user_posts/src/Form/UserPostsPageFilterForm.php +++ b/html/modules/custom/reliefweb_user_posts/src/Form/UserPostsPageFilterForm.php @@ -30,14 +30,29 @@ public function buildForm(array $form, FormStateInterface $form_state, ?Moderati // Link to create a new entity. $url_options = ['attributes' => ['target' => '_blank']]; - $links = $this->t('Create a new Job vacancy or a new Training program', [ - '@job_url' => Url::fromRoute('node.add', [ - 'node_type' => 'job', - ], $url_options)->toString(), - '@training_url' => Url::fromRoute('node.add', [ - 'node_type' => 'training', - ], $url_options)->toString(), - ]); + if ($user && $user->hasPermission('create report content')) { + $links = $this->t('Create a new Job vacancy, a new Training program or a new Report', [ + '@job_url' => Url::fromRoute('node.add', [ + 'node_type' => 'job', + ], $url_options)->toString(), + '@training_url' => Url::fromRoute('node.add', [ + 'node_type' => 'training', + ], $url_options)->toString(), + '@report_url' => Url::fromRoute('node.add', [ + 'node_type' => 'report', + ], $url_options)->toString(), + ]); + } + else { + $links = $this->t('Create a new Job vacancy or a new Training program', [ + '@job_url' => Url::fromRoute('node.add', [ + 'node_type' => 'job', + ], $url_options)->toString(), + '@training_url' => Url::fromRoute('node.add', [ + 'node_type' => 'training', + ], $url_options)->toString(), + ]); + } // Add intro. $form['intro'] = [ diff --git a/html/modules/custom/reliefweb_user_posts/src/Services/UserPostsService.php b/html/modules/custom/reliefweb_user_posts/src/Services/UserPostsService.php index 32b11619b..b77f3ea9f 100644 --- a/html/modules/custom/reliefweb_user_posts/src/Services/UserPostsService.php +++ b/html/modules/custom/reliefweb_user_posts/src/Services/UserPostsService.php @@ -87,6 +87,7 @@ public function getBundle() { return [ 'job', 'training', + 'report', ]; } @@ -108,15 +109,25 @@ public function getTitle() { * {@inheritdoc} */ public function getStatuses() { - return [ - 'draft' => $this->t('draft'), - 'pending' => $this->t('pending'), - 'published' => $this->t('published'), - 'on-hold' => $this->t('on-hold'), - 'refused' => $this->t('refused'), - 'expired' => $this->t('expired'), - 'duplicate' => $this->t('duplicate'), + $statuses = [ + 'draft' => $this->t('Draft'), + 'pending' => $this->t('Pending'), + 'published' => $this->t('Published'), + 'on-hold' => $this->t('On-hold'), + 'refused' => $this->t('Refused'), + 'expired' => $this->t('Expired'), + 'duplicate' => $this->t('Duplicate'), ]; + + if ($this->currentUser->hasRole('contributor')) { + $statuses += [ + 'to-review' => $this->t('To review'), + 'embargoed' => $this->t('Embargoed'), + 'reference' => $this->t('Reference'), + ]; + } + + return $statuses; } /** @@ -229,9 +240,12 @@ public function getRows(array $results) { $cells['deadline'] = $this->formatDate($entity->field_registration_deadline->value); } } - else { + elseif ($entity->bundle() === 'job') { $cells['deadline'] = $this->formatDate($entity->field_job_closing_date->value); } + else { + $cells['deadline'] = $this->t('N/A'); + } $rows[] = $cells; } @@ -261,6 +275,14 @@ protected function initFilterDefinitions(array $filters = []) { ]; // Filter by bundle. + $allowed_bundles = [ + 'job' => $this->t('Job'), + 'training' => $this->t('Training'), + ]; + if ($this->currentUser->hasPermission('create report content')) { + $allowed_bundles['report'] = $this->t('Report'); + } + $definitions['bundle'] = [ 'type' => 'property', 'field' => 'type', @@ -268,10 +290,7 @@ protected function initFilterDefinitions(array $filters = []) { 'shortcut' => 'ty', 'form' => 'other', 'operator' => 'OR', - 'values' => [ - 'job' => $this->t('Job'), - 'training' => $this->t('Training'), - ], + 'values' => $allowed_bundles, ]; // Limit sources. @@ -356,6 +375,9 @@ protected function getSourcesTheUserHasPostedFor($filter, $term, $conditions, ar elseif (isset($rights[$source->value]['training']) && $rights[$source->value]['training'] > $min_right) { $allowed_sources[] = $source; } + elseif (isset($rights[$source->value]['report']) && $rights[$source->value]['report'] > $min_right) { + $allowed_sources[] = $source; + } } } @@ -462,6 +484,9 @@ protected function filterQuery(Select $query, array $filters = []) { if (empty($filters['bundle']) || !empty($filters['bundle']['training'])) { $types[] = 'training'; } + if (empty($filters['bundle']) || !empty($filters['bundle']['report'])) { + $types[] = 'report'; + } // Get the user rights keyed by source ids and store the ones // for which the user is allowed to post. diff --git a/html/modules/custom/reliefweb_users/src/Controller/UserController.php b/html/modules/custom/reliefweb_users/src/Controller/UserController.php index b7cdf9060..a9215068d 100644 --- a/html/modules/custom/reliefweb_users/src/Controller/UserController.php +++ b/html/modules/custom/reliefweb_users/src/Controller/UserController.php @@ -127,7 +127,7 @@ protected function usersAdminList($storage, $query_parameters) { 'mail' => ['data' => $this->t('Mail'), 'field' => 'u.mail'], 'status' => ['data' => $this->t('Status'), 'field' => 'u.status'], 'role' => $this->t('Roles'), - 'sources' => $this->t('Sources (Job, Training)'), + 'sources' => $this->t('Sources (Job, Training, Reports)'), 'created' => ['data' => $this->t('Member for'), 'field' => 'u.created'], 'access' => ['data' => $this->t('Last access'), 'field' => 'u.access'], 'edit' => $this->t('Edit'), @@ -170,7 +170,7 @@ protected function usersAdminList($storage, $query_parameters) { } // Posting rights filter. - if (isset($filters['job_rights']) || isset($filters['training_rights'])) { + if (isset($filters['job_rights']) || isset($filters['training_rights']) || isset($filters['report_rights'])) { $query->innerJoin('taxonomy_term__field_user_posting_rights', 'fpr', '%alias.field_user_posting_rights_id = u.uid'); if (isset($filters['job_rights'])) { $query->condition('fpr.field_user_posting_rights_job', $rights[$filters['job_rights']], '='); @@ -178,6 +178,9 @@ protected function usersAdminList($storage, $query_parameters) { if (isset($filters['training_rights'])) { $query->condition('fpr.field_user_posting_rights_training', $rights[$filters['training_rights']], '='); } + if (isset($filters['report_rights'])) { + $query->condition('fpr.field_user_posting_rights_report', $rights[$filters['report_rights']], '='); + } } // Set group by. @@ -272,6 +275,7 @@ public function getUserSources(array &$users) { $query->addField('f', 'field_user_posting_rights_id', 'uid'); $query->addField('f', 'field_user_posting_rights_job', 'job'); $query->addField('f', 'field_user_posting_rights_training', 'training'); + $query->addField('f', 'field_user_posting_rights_report', 'report'); $query->addField('f', 'entity_id', 'tid'); $query->addExpression('COALESCE(fs.field_shortname_value, td.name)', 'name'); $query->condition('f.field_user_posting_rights_id', array_keys($users), 'IN'); @@ -281,11 +285,22 @@ public function getUserSources(array &$users) { foreach ($query->execute() as $record) { $job = $record->job; $training = $record->training; + $report = $record->report; + $link = Link::fromTextAndUrl($record->name, URL::fromUserInput('/taxonomy/term/' . $record->tid . '/user-posting-rights', [ 'attributes' => ['target' => '_blank'], ])); - $row = '
  • ' . $link->toString() . '
  • '; - $sources[$record->uid][$record->tid] = $row; + $row = [ + '
  • ', + $link->toString(), + '', + '' . $this->getRightsLabel('job', $job) . '', + '' . $this->getRightsLabel('training', $training) . '', + '' . $this->getRightsLabel('report', $report) . '', + '', + '
  • ', + ]; + $sources[$record->uid][$record->tid] = implode(' ', $row); } // Add the formatted list of sources to the user objects. @@ -294,4 +309,28 @@ public function getUserSources(array &$users) { } } + /** + * Get human readable rights. + */ + protected function getRightsLabel(string $type, string $right) { + $types = [ + 'job' => $this->t('Job'), + 'training' => $this->t('Training'), + 'report' => $this->t('Report'), + ]; + $rights = [ + 0 => $this->t('Unverified'), + 1 => $this->t('Blocked'), + 2 => $this->t('Allowed'), + 3 => $this->t('Trusted'), + ]; + + $label = [ + $types[$type], + $rights[$right], + ]; + + return implode(' - ', $label); + } + } diff --git a/html/modules/custom/reliefweb_users/src/Form/UserPageFilterForm.php b/html/modules/custom/reliefweb_users/src/Form/UserPageFilterForm.php index ab451f272..18f688728 100644 --- a/html/modules/custom/reliefweb_users/src/Form/UserPageFilterForm.php +++ b/html/modules/custom/reliefweb_users/src/Form/UserPageFilterForm.php @@ -113,6 +113,13 @@ public function buildForm(array $form, FormStateInterface $form_state, ?AccountI '#default_value' => $filters['training_rights'] ?? 'any', ]; + $form['filters']['report_rights'] = [ + '#type' => 'radios', + '#title' => $this->t('Report posting rights'), + '#options' => ['any' => 'Any'] + $rights, + '#default_value' => $filters['report_rights'] ?? 'any', + ]; + $form['filters']['name'] = [ '#type' => 'textfield', '#title' => $this->t('Name'), @@ -197,6 +204,7 @@ protected function getFilters(FormStateInterface $form_state) { 'posted' => isset($inputs['posted']) && is_array($inputs['posted']) ? array_intersect_key($content_types, $inputs['posted']) : NULL, 'job_rights' => isset($inputs['job_rights'], $rights[$inputs['job_rights']]) ? $inputs['job_rights'] : NULL, 'training_rights' => isset($inputs['training_rights'], $rights[$inputs['training_rights']]) ? $inputs['training_rights'] : NULL, + 'report_rights' => isset($inputs['report_rights'], $rights[$inputs['report_rights']]) ? $inputs['report_rights'] : NULL, ]; } diff --git a/html/modules/custom/reliefweb_utility/reliefweb_utility.module b/html/modules/custom/reliefweb_utility/reliefweb_utility.module index d8935cb6c..24e49d412 100644 --- a/html/modules/custom/reliefweb_utility/reliefweb_utility.module +++ b/html/modules/custom/reliefweb_utility/reliefweb_utility.module @@ -5,6 +5,7 @@ * Helper functions that applies to a variety of things on ReliefWeb. */ +use Drupal\Core\File\Exception\FileException; use Drupal\Core\Template\Attribute; use Drupal\file\FileInterface; diff --git a/html/themes/custom/common_design_subtheme/common_design_subtheme.libraries.yml b/html/themes/custom/common_design_subtheme/common_design_subtheme.libraries.yml index 18eb9167f..a399b6f5b 100644 --- a/html/themes/custom/common_design_subtheme/common_design_subtheme.libraries.yml +++ b/html/themes/custom/common_design_subtheme/common_design_subtheme.libraries.yml @@ -171,11 +171,6 @@ rw-document: theme: components/rw-document/rw-document.css: {} -rw-dsr: - css: - theme: - components/rw-dsr/rw-dsr.css: {} - rw-entity-meta: css: theme: diff --git a/html/themes/custom/common_design_subtheme/components/rw-dsr/README.md b/html/themes/custom/common_design_subtheme/components/rw-dsr/README.md deleted file mode 100644 index a5b4d3647..000000000 --- a/html/themes/custom/common_design_subtheme/components/rw-dsr/README.md +++ /dev/null @@ -1,4 +0,0 @@ -ReliefWeb DSR -============= - -This component provides rules for the Digital Situation Report block on country pages. diff --git a/html/themes/custom/common_design_subtheme/components/rw-dsr/rw-dsr.css b/html/themes/custom/common_design_subtheme/components/rw-dsr/rw-dsr.css deleted file mode 100644 index 149fa7578..000000000 --- a/html/themes/custom/common_design_subtheme/components/rw-dsr/rw-dsr.css +++ /dev/null @@ -1,115 +0,0 @@ -/* OCHA digital sitrep */ -.rw-dsr .rw-dsr__title { - margin-bottom: 24px; -} -.rw-dsr .rw-dsr__title small { - white-space: nowrap; - font-weight: normal; -} -.rw-dsr .rw-dsr__title span.rw-dsr__ocha { - position: relative; - display: inline-block; - /* Width of the logo below. */ - width: 80px; - /* Space between the logo and the rest of the title. */ - margin-right: 8px; - /* Better default when the logo cannot be loaded. */ - text-align: center; - letter-spacing: 0.5px; - /* h3 styles */ - font-size: 20px; - font-weight: bold; - /* Better vertical alignment. */ - line-height: 1; -} -.rw-dsr .rw-dsr__title span.rw-dsr__ocha:after { - position: absolute; - top: 50%; - left: 0; - overflow: hidden; - width: 80px; - height: 20px; - /* 1px more that 1/2 the height for better alignment with the rest of the - * digital sitrep title as the font size is 18px. */ - margin-top: -11px; - content: ""; - /* The logo has a white background and will be display above the text. */ - background: url("../../img/logos/ocha-logo-sprite.png") 0 -57px no-repeat; - background: rgba(0, 0, 0, 0) url("../../img/logos/ocha-logo-sprite.svg") 0 -57px no-repeat; -} -.rw-dsr ul { - padding-left: 18px; -} -.rw-dsr li { - margin-bottom: 16px; - line-height: 1.5; -} -.rw-dsr figure { - display: none; - max-width: 540px; - margin: 0; -} -.rw-dsr figure img { - display: block; - width: 100%; -} -.rw-dsr figure figcaption { - margin-top: 4px; - font-size: 15px; - font-style: italic; -} -.rw-dsr .view-more { - margin-top: 12px; -} -.rw-dsr .view-more a { - margin-right: 0; - padding-right: 0; - text-decoration: none; - font-weight: bold; - font-style: normal; -} -.rw-dsr .rw-dsr__title + .view-more, -.rw-dsr .rw-dsr__title + .view-more a { - margin-top: 0; - text-align: left; -} - -@media screen and (min-width: 768px) { - .rw-dsr { - display: flex; - flex-wrap: wrap; - align-items: start; - justify-content: space-between; - } - .rw-dsr > .rw-dsr__title, - .rw-dsr > footer { - width: 100%; - } - .rw-dsr--with-illustration > ul, - .rw-dsr--with-illustration > figure { - display: block; - width: 48%; - /* Ensure the top of the highlights is aligned with the illustration. - * - * @todo currently the top margin of the highlights (ul) comes from the - * browser user agent styling (ex: margin-block-start: 1em;). Setting it - * to 0 here means the spacing between the title section and the highlights - * differs when there is an illustration or not which is not consistent. - * So it would be better to have a fixed margin (0 or something else) - * regardless of the presence of the illustration. This margin should be - * consistent with the other sections on the page the DSR one is displayed - * for a more homogeneous experience. See RW-55. */ - margin-top: 0; - } -} - -/* Ugly hack for IE8 to hide the text due bad handling of z-index. */ -@media \0screen { - .rw-dsr .rw-dsr__title span.rw-dsr__ocha { - overflow: hidden; - margin-top: -4px; - padding-left: 80px; - vertical-align: middle; - white-space: nowrap; - } -} diff --git a/html/themes/custom/common_design_subtheme/components/rw-moderation/rw-moderation.css b/html/themes/custom/common_design_subtheme/components/rw-moderation/rw-moderation.css index da4f10add..7bd28d423 100644 --- a/html/themes/custom/common_design_subtheme/components/rw-moderation/rw-moderation.css +++ b/html/themes/custom/common_design_subtheme/components/rw-moderation/rw-moderation.css @@ -27,7 +27,7 @@ } .rw-moderation-filters .rw-moderation-filter-status .form-checkboxes { display: grid; - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); grid-gap: 0 24px; margin-bottom: -12px; } diff --git a/html/themes/custom/common_design_subtheme/components/rw-people/rw-people.css b/html/themes/custom/common_design_subtheme/components/rw-people/rw-people.css index 34a049149..cad2fa7ed 100644 --- a/html/themes/custom/common_design_subtheme/components/rw-people/rw-people.css +++ b/html/themes/custom/common_design_subtheme/components/rw-people/rw-people.css @@ -8,7 +8,8 @@ } .reliefweb-users-form .form-item-job-rights label:after, -.reliefweb-users-form .form-item-training-rights label:after { +.reliefweb-users-form .form-item-training-rights label:after, +.reliefweb-users-form .form-item-report-rights label:after { display: inline-block; width: 12px; height: 12px; @@ -18,19 +19,23 @@ border-radius: 4px; } .reliefweb-users-form .form-item-job-rights input[value="unverified"] + label:after, -.reliefweb-users-form .form-item-training-rights input[value="unverified"] + label:after { +.reliefweb-users-form .form-item-training-rights input[value="unverified"] + label:after, +.reliefweb-users-form .form-item-report-rights input[value="unverified"] + label:after { background: #f49e2c; } .reliefweb-users-form .form-item-job-rights input[value="blocked"] + label:after, -.reliefweb-users-form .form-item-training-rights input[value="blocked"] + label:after { +.reliefweb-users-form .form-item-training-rights input[value="blocked"] + label:after, +.reliefweb-users-form .form-item-report-rights input[value="blocked"] + label:after { background: #da190b; } .reliefweb-users-form .form-item-job-rights input[value="allowed"] + label:after, -.reliefweb-users-form .form-item-training-rights input[value="allowed"] + label:after { +.reliefweb-users-form .form-item-training-rights input[value="allowed"] + label:after, +.reliefweb-users-form .form-item-report-rights input[value="allowed"] + label:after { background: #076d96; } .reliefweb-users-form .form-item-job-rights input[value="trusted"] + label:after, -.reliefweb-users-form .form-item-training-rights input[value="trusted"] + label:after { +.reliefweb-users-form .form-item-training-rights input[value="trusted"] + label:after, +.reliefweb-users-form .form-item-report-rights input[value="trusted"] + label:after { background: #88bb09; } .reliefweb-users-form .form-actions.form-item { @@ -103,32 +108,26 @@ .users-list li + li { border-top: 1px dotted var(--cd-reliefweb-brand-grey--light); } -.users-list li[data-job]:before { + +.posting-rights--wrapper { + display: flex; + gap: 6px; +} + +.posting-rights--wrapper .posting-rights { display: inline-block; - float: right; width: 12px; height: 12px; - margin: 4px 16px 0 0; - content: ""; border-radius: 4px; background: var(--cd-reliefweb-orange); - box-shadow: 16px 0 var(--cd-reliefweb-orange); + color: rgba(0,0,0,0); } -.users-list li[data-job="1"]:before { +.posting-rights--wrapper .posting-rights[data-posting-right="1"] { background: var(--cd-reliefweb-red); } -.users-list li[data-job="2"]:before { +.posting-rights--wrapper .posting-rights[data-posting-right="2"] { background: var(--cd-reliefweb-blue); } -.users-list li[data-job="3"]:before { +.posting-rights--wrapper .posting-rights[data-posting-right="3"] { background: var(--cd-reliefweb-green); } -.users-list li[data-training="1"]:before { - box-shadow: 16px 0 var(--cd-reliefweb-red); -} -.users-list li[data-training="2"]:before { - box-shadow: 16px 0 var(--cd-reliefweb-blue); -} -.users-list li[data-training="3"]:before { - box-shadow: 16px 0 var(--cd-reliefweb-green); -} diff --git a/html/themes/custom/common_design_subtheme/components/rw-user/rw-user.css b/html/themes/custom/common_design_subtheme/components/rw-user/rw-user.css index 573570694..67dbf4496 100644 --- a/html/themes/custom/common_design_subtheme/components/rw-user/rw-user.css +++ b/html/themes/custom/common_design_subtheme/components/rw-user/rw-user.css @@ -52,6 +52,7 @@ } .rw-user-dashboard li + li { margin-top: 32px; + break-inside: avoid; } [dir="ltr"] .rw-user-dashboard li { padding-right: 32px; diff --git a/html/themes/custom/common_design_subtheme/templates/form/node-edit-form--report.html.twig b/html/themes/custom/common_design_subtheme/templates/form/node-edit-form--report.html.twig index eb7614dbe..677e8c843 100644 --- a/html/themes/custom/common_design_subtheme/templates/form/node-edit-form--report.html.twig +++ b/html/themes/custom/common_design_subtheme/templates/form/node-edit-form--report.html.twig @@ -18,32 +18,56 @@ {{ attach_library('common_design_subtheme/rw-form') }} {# Table of contents. #} -{{ - render_var({ +{% set field_sections = { + 'content': 'Content'|t, + 'files': 'Files'|t, + 'dates': 'Dates'|t, + 'source': 'Source'|t, + 'countries-disasters': 'Countries & Disasters'|t, + 'details': 'Details'|t, + 'headline': 'Headline'|t, +}%} + +{% set submission_sections = { + 'editorial-flags': 'Editorial flags'|t, + 'actions': 'Save'|t, + 'edit-revision-information': 'Revisions'|t, +}%} + +{% if 'contributor' in user.getroles(TRUE) %} + {% set field_sections = { + 'content': 'Content'|t, + 'files': 'Files'|t, + 'dates': 'Dates'|t, + 'source': 'Source'|t, + 'countries-disasters': 'Countries & Disasters'|t, + 'details': 'Details'|t, + }%} + + {% set submission_sections = { + 'editorial-flags': 'Notify'|t, + 'actions': 'Save'|t, + 'edit-revision-information': 'Revisions'|t, + }%} +{% endif %} + +{% set toc = { '#theme': 'reliefweb_entities_table_of_contents', '#sections': { 'fields': { 'title': 'Fields'|t, - 'sections': { - 'content': 'Content'|t, - 'files': 'Files'|t, - 'dates': 'Dates'|t, - 'source': 'Source'|t, - 'countries-disasters': 'Countries & Disasters'|t, - 'details': 'Details'|t, - 'headline': 'Headline'|t, - }, + 'sections': field_sections, }, 'submission': { 'title': 'Submission'|t, - 'sections': { - 'editorial-flags': 'Editorial flags'|t, - 'actions': 'Save'|t, - 'edit-revision-information': 'Revisions'|t, - }, + 'sections': submission_sections, }, }, - }) + } +%} + +{{ + render_var(toc) }} {# Form content. #} @@ -88,20 +112,29 @@ {{ form.field_theme }} -
    - {% trans %}Headline information{% endtrans %} - {{ form.field_headline }} - {{ form.field_headline_title }} - {{ form.field_headline_summary }} - {{ form.field_headline_image }} -
    +{% if 'contributor' not in user.getroles(TRUE) %} +
    + {% trans %}Headline information{% endtrans %} + {{ form.field_headline }} + {{ form.field_headline_title }} + {{ form.field_headline_summary }} + {{ form.field_headline_image }} +
    +{% endif %} -
    - {% trans %}Editorial flags{% endtrans %} - {{ form.field_bury }} - {{ form.field_feature }} - {{ form.field_notify }} -
    +{% if 'contributor' not in user.getroles(TRUE) %} +
    + {% trans %}Editorial flags{% endtrans %} + {{ form.field_bury }} + {{ form.field_feature }} + {{ form.field_notify }} +
    +{% else %} +
    + {% trans %}Notify{% endtrans %} + {{ form.field_notify }} +
    +{% endif %}
    {% trans %}Form submission and other actions{% endtrans %} diff --git a/html/themes/custom/common_design_subtheme/templates/rw-modules/reliefweb_dsr/reliefweb-digital-situation-report.html.twig b/html/themes/custom/common_design_subtheme/templates/rw-modules/reliefweb_dsr/reliefweb-digital-situation-report.html.twig deleted file mode 100644 index 98f1da5f1..000000000 --- a/html/themes/custom/common_design_subtheme/templates/rw-modules/reliefweb_dsr/reliefweb-digital-situation-report.html.twig +++ /dev/null @@ -1,6 +0,0 @@ -{% set title_attributes = title_attributes.addClass('cd-block-title') %} - -{{ attach_library('common_design/cd-block-title') }} -{{ attach_library('common_design_subtheme/rw-dsr') }} - -{% include '@reliefweb_dsr/reliefweb-digital-situation-report.html.twig' %} diff --git a/html/themes/custom/common_design_subtheme/templates/user/user.html.twig b/html/themes/custom/common_design_subtheme/templates/user/user.html.twig index 8027abb72..4eb893d1d 100644 --- a/html/themes/custom/common_design_subtheme/templates/user/user.html.twig +++ b/html/themes/custom/common_design_subtheme/templates/user/user.html.twig @@ -36,17 +36,27 @@ {% trans %}Manage your bookmarks{% endtrans %}

    {% trans %}Track content relevant to you on the site.{% endtrans %}

    + {% if user.hasPermission('create job content') %}
  • {% trans %}Post a job vacancy{% endtrans %}

    {% trans %}Advertise job, consulting and internships vacancies.{% endtrans %}

  • + {% endif %} + {% if user.hasPermission('create training content') %}
  • {% trans %}Post a training program{% endtrans %}

    {% trans %}Advertise training programs for the humanitarian community.{% endtrans %}

  • + {% endif %} + {% if user.hasPermission('create report content') %} +
  • + {% trans %}Post a report{% endtrans %} +

    {% trans %}Post a report for the humanitarian community.{% endtrans %}

    +
  • + {% endif %}
  • {% trans %}View your posts{% endtrans %} -

    {% trans %}Manage the Job vacancies or Training programs you posted.{% endtrans %}

    +

    {% trans %}Manage the Job vacancies, Training programs or Reports you posted.{% endtrans %}

  • diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index d38cc167f..5261cc294 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -22,7 +22,7 @@ services: MEMCACHE_MAX_MEMORY: 64 mysql: - image: public.ecr.aws/unocha/mysql:10.11 + image: public.ecr.aws/unocha/mysql:11 hostname: rwint-test-mysql container_name: rwint-test-mysql environment: