diff --git a/README.md b/README.md index 11890913c2..1edf9ff9e4 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ To access the Drupal Portal to make any additional configurations, you will need Log out as root, and log in with your own user account. -**Note:** You will need to repeat these steps any time you re-load the database from a backup. +**Note:** You will need to repeat these steps any time you re-load the database from a backup. ### Automated tests setup (cypress) @@ -183,12 +183,12 @@ We use [Cypress](cypress.io). Note that we use only the Cypress App, _not_ Cypre * Tests run in the `cypress` Docker container. * The tests themselves are in the `automated_tests/e2e-cypress` directory. -* Twig debugging will break some of the tests! Turn it off before running tests to get the most accurate results. +* Twig debugging will break some of the tests! Turn it off before running tests to get the most accurate results. ## Minimal setup for headless tests -1. Supply Drupal *credentials* for the automated tests: Edit the file `env.local.cypress`. Supply a valid Drupal user name and password for `cypressCmsUser` and `cypressCmsPass`. +1. Supply Drupal *credentials* for the automated tests: Edit the file `env.local.cypress`. Supply a valid Drupal user name and password for `cypressCmsUser` and `cypressCmsPass`. 2. Run `docker compose up` to (re-)create the cypress container with the new environment variables. @@ -196,7 +196,7 @@ We use [Cypress](cypress.io). Note that we use only the Cypress App, _not_ Cypre You can run `npx cypress run --spec cypress/e2e` to run the entire test suite, or specify a smaller subset like `cypress/e2e/functional`. - The **Report** will be written to automated_tests/e2e-cypress/cypress/reports/html/index.html and you can open it in your web browser by navigating to that file and opening it. Cypress will report that it wrote the tests to /app/e2e-cypress/cypress/reports/html/index.html, which is the location of that file in the volume mounted to the cypress docker container. + The **Report** will be written to automated_tests/e2e-cypress/cypress/reports/html/index.html and you can open it in your web browser by navigating to that file and opening it. Cypress will report that it wrote the tests to /app/e2e-cypress/cypress/reports/html/index.html, which is the location of that file in the volume mounted to the cypress docker container. Note: The first time you run bin/init, it will create the `env.local.cypress` file by copying `env.default.cypress`. The default `cypressBaseUrl` in that file should be correct for running tests against your local dev site. @@ -232,7 +232,7 @@ This assumes you're using homebrew. The "network client" you're enabling this for is the virtual machine running in your cypress container. -4. Reboot your computer. (You need to reboot once after installing XQuartz. Thereafter, when you change your XQuartz settings you need to restart XQuartz, but not reboot.) +4. Reboot your computer. (You need to reboot once after installing XQuartz. Thereafter, when you change your XQuartz settings you need to restart XQuartz, but not reboot.) Proceed to [Allow cypress to open an X window](#allow-cypress-to-open-an-x-window) @@ -320,6 +320,22 @@ If you make any changes to the `scss` or `js` files, make sure to check for lint [back to top](#usagov-2021) +## Checking PHP Code style and syntax errors + +PHPCodesniffer and the parallel linting tools should be installed automatically on a local environment via `composer install`. PHPCodeSniffer is used to ensure new code follows Drupal's coding standard. The parallel linter will check for PHP syntax errors. If they detect any errors, they must be fixed before a PR of changes can be accepted. + +The following composer scripts are aliases for running these tools. + +* Check for code style errors across all project files. Must have zero errors: + `./bin/composer phpcs-errors`: +* Check for code style errors and warnings across all project files. + `./bin/composer phpcs-strict` +* Check for code style errors in current branch. Must have zero errors: + `./bin/composer phpcs-changes` +* Check for code style errors and warnings in current branch. + `./bin/composer phpcs-changes-strict` +* Check for PHP lint errors + `./bin/composer php-lint` ## Project Restart/Reset Sometimes, Docker problems arise after an upgrade and a more complete restart is needed. After closing down and destroying the existing containers, networks, and volumes the procedure is the same as the full project setup. diff --git a/bin/changed-files b/bin/changed-files new file mode 100755 index 0000000000..5c39395093 --- /dev/null +++ b/bin/changed-files @@ -0,0 +1,9 @@ +#!/bin/bash + +CURRENT_BRANCH=`git branch --show-current` +# Get the changes between current branch and dev +CHANGED=`git diff --name-only --diff-filter=ARM dev $CURRENT_BRANCH` + +if [ "$CURRENT_BRANCH" != "dev" ]; then + echo $CHANGED; +fi diff --git a/bin/composer-reqs b/bin/composer-reqs new file mode 100755 index 0000000000..aa4fc8c6a8 --- /dev/null +++ b/bin/composer-reqs @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +docker compose run \ + --rm \ + --no-deps \ + --workdir /var/www \ + composer \ + composer "$@" diff --git a/bin/deploy/git-annotation-parser.sh b/bin/deploy/git-annotation-parser.sh index 5f8b25f382..17c5778827 100755 --- a/bin/deploy/git-annotation-parser.sh +++ b/bin/deploy/git-annotation-parser.sh @@ -21,16 +21,13 @@ else exit 1 fi -# just testing? -if [ x$1 == x"--dryrun" ]; then - export echo=echo - shift -fi - -SPACE=${1:-please-provide-space-as-first-argument} -SSPACE=$(echo "$SPACE" | tr '[:upper:]' '[:lower:]') ## lowercase, so tags are properly formatted -#assertCurSpace "$SPACE" ### <-- no need to assert that we're actually in $SPACE, because we're not doing anything w/ CF - just git +SPACE=$1 +if [ -z "$SPACE" ]; then + echo "First argument must be a valid CF space name" + exit 1 +fi; shift +SSPACE=$(echo "$SPACE" | tr '[:upper:]' '[:lower:]') ## lowercase, so tags are properly formatted # 1. Find the name of the latest annotated git tag matching our production post-deployment tag format # 2. Query the content field of the reference attached to the tag, and make sure it contains correctly formated build number and digest hashes diff --git a/bin/src/newrelic.sh b/bin/src/newrelic.sh index 4f7ed5a00f..28d0dac447 100755 --- a/bin/src/newrelic.sh +++ b/bin/src/newrelic.sh @@ -29,7 +29,7 @@ if [ "$(uname -m)" != 'aarch64' ]; then -e 's/;\?newrelic.loglevel =.*/newrelic.loglevel = "info"/' \ -e 's/;\?newrelic.enabled =.*/newrelic.enabled = false/' \ -e 's/;\?newrelic.error_collector.record_database_errors =.*/newrelic.error_collector.record_database_errors = true/' \ - /etc/php81/conf.d/newrelic.ini + /etc/php83/conf.d/newrelic.ini NR_LATEST_VERSION="$(curl -sS https://download.newrelic.com/php_agent/release/ | sed -n 's/.*>\(.*linux\-musl\).tar.gz<.*/\1/p')" export NR_LATEST_VERSION diff --git a/composer.json b/composer.json index c691cb717f..e16e6a0886 100644 --- a/composer.json +++ b/composer.json @@ -164,5 +164,29 @@ "drupal/ckeditor_templates" ] } + }, + "require-dev": { + "drupal/coder": "^8.3", + "php-parallel-lint/php-parallel-lint": "^1.4" + }, + "scripts": { + "changed-files": [ + "bin/changed-files" + ], + "phpcs-errors": [ + "vendor/bin/phpcs -n --standard=.phpcs.xml.dist" + ], + "phpcs-strict": [ + "vendor/bin/phpcs --standard=.phpcs.xml.dist" + ], + "phpcs-changes": [ + "vendor/bin/phpcs -n --standard=.phpcs.xml.dist `bin/changed-files`" + ], + "phpcs-changes-strict": [ + "vendor/bin/phpcs --standard=.phpcs.xml.dist `bin/changed-files`" + ], + "php-lint": [ + "vendor/bin/parallel-lint -e php,module,inc,install,test,profile,theme ./web/modules/custom ./web/themes/custom" + ] } } diff --git a/composer.lock b/composer.lock index 73db06e774..8cf213b5e2 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": "93324a928ad5a44abbc3c4ab0c967cd6", + "content-hash": "750e25aca453a5fccca5ae752bbcd6e3", "packages": [ { "name": "asm89/stack-cors", @@ -11647,7 +11647,448 @@ "time": "2023-04-15T19:07:00+00:00" } ], - "packages-dev": [], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2023-01-05T11:28:13+00:00" + }, + { + "name": "drupal/coder", + "version": "8.3.26", + "source": { + "type": "git", + "url": "https://github.com/pfrenssen/coder.git", + "reference": "fd98546ce3373aa7767240901eda47963ce64c82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pfrenssen/coder/zipball/fd98546ce3373aa7767240901eda47963ce64c82", + "reference": "fd98546ce3373aa7767240901eda47963ce64c82", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1 || ^1.0.0", + "ext-mbstring": "*", + "php": ">=7.2", + "sirbrillig/phpcs-variable-analysis": "^2.11.7", + "slevomat/coding-standard": "^8.11", + "squizlabs/php_codesniffer": "^3.9.1", + "symfony/yaml": ">=3.4.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.7.12", + "phpunit/phpunit": "^8.0" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "Drupal\\": "coder_sniffer/Drupal/", + "DrupalPractice\\": "coder_sniffer/DrupalPractice/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Coder is a library to review Drupal code.", + "homepage": "https://www.drupal.org/project/coder", + "keywords": [ + "code review", + "phpcs", + "standards" + ], + "support": { + "issues": "https://www.drupal.org/project/issues/coder", + "source": "https://www.drupal.org/project/coder" + }, + "time": "2024-11-28T23:14:29+00:00" + }, + { + "name": "php-parallel-lint/php-parallel-lint", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git", + "reference": "6db563514f27e19595a19f45a4bf757b6401194e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/6db563514f27e19595a19f45a4bf757b6401194e", + "reference": "6db563514f27e19595a19f45a4bf757b6401194e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.3.0" + }, + "replace": { + "grogy/php-parallel-lint": "*", + "jakub-onderka/php-parallel-lint": "*" + }, + "require-dev": { + "nette/tester": "^1.3 || ^2.0", + "php-parallel-lint/php-console-highlighter": "0.* || ^1.0", + "squizlabs/php_codesniffer": "^3.6" + }, + "suggest": { + "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet" + }, + "bin": [ + "parallel-lint" + ], + "type": "library", + "autoload": { + "classmap": [ + "./src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "ahoj@jakubonderka.cz" + } + ], + "description": "This tool checks the syntax of PHP files about 20x faster than serial check.", + "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint", + "keywords": [ + "lint", + "static analysis" + ], + "support": { + "issues": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/issues", + "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.4.0" + }, + "time": "2024-03-27T12:14:49+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.33.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0" + }, + "time": "2024-10-13T11:25:22+00:00" + }, + { + "name": "sirbrillig/phpcs-variable-analysis", + "version": "v2.11.21", + "source": { + "type": "git", + "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", + "reference": "eb2b351927098c24860daa7484e290d3eed693be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/eb2b351927098c24860daa7484e290d3eed693be", + "reference": "eb2b351927098c24860daa7484e290d3eed693be", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "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 || ^10.5.32 || ^11.3.3", + "sirbrillig/phpcs-import-detection": "^1.1", + "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "VariableAnalysis\\": "VariableAnalysis/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Sam Graham", + "email": "php-codesniffer-variableanalysis@illusori.co.uk" + }, + { + "name": "Payton Swick", + "email": "payton@foolord.com" + } + ], + "description": "A PHPCS sniff to detect problems with variables.", + "keywords": [ + "phpcs", + "static analysis" + ], + "support": { + "issues": "https://github.com/sirbrillig/phpcs-variable-analysis/issues", + "source": "https://github.com/sirbrillig/phpcs-variable-analysis", + "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" + }, + "time": "2024-12-02T16:37:49+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "8.15.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "7d1d957421618a3803b593ec31ace470177d7817" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/7d1d957421618a3803b593ec31ace470177d7817", + "reference": "7d1d957421618a3803b593ec31ace470177d7817", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.23.1", + "squizlabs/php_codesniffer": "^3.9.0" + }, + "require-dev": { + "phing/phing": "2.17.4", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.10.60", + "phpstan/phpstan-deprecation-rules": "1.1.4", + "phpstan/phpstan-phpunit": "1.3.16", + "phpstan/phpstan-strict-rules": "1.5.2", + "phpunit/phpunit": "8.5.21|9.6.8|10.5.11" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "keywords": [ + "dev", + "phpcs" + ], + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/8.15.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2024-03-09T15:20:58+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.11.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/19473c30efe4f7b3cd42522d0b2e6e7f243c6f87", + "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-11-16T12:02:36+00:00" + } + ], "aliases": [], "minimum-stability": "dev", "stability-flags": { @@ -11661,7 +12102,7 @@ }, "prefer-stable": true, "prefer-lowest": false, - "platform": {}, - "platform-dev": {}, - "plugin-api-version": "2.6.0" + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.2.0" } diff --git a/config/sync/menu_breadcrumb.settings.yml b/config/sync/menu_breadcrumb.settings.yml index f9b4752401..71042fa0a8 100644 --- a/config/sync/menu_breadcrumb.settings.yml +++ b/config/sync/menu_breadcrumb.settings.yml @@ -12,13 +12,18 @@ add_home: true front_title: 0 exclude_empty_url: false exclude_disabled_menu_items: false -derived_active_trail: false +derived_active_trail: true menu_breadcrumb_menus: main: enabled: 0 weight: -10 taxattach: 0 langhandle: 0 + account: + enabled: 0 + weight: 0 + taxattach: 0 + langhandle: 0 admin: enabled: 0 weight: 0 @@ -44,12 +49,27 @@ menu_breadcrumb_menus: weight: 0 taxattach: 0 langhandle: 1 + primary-footer: + enabled: 0 + weight: 0 + taxattach: 0 + langhandle: 0 + primary-footer-spanish: + enabled: 0 + weight: 0 + taxattach: 0 + langhandle: 0 tools: enabled: 0 weight: 0 taxattach: 0 langhandle: 0 - account: + top-navigation: + enabled: 0 + weight: 0 + taxattach: 0 + langhandle: 0 + top-navigation-es: enabled: 0 weight: 0 taxattach: 0 diff --git a/scripts/tome-static.sh b/scripts/tome-static.sh index a0c202c578..4b4f4dd61d 100755 --- a/scripts/tome-static.sh +++ b/scripts/tome-static.sh @@ -32,5 +32,13 @@ mkdir -p /var/www/html # time drush tome:static -y --uri=$URI --process-count=5 --path-count=1 time drush tome:static -y --uri=$URI --process-count=$TOME_PROCESS_COUNT --path-count=10 TOME_SUCCESS=$? + echo "Finished Static Site Generation : "$(date) + +if [ "$TOME_SUCCESS" -eq 0 ]; then + # path is relative to drupal's web dir + time drush usapubcsv --uri=$URI modules/custom/usagov_ssg_postprocessing/files/published-pages.csv + echo "Exported published-pages.csv" +fi + exit $TOME_SUCCESS diff --git a/web/modules/custom/usa_twig_vars/src/TaxonomyDatalayerBuilder.php b/web/modules/custom/usa_twig_vars/src/TaxonomyDatalayerBuilder.php index cce076f5f1..264cb50d86 100644 --- a/web/modules/custom/usa_twig_vars/src/TaxonomyDatalayerBuilder.php +++ b/web/modules/custom/usa_twig_vars/src/TaxonomyDatalayerBuilder.php @@ -4,7 +4,7 @@ use Drupal\Core\Breadcrumb\BreadcrumbManager; use Drupal\Core\Entity\EntityMalformedException; -use Drupal\Core\Routing\CurrentRouteMatch; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\node\Entity\Node; /** @@ -48,7 +48,7 @@ class TaxonomyDatalayerBuilder { private string $isFront; public function __construct( - private CurrentRouteMatch $routeMatch, + private RouteMatchInterface $routeMatch, private BreadcrumbManager $breadcrumbManager, public Node $node, bool $isFront, @@ -151,7 +151,8 @@ public function fromBreadcrumb(): array { break; } - $url = $crumb->getUrl()->toString() ?: $this->node->toUrl()->toString(); + $url = $crumb->getUrl()->setOption('language', $this->node->language())->toString() + ?: $this->node->toUrl()->setOption('language', $this->node->language())->toString(); if ($url === '/es') { $url = self::HOME_URL_ES; diff --git a/web/modules/custom/usa_twig_vars/usa_twig_vars.module b/web/modules/custom/usa_twig_vars/usa_twig_vars.module index 35c7fbbb78..e6f71cb75b 100644 --- a/web/modules/custom/usa_twig_vars/usa_twig_vars.module +++ b/web/modules/custom/usa_twig_vars/usa_twig_vars.module @@ -7,7 +7,6 @@ use Drupal\Core\Render\Markup; use Drupal\Core\Url; -use Drupal\block\Entity\Block; use Drupal\node\Entity\Node; use Drupal\node\NodeInterface; use Drupal\path_alias\PathAliasInterface; diff --git a/web/modules/custom/usagov_benefit_category_search/src/EventSubscriber/DatalayerAlterSubscriber.php b/web/modules/custom/usagov_benefit_category_search/src/EventSubscriber/DatalayerAlterSubscriber.php index 7c7dddfecc..8c7555faa0 100644 --- a/web/modules/custom/usagov_benefit_category_search/src/EventSubscriber/DatalayerAlterSubscriber.php +++ b/web/modules/custom/usagov_benefit_category_search/src/EventSubscriber/DatalayerAlterSubscriber.php @@ -25,7 +25,14 @@ public static function getSubscribedEvents() { * Adds category information to the datalayer. */ public function onDatalayerAlter(DatalayerAlterEvent $event): void { - $node = \Drupal::routeMatch()->getParameter('node'); + + // This listener is only interested in nodes, which have numeric IDs. + // Taxonomy terms IDs are prefixed with "t_" + if (!isset($event->datalayer['nodeID']) || !is_numeric($event->datalayer['nodeID'])) { + return; + } + + $node = Node::load($event->datalayer['nodeID']); $event->datalayer['hasBenefitCategory'] = FALSE; if ($node instanceof Node && $node->getType() === 'basic_page') { diff --git a/web/modules/custom/usagov_ssg_postprocessing/src/Data/PublishedPagesRow.php b/web/modules/custom/usagov_ssg_postprocessing/src/Data/PublishedPagesRow.php new file mode 100644 index 0000000000..7ff92993bf --- /dev/null +++ b/web/modules/custom/usagov_ssg_postprocessing/src/Data/PublishedPagesRow.php @@ -0,0 +1,232 @@ +getId()) { + 'es' => "USAGov Español", + 'en' => "USAGov English", + default => "USAGov English", + }; + } + + private static function getHierarchy(array $data): int { + $texts = array_filter($data, fn($key) => str_starts_with($key, 'Taxonomy_URL_'), ARRAY_FILTER_USE_KEY); + return count(array_unique($texts)); + } + + public function toArray(): array { + $array = [ + $this->hierarchy, + $this->pageType, + $this->pageSubType ?? '', + $this->contentType, + $this->friendlyURL, + $this->pageID, + $this->pageTitle, + $this->fullURL, + $this->taxonomyText1, + $this->taxonomyText2, + $this->taxonomyText3, + $this->taxonomyText4, + $this->taxonomyText5, + $this->taxonomyText6, + $this->taxonomyURL1, + $this->taxonomyURL2, + $this->taxonomyURL3, + $this->taxonomyURL4, + $this->taxonomyURL5, + $this->taxonomyURL6, + $this->isHomePage, + $this->toggleURL, + $this->hasBenefitCategory, + ]; + + // Keeps existing behavior of only including these columns if they have something + if ($this->hasBenefitCategory !== "") { + $array[] = $this->benefitCategories; + } + return $array; + } + + public static function datalayerForNode(array $data, Node $node, string $baseURL): self { + $title = $node->getTitle(); + + // Federal Agency nodes tack on the acronym because the original implementation + // pulled the title from the HTML + if ($node->getType() === 'directory_record') { + $acronym = $node->get('field_acronym')->getValue(); + if ($acronym) { + $title = sprintf("%s (%s)", trim($title), trim($acronym[0]['value'])); + } + } + + if ($data['homepageTest'] === 'homepage' && $data['language'] === 'en') { + $friendlyURL = TaxonomyDatalayerBuilder::HOME_URL_EN; + $fullURL = $baseURL . TaxonomyDatalayerBuilder::HOME_URL_EN; + } + elseif ($data['homepageTest'] === 'homepage' && $data['language'] === 'es') { + $friendlyURL = TaxonomyDatalayerBuilder::HOME_URL_ES; + $fullURL = $baseURL . TaxonomyDatalayerBuilder::HOME_URL_ES; + } + else { + $friendlyURL = $node->toUrl('canonical', + options: ['language' => $node->language()] + )->toString(); + $fullURL = $node->toUrl( + options: ['absolute' => TRUE, 'language' => $node->language()] + )->toString(); + } + + $toggleURL = NULL; + if (isset($node->field_language_toggle[0]) && $node->field_language_toggle[0]?->target_id) { + if ($data['homepageTest'] === 'homepage' && $data['language'] === 'en') { + $toggleURL = $baseURL . TaxonomyDatalayerBuilder::HOME_URL_ES; + } + elseif ($data['homepageTest'] === 'homepage' && $data['language'] === 'es') { + $toggleURL = $baseURL . TaxonomyDatalayerBuilder::HOME_URL_EN; + } + else { + $toggleNode = \Drupal::entityTypeManager() + ->getStorage('node') + ->load($node->field_language_toggle[0]->target_id); + $url = Url::fromRoute( + 'entity.node.canonical', + ['node' => $toggleNode->id()], + ['absolute' => TRUE, 'language' => $toggleNode->language()] + ); + $toggleURL = $url->toString(); + } + } + + return new self( + hierarchy: self::getHierarchy($data), + pageType: $data['Page_Type'], + pageSubType: $data['basicPagesubType'], + contentType: $data['contentType'], + friendlyURL: $friendlyURL, + pageID: $data['nodeID'], + pageTitle: $title, + fullURL: $fullURL, + taxonomyText1: self::getTaxLevel1($node->language()), + taxonomyText2: $data['Taxonomy_Text_2'], + taxonomyText3: $data['Taxonomy_Text_3'], + taxonomyText4: $data['Taxonomy_Text_4'], + taxonomyText5: $data['Taxonomy_Text_5'], + taxonomyText6: $data['Taxonomy_Text_6'], + taxonomyURL1: $data['Taxonomy_URL_1'], + taxonomyURL2: $data['Taxonomy_URL_2'], + taxonomyURL3: $data['Taxonomy_URL_3'], + taxonomyURL4: $data['Taxonomy_URL_4'], + taxonomyURL5: $data['Taxonomy_URL_5'], + taxonomyURL6: $data['Taxonomy_URL_6'], + isHomePage: $data['homepageTest'], + toggleURL: $toggleURL ?? 'None', + hasBenefitCategory: $data['hasBenefitCategory'] ? '1' : '', + benefitCategories: $data['benefitCategories'] ?? '', + ); + } + + public static function datalayerForWizard(array $data, Term $term, string $baseURL): self { + if ($heading = $term->get('field_heading')->getValue()) { + $title = $heading[0]['value']; + } + else { + $title = $term->getName(); + } + + $friendlyURL = $term->toUrl('canonical', + options: ['language' => $term->language()] + )->toString(); + $fullURL = $term->toUrl( + options: ['absolute' => TRUE, 'language' => $term->language()] + )->toString(); + + $toggleURL = 'None'; + if (isset($term->field_language_toggle[0])) { + $toggleTerm = \Drupal::entityTypeManager() + ->getStorage('taxonomy_term') + ->load($term->field_language_toggle[0]->target_id); + $url = Url::fromRoute( + 'entity.taxonomy_term.canonical', + ['taxonomy_term' => $toggleTerm->id()], + ['absolute' => TRUE, 'language' => $toggleTerm->language()] + ); + $toggleURL = $url->toString(); + } + return new self( + hierarchy: self::getHierarchy($data), + pageType: $data['Page_Type'], + pageSubType: $data['basicPagesubType'], + contentType: $data['contentType'], + friendlyURL: $friendlyURL, + pageID: 't_' . $data['taxonomyID'], + pageTitle: $title, + fullURL: $fullURL, + taxonomyText1: self::getTaxLevel1($term->language()), + taxonomyText2: $data['Taxonomy_Text_2'], + taxonomyText3: $data['Taxonomy_Text_3'], + taxonomyText4: $data['Taxonomy_Text_4'], + taxonomyText5: $data['Taxonomy_Text_5'], + taxonomyText6: $data['Taxonomy_Text_6'], + taxonomyURL1: $data['Taxonomy_URL_1'], + taxonomyURL2: $data['Taxonomy_URL_2'], + taxonomyURL3: $data['Taxonomy_URL_3'], + taxonomyURL4: $data['Taxonomy_URL_4'], + taxonomyURL5: $data['Taxonomy_URL_5'], + taxonomyURL6: $data['Taxonomy_URL_6'], + isHomePage: $data['homepageTest'], + toggleURL: $toggleURL, + hasBenefitCategory: $data['hasBenefitCategory'] ? '1' : '', + benefitCategories: $data['benefitCategories'] ?? '', + ); + } + +} diff --git a/web/modules/custom/usagov_ssg_postprocessing/src/Drush/Commands/PublishedPagesCommands.php b/web/modules/custom/usagov_ssg_postprocessing/src/Drush/Commands/PublishedPagesCommands.php new file mode 100644 index 0000000000..66cad179f8 --- /dev/null +++ b/web/modules/custom/usagov_ssg_postprocessing/src/Drush/Commands/PublishedPagesCommands.php @@ -0,0 +1,238 @@ +get('entity_type.manager'), + configFactory: $container->get('config.factory'), + dispatcher: $container->get('event_dispatcher'), + request: $container->get('request_stack')->getCurrentRequest(), + router: $container->get('router.no_access_checks'), + pathAliasManager: $container->get('path_alias.manager'), + breadcrumb: $container->get('breadcrumb'), + languageManager: $container->get('language_manager') + ); + } + + /** + * Export published pages CSV + */ + #[CLI\Command(name: 'usagov:published-csv', aliases: ['usapubcsv'])] + #[CLI\Argument(name: 'outfile', description: 'Path for output file')] + #[CLI\Usage( + name: 'usagov_ssg_postprocessing:published-csv', + description: 'Usage description') + ] + public function publishedCsv($outfile) { + $this->output()->writeln('Publishing CSV to ' . $outfile . ''); + + $out = fopen($outfile, 'w'); + + if (!str_starts_with($outfile, '/')) { + $this->logger()->warning('Relative path given, current working dir: {dir}', ['dir' => getcwd()]); + + } + if (FALSE === $out) { + $this->output()->writeln("Can not write to destination file."); + exit(1); + } + fputcsv($out, $this->csvHeader); + // Render published pages to output file + $this->saveNodeRows($out); + $this->saveWizardRows($out); + fclose($out); + } + + protected function saveNodeRows($out): void { + $nids = $this->entityTypeManager + ->getStorage('node') + ->getQuery() + ->condition('type', [ + 'basic_page', + 'bears_life_event', + 'directory_record', + 'federal_directory_index', + 'state_directory_record', + 'wizard_step', + ], 'IN') + ->condition('status', 1) //published + ->sort('nid', 'ASC') + ->accessCheck(TRUE) + ->sort('nid') + ->execute(); + + foreach ($nids as $nid) { + $node = $this->entityTypeManager->getStorage('node')->load($nid); + $row = $this->getNodeRow($node)->toArray(); + + $row = array_map(fn($col) => trim($col), $row); + fputcsv($out, $row); + + $origLanguage = $node->language(); + if ($languages = $node->getTranslationLanguages()) { + foreach ($languages as $lang) { + if ($lang->getId() !== $origLanguage->getId()) { + // export translated node + $trNode = $node->getTranslation($lang->getId()); + $trRow = $this->getNodeRow($trNode); + $fields = array_map(fn($field) => trim($field), $trRow->toArray()); + fputcsv($out, $fields); + } + } + } + } + } + + protected function saveWizardRows($out): void { + $tids = $this->entityTypeManager + ->getStorage('taxonomy_term') + ->getQuery() + ->condition('vid', 'wizard') + ->condition('status', 1) //published + ->sort('tid', 'ASC') + ->accessCheck(TRUE) + ->sort('tid') + ->execute(); + + foreach ($tids as $tid) { + $wizard = $this->entityTypeManager->getStorage('taxonomy_term')->load($tid); + $row = $this->getWizardRow($wizard); + fputcsv($out, $row->toArray()); + } + } + + protected function getNodeRow(Node $node): PublishedPagesRow { + $front_uri = $this->configFactory->get('system.site')->get('page.front'); + $alias = $this->pathAliasManager->getAliasByPath('/node/' . $node->id()); + + $isFront = ($alias === $front_uri); + + $pageType = usa_twig_vars_get_page_type($node); + + // The following is "dragons abound here" but Drupal does not make it possible + // to change the language for building breadcrumbs after a request has started. + $negotiatedProp = new \ReflectionProperty(get_class($this->languageManager), 'negotiatedLanguages'); + $value = $negotiatedProp->getValue($this->languageManager); + $value['language_content'] = $node->language(); + $negotiatedProp->setValue($this->languageManager, $value); + + // To get the right breadcrumb/active trail for this routeMatch, the menu_breadcrumb module + // must be configured to "Derive MenuActiveTrail from RouteMatch" + $datalayer = new TaxonomyDatalayerBuilder( + routeMatch: $this->getRouteMatchForNode($node), + breadcrumbManager: $this->breadcrumb, + node: $node, + isFront: $isFront, + basicPagesubType: $pageType ?? NULL, + ); + $data = $datalayer->build(); + + $data = $this->alterDatalayer($data); + + $baseURL = $this->request->getSchemeAndHttpHost(); + return PublishedPagesRow::datalayerForNode($data, $node, $baseURL); + } + + /** + * Get a valid routeMatch object for a node + * + * To get the same datalayer output, we need to set up a routeMatch for each + * entity we are exporting that the datalayer module can look up via the + * breadcrumb manager. + */ + private function getRouteMatchForNode(Node $node): RouteMatchInterface { + $route = $this->router->match('/node/' . $node->id()); + + return new RouteMatch( + route_name: $route['_route'], + route: $route['_route_object'], + parameters: ['node' => $node], + raw_parameters: ['node' => $node->id(), 'language' => $node->language()->getId()] + ); + } + + protected function getWizardRow(Term $wizard): PublishedPagesRow { + $builder = new WizardDataLayer($wizard, $this->entityTypeManager); + $data = $builder->getData([]); + + $baseURL = $this->request->getSchemeAndHttpHost(); + return PublishedPagesRow::datalayerForWizard($data, $wizard, $baseURL); + } + + private function alterDatalayer(array $data): array { + // Let other modules add to the datalayer payload. + $datalayerEvent = new DatalayerAlterEvent($data); + $this->dispatcher->dispatch($datalayerEvent, DatalayerAlterEvent::EVENT_NAME); + return $datalayerEvent->datalayer; + } + +} diff --git a/web/modules/custom/usagov_ssg_postprocessing/src/EventSubscriber/PublishedPagesSubscriber.php b/web/modules/custom/usagov_ssg_postprocessing/src/EventSubscriber/PublishedPagesSubscriber.php deleted file mode 100644 index 1818ed91d5..0000000000 --- a/web/modules/custom/usagov_ssg_postprocessing/src/EventSubscriber/PublishedPagesSubscriber.php +++ /dev/null @@ -1,259 +0,0 @@ -getHtml(); - - // LIBXML_SCHEMA_CREATE fixes a problem wherein DOMDocument would remove closing HTML - // tags within quoted text in a script element. See https://bugs.php.net/bug.php?id=74628 - $document = new \DOMDocument(); - @$document->loadHTML($html, LIBXML_SCHEMA_CREATE); - $xpath = new \DOMXPath($document); - - $csv_path = "modules/custom/usagov_ssg_postprocessing/files/published-pages.csv"; - $csv = []; - $fp = fopen($csv_path, 'c+'); - if ($fp != FALSE) { - flock($fp, LOCK_EX); - while (($line = fgetcsv($fp)) != FALSE) { - $csv[] = $line; - } - } - - // Set the pointer to the end of the array by default. - end($csv); - $pointer = (key($csv) == FALSE) ? 0 : key($csv) + 1; - - /** @var \DOMElement $node */ - foreach ($xpath->query('/html/head/script[contains(@id, "taxonomy-data")]') as $node) { - $script = $node->nodeValue; - $script = trim($script); - $script = ltrim($script, "dataLayer = "); - $script = rtrim($script, ";"); - $script = rtrim($script, ","); - // The following line prevents json_decode() from failing when there is an extra comma after the final element in an array. - $script = str_replace(",\n }", "\n }", $script); - $decoded = json_decode($script, TRUE); - $decoded = $decoded[0]; - if (empty($decoded)) { - print "WARNING: PublishedPagesSubscriber.php is skiping a page due to a bad dataLayer\n"; - continue; - } - - $url_replace = [ - "Taxonomy_URL_1", - "Taxonomy_URL_2", - "Taxonomy_URL_3", - "Taxonomy_URL_4", - "Taxonomy_URL_5", - "Taxonomy_URL_6" - ]; - - $header_replace = [ - "nodeID" => "Page ID", - "taxonomyID" => "Page ID", - "language" => "Taxonomy Level 1", - "Taxonomy_Text_2" => "Taxonomy Level 2", - "Taxonomy_Text_3" => "Taxonomy Level 3", - "Taxonomy_Text_4" => "Taxonomy Level 4", - "Taxonomy_Text_5" => "Taxonomy Level 5", - "Taxonomy_Text_6" => "Taxonomy Level 6", - "Taxonomy_URL_1" => "Taxonomy URL Level 1", - "Taxonomy_URL_2" => "Taxonomy URL Level 2", - "Taxonomy_URL_3" => "Taxonomy URL Level 3", - "Taxonomy_URL_4" => "Taxonomy URL Level 4", - "Taxonomy_URL_5" => "Taxonomy URL Level 5", - "Taxonomy_URL_6" => "Taxonomy URL Level 6", - "Page_Type" => "Page Type", - "basicPagesubType" => "Page Sub Type", - "contentType" => "Content Type", - - "homepageTest" => "Homepage?", - ]; - - $content_replace = [ - "en" => "USAGov English", - "es" => "USAGov Español", - ]; - - $order_map = [ - "Hierarchy Level", - "Page Type", - "Page Sub Type", - "Content Type", - "Friendly URL", - "Page ID", - "Page Title", - "Full URL", - "Taxonomy Level 1", - "Taxonomy Level 2", - "Taxonomy Level 3", - "Taxonomy Level 4", - "Taxonomy Level 5", - "Taxonomy Level 6", - "Taxonomy URL Level 1", - "Taxonomy URL Level 2", - "Taxonomy URL Level 3", - "Taxonomy URL Level 4", - "Taxonomy URL Level 5", - "Taxonomy URL Level 6", - "Homepage?", - "Toggle URL", - ]; - - // If the nodeID matches a line in the csv array, set the pointer to that element. - // TODO: this might be fragile. - if (!empty($csv)) { - $nodeIDElement = array_search("Page ID", $csv[0]); - $languageElement = array_search("Taxonomy Level 1", $csv[0]); - foreach ($csv as $key => $line) { - - if (!empty($line[$nodeIDElement]) && !empty($decoded["nodeID"]) && $line[$nodeIDElement] == $decoded["nodeID"]) { - if ($line[$languageElement] == $content_replace[$decoded["language"]]) { - $pointer = $key; - } - } - - if (!empty($line[$nodeIDElement]) && !empty($decoded["taxonomyID"]) && $line[$nodeIDElement] == 't_' . $decoded["taxonomyID"]) { - if ($line[$languageElement] == $content_replace[$decoded["language"]]) { - $pointer = $key; - } - } - } - } - - $host = \Drupal::request()->getSchemeAndHttpHost(); - - $title = $xpath->query('/html/head/title')->item(0)->nodeValue; - $title = (!empty($title)) ? str_replace(" | USAGov", "", $title) : "Not Found"; - - $decoded["Page Title"] = $title; - - $toggle_url = $xpath->query('/html/head/link[contains(@data-type, "altlang")]/@href')->item(0)->nodeValue; - $decoded["Toggle URL"] = ($toggle_url) ? $toggle_url : "None"; - - $hierarchy = 0; - $prev = ""; - foreach ($decoded as $key => $term) { - if (in_array($key, $url_replace)) { - if ($term != $prev) { - $hierarchy++; - $prev = $term; - } - } - } - $decoded["Hierarchy Level"] = $hierarchy; - - $url = ""; - foreach ($decoded as $name => $term) { - if ($name == "Taxonomy_Text_1") { - unset($decoded[$name]); - } - if ($name == "Taxonomy_URL_6") { - $url = $term; - } - if ($name == "language") { - $term = $content_replace[$term]; - } - foreach ($header_replace as $key => $item) { - if ($name == $key) { - if ($name == 'taxonomyID') { - $decoded[$item] = 't_' . $term; - } - else { - $decoded[$item] = $term; - } - unset($decoded[$name]); - } - } - } - // Tome can end up requesting existing URLs with the raw `/node/NID` path - // if a redirect to a node is set to the wrong language. It then proceeds - // which retrieves the wrong taxonomy info, which we should discard. - if (str_starts_with($url, '/node/') || str_starts_with($url, '/es/node/')) { - if ($fp != FALSE) { - fclose($fp); - } - return; - } - - // If this page is more than 5 levels deep in the taxonomy hierarchy, - // Then we may not be able to reconstruct its URL from the taxonomy URL. - // We can get reliably get it from the node. We could do this for all - // nodes, but that could negatively impact export performance. - if ($decoded['Page ID'] && $hierarchy > 5) { - $nid = $decoded['Page ID']; - if (!empty($nid)) { - if (substr($nid, 0, 2) === 't_') { - $tid = intval(substr($nid, 2)); - $termEntity = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->load($tid); - if (!empty($termEntity)) { - $url = $termEntity->toUrl()->toString(); - } - } - else { - $nodeEntity = \Drupal::entityTypeManager()->getStorage('node')->load($nid); - if (!empty($nodeEntity)) { - $url = $nodeEntity->toUrl()->toString(); - } - } - } - } - - $decoded["Friendly URL"] = (empty($url)) ? "/" : $url; - $decoded["Full URL"] = (empty($url)) ? $host . "/" : $host . $url; - - $orderedArray = array_merge(array_flip($order_map), $decoded); - - if (empty($csv)) { - foreach ($orderedArray as $name => $term) { - $csvheader[] = $name; - } - $csv[$pointer] = $csvheader; - $pointer++; - } - - foreach ($orderedArray as $name => $term) { - $csvline[] = $term; - } - - $csv[$pointer] = $csvline; - - if ($fp != FALSE) { - ftruncate($fp, 0); - rewind($fp); - foreach ($csv as $fields) { - fputcsv($fp, $fields); - } - fclose($fp); - } - } - } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() { - $events[TomeStaticEvents::MODIFY_HTML][] = ['modifyHtml']; - return $events; - } - -} diff --git a/web/modules/custom/usagov_ssg_postprocessing/usagov_ssg_postprocessing.routing.yml b/web/modules/custom/usagov_ssg_postprocessing/usagov_ssg_postprocessing.routing.yml index 55363e08b3..bbaeaa631f 100644 --- a/web/modules/custom/usagov_ssg_postprocessing/usagov_ssg_postprocessing.routing.yml +++ b/web/modules/custom/usagov_ssg_postprocessing/usagov_ssg_postprocessing.routing.yml @@ -15,21 +15,21 @@ usagov_ssg_postprocessing.toggle_tome: _permission: 'administer site configuration' usagov_ssg_postprocessing.content: - path: '/static-site-status' - defaults: - _controller: '\Drupal\usagov_ssg_postprocessing\Controller\SsgStatController::content' + path: '/static-site-status' + defaults: + _controller: '\Drupal\usagov_ssg_postprocessing\Controller\SsgStatController::content' _title: 'Tome Status' options: no_cache: 'TRUE' - requirements: - _permission: 'access administration pages' + requirements: + _permission: 'access administration pages' usagov_ssg_postprocessing.site_lag_test: - path: '/site-lag-test' - defaults: - _controller: '\Drupal\usagov_ssg_postprocessing\Controller\SsgStatController::siteLagTest' + path: '/site-lag-test' + defaults: + _controller: '\Drupal\usagov_ssg_postprocessing\Controller\SsgStatController::siteLagTest' _title: 'Tome Status' options: no_cache: 'TRUE' - requirements: + requirements: _permission: 'access administration pages' diff --git a/web/modules/custom/usagov_ssg_postprocessing/usagov_ssg_postprocessing.services.yml b/web/modules/custom/usagov_ssg_postprocessing/usagov_ssg_postprocessing.services.yml index 564929322a..42ab4a2e4e 100644 --- a/web/modules/custom/usagov_ssg_postprocessing/usagov_ssg_postprocessing.services.yml +++ b/web/modules/custom/usagov_ssg_postprocessing/usagov_ssg_postprocessing.services.yml @@ -8,10 +8,6 @@ services: class: '\Drupal\usagov_ssg_postprocessing\EventSubscriber\PagerPathSubscriber' tags: - { name: 'event_subscriber' } - usagov_ssg_postprocessing_published_pages_events_subscriber: - class: '\Drupal\usagov_ssg_postprocessing\EventSubscriber\PublishedPagesSubscriber' - tags: - - { name: 'event_subscriber' } usagov_ssg_postprocessing_request_prepare_events_subscriber: class: '\Drupal\usagov_ssg_postprocessing\EventSubscriber\RequestPrepareSubscriber' arguments: ['@path_alias.manager', '@entity_type.manager', '@current_route_match'] diff --git a/web/modules/custom/usagov_wizard/src/EventSubscriber/DatalayerAlterSubscriber.php b/web/modules/custom/usagov_wizard/src/EventSubscriber/DatalayerAlterSubscriber.php index 8210c7f883..12485e9e8b 100644 --- a/web/modules/custom/usagov_wizard/src/EventSubscriber/DatalayerAlterSubscriber.php +++ b/web/modules/custom/usagov_wizard/src/EventSubscriber/DatalayerAlterSubscriber.php @@ -5,10 +5,9 @@ use Drupal\Core\Breadcrumb\BreadcrumbManager; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Routing\CurrentRouteMatch; -use Drupal\Core\Url; -use Drupal\taxonomy\Entity\Term; use Drupal\usa_twig_vars\Event\DatalayerAlterEvent; use Drupal\usagov_wizard\MenuChecker; +use Drupal\usagov_wizard\WizardDataLayer; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -41,97 +40,8 @@ public function onDatalayerAlter(DatalayerAlterEvent $event): void { return; } - $termStorage = $this->entityTypeManager->getStorage('taxonomy_term'); - - $isStartPage = FALSE; - $children = $termStorage->loadChildren($term->id()); - $isResult = empty($children); - - if ($term->hasField('parent')) { - $parentTID = $term->parent->getValue()[0]['target_id']; - if ($parentTID === '0') { - $isStartPage = TRUE; - } - } - - if ($isStartPage) { - $page_type = 'wizard-start'; - } - elseif ($isResult) { - $page_type = 'wizard-result'; - } - else { - $page_type = 'wizard-question'; - } - - // keep the same order - unset($event->datalayer['hasBenefitCategory']); - // make any changes need to $event->datalayer array - $event->datalayer['taxonomyID'] = $term->id(); - $event->datalayer['contentType'] = $term->bundle(); - $event->datalayer['language'] = $term->language()->getId(); - $event->datalayer['homepageTest'] = 'not_homepage'; - $event->datalayer['basicPagesubType'] = NULL; - $event->datalayer['Page_Type'] = $page_type; - $event->datalayer['hasBenefitCategory'] = FALSE; - - $rootTerm = NULL; - $parents = []; - if ($term->hasField('parent') && !$term->get('parent')->isEmpty()) { - $parents = $this->entityTypeManager - ->getStorage('taxonomy_term') - ->loadAllParents($term->id()); - // Sort parents so "oldest ancestor" is first. - $parents = array_reverse($parents); - $rootTerm = $parents[array_key_first($parents)]; - } - - if ($rootTerm) { - $crumbs = usagov_wizard_get_term_breadcrumb($rootTerm); - // Here the first two items will give us the home page - // and the main scam page - $crumbs = array_slice($crumbs, 0, 2); - foreach ($crumbs as $crumb) { - $data[$crumb['url']] = $crumb['text']; - } - } - - // the rest comes from the parents of this term - foreach ($parents as $parentTerm) { - $path = $parentTerm->get('path'); - $termURL = $path->alias; - // pathalias field items don't prepend the language code for Spanish terms - if ($parentTerm->language()->getId() === 'es') { - $termURL = '/es' . $termURL; - } - $data[$termURL] = $parentTerm->getName(); - } - - $count = count($data); - - $i = 0; - foreach ($data as $url => $text) { - $i++; - $urls['Taxonomy_Text_' . $i] = $text; - $urls['Taxonomy_URL_' . $i] = $url; - - if ($i === 6) { - break; - } - } - - if ($i < 6) { - $lastURL = array_key_last($data); - $lastText = $data[$lastURL]; - - for ($i = $count; $i < 6; $i++) { - $urls['Taxonomy_Text_' . ($i + 1)] = $lastText; - $urls['Taxonomy_URL_' . ($i + 1)] = $lastURL; - } - } - - ksort($urls); - $event->datalayer = array_merge($event->datalayer, $urls); + $builder = new WizardDataLayer($term, $this->entityTypeManager); + $event->datalayer = $builder->getData($event->datalayer); } } diff --git a/web/modules/custom/usagov_wizard/src/WizardDataLayer.php b/web/modules/custom/usagov_wizard/src/WizardDataLayer.php new file mode 100644 index 0000000000..a3cb507815 --- /dev/null +++ b/web/modules/custom/usagov_wizard/src/WizardDataLayer.php @@ -0,0 +1,113 @@ +typeManager->getStorage('taxonomy_term'); + + $isStartPage = FALSE; + $children = $termStorage->loadChildren($this->term->id()); + $isResult = empty($children); + + if ($this->term->hasField('parent')) { + $parentTID = $this->term->parent->getValue()[0]['target_id']; + if ($parentTID === '0') { + $isStartPage = TRUE; + } + } + + if ($isStartPage) { + $page_type = 'wizard-start'; + } + elseif ($isResult) { + $page_type = 'wizard-result'; + } + else { + $page_type = 'wizard-question'; + } + + // keep the same order + unset($data['hasBenefitCategory']); + // make any changes need to $event->datalayer array + $data['taxonomyID'] = $this->term->id(); + $data['contentType'] = $this->term->bundle(); + $data['language'] = $this->term->language()->getId(); + $data['homepageTest'] = 'not_homepage'; + $data['basicPagesubType'] = NULL; + $data['Page_Type'] = $page_type; + $data['hasBenefitCategory'] = FALSE; + + $rootTerm = NULL; + $parents = []; + if ( + $this->term->hasField('parent') + && !$this->term->get('parent')->isEmpty() + ) { + $parents = $this->typeManager + ->getStorage('taxonomy_term') + ->loadAllParents($this->term->id()); + // Sort parents so "oldest ancestor" is first. + $parents = array_reverse($parents); + $rootTerm = $parents[array_key_first($parents)]; + } + + if ($rootTerm) { + $crumbs = usagov_wizard_get_term_breadcrumb($rootTerm); + // Here the first two items will give us the home page + // and the main scam page + $crumbs = array_slice($crumbs, 0, 2); + foreach ($crumbs as $crumb) { + $raw[$crumb['url']] = $crumb['text']; + } + } + + // the rest comes from the parents of this term + foreach ($parents as $parentTerm) { + $path = $parentTerm->get('path'); + $termURL = $path->alias; + // pathalias field items don't prepend the language code for Spanish terms + if ($parentTerm->language()->getId() === 'es') { + $termURL = '/es' . $termURL; + } + $raw[$termURL] = $parentTerm->getName(); + } + + $count = count($raw); + + $i = 0; + foreach ($raw as $url => $text) { + $i++; + $urls['Taxonomy_Text_' . $i] = $text; + $urls['Taxonomy_URL_' . $i] = $url; + + if ($i === 6) { + break; + } + } + + if ($i < 6) { + $lastURL = array_key_last($raw); + $lastText = $raw[$lastURL]; + + for ($i = $count; $i < 6; $i++) { + $urls['Taxonomy_Text_' . ($i + 1)] = $lastText; + $urls['Taxonomy_URL_' . ($i + 1)] = $lastURL; + } + } + + ksort($urls); + return array_merge($data, $urls); + } + +} diff --git a/web/modules/custom/usagov_wizard/usagov_wizard.module b/web/modules/custom/usagov_wizard/usagov_wizard.module index 7f3cf97fe0..11969f48c7 100644 --- a/web/modules/custom/usagov_wizard/usagov_wizard.module +++ b/web/modules/custom/usagov_wizard/usagov_wizard.module @@ -411,6 +411,10 @@ function usagov_wizard_get_term_breadcrumb(Term $rootTerm): array { foreach ($crumbs as $crumb) { if (isset($crumb['uri'])) { $crumb['url'] = Url::fromUri($crumb['uri'])->toString(); + // Sometimes Drupal doesn't prepend Spanish path prefix. + if ($rootTerm->language()->getId() === 'es' && !str_starts_with($crumb['url'], '/es/')) { + $crumb['url'] = '/es' . $crumb['url']; + } } if (isset($crumb['title'])) { $crumb['text'] = ($crumb['title']); diff --git a/web/themes/custom/usagov/sass/_uswds-theme-custom-styles.scss b/web/themes/custom/usagov/sass/_uswds-theme-custom-styles.scss index 2db0e78830..971137b756 100644 --- a/web/themes/custom/usagov/sass/_uswds-theme-custom-styles.scss +++ b/web/themes/custom/usagov/sass/_uswds-theme-custom-styles.scss @@ -924,3 +924,12 @@ input::-webkit-textfield-decoration-container { max-width: 100%; } } + +/* Dialog sizing code for USAGOV-150 */ +.ui-dialog.ckeditor5-paragraph-embed-modal { + top: 5vh !important; + min-height: 80vh !important; +} +.ui-dialog.ckeditor5-paragraph-embed-modal > .ui-dialog-content { + height: 80vh !important; +} diff --git a/web/themes/custom/usagov/usagov.theme b/web/themes/custom/usagov/usagov.theme index 4a2e2d4693..8d7949e074 100644 --- a/web/themes/custom/usagov/usagov.theme +++ b/web/themes/custom/usagov/usagov.theme @@ -27,8 +27,9 @@ function usagov_preprocess_html(&$variables) { function generate_faq(&$node) { $faq = []; - // While a node may have its FAQ-Page checked, this does not guarantee the body field is not empty. - // When it is empty, there is nothing we can do here. We will bail to prevent errors (see USAGOV-1925). + // While a node may have its FAQ-Page checked, this does not guarantee the + // body field is not empty. When it is empty, there is nothing we can do here. + // We will bail to prevent errors (see USAGOV-1925). if (empty($node->get('body')) || empty($node->get('body')[0]) || empty($node->get('body')[0]->value)) { return; } @@ -145,7 +146,8 @@ function generate_faq(&$node) { function faq_process_components(&$dom) { // Iterate through embedded paragraphs $embeddedParagraphElements = $dom->getElementsByTagName('drupal-paragraph'); - // Loop backwards to allow for removing elements along the way without losing track of our position + // Loop backwards to allow for removing elements along the way without losing + // track of our position for ($n = $embeddedParagraphElements->length - 1; $n >= 0; --$n) { $element = $embeddedParagraphElements->item($n); // Get the component @@ -155,8 +157,8 @@ function faq_process_components(&$dom) { // Process component by its type if ($component == NULL) { - // No entity exists for this paragraph component. It is probably a reference to a deleted component. - // Remove the element and move on. + // No entity exists for this paragraph component. It is probably a reference + // to a deleted component. Remove the element and move on. $element->parentNode->removeChild($element); break; } @@ -192,7 +194,6 @@ function faq_process_components(&$dom) { } elseif ($componentType == "uswds_alert" && $component?->get('field_alert_status')[0]?->value == NULL) { // $component is a grey alert - // Insert a

element with the alert's body content $alertBody = $component->get('field_alert_body')[0]->value; $alertElement = $dom->createElement('p'); @@ -265,7 +266,8 @@ function faq_answer_inner_html(&$answer) { } /** - * children_of_active_menu_item is a recursive helper function for finding the children of the active menu item + * children_of_active_menu_item is a recursive helper function for finding the + * children of the active menu item */ function children_of_active_menu_item(&$items) { $result = NULL; @@ -381,7 +383,6 @@ function usagov_preprocess_block(&$variables) { count($childNode->get('field_navigation_banner_image')) && $childNode->get('field_navigation_banner_image')[0]->entity ) { - $file_url_generator = \Drupal::service('file_url_generator'); // To allow for image_style (webp compression) to apply. $banner_uri = $childNode->get('field_navigation_banner_image')[0]->entity->get('field_media_image')->entity->getFileUri(); $data->navigation_banner_image = $banner_uri;