diff --git a/.dockerignore b/.dockerignore index 249b4d4..1f68dcc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,6 +11,7 @@ !composer.json !composer.lock !composer.patches.json +!patches.lock.json !symfony.lock !load.environment.php diff --git a/PATCHES/user_expire-customize-notification-email.patch b/PATCHES/user_expire-customize-notification-email.patch deleted file mode 100644 index fc67957..0000000 --- a/PATCHES/user_expire-customize-notification-email.patch +++ /dev/null @@ -1,179 +0,0 @@ -diff --git a/config/install/user_expire.settings.yml b/config/install/user_expire.settings.yml -index 62f9e8c7d5a064d87448b0faf5811a08709370c7..ea9fb144d2add8031bcafc516fb46cca209eb6c9 100644 ---- a/config/install/user_expire.settings.yml -+++ b/config/install/user_expire.settings.yml -@@ -1,3 +1,7 @@ - frequency: 172800 - offset: 604800 - user_expire_roles: {} -+send_mail: true -+mail: -+ subject: "[site:name]: Account expiration warning" -+ body: "Hello [user:display-name]\r\n\r\nBecause you have not logged in recently, your account at [site:name] will be blocked in the near future. If you still use this site, please log in [site:login-url] to prevent your account being blocked.\r\n\r\n-- [site:name] team" -diff --git a/config/schema/user_expire.schema.yml b/config/schema/user_expire.schema.yml -index a46bc68eeea8b41fa1e7bde72008daf77d54032d..be0c3fca4e7604994e5c0070942ef79920eab454 100644 ---- a/config/schema/user_expire.schema.yml -+++ b/config/schema/user_expire.schema.yml -@@ -7,9 +7,21 @@ user_expire.settings: - frequency: - type: integer - label: 'Frequency time in seconds' -+ mail: -+ type: mapping -+ mapping: -+ subject: -+ type: string -+ label: 'Subject line for the notification email' -+ body: -+ type: string -+ label: 'Body for the notification email' - offset: - type: integer - label: 'Warning offset time in seconds' -+ send_mail: -+ type: boolean -+ label: 'Flag that enables or disables expiry emails' - user_expire_roles: - type: sequence - label: 'Roles and expire value' -diff --git a/src/Form/UserExpireSettingsForm.php b/src/Form/UserExpireSettingsForm.php -index f7824c09ad10612818c2cc3e2cc2a85e590377a7..cd2c4e515d0887df9a70fe0c7c72bc599de84033 100644 ---- a/src/Form/UserExpireSettingsForm.php -+++ b/src/Form/UserExpireSettingsForm.php -@@ -109,6 +109,50 @@ class UserExpireSettingsForm extends ConfigFormBase { - ]; - } - -+ // Enable or disable email notifications. -+ $form['send_mail'] = [ -+ '#type' => 'checkbox', -+ '#title' => $this->t('Send notification emails'), -+ '#default_value' => $config->get('send_mail') ?: TRUE, -+ '#description' => $this->t('Send a notification email to the user, starting at the defined offset time before account expiry.'), -+ ]; -+ -+ // Notification email template. -+ $form['mail'] = [ -+ '#type' => 'fieldset', -+ '#title' => $this->t('Notification email'), -+ ]; -+ -+ $form['mail']['settings'] = [ -+ '#type' => 'container', -+ '#states' => [ -+ // Hide the additional settings when this email is disabled. -+ 'invisible' => [ -+ 'input[name="send_mail"]' => ['checked' => FALSE], -+ ], -+ ], -+ ]; -+ -+ $form['mail']['settings']['notification_subject'] = [ -+ '#type' => 'textfield', -+ '#title' => $this->t('Subject'), -+ '#default_value' => $config->get('mail.subject') ?: '', -+ '#description' => $this->t('Subject line for the notification email.'), -+ '#maxlength' => 180, -+ ]; -+ -+ $form['mail']['settings']['notification_body'] = [ -+ '#type' => 'textarea', -+ '#title' => $this->t('Body'), -+ '#default_value' => $config->get('mail.body') ?: '', -+ '#description' => $this->t('Body for the notification email.'), -+ '#rows' => 15, -+ ]; -+ -+ $form['mail']['settings']['help'] = [ -+ '#markup' => $this->t('Available token variables for use in the email are: [site:name], [site:url], [site:mail], [user:display-name], [user:account-name], [user:mail], [site:login-url], [site:url-brief], [user:edit-url], [user:one-time-login-url], [user:cancel-url]'), -+ ]; -+ - return parent::buildForm($form, $form_state); - } - -@@ -160,6 +204,13 @@ class UserExpireSettingsForm extends ConfigFormBase { - } - - $config->set('user_expire_roles', $rules); -+ -+ // The notification email. -+ $config->set('send_mail', $form_state->getValue('send_mail')); -+ -+ $config->set('mail.subject', $form_state->getValue('notification_subject')); -+ $config->set('mail.body', $form_state->getValue('notification_body')); -+ - $config->save(); - } - -diff --git a/user_expire.module b/user_expire.module -index d4dcd8ba28b51ee8f54a7626d8b10443244520d9..9365c5db3b87e88dd03611a03656370e2b4f99cb 100644 ---- a/user_expire.module -+++ b/user_expire.module -@@ -5,6 +5,7 @@ - * Main module file for User expire module. - */ - -+use Drupal\Component\Render\PlainTextOutput; - use Drupal\Core\Database\Query\Condition; - use Drupal\Core\Database\StatementInterface; - use Drupal\Core\Datetime\DrupalDateTime; -@@ -332,12 +333,15 @@ function user_expire_expire_by_role_warning(): void { - ]); - } - else { -- $logger->info('Warning about expiring account @name by role', ['@name' => $account->getAccountName()]); -- \Drupal::service('plugin.manager.mail')->mail('user_expire', 'expiration_warning', $account->getEmail(), $account->getPreferredLangcode(), -- [ -- 'account' => $account, -- ] -- ); -+ // Send a notification email if configured to do so. -+ if ($config->get('send_mail')) { -+ $logger->info('Sending warning about expiring account @name by role', ['@name' => $account->getAccountName()]); -+ \Drupal::service('plugin.manager.mail')->mail('user_expire', 'expiration_warning', $account->getEmail(), $account->getPreferredLangcode(), -+ [ -+ 'account' => $account, -+ ] -+ ); -+ } - } - } - } -@@ -439,20 +443,22 @@ function user_expire_get_role_rules(): mixed { - */ - function user_expire_mail($key, &$message, $params): void { - if ($key == 'expiration_warning') { -- $site_name = \Drupal::config('system.site')->get('name'); -- // The subject. -- $message['subject'] = t('@site_name: Account expiration warning', ['@site_name' => $site_name]); -- // The body. -- $message['body'][] = t('Hello @user', ['@user' => $params['account']->getAccountName()]); -- // An empty string gives a newline. -- $message['body'][] = ''; -- $message['body'][] = t('Because you have not logged in recently, your account at @site_name will be blocked in the near future. If you still use this site, please log in @login_url to avoid having your account blocked.', -- [ -- '@site_name' => $site_name, -- '@login_url' => Url::fromRoute('entity.user.canonical', ['user' => \Drupal::currentUser()->id()], ['absolute' => TRUE])->toString(), -- ] -- ); -- $message['body'][] = ''; -- $message['body'][] = t('Thanks, @site_name', ['@site_name' => $site_name]); -+ $token_service = \Drupal::token(); -+ $language_manager = \Drupal::languageManager(); -+ $langcode = $message['langcode']; -+ $variables = ['user' => $params['account']]; -+ -+ $language = $language_manager->getLanguage($params['account']->getPreferredLangcode()); -+ $original_language = $language_manager->getConfigOverrideLanguage(); -+ $language_manager->setConfigOverrideLanguage($language); -+ -+ $config_factory = \Drupal::configFactory(); -+ $config = $config_factory->get('user_expire.settings'); -+ -+ $token_options = ['langcode' => $langcode, 'callback' => 'user_mail_tokens', 'clear' => TRUE]; -+ $message['subject'] .= PlainTextOutput::renderFromHtml($token_service->replace($config->get('mail.subject'), $variables, $token_options)); -+ $message['body'][] = $token_service->replace($config->get('mail.body'), $variables, $token_options); -+ -+ $language_manager->setConfigOverrideLanguage($original_language); - } - } diff --git a/PATCHES/user_expire-reset-expiration-on-reactivation.patch b/PATCHES/user_expire-reset-expiration-on-reactivation.patch deleted file mode 100644 index 8d866ae..0000000 --- a/PATCHES/user_expire-reset-expiration-on-reactivation.patch +++ /dev/null @@ -1,51 +0,0 @@ -diff --git a/tests/src/Functional/UserExpireTest.php b/tests/src/Functional/UserExpireTest.php -index c746350f47259d17d458e4cfdee81e541ea55062..b427dfee1793e26094154573375f66927b56e7be 100644 ---- a/tests/src/Functional/UserExpireTest.php -+++ b/tests/src/Functional/UserExpireTest.php -@@ -167,6 +167,18 @@ class UserExpireTest extends BrowserTestBase { - // Ensure they are disabled. - $this->drupalGet("user/" . $new_basic_account->id() . "/edit"); - $this->assertSession()->responseContains('type="radio" id="edit-status-0" name="status" value="0" checked="checked" class="form-radio"', $this->t('User account is currently disabled.')); -+ -+ // Manually unblock the user. -+ $edit = []; -+ $edit['status'] = 1; -+ $this->submitForm($edit, $this->t('Save')); -+ -+ // Process it. -+ user_expire_expire_by_role(); -+ -+ // Ensure they are still active. -+ $this->drupalGet("user/" . $new_basic_account->id() . "/edit"); -+ $this->assertSession()->responseContains('type="radio" id="edit-status-1" name="status" value="1" checked="checked" class="form-radio"', $this->t('User account is currently active.')); - } - - } -diff --git a/user_expire.module b/user_expire.module -index d4dcd8ba28b51ee8f54a7626d8b10443244520d9..fae1de17d4bb1b0a1a3819446c65817daaccf424 100644 ---- a/user_expire.module -+++ b/user_expire.module -@@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityInterface; - use Drupal\Core\Form\FormStateInterface; - use Drupal\Core\Routing\RouteMatchInterface; - use Drupal\Core\Url; -+use Drupal\user\Entity\User; - use Drupal\user\RoleInterface; - - /** -@@ -456,3 +457,15 @@ function user_expire_mail($key, &$message, $params): void { - $message['body'][] = t('Thanks, @site_name', ['@site_name' => $site_name]); - } - } -+ -+/** -+ * Implements hook_ENTITY_TYPE_presave() for user entities. -+ * -+ * If the account was blocked but is now active, update the expiry so it is -+ * not re-blocked by the next cron run. -+ */ -+function user_expire_user_presave(User $account) { -+ if (!empty($account->original) && $account->original->isBlocked() && $account->isActive()) { -+ $account->setLastAccessTime(\Drupal::time()->getRequestTime()); -+ } -+} diff --git a/PATCHES/username_enumeration_prevention-user-login-block-form-3312288.patch b/PATCHES/username_enumeration_prevention-user-login-block-form-3312288.patch index 5fa6746..86b27bc 100644 --- a/PATCHES/username_enumeration_prevention-user-login-block-form-3312288.patch +++ b/PATCHES/username_enumeration_prevention-user-login-block-form-3312288.patch @@ -1,20 +1,46 @@ -diff --git a/username_enumeration_prevention.module b/username_enumeration_prevention.module -index 1038f75b1d823887965f79df9d3b76fc396031a1..075abaa22c8392f810093e066990934328fdd01c 100644 ---- a/username_enumeration_prevention.module -+++ b/username_enumeration_prevention.module -@@ -86,3 +86,15 @@ function username_enumeration_prevention_pass_submit($form, FormStateInterface $ - \Drupal::messenger()->addMessage(t('If the username or email address exists and is active, further instructions have been sent to your email address.')); - $form_state->setRedirect('user.page'); - } +diff --git a/src/Hook/UsernameEnumerationPreventionHooks.php b/src/Hook/UsernameEnumerationPreventionHooks.php +new file mode 100644 +index 0000000..25fbf14 +--- /dev/null ++++ b/src/Hook/UsernameEnumerationPreventionHooks.php +@@ -0,0 +1,40 @@ ++getRouteName() === "system.404" ) { -+ $settings['path']['currentPath'] = ''; ++readonly class UsernameEnumerationPreventionHooks { ++ ++ /** ++ * The UsernameEnumerationPreventionHooks constructor. ++ * ++ * @param \Drupal\Core\Routing\CurrentRouteMatch $routeMatch ++ * The route match. ++ */ ++ public function __construct( ++ #[Autowire('@current_route_match')] ++ private CurrentRouteMatch $routeMatch, ++ ) { ++ } ++ ++ /** ++ * Implements hook_js_settings_alter(). ++ */ ++ #[Hook('js_settings_alter', order: Order::Last)] ++ public function jsSettingsAlter(array &$settings, AttachedAssetsInterface $assets): void { ++ if ($this->routeMatch->getRouteName() === 'system.404') { ++ $settings['path']['currentPath'] = ''; ++ } + } -+} + ++} diff --git a/composer.json b/composer.json index 7093661..bb92db0 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "php": ">=8.3", "ext-gd": "*", "composer/installers": "^2", - "cweagans/composer-patches": "^1.7", + "cweagans/composer-patches": "^2.0", "drupal/admin_denied": "^2.0", "drupal/amazon_ses": "^3.0", "drupal/components": "^3.0@beta", @@ -55,7 +55,7 @@ "drupal/xmlsitemap": "^2.0.0-beta1", "drush/drush": "^13", "oomphinc/composer-installers-extender": "^2.0", - "orakili/composer-drupal-info-file-patch-helper": "^1", + "orakili/composer-drupal-info-file-patch-helper": "^2", "unocha/common_design": "^9", "unocha/gtm_barebones": "^1.1", "unocha/ocha_monitoring": "^1.0" @@ -108,11 +108,12 @@ } }, "extra": { - "enable-patching": true, - "composer-exit-on-patch-failure": true, - "patches-file": "composer.patches.json", - "patchLevel": { - "drupal/core": "-p2" + "composer-patches": { + "patches-file": "composer.patches.json", + "exit-on-patch-failure": true, + "package-depths": { + "drupal/core": 2 + } }, "installer-types": [ "bower-asset", diff --git a/composer.lock b/composer.lock index b434ae2..c9d7108 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": "defe0d78cfca532821395db6fbf24f5a", + "content-hash": "e3b4a99fdc01a320541c63dccb2df168", "packages": [ { "name": "asm89/stack-cors", @@ -958,31 +958,100 @@ }, "time": "2024-12-13T19:25:56+00:00" }, + { + "name": "cweagans/composer-configurable-plugin", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/cweagans/composer-configurable-plugin.git", + "reference": "15433906511a108a1806710e988629fd24b89974" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweagans/composer-configurable-plugin/zipball/15433906511a108a1806710e988629fd24b89974", + "reference": "15433906511a108a1806710e988629fd24b89974", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "require-dev": { + "codeception/codeception": "~4.0", + "codeception/module-asserts": "^2.0", + "composer/composer": "~2.0", + "php-coveralls/php-coveralls": "~2.0", + "php-parallel-lint/php-parallel-lint": "^1.0.0", + "phpro/grumphp": "^1.8.0", + "sebastian/phpcpd": "^6.0", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "cweagans\\Composer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Cameron Eagans", + "email": "me@cweagans.net" + } + ], + "description": "Provides a lightweight configuration system for Composer plugins.", + "support": { + "issues": "https://github.com/cweagans/composer-configurable-plugin/issues", + "source": "https://github.com/cweagans/composer-configurable-plugin/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/cweagans", + "type": "github" + } + ], + "time": "2023-02-12T04:58:58+00:00" + }, { "name": "cweagans/composer-patches", - "version": "1.7.3", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/cweagans/composer-patches.git", - "reference": "e190d4466fe2b103a55467dfa83fc2fecfcaf2db" + "reference": "bfa6018a5f864653d9ed899b902ea72f858a2cf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweagans/composer-patches/zipball/e190d4466fe2b103a55467dfa83fc2fecfcaf2db", - "reference": "e190d4466fe2b103a55467dfa83fc2fecfcaf2db", + "url": "https://api.github.com/repos/cweagans/composer-patches/zipball/bfa6018a5f864653d9ed899b902ea72f858a2cf7", + "reference": "bfa6018a5f864653d9ed899b902ea72f858a2cf7", "shasum": "" }, "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3.0" + "composer-plugin-api": "^2.0", + "cweagans/composer-configurable-plugin": "^2.0", + "ext-json": "*", + "php": ">=8.0.0" }, "require-dev": { - "composer/composer": "~1.0 || ~2.0", - "phpunit/phpunit": "~4.6" + "codeception/codeception": "~4.0", + "codeception/module-asserts": "^2.0", + "codeception/module-cli": "^2.0", + "codeception/module-filesystem": "^2.0", + "composer/composer": "~2.0", + "php-coveralls/php-coveralls": "~2.0", + "php-parallel-lint/php-parallel-lint": "^1.0.0", + "phpro/grumphp": "^1.8.0", + "sebastian/phpcpd": "^6.0", + "squizlabs/php_codesniffer": "^4.0" }, "type": "composer-plugin", "extra": { - "class": "cweagans\\Composer\\Patches" + "_": "The following two lines ensure that composer-patches is loaded as early as possible.", + "class": "cweagans\\Composer\\Plugin\\Patches", + "plugin-modifies-downloads": true, + "plugin-modifies-install-path": true }, "autoload": { "psr-4": { @@ -1002,9 +1071,15 @@ "description": "Provides a way to patch Composer packages.", "support": { "issues": "https://github.com/cweagans/composer-patches/issues", - "source": "https://github.com/cweagans/composer-patches/tree/1.7.3" + "source": "https://github.com/cweagans/composer-patches/tree/2.0.0" }, - "time": "2022-12-20T22:53:13+00:00" + "funding": [ + { + "url": "https://github.com/cweagans", + "type": "github" + } + ], + "time": "2025-10-30T23:44:22+00:00" }, { "name": "dflydev/dot-access-data", @@ -5124,20 +5199,21 @@ }, { "name": "orakili/composer-drupal-info-file-patch-helper", - "version": "1.0.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/orakili/composer-drupal-info-file-patch-helper.git", - "reference": "26dc73c0ad6a55486c09c75fd1cd091c79f98967" + "reference": "563a35549ae511467c9247c2bbe5eca0d19998b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orakili/composer-drupal-info-file-patch-helper/zipball/26dc73c0ad6a55486c09c75fd1cd091c79f98967", - "reference": "26dc73c0ad6a55486c09c75fd1cd091c79f98967", + "url": "https://api.github.com/repos/orakili/composer-drupal-info-file-patch-helper/zipball/563a35549ae511467c9247c2bbe5eca0d19998b4", + "reference": "563a35549ae511467c9247c2bbe5eca0d19998b4", "shasum": "" }, "require": { "composer-plugin-api": "^2.0", + "cweagans/composer-patches": "^2", "php": ">=8.1" }, "type": "composer-plugin", @@ -5161,9 +5237,9 @@ ], "support": { "issues": "https://github.com/orakili/composer-drupal-info-file-patch-helper/issues", - "source": "https://github.com/orakili/composer-drupal-info-file-patch-helper/tree/1.0.1" + "source": "https://github.com/orakili/composer-drupal-info-file-patch-helper/tree/2.0.0" }, - "time": "2022-12-20T03:47:48+00:00" + "time": "2026-01-07T05:26:54+00:00" }, { "name": "pear/archive_tar", diff --git a/docker/Dockerfile b/docker/Dockerfile index b856169..7a2327c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -51,6 +51,7 @@ COPY --from=builder /srv/www/composer.json /srv/www/composer.json COPY --from=builder /srv/www/composer.patches.json /srv/www/composer.patches.json COPY --from=builder /srv/www/composer.lock /srv/www/composer.lock COPY --from=builder /srv/www/PATCHES /srv/www/PATCHES +COPY --from=builder /srv/www/patches.lock.json /srv/www/patches.lock.json COPY --from=builder /srv/www/scripts /srv/www/scripts # Uncomment if you have custom nginx configuration. # Place the custom conf files in repo-folder/docker/etc/nginx/custom. diff --git a/local/docker-compose.yml b/local/docker-compose.yml index aa0736b..eab8f11 100644 --- a/local/docker-compose.yml +++ b/local/docker-compose.yml @@ -86,6 +86,7 @@ services: - "../composer.patches.json:/srv/www/composer.patches.json:rw" - "../composer.lock:/srv/www/composer.lock:rw" - "../PATCHES:/srv/www/PATCHES:rw" + - "../patches.lock.json:/srv/www/patches.lock.json:rw" # # Mount contrib modules and themes. # - "../html/modules/contrib:/srv/www/html/modules/contrib:rw" # - "../html/themes/contrib:/srv/www/html/themes/contrib:rw" diff --git a/patches.lock.json b/patches.lock.json new file mode 100644 index 0000000..b37edf5 --- /dev/null +++ b/patches.lock.json @@ -0,0 +1,63 @@ +{ + "_hash": "4e1602c271ede893ed8a458ac92c5f3f8ce02b94ceb98e8f7e2117da9de84dd2", + "patches": { + "drupal/imageapi_optimize_webp": [ + { + "package": "drupal/imageapi_optimize_webp", + "description": "Fix derivatives for webp source images", + "url": "PATCHES/imageapi_optimize_webp-webp-source-image.patch", + "sha256": "d569843481f6af47f189e18fc552efd2a2ede1d0ba234676be48e273a52f6653", + "depth": 1, + "extra": { + "provenance": "patches-file:composer.patches.json" + } + }, + { + "package": "drupal/imageapi_optimize_webp", + "description": "Support imagemagick toolkit", + "url": "PATCHES/imageapi_optimize_webp-imagemagick-toolkit.patch", + "sha256": "187f35fa7b541e310845e404eb8cb057f8586ea56e01867d9651577d3f8638d6", + "depth": 1, + "extra": { + "provenance": "patches-file:composer.patches.json" + } + } + ], + "drupal/maintenance200": [ + { + "package": "drupal/maintenance200", + "description": "D11 compatibility - #3431791", + "url": "PATCHES/maintenance200-D11-compatibility-3431791.patch", + "sha256": "a18c917b199168ded802407a11e2d9eb75b1933c75aea4e60701c3ba0f6ea623", + "depth": 1, + "extra": { + "provenance": "patches-file:composer.patches.json" + } + } + ], + "drupal/social_auth": [ + { + "package": "drupal/social_auth", + "description": "Type Error - #3496656", + "url": "PATCHES/social_auth-type-error-3496656.patch", + "sha256": "3640ab58ebe8cc368dd9945ad38ee42ce09cfa5873167778e28fc19c4d16572d", + "depth": 1, + "extra": { + "provenance": "patches-file:composer.patches.json" + } + } + ], + "drupal/username_enumeration_prevention": [ + { + "package": "drupal/username_enumeration_prevention", + "description": "Avoid leaking the path via Drupal.settings json", + "url": "PATCHES/username_enumeration_prevention-user-login-block-form-3312288.patch", + "sha256": "d69c3af6d81a028ee001324682c5ddce14124632b4a83e240cd2f838905ede87", + "depth": 1, + "extra": { + "provenance": "patches-file:composer.patches.json" + } + } + ] + } +}