diff --git a/config/install/symfony_mailer.settings.yml b/config/install/symfony_mailer.settings.yml index fc14e59..c520ef0 100644 --- a/config/install/symfony_mailer.settings.yml +++ b/config/install/symfony_mailer.settings.yml @@ -1 +1,2 @@ default_transport: sendmail +override: [] diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.contact.copy.yml b/config/mailer_override/symfony_mailer.mailer_policy.contact.copy.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.contact.copy.yml rename to config/mailer_override/symfony_mailer.mailer_policy.contact.copy.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.contact.mail.yml b/config/mailer_override/symfony_mailer.mailer_policy.contact.mail.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.contact.mail.yml rename to config/mailer_override/symfony_mailer.mailer_policy.contact.mail.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.contact.yml b/config/mailer_override/symfony_mailer.mailer_policy.contact.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.contact.yml rename to config/mailer_override/symfony_mailer.mailer_policy.contact.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.contact_form.copy.yml b/config/mailer_override/symfony_mailer.mailer_policy.contact_form.copy.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.contact_form.copy.yml rename to config/mailer_override/symfony_mailer.mailer_policy.contact_form.copy.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.contact_form.mail.yml b/config/mailer_override/symfony_mailer.mailer_policy.contact_form.mail.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.contact_form.mail.yml rename to config/mailer_override/symfony_mailer.mailer_policy.contact_form.mail.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.contact_form.yml b/config/mailer_override/symfony_mailer.mailer_policy.contact_form.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.contact_form.yml rename to config/mailer_override/symfony_mailer.mailer_policy.contact_form.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.simplenews.subscribe.yml b/config/mailer_override/symfony_mailer.mailer_policy.simplenews.subscribe.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.simplenews.subscribe.yml rename to config/mailer_override/symfony_mailer.mailer_policy.simplenews.subscribe.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.simplenews.validate.yml b/config/mailer_override/symfony_mailer.mailer_policy.simplenews.validate.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.simplenews.validate.yml rename to config/mailer_override/symfony_mailer.mailer_policy.simplenews.validate.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.simplenews_newsletter.yml b/config/mailer_override/symfony_mailer.mailer_policy.simplenews_newsletter.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.simplenews_newsletter.yml rename to config/mailer_override/symfony_mailer.mailer_policy.simplenews_newsletter.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.update.status_notify.yml b/config/mailer_override/symfony_mailer.mailer_policy.update.status_notify.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.update.status_notify.yml rename to config/mailer_override/symfony_mailer.mailer_policy.update.status_notify.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.cancel_confirm.yml b/config/mailer_override/symfony_mailer.mailer_policy.user.cancel_confirm.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.cancel_confirm.yml rename to config/mailer_override/symfony_mailer.mailer_policy.user.cancel_confirm.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.password_reset.yml b/config/mailer_override/symfony_mailer.mailer_policy.user.password_reset.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.password_reset.yml rename to config/mailer_override/symfony_mailer.mailer_policy.user.password_reset.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.register_admin_created.yml b/config/mailer_override/symfony_mailer.mailer_policy.user.register_admin_created.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.register_admin_created.yml rename to config/mailer_override/symfony_mailer.mailer_policy.user.register_admin_created.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.register_no_approval_required.yml b/config/mailer_override/symfony_mailer.mailer_policy.user.register_no_approval_required.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.register_no_approval_required.yml rename to config/mailer_override/symfony_mailer.mailer_policy.user.register_no_approval_required.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.register_pending_approval.yml b/config/mailer_override/symfony_mailer.mailer_policy.user.register_pending_approval.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.register_pending_approval.yml rename to config/mailer_override/symfony_mailer.mailer_policy.user.register_pending_approval.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.register_pending_approval_admin.yml b/config/mailer_override/symfony_mailer.mailer_policy.user.register_pending_approval_admin.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.register_pending_approval_admin.yml rename to config/mailer_override/symfony_mailer.mailer_policy.user.register_pending_approval_admin.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.status_activated.yml b/config/mailer_override/symfony_mailer.mailer_policy.user.status_activated.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.status_activated.yml rename to config/mailer_override/symfony_mailer.mailer_policy.user.status_activated.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.status_blocked.yml b/config/mailer_override/symfony_mailer.mailer_policy.user.status_blocked.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.status_blocked.yml rename to config/mailer_override/symfony_mailer.mailer_policy.user.status_blocked.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.status_canceled.yml b/config/mailer_override/symfony_mailer.mailer_policy.user.status_canceled.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user.status_canceled.yml rename to config/mailer_override/symfony_mailer.mailer_policy.user.status_canceled.yml diff --git a/modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user_registrationpassword.register_confirmation_with_pass.yml b/config/mailer_override/symfony_mailer.mailer_policy.user_registrationpassword.register_confirmation_with_pass.yml similarity index 100% rename from modules/symfony_mailer_bc/config/optional/symfony_mailer.mailer_policy.user_registrationpassword.register_confirmation_with_pass.yml rename to config/mailer_override/symfony_mailer.mailer_policy.user_registrationpassword.register_confirmation_with_pass.yml diff --git a/config/schema/symfony_mailer.schema.yml b/config/schema/symfony_mailer.schema.yml index eedde08..cfdbaac 100644 --- a/config/schema/symfony_mailer.schema.yml +++ b/config/schema/symfony_mailer.schema.yml @@ -7,6 +7,12 @@ symfony_mailer.settings: default_transport: type: string label: Default transport ID + override: + type: sequence + label: 'Override settings' + sequence: + type: integer + label: 'State' symfony_mailer.mailer_transport.*: type: config_entity diff --git a/drush.services.yml b/drush.services.yml index 5eb026b..9a359fe 100644 --- a/drush.services.yml +++ b/drush.services.yml @@ -1,6 +1,6 @@ services: symfony_mailer.commands: class: \Drupal\symfony_mailer\Commands\MailerCommands - arguments: ['@plugin.manager.email_builder'] + arguments: ['@symfony_mailer.override_manager'] tags: - { name: drush.command } diff --git a/modules/symfony_mailer_bc/src/MailerBcConfigOverride.php b/modules/symfony_mailer_bc/src/MailerBcConfigOverride.php deleted file mode 100644 index 5622d15..0000000 --- a/modules/symfony_mailer_bc/src/MailerBcConfigOverride.php +++ /dev/null @@ -1,65 +0,0 @@ - TRUE, - 'password_reset' => TRUE, - 'status_activated' => TRUE, - 'status_blocked' => TRUE, - 'status_canceled' => TRUE, - 'register_admin_created' => TRUE, - 'register_no_approval_required' => TRUE, - 'register_pending_approval' => TRUE, - ]; - } - - // The notification address is configured using Mailer Policy for - // UpdateEmailBuilder. Set a dummy value in update.settings to force the - // update module to send an email. NB UpdateEmailBuilder ignores the passed - // 'To' address so the dummy value will never be used. - if (in_array('update.settings', $names)) { - $overrides['update.settings']['notification']['emails'] = ['dummy']; - } - - return $overrides; - } - - /** - * {@inheritdoc} - */ - public function getCacheSuffix() { - return 'MailerBcConfigOverride'; - } - - /** - * {@inheritdoc} - */ - public function getCacheableMetadata($name) { - return new CacheableMetadata(); - } - - /** - * {@inheritdoc} - */ - public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) { - return NULL; - } - -} diff --git a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/ContactEmailBuilder.php b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/ContactEmailBuilder.php index 3555285..ef6d310 100644 --- a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/ContactEmailBuilder.php +++ b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/ContactEmailBuilder.php @@ -17,7 +17,7 @@ use Drupal\symfony_mailer\EmailInterface; * "mail" = @Translation("Message"), * "copy" = @Translation("Sender copy"), * }, - * proxy = {"contact.user_mail", "contact.user_copy"} + * override = {"contact.user_mail", "contact.user_copy"} * ) * * @todo Notes for adopting Symfony Mailer into Drupal core. This builder can diff --git a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/ContactPageEmailBuilder.php b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/ContactPageEmailBuilder.php index a23ed1c..b87cbcf 100644 --- a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/ContactPageEmailBuilder.php +++ b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/ContactPageEmailBuilder.php @@ -22,7 +22,7 @@ use Drupal\symfony_mailer\Entity\MailerPolicy; * "autoreply" = @Translation("Auto-reply"), * }, * has_entity = TRUE, - * proxy = { + * override = { * "contact.page_mail", * "contact.page_copy", * "contact.page_autoreply", diff --git a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/SimplenewsEmailBuilder.php b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/SimplenewsEmailBuilder.php index 2b8a573..5c6574d 100644 --- a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/SimplenewsEmailBuilder.php +++ b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/SimplenewsEmailBuilder.php @@ -16,10 +16,10 @@ use Drupal\symfony_mailer\Entity\MailerPolicy; * "subscribe" = @Translation("Subscription confirmation"), * "validate" = @Translation("Validate"), * }, - * proxy = {"simplenews.subscribe_combined", "simplenews.validate"}, + * override = {"simplenews.subscribe_combined", "simplenews.validate"}, * common_adjusters = {"email_subject", "email_body"}, * import = @Translation("Simplenews subscriber settings"), - * import_warning = @Translation("This overrides the default HTML messages with imported plain text versions."), + * import_warning = @Translation("This overrides the default HTML messages with imported plain text versions"), * ) */ class SimplenewsEmailBuilder extends SimplenewsEmailBuilderBase { diff --git a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/SimplenewsNewsletterEmailBuilder.php b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/SimplenewsNewsletterEmailBuilder.php index 2295888..b116be1 100644 --- a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/SimplenewsNewsletterEmailBuilder.php +++ b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/SimplenewsNewsletterEmailBuilder.php @@ -19,7 +19,8 @@ use Drupal\symfony_mailer\Entity\MailerPolicy; * "node" = @Translation("Issue"), * }, * has_entity = TRUE, - * proxy = {"simplenews.node", "simplenews.test"}, + * override = {"simplenews.node", "simplenews.test"}, + * override_warning = @Translation("Not tested for large numbers of recipients"), * common_adjusters = {"email_subject", "email_from"}, * import = @Translation("Simplenews newsletter settings"), * ) diff --git a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/UpdateEmailBuilder.php b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/UpdateEmailBuilder.php index 9495ab5..034da49 100644 --- a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/UpdateEmailBuilder.php +++ b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/UpdateEmailBuilder.php @@ -18,10 +18,20 @@ use Drupal\update\UpdateManagerInterface; * @EmailBuilder( * id = "update", * sub_types = { "status_notify" = @Translation("Available updates") }, - * proxy = TRUE, + * override = TRUE, * common_adjusters = {"email_subject", "email_body", "email_to"}, * import = @Translation("Update notification addresses"), + * config_overrides = { + * "update.settings" = { + * "notification" = { "emails" = "dummy" }, + * }, + * }, * ) + * + * The notification address is configured using Mailer Policy for + * UpdateEmailBuilder. Set a dummy value in update.settings to force the update + * module to send an email. NB UpdateEmailBuilder ignores the passed 'To' + * address so the dummy value will never be used. */ class UpdateEmailBuilder extends EmailBuilderBase { diff --git a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/UserEmailBuilder.php b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/UserEmailBuilder.php index 90a4410..c64eba4 100644 --- a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/UserEmailBuilder.php +++ b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/UserEmailBuilder.php @@ -26,10 +26,24 @@ use Drupal\user\UserInterface; * "status_blocked" = @Translation("Account blocked"), * "status_canceled" = @Translation("Account cancelled"), * }, - * proxy = TRUE, + * override = TRUE, * common_adjusters = {"email_subject", "email_body", "email_skip_sending"}, * import = @Translation("User email settings"), - * import_warning = @Translation("This overrides the default HTML messages with imported plain text versions."), + * import_warning = @Translation("This overrides the default HTML messages with imported plain text versions"), + * config_overrides = { + * "user.settings" = { + * "notify" = { + * "cancel_confirm" = TRUE, + * "password_reset" = TRUE, + * "status_activated" = TRUE, + * "status_blocked" = TRUE, + * "status_canceled" = TRUE, + * "register_admin_created" = TRUE, + * "register_no_approval_required" = TRUE, + * "register_pending_approval" = TRUE, + * }, + * }, + * }, * ) * * @todo Notes for adopting Symfony Mailer into Drupal core. This builder can diff --git a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/UserRegistrationPasswordEmailBuilder.php b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/UserRegistrationPasswordEmailBuilder.php index 5c7dcf2..578c7a4 100644 --- a/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/UserRegistrationPasswordEmailBuilder.php +++ b/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/UserRegistrationPasswordEmailBuilder.php @@ -12,7 +12,7 @@ use Drupal\symfony_mailer\EmailInterface; * sub_types = { * "register_confirmation_with_pass" = @Translation("Welcome (no approval required, password is set)"), * }, - * proxy = TRUE, + * override = TRUE, * ) */ class UserRegistrationPasswordEmailBuilder extends UserEmailBuilder { diff --git a/modules/symfony_mailer_bc/symfony_mailer_bc.module b/modules/symfony_mailer_bc/symfony_mailer_bc.module index f52b733..dbe754c 100644 --- a/modules/symfony_mailer_bc/symfony_mailer_bc.module +++ b/modules/symfony_mailer_bc/symfony_mailer_bc.module @@ -25,18 +25,22 @@ function symfony_mailer_bc_module_implements_alter(&$implementations, $hook) { function symfony_mailer_bc_form_alter(&$form, FormStateInterface $form_state, $form_id) { $config_all = [ 'contact_form_edit_form' => [ + 'plugin_id' => 'contact_form', 'remove' => ['recipients'], 'entity' => 'mail', ], 'simplenews_admin_settings_newsletter' => [ + 'plugin_id' => 'simplenews', 'remove' => ['simplenews_default_options', 'simplenews_sender_info'], ], 'simplenews_admin_settings_subscription' => [ + 'plugin_id' => 'simplenews', 'remove' => ['subscription_mail'], 'type' => 'simplenews', ], 'simplenews_newsletter_edit_form' => [ + 'plugin_id' => 'simplenews_newsletter', 'remove' => [ 'email', 'simplenews_sender_information', @@ -45,10 +49,12 @@ function symfony_mailer_bc_form_alter(&$form, FormStateInterface $form_state, $f 'entity' => 'node', ], 'update_settings' => [ + 'plugin_id' => 'update', 'remove' => ['update_notify_emails'], 'type' => 'update', ], 'user_admin_settings' => [ + 'plugin_id' => 'user', 'remove' => [ 'mail_notification_address', 'email_admin_created', @@ -72,7 +78,10 @@ function symfony_mailer_bc_form_alter(&$form, FormStateInterface $form_state, $f $config_all['simplenews_newsletter_add_form'] = $config_all['simplenews_newsletter_edit_form']; if ($config = $config_all[$form_id] ?? NULL) { - $helper = Drupal::service('symfony_mailer.helper'); + $override_manager = \Drupal::service('symfony_mailer.override_manager'); + if (!$override_manager->isEnabled($config['plugin_id'])) { + return; + } // Hide fields that are replaced by Mailer Policy. foreach ($config['remove'] ?? [] as $key) { @@ -80,6 +89,7 @@ function symfony_mailer_bc_form_alter(&$form, FormStateInterface $form_state, $f } // Add policy elements on entity forms. + $helper = Drupal::service('symfony_mailer.helper'); if (!empty($config['entity'])) { $form['mailer_policy'] = $helper->renderEntityPolicy($form_state->getFormObject()->getEntity(), $config['entity']); } @@ -89,8 +99,14 @@ function symfony_mailer_bc_form_alter(&$form, FormStateInterface $form_state, $f $form['mailer_policy'] = $helper->renderTypePolicy($config['type']); } } +} - if ($form_id == 'contact_form_add_form') { +/** + * Implements hook_form_FORM_ID_alter(). + */ +function symfony_mailer_bc_form_contact_form_add_form_alter(&$form, FormStateInterface $form_state, $form_id) { + $override_manager = \Drupal::service('symfony_mailer.override_manager'); + if ($override_manager->isEnabled('contact_form')) { // Set a default value for the hidden field so the form can verify. $form['recipients']['#default_value'] = \Drupal::config('system.site')->get('mail'); } diff --git a/modules/symfony_mailer_bc/symfony_mailer_bc.services.yml b/modules/symfony_mailer_bc/symfony_mailer_bc.services.yml deleted file mode 100644 index 45ab40c..0000000 --- a/modules/symfony_mailer_bc/symfony_mailer_bc.services.yml +++ /dev/null @@ -1,5 +0,0 @@ -services: - symfony_mailer_bc.overrider: - class: Drupal\symfony_mailer_bc\MailerBcConfigOverride - tags: - - {name: config.factory.override} diff --git a/src/Annotation/EmailBuilder.php b/src/Annotation/EmailBuilder.php index eee7a97..b3b275e 100644 --- a/src/Annotation/EmailBuilder.php +++ b/src/Annotation/EmailBuilder.php @@ -66,15 +66,22 @@ class EmailBuilder extends Plugin { public $has_entity = FALSE; /** - * Information about replacing (proxy) for another module. + * Information about overriding emails for another module. * - * The value is an array of email IDs to proxy. The annotation may set the + * The value is an array of email IDs to override. The annotation may set the * value TRUE which is automatically converted to an single-value array * containing the plugin ID. * * @var bool|string[] */ - public $proxy = []; + public $override = []; + + /** + * Human-readable warning for overriding. + * + * @var string + */ + public $override_warning = ''; /** * Array of common adjuster IDs. @@ -97,4 +104,11 @@ class EmailBuilder extends Plugin { */ public $import_warning = ''; + /** + * Array of config overrides. + * + * @var array + */ + public $config_overrides = []; + } diff --git a/src/Commands/MailerCommands.php b/src/Commands/MailerCommands.php index 784fae3..af0d7b1 100644 --- a/src/Commands/MailerCommands.php +++ b/src/Commands/MailerCommands.php @@ -3,7 +3,7 @@ namespace Drupal\symfony_mailer\Commands; use Consolidation\OutputFormatters\StructuredData\RowsOfFields; -use Drupal\symfony_mailer\Processor\EmailBuilderManagerInterface; +use Drupal\symfony_mailer\Processor\OverrideManagerInterface; use Drush\Commands\DrushCommands; /** @@ -12,63 +12,89 @@ use Drush\Commands\DrushCommands; class MailerCommands extends DrushCommands { /** - * The email builder manager. + * The override manager. * - * @var \Drupal\symfony_mailer\Processor\EmailBuilderManagerInterface + * @var \Drupal\symfony_mailer\Processor\OverrideManagerInterface */ - protected $builderManager; + protected $overrideManager; /** * Constructs the MailerCommands object. * - * @param \Drupal\symfony_mailer\Processor\EmailBuilderManagerInterface $email_builder_manager - * The email builder manager. + * @param \Drupal\symfony_mailer\Processor\OverrideManagerInterface $override_manager + * The override manager. */ - public function __construct(EmailBuilderManagerInterface $email_builder_manager) { - $this->builderManager = $email_builder_manager; + public function __construct(OverrideManagerInterface $override_manager) { + $this->overrideManager = $override_manager; } /** - * Imports legacy config. + * Executes an override action. * + * @param string $action + * Action to run: 'import', 'enable', or 'disable'. * @param string $id - * EmailBuilder ID to import. - * @param array $options - * An associative array of options whose values come from cli, aliases, - * config, etc. + * (optional) Override ID, or omit to execute all. * - * @option skip - * Skip the import. - * - * @command mailer:import + * @command mailer:override */ - public function import(string $id = NULL, array $options = ['skip' => FALSE]) { - if ($options['skip']) { - $this->builderManager->setImportState($id, EmailBuilderManagerInterface::IMPORT_SKIPPED); + public function override(string $action, string $id = OverrideManagerInterface::ALL_OVERRIDES) { + $info = $this->overrideManager->getInfo($id); + $action_name = $info['action_names'][$action] ?? NULL; + if (!$action_name) { + throw new NotFoundHttpException(); } - elseif ($id) { - $this->builderManager->import($id); + + $warnings = $this->overrideManager->action($id, $action, TRUE); + if (!$warnings) { + $this->logger->warning(dt('No available actions')); + return FALSE; + } + + // Use the last warning as the description. + $warnings = $this->overrideManager->action($id, $action, TRUE); + $description = array_pop($warnings); + foreach ($warnings as $warning) { + $warning = preg_replace("|]*>|", "'", $warning); + $this->output()->writeln($warning); + } + if (!$this->io()->confirm(dt('!description Do you want to continue?', ['!description' => $description]))) { + throw new UserAbortException(); + } + + $this->overrideManager->action($id, $action); + $args = ['%name' => $info['name'], '%action' => $action_name]; + if ($id == OverrideManagerInterface::ALL_OVERRIDES) { + $this->logger()->success(dt('Completed %action for all overrides', $args)); } else { - $this->builderManager->importAll(); + $this->logger()->success(dt('Completed %action for override %name', $args)); } } /** - * Shows information about legacy config importing. + * Gets information about Mailer overrides. * * @param array $options * An associative array of options whose values come from cli, aliases, * config, etc. * - * @command mailer:import-info + * @command mailer:override-info * @field-labels * name: Name * state_name: State - * warning: Warning + * import: Import */ - public function importInfo(array $options = ['format' => 'table']) { - $info = $this->builderManager->getImportInfo(); + public function overrideInfo(array $options = ['format' => 'table']) { + $info = $this->overrideManager->getInfo(); + foreach ($info as &$row) { + if ($warning = $row['warning']) { + $row['name'] .= "\nWarning: $warning"; + } + if ($import_warning = $row['import_warning']) { + $row['import'] .= "\nWarning: $import_warning"; + } + } return new RowsOfFields($info); } diff --git a/src/Controller/SymfonyMailerController.php b/src/Controller/SymfonyMailerController.php index 6c8de48..88e2da3 100644 --- a/src/Controller/SymfonyMailerController.php +++ b/src/Controller/SymfonyMailerController.php @@ -6,7 +6,7 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Url; use Drupal\symfony_mailer\Entity\MailerPolicy; use Drupal\symfony_mailer\MailerTransportInterface; -use Drupal\symfony_mailer\Processor\EmailBuilderManagerInterface; +use Drupal\symfony_mailer\Processor\OverrideManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -16,20 +16,20 @@ use Symfony\Component\HttpFoundation\Request; class SymfonyMailerController extends ControllerBase { /** - * The email builder manager. + * The override manager. * - * @var \Drupal\symfony_mailer\Processor\EmailBuilderManagerInterface + * @var \Drupal\symfony_mailer\Processor\OverrideManagerInterface */ - protected $builderManager; + protected $overrideManager; /** * Constructs the MailerCommands object. * - * @param \Drupal\symfony_mailer\Processor\EmailBuilderManagerInterface $email_builder_manager - * The email builder manager. + * @param \Drupal\symfony_mailer\Processor\OverrideManagerInterface $override_manager + * The override manager. */ - public function __construct(EmailBuilderManagerInterface $email_builder_manager) { - $this->builderManager = $email_builder_manager; + public function __construct(OverrideManagerInterface $override_manager) { + $this->overrideManager = $override_manager; } /** @@ -37,7 +37,7 @@ class SymfonyMailerController extends ControllerBase { */ public static function create(ContainerInterface $container) { return new static( - $container->get('plugin.manager.email_builder') + $container->get('symfony_mailer.override_manager') ); } @@ -46,87 +46,86 @@ class SymfonyMailerController extends ControllerBase { * * @return array * Render array. + * + * @deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. + * Instead you should use overrideStatus(). + * + * @see https://www.drupal.org/node/3354665 */ public function importStatus() { + @trigger_error('The route symfony_mailer.import.status is deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. Instead you should use symfony_mailer.override.status. See https://www.drupal.org/node/3354665', E_USER_DEPRECATED); + return $this->redirect('symfony_mailer.override.status'); + } + + /** + * Returns a page about override management status. + * + * @return array + * Render array. + */ + public function overrideStatus() { + $info = $this->overrideManager->getInfo(); + if ($info) { + $info[OverrideManagerInterface::ALL_OVERRIDES] = $this->overrideManager->getInfo(OverrideManagerInterface::ALL_OVERRIDES); + } + $build = [ '#type' => 'table', '#header' => [ 'name' => $this->t('Name'), 'state_name' => $this->t('State'), - 'warning' => $this->t('Warning'), + 'import' => $this->t('Import'), 'operations' => $this->t('Operations'), ], - '#rows' => $this->builderManager->getImportInfo(), - '#empty' => $this->t('There is no config to import.'), + '#rows' => $info, + '#empty' => $this->t('There are no overrides available.'), ]; foreach ($build['#rows'] as $id => &$row) { - $state = $row['state']; - unset($row['state']); - - $operations['import'] = [ - 'title' => ($state == EmailBuilderManagerInterface::IMPORT_COMPLETE) ? $this->t('Re-import') : $this->t('Import'), - 'url' => Url::fromRoute('symfony_mailer.import.import', ['id' => $id]), - ]; - - if ($state == EmailBuilderManagerInterface::IMPORT_READY) { - $operations['skip'] = [ - 'title' => $this->t('Skip'), - 'url' => Url::fromRoute('symfony_mailer.import.skip', ['id' => $id]), - ]; + $operations = []; + + // Calculate the available operations. + foreach ($row['action_names'] as $action => $label) { + if ($label) { + $operations[$action] = [ + 'title' => $label, + 'url' => Url::fromRoute('symfony_mailer.override.action', ['action' => $action, 'id' => $id]), + ]; + } } $row['operations']['data'] = [ '#type' => 'operations', '#links' => $operations, ]; - } - return $build; - } + if ($row['warning']) { + // Combine the warning into the name column. + $row['name'] = [ + 'data' => [ + '#type' => 'inline_template', + '#template' => '{{ name }}
Warning: {{ warning }}', + '#context' => $row, + ], + ]; + } - /** - * Imports all config not yet imported. - * - * @return \Symfony\Component\HttpFoundation\RedirectResponse - * A redirect to the import status page. - */ - public function importAll() { - $this->builderManager->importAll(); - $this->messenger()->addStatus($this->t('Imported all configuration')); - return $this->redirect('symfony_mailer.import.status'); - } + if ($row['import_warning']) { + // Combine the import warning into the import column. + $row['import'] = [ + 'data' => [ + '#type' => 'inline_template', + '#template' => '{{ import }}
Warning: {{ import_warning }}', + '#context' => $row, + ], + ]; + } - /** - * Imports config for the specified id. - * - * @param string $id - * The ID. - * - * @return \Symfony\Component\HttpFoundation\RedirectResponse - * A redirect to the import status page. - */ - public function import(string $id) { - $this->builderManager->import($id); - $label = $this->builderManager->getDefinition($id)['label']; - $this->messenger()->addStatus($this->t('Imported configuration for %label.', ['%label' => $label])); - return $this->redirect('symfony_mailer.import.status'); - } + // Remove any extra keys. + $row = array_intersect_key($row, $build['#header']); + } - /** - * Skips importing config for the specified id. - * - * @param string $id - * The ID. - * - * @return \Symfony\Component\HttpFoundation\RedirectResponse - * A redirect to the import status page. - */ - public function skip(string $id) { - $this->builderManager->setImportState($id, EmailBuilderManagerInterface::IMPORT_SKIPPED); - $label = $this->builderManager->getDefinition($id)['label']; - $this->messenger()->addStatus($this->t('Skipped importing configuration for %label.', ['%label' => $label])); - return $this->redirect('symfony_mailer.import.status'); + return $build; } /** diff --git a/src/Form/OverrideActionForm.php b/src/Form/OverrideActionForm.php new file mode 100644 index 0000000..47b75b1 --- /dev/null +++ b/src/Form/OverrideActionForm.php @@ -0,0 +1,165 @@ +overrideManager = $override_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('symfony_mailer.override_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return ($this->id == OverrideManagerInterface::ALL_OVERRIDES) ? + $this->t('Are you sure you want to do %action for all overrides?', $this->args) : + $this->t('Are you sure you want to do %action for override %name?', $this->args); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('symfony_mailer.override.status'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->description; + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->actionName; + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'symfony_mailer_override_action_form'; + } + + /** + * {@inheritdoc} + * + * @param array $form + * A nested array form elements comprising the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param string $id + * The override ID. + * @param string $action + * The action to execute. + */ + public function buildForm(array $form, FormStateInterface $form_state, string $id = '', string $action = '') { + $this->id = $id; + $this->action = $action; + $info = $this->overrideManager->getInfo($id); + $this->actionName = $info['action_names'][$action] ?? NULL; + if (!$this->actionName) { + throw new NotFoundHttpException(); + } + $this->args = ['%name' => $info['name'], '%action' => $this->actionName]; + + // Use the last warning as the description. + $warnings = $this->overrideManager->action($id, $action, TRUE); + $this->description = $warnings ? array_pop($warnings) : $this->t('No available actions'); + $form['warnings'] = [ + '#theme' => 'item_list', + '#title' => $this->t('Warnings'), + '#items' => $warnings, + '#access' => !empty($warnings), + ]; + + $form = parent::buildForm($form, $form_state); + $form['actions']['submit']['#attributes']['disabled'] = empty($warnings); + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->overrideManager->action($this->id, $this->action); + $message = ($this->id == OverrideManagerInterface::ALL_OVERRIDES) ? + $this->t('Completed %action for all overrides', $this->args) : + $this->t('Completed %action for override %name', $this->args); + $this->messenger()->addStatus($message); + $this->logger('symfony_mailer')->notice($message); + $form_state->setRedirectUrl($this->getCancelUrl()); + } + +} diff --git a/src/Processor/EmailBuilderManager.php b/src/Processor/EmailBuilderManager.php index e8fe9e0..1fef4a5 100644 --- a/src/Processor/EmailBuilderManager.php +++ b/src/Processor/EmailBuilderManager.php @@ -2,17 +2,19 @@ namespace Drupal\symfony_mailer\Processor; -use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Config\ConfigFactoryOverrideInterface; +use Drupal\Core\Config\StorageInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\KeyValueStore\KeyValueFactoryInterface; +use Drupal\Core\Plugin\DefaultPluginManager; use Drupal\Core\StringTranslation\StringTranslationTrait; /** * Provides the email builder plugin manager. */ -class EmailBuilderManager extends DefaultPluginManager implements EmailBuilderManagerInterface { +class EmailBuilderManager extends DefaultPluginManager implements EmailBuilderManagerInterface, ConfigFactoryOverrideInterface { use StringTranslationTrait; @@ -24,27 +26,29 @@ class EmailBuilderManager extends DefaultPluginManager implements EmailBuilderMa protected $entityTypeManager; /** - * The key value storage. + * Whether cache has been built. * - * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface + * @var bool */ - protected $keyValue; + protected $builtCache = FALSE; /** - * Mapping from state code to human-readable string. + * Array of registered override plugin settings. + * + * The key is the email ID to override and the value is the plugin ID. * * @var string[] */ - protected $stateName; + protected $overrideMapping = []; /** - * Array of registered proxy plugin settings. + * Array of config overrides. * - * The key is the email ID to proxy and the value is the plugin ID. + * As required by ConfigFactoryOverrideInterface::loadOverrides(). * - * @var string[] + * @var array */ - protected $proxyMapping; + protected $configOverrides = []; /** * Constructs the EmailBuilderManager object. @@ -58,21 +62,12 @@ class EmailBuilderManager extends DefaultPluginManager implements EmailBuilderMa * The module handler to invoke the alter hook with. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. - * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory - * The key value store. */ - public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, KeyValueFactoryInterface $key_value_factory) { + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager) { parent::__construct('Plugin/EmailBuilder', $namespaces, $module_handler, 'Drupal\symfony_mailer\Processor\EmailBuilderInterface', 'Drupal\symfony_mailer\Annotation\EmailBuilder'); $this->entityTypeManager = $entity_type_manager; - $this->keyValue = $key_value_factory->get('mailer'); $this->setCacheBackend($cache_backend, 'symfony_mailer_builder_plugins'); $this->alterInfo('mailer_builder_info'); - - $this->stateName = [ - self::IMPORT_READY => $this->t('Ready'), - self::IMPORT_COMPLETE => $this->t('Complete'), - self::IMPORT_SKIPPED => $this->t('Skipped'), - ]; } /** @@ -88,22 +83,22 @@ class EmailBuilderManager extends DefaultPluginManager implements EmailBuilderMa if ($definition['has_entity']) { if ($entity_type = $this->entityTypeManager->getDefinition($type, FALSE)) { $default_label = $entity_type->getLabel(); - $proxy_provider = $entity_type->getProvider(); + $override_provider = $entity_type->getProvider(); } } elseif ($this->moduleHandler->moduleExists($type)) { $default_label = $this->moduleHandler->getName($type); - $proxy_provider = $type; + $override_provider = $type; } - if ($definition['proxy']) { + if ($definition['override']) { // Default the provider, or fallback to a dummy provider that will cause // the definition to be removed if the related module is not installed. // @see DefaultPluginManager::findDefinitions() - $definition['provider'] = $proxy_provider ?? '_'; + $definition['provider'] = $override_provider ?? '_'; - if ($definition['proxy'] === TRUE) { - $definition['proxy'] = [$plugin_id]; + if ($definition['override'] === TRUE) { + $definition['override'] = [$plugin_id]; } } @@ -117,36 +112,15 @@ class EmailBuilderManager extends DefaultPluginManager implements EmailBuilderMa * {@inheritdoc} */ public function getImportInfo() { - $state_all = $this->keyValue->get('import', []); - - foreach ($this->getDefinitions() as $id => $definition) { - if ($definition['import']) { - $state = $state_all[$id] ?? self::IMPORT_READY; - - $info[$id] = [ - 'name' => "$definition[import] ($id)", - 'state' => $state, - 'state_name' => $this->stateName[$state], - 'warning' => $definition['import_warning'] ?? NULL, - ]; - } - } - - return $info ?? []; + @trigger_error('EmailBuilderManagerInterface::getImportInfo() is deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. Instead you should use OverrideManagerInterface::getInfo(). See https://www.drupal.org/node/3354665', E_USER_DEPRECATED); + return \Drupal::service('symfony_mailer.override_manager')->getInfo(); } /** * {@inheritdoc} */ public function importRequired() { - $state_all = $this->keyValue->get('import', []); - - foreach ($this->getDefinitions() as $id => $definition) { - if ($definition['import'] && (($state_all[$id] ?? self::IMPORT_READY) == self::IMPORT_READY)) { - return TRUE; - } - } - + @trigger_error('EmailBuilderManagerInterface::importRequired() is deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. The concept has been removed and you can assume a value of FALSE. See https://www.drupal.org/node/3354665', E_USER_DEPRECATED); return FALSE; } @@ -154,62 +128,109 @@ class EmailBuilderManager extends DefaultPluginManager implements EmailBuilderMa * {@inheritdoc} */ public function import(string $id) { - $this->createInstance($id)->import(); - $this->setImportState($id, self::IMPORT_COMPLETE); + @trigger_error('EmailBuilderManagerInterface::import() is deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. Instead you should use OverrideManagerInterface::action(). See https://www.drupal.org/node/3354665 See https://www.drupal.org/node/3354665', E_USER_DEPRECATED); + \Drupal::service('symfony_mailer.override_manager')->action($id, 'import'); } /** * {@inheritdoc} */ public function importAll() { - foreach ($this->getImportInfo() as $id => $info) { - if ($info['state'] == self::IMPORT_READY) { - $this->import($id); - } - } + @trigger_error('EmailBuilderManagerInterface::import() is deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. Instead you should use OverrideManagerInterface::action() See https://www.drupal.org/node/3354665', E_USER_DEPRECATED); + \Drupal::service('symfony_mailer.override_manager')->action('_', 'import'); } /** * {@inheritdoc} */ public function setImportState(string $id, int $state) { - $state_all = $this->keyValue->get('import'); - $state_all[$id] = $state; - $this->keyValue->set('import', $state_all); + @trigger_error('EmailBuilderManagerInterface::setImportState() is deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. Instead you should use OverrideManagerInterface::action(). See https://www.drupal.org/node/3354665', E_USER_DEPRECATED); } /** * {@inheritdoc} */ public function createInstanceFromMessage(array $message) { + $this->buildCache(); $suggestions = [ "$message[module].$message[key]", $message['module'], ]; - $proxy_mapping = $this->getProxyMapping(); - foreach ($suggestions as $plugin_id) { if ($this->hasDefinition($plugin_id)) { return $this->createInstance($plugin_id); } - if ($proxy_id = $proxy_mapping[$plugin_id] ?? NULL) { - return $this->createInstance($proxy_id); + if ($override_id = $this->overrideMapping[$plugin_id] ?? NULL) { + return $this->createInstance($override_id); } } } - protected function getProxyMapping() { - if (is_null($this->proxyMapping)) { - $this->proxy = []; + /** + * {@inheritdoc} + */ + public function loadOverrides($names) { + if (!array_diff($names, ["core.extension", "symfony_mailer.settings"])) { + // Avoid an infinite loop. + return []; + } + + $this->buildCache(); + $return = array_intersect_key($this->configOverrides, array_flip($names)); + return $return; + + } + + /** + * {@inheritdoc} + */ + public function getCacheSuffix() { + return 'MailerBcConfigOverride'; + } + + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($name) { + return new CacheableMetadata(); + } + + /** + * {@inheritdoc} + */ + public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) { + return NULL; + } + + /** + * {@inheritdoc} + * + * Expose findDefinitions() as public, for internal use only. + */ + public function findDefinitions() { + $definitions = parent::findDefinitions(); + return $definitions; + } + + /** + * Returns information about available overrides. + * + * @return array + * Array where key equals a type that is overridden and value equals the + * plug-in ID to use. + */ + protected function buildCache() { + if (!$this->builtCache) { foreach ($this->getDefinitions() as $id => $definition) { - foreach ($definition['proxy'] as $proxy_id) { - $this->proxyMapping[$proxy_id] = $id; + $this->configOverrides = array_merge($this->configOverrides, $definition['config_overrides']); + + foreach ($definition['override'] as $override_id) { + $this->overrideMapping[$override_id] = $id; } } + $this->builtCache = TRUE; } - - return $this->proxyMapping; } } diff --git a/src/Processor/EmailBuilderManagerInterface.php b/src/Processor/EmailBuilderManagerInterface.php index 405f9ed..b52bca6 100644 --- a/src/Processor/EmailBuilderManagerInterface.php +++ b/src/Processor/EmailBuilderManagerInterface.php @@ -11,18 +11,33 @@ interface EmailBuilderManagerInterface extends PluginManagerInterface { /** * Import not yet done, ready to import. + * + * @deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. + * There is no equivalent. + * + * @see https://www.drupal.org/node/3354665 */ const IMPORT_READY = 0; /** * Import complete. + * + * @deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. + * Instead you should use 'OverrideManagerInterface::STATE_IMPORTED'. + * + * @see https://www.drupal.org/node/3354665 */ const IMPORT_COMPLETE = 1; /** * Import skipped. + * + * @deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. + * Instead you should use 'OverrideManagerInterface::STATE_IMPORTED'. + * + * @see https://www.drupal.org/node/3354665 */ - const IMPORT_SKIPPED = 2; + const STATE_ENABLED = 2; /** * Gets information about config importing. @@ -33,6 +48,11 @@ interface EmailBuilderManagerInterface extends PluginManagerInterface { * - state: State, one of the IMPORT_ constants. * - state_name: A human-readable name for the state. * - warning: A human-readable warning. + * + * @deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. + * Instead you should use OverrideManagerInterface::getInfo(). + * + * @see https://www.drupal.org/node/3354665 */ public function getImportInfo(); @@ -41,6 +61,11 @@ interface EmailBuilderManagerInterface extends PluginManagerInterface { * * @return bool * TRUE if import is required. + * + * @deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. + * The concept has been removed and you can assume a value of FALSE. + * + * @see https://www.drupal.org/node/3354665 */ public function importRequired(); @@ -49,11 +74,21 @@ interface EmailBuilderManagerInterface extends PluginManagerInterface { * * @param string $id * The plugin ID. + * + * @deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. + * Instead you should use OverrideManagerInterface::action() + * + * @see https://www.drupal.org/node/3354665 */ public function import(string $id); /** * Imports all config not yet imported. + * + * @deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. + * Instead you should use OverrideManagerInterface::bulkAction() + * + * @see https://www.drupal.org/node/3354665 */ public function importAll(); @@ -64,6 +99,11 @@ interface EmailBuilderManagerInterface extends PluginManagerInterface { * The plugin ID. * @param int $state * The state, one of the IMPORT_ constants. + * + * @deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. + * Instead you should use OverrideManagerInterface::action() + * + * @see https://www.drupal.org/node/3354665 */ public function setImportState(string $id, int $state); diff --git a/src/Processor/OverrideManager.php b/src/Processor/OverrideManager.php new file mode 100644 index 0000000..51e842d --- /dev/null +++ b/src/Processor/OverrideManager.php @@ -0,0 +1,374 @@ +builderManager = $email_builder_manager; + $this->configManager = $config_manager; + $this->configStorage = $config_storage; + $this->entityTypeManager = $config_manager->getEntityTypeManager(); + $this->configFactory = $config_manager->getConfigFactory(); + $this->overrideStorage = new ExtensionInstallStorage($this->configStorage, 'config/mailer_override', StorageInterface::DEFAULT_COLLECTION, FALSE, ''); + + $this->stateName = [ + self::STATE_DISABLED => $this->t('Disabled'), + self::STATE_ENABLED => $this->t('Enabled'), + self::STATE_IMPORTED => $this->t('Enabled & imported'), + ]; + $this->actionName = [ + self::STATE_DISABLED => [ + 'import' => $this->t('Enable & import'), + 'enable' => $this->t('Enable'), + 'disable' => $this->t('Delete'), + ], + self::STATE_ENABLED => [ + 'import' => $this->t('Import'), + 'disable' => $this->t('Disable'), + 'enable' => $this->t('Reset'), + ], + self::STATE_IMPORTED => [ + 'disable' => $this->t('Disable'), + 'enable' => $this->t('Reset'), + 'import' => $this->t('Re-import'), + ], + self::ALL_OVERRIDES => [ + 'import' => $this->t('Enable & import'), + 'enable' => $this->t('Enable'), + 'disable' => $this->t('Disable'), + ], + ]; + $this->actionWarning = [ + 'disable' => $this->t('Related Mailer Policy will be deleted.'), + 'enable' => $this->t('Related Mailer Policy will be reset to default values.'), + 'import' => $this->t('Importing overwrites existing policy.'), + ]; + + $this->policyConfigPrefix = $this->entityTypeManager->getDefinition('mailer_policy')->getConfigPrefix(); + } + + /** + * {@inheritdoc} + */ + public function isEnabled(string $id) { + $state = $this->configFactory->get('symfony_mailer.settings')->get("override.$id", self::STATE_DISABLED); + return $this->forceEnabled || ($state != self::STATE_DISABLED); + } + + /** + * {@inheritdoc} + */ + public function getInfo(string $filterId = NULL) { + if ($filterId == self::ALL_OVERRIDES) { + return [ + 'name' => $this->t('*All*'), + 'warning' => '', + 'state_name' => '', + 'import' => '', + 'import_warning' => '', + 'action_names' => [ + 'import' => $this->actionName[self::ALL_OVERRIDES]['import'], + 'enable' => $this->actionName[self::ALL_OVERRIDES]['enable'], + 'disable' => $this->actionName[self::ALL_OVERRIDES]['disable'], + ], + ]; + } + + $settings = $this->configFactory->get('symfony_mailer.settings')->get('override'); + $info = []; + + // Get all definitions, ignoring the current enabled status. + $this->forceEnabled = TRUE; + $definitions = $this->builderManager->findDefinitions(); + asort($definitions); + $this->forceEnabled = FALSE; + + foreach ($definitions as $id => $definition) { + // The key 'proxy' is the deprecated equivalent of 'override' and it + // indicates a plug-in that doesn't support disabling. + if ($definition['override'] || !empty($definition['proxy'])) { + if (!isset($settings[$id])) { + $settings[$id] = $definition['override'] ? self::STATE_DISABLED : self::STATE_ENABLED; + $save = TRUE; + } + $state = $settings[$id]; + + $info[$id] = [ + 'name' => $definition['label'], + 'warning' => $definition['override_warning'], + 'state_name' => $this->stateName[$state], + 'import' => $definition['import'], + 'import_warning' => $definition['import_warning'], + 'state' => $state, + 'action_names' => [ + 'import' => !empty($definition['import']) ? $this->actionName[$state]['import'] : FALSE, + 'enable' => empty($definition['proxy']) ? $this->actionName[$state]['enable'] : FALSE, + 'disable' => empty($definition['proxy']) ? $this->actionName[$state]['disable'] : FALSE, + ], + ]; + } + } + + if (!empty($save) || (count($settings) > count($info))) { + // Fix missing or extra values in settings. + $settings = array_intersect_key($settings, $info); + $this->configFactory->getEditable('symfony_mailer.settings')->set('override', $settings)->save(); + } + + return $filterId ? ($info[$filterId] ?? NULL) : $info; + } + + /** + * {@inheritdoc} + */ + public function action(string $id, string $action, bool $confirming = FALSE) { + $info = $this->getInfo($id); + if (empty($info['action_names'][$action])) { + throw new \LogicException("Invalid override action '$action'"); + } + + if ($id == self::ALL_OVERRIDES) { + [$steps, $warnings] = $this->bulkActionSteps($action); + } + else { + $steps[$id] = $action; + } + + if ($confirming) { + // Return warnings. + if (!$steps) { + return NULL; + } + if ($info['warning'] && ($info['state'] == self::STATE_DISABLED) && ($action != 'disable')) { + $warnings[] = $info['warning']; + } + if ($action == 'import' && $info['import_warning']) { + $warnings[] = $info['import_warning']; + } + $warnings[] = $this->actionWarning[$action]; + return $warnings; + } + + foreach ($steps as $loop_id => $loop_action) { + $this->doAction($loop_id, $loop_action); + } + } + + /** + * Internal helper function to executes an action. + * + * @param string $id + * The override ID. + * @param string $action + * The action to execute. + */ + protected function doAction(string $id, string $action) { + // Save the state and clear cached definitions so that we can use a newly + // enabled definition later in this function. + $settings = $this->configFactory->getEditable('symfony_mailer.settings'); + $existing_state = $settings->get("override.$id"); + $new_state = self::ACTIONS[$action]; + $settings->set("override.$id", $new_state)->save(); + $this->builderManager->clearCachedDefinitions(); + $config_names = $this->overrideStorage->listAll($this->policyConfigPrefix . ".$id"); + + if ($action == 'disable') { + $this->deleteConfig($config_names); + } + else { + // When importing from disabled state, first have to enable. + $do_defaults = ($action == 'enable') || ($action == 'import' && $existing_state == self::STATE_DISABLED); + + if ($do_defaults) { + $this->defaultConfig($config_names); + } + + if ($action == 'import') { + $this->builderManager->createInstance($id)->import(); + } + } + } + + /** + * Gets the steps required for a bulk override action. + * + * @param string $action + * The action to execute. + * + * @return array + * List of two items: + * - steps: array keyed by plugin ID with value equal to the action to run. + * - warnings: array of warning messages to display. + */ + protected function bulkActionSteps(string $action) { + $steps = []; + $warnings = []; + $all_info = $this->getInfo(); + $new_state = self::ACTIONS[$action]; + + foreach ($all_info as $id => $info) { + // Skip if already in the required state. + if ($info['state'] == $new_state) { + continue; + } + if (($new_state == self::STATE_ENABLED) && ($info['state'] == self::STATE_IMPORTED)) { + continue; + } + + // Skip enable if there is a warning. + $args = ['%name' => $info['name'], '%warning' => $info['warning'], '%import_warning' => $info['import_warning']]; + if ($info['warning'] && ($action != 'disable')) { + $warnings[] = $this->t('Skipped %name: %warning', $args); + continue; + } + + // Skip importing if not available or there is a warning. + if ($action == 'import' && (!$info['import'] || $info['import_warning'])) { + $loop_action = 'enable'; + + if ($info['state'] == self::STATE_ENABLED) { + continue; + } + + $warnings[] = $info['import_warning'] ? + $this->t('Import skipped for %name: %import_warning', $args) : + $this->t('Import unavailable for %name', $args); + } + else { + $loop_action = $action; + } + + $warnings[] = $this->t('Run %action for override %name', ['%name' => $info['name'], '%action' => $loop_action]); + $steps[$id] = $loop_action; + } + + return [$steps, $warnings]; + } + + /** + * Sets default configuration for Mailer override. + * + * @param string[] $config_names + * The configuration names. + */ + protected function defaultConfig(array $config_names) { + foreach ($this->overrideStorage->readMultiple($config_names) as $name => $values) { + $config_type = $this->configManager->getEntityTypeIdByName($name); + $storage = $this->entityTypeManager->getStorage($config_type); + $entity_type = $this->entityTypeManager->getDefinition($config_type); + $id = ConfigEntityStorage::getIDFromConfigName($name, $entity_type->getConfigPrefix()); + + if ($entity = $storage->load($id)) { + $uuid = $entity->uuid(); + $storage->updateFromStorageRecord($entity, $values); + $entity->set('uuid', $uuid); + } + else { + $entity = $storage->createFromStorageRecord($values); + } + $entity->save(); + } + } + + /** + * Deletes configuration. + * + * @param string[] $config_names + * The configuration names. + */ + protected function deleteConfig(array $config_names) { + // Delete config. + foreach ($config_names as $name) { + $this->configStorage->delete($name); + } + } + +} diff --git a/src/Processor/OverrideManagerInterface.php b/src/Processor/OverrideManagerInterface.php new file mode 100644 index 0000000..3b2832d --- /dev/null +++ b/src/Processor/OverrideManagerInterface.php @@ -0,0 +1,79 @@ + self::STATE_IMPORTED, + 'enable' => self::STATE_ENABLED, + 'disable' => self::STATE_DISABLED, + ]; + + /** + * All overrides. + * + * Special value passed for an override ID meaning to apply to all overrides. + */ + const ALL_OVERRIDES = '_'; + + /** + * Gets information about Mailer overrides. + * + * @param string $filterId + * (optional) If set, return only the matching override ID, or NULL if it + * does not exist. If omitted, return an array of all overrides. If set to + * ALL_OVERRIDES, then return a single entry with human-readable strings + * describing the an action applied to all overrides. + * + * @return array + * Array keyed by plugin ID with values as an array with these keys: + * - name: Human-readable name for this override. + * - import_warning: Human-readable warning for this override. + * - state: State, one of the STATE_ constants. + * - state_name: Human-readable name for the state. + * - import: Human-readable description of the import operation. + * - import_warning: Human-readable warning for the import operation. + */ + public function getInfo(string $filterId = NULL); + + /** + * Executes an override action. + * + * @param string $id + * The override ID, or ALL_OVERRIDES for all overrides. + * @param string $action + * The action to execute. + * @param bool $confirming + * (optional) Indicates to show human-readable warnings for confirming the + * action, then exit without running anything. + * + * @return string[]|null + * An array of warnings (if $confirming is set) or NULL. + */ + public function action(string $id, string $action, bool $confirming = FALSE); + +} diff --git a/symfony_mailer.install b/symfony_mailer.install index 037c965..d10eea9 100644 --- a/symfony_mailer.install +++ b/symfony_mailer.install @@ -178,3 +178,22 @@ function symfony_mailer_update_10007() { } } } + +/** + * Set initial values for override configuration. + */ +function symfony_mailer_update_10008() { + $key_value = \Drupal::keyValue('mailer'); + foreach ($key_value->get('import', []) as $id => $state) { + // Map IMPORT_COMPLETE (1) to STATE_IMPORTED (1) and everything else to + // STATE_ENABLED (2). + $settings[$id] = ($state == 1) ? 1 : 2; + } + + if (isset($settings)) { + \Drupal::configFactory()->getEditable('symfony_mailer.settings') + ->set('override', $settings) + ->save(); + $key_value->deleteAll(); + } +} diff --git a/symfony_mailer.links.action.yml b/symfony_mailer.links.action.yml index 87a5e38..1418e5f 100644 --- a/symfony_mailer.links.action.yml +++ b/symfony_mailer.links.action.yml @@ -3,9 +3,3 @@ symfony_mailer.mailer_policy.add: title: 'Add policy' appears_on: - entity.mailer_policy.collection - -symfony_mailer.import.all: - route_name: symfony_mailer.import.all - title: 'Import all' - appears_on: - - symfony_mailer.import.status diff --git a/symfony_mailer.links.task.yml b/symfony_mailer.links.task.yml index e9413d3..1c0b687 100644 --- a/symfony_mailer.links.task.yml +++ b/symfony_mailer.links.task.yml @@ -10,11 +10,11 @@ symfony_mailer.mailer_transport.collection: base_route: entity.mailer_policy.collection route_name: entity.mailer_transport.collection -symfony_mailer.import.status: - title: Import - description: 'Import configuration.' +symfony_mailer.override.status: + title: Override + description: 'Override status.' base_route: entity.mailer_policy.collection - route_name: symfony_mailer.import.status + route_name: symfony_mailer.override.status symfony_mailer.test: title: Test diff --git a/symfony_mailer.module b/symfony_mailer.module index 8b20ca9..38e5a38 100644 --- a/symfony_mailer.module +++ b/symfony_mailer.module @@ -42,6 +42,14 @@ function symfony_mailer_theme_suggestions_email(array $variables) { * Implements hook_mailer_builder_info_alter(). */ function symfony_mailer_mailer_builder_info_alter(array &$email_builders) { + // Disable overrides based on configuration. + $override_manager = \Drupal::service('symfony_mailer.override_manager'); + foreach ($email_builders as $id => $definition) { + if ($definition['override'] && !$override_manager->isEnabled($id)) { + unset($email_builders[$id]); + } + } + // Add EmailBuilder definitions for any implementations of hook_mail() that // don't already have one, using LegacyEmailBuilder. $module_handler = \Drupal::moduleHandler(); @@ -100,21 +108,14 @@ function template_preprocess_email_wrap(array &$variables) { * Implements hook_help(). */ function symfony_mailer_help($route_name, RouteMatchInterface $route_match) { - // Remind site administrators if there are pending import operations. We - // don't need to issue the message on the import pages. - if ((substr($route_name, 0, strlen('symfony_mailer.import.')) != 'symfony_mailer.import.') && \Drupal::currentUser()->hasPermission('administer mailer') && \Drupal::service('plugin.manager.email_builder')->importRequired()) { - $params = [':import_config' => Url::fromRoute('symfony_mailer.import.status')->toString()]; - \Drupal::messenger()->addError(t('There are Mailer configuration import operations pending: import.', $params)); - } - switch ($route_name) { case 'entity.mailer_policy.collection': return '

' . t('Configure Mailer Policy to customise outgoing emails in many different ways. There are many possible policies to apply including: subject; body; addresses (from, to, ...); theme; transport; convert to plain text. Each policy can be set globally or for emails of a specific type.') . '

'; - case 'symfony_mailer.import.status': + case 'symfony_mailer.override.status': $params = [':mailer_policy' => Url::fromRoute('entity.mailer_policy.collection')->toString()]; - $output = '

' . t('You can import configuration from existing modules to create an equivalent Mailer Policy. It is recommended to run the import at the beginning, before you start editing policy.', $params) . '

'; - $output .= '

' . t('Warning importing overwrites existing policy. If you have already created working policy then you should skip the import.'); + $output = '

' . t('You can override email formatting from other modules with enhanced versions using all the features of the Mailer and Mailer Policy. Each type of email override can be enabled or disabled independently.', $params); + $output .= ' ' . t("When enabling an override, it is recommended also to import, which creates Policy equivalent to the other module's configuration.") . '

'; return $output; case 'symfony_mailer.test': diff --git a/symfony_mailer.routing.yml b/symfony_mailer.routing.yml index 89977c0..0be9aef 100644 --- a/symfony_mailer.routing.yml +++ b/symfony_mailer.routing.yml @@ -76,6 +76,8 @@ entity.mailer_policy.delete_form: requirements: _entity_access: 'mailer_policy.delete' +# @deprecated in symfony_mailer:1.3.0 and is removed from symfony_mailer:2.0.0. +# Instead you should use symfony_mailer.override.status. symfony_mailer.import.status: path: '/admin/config/system/mailer/import' defaults: @@ -84,27 +86,19 @@ symfony_mailer.import.status: requirements: _permission: 'administer mailer' -symfony_mailer.import.all: - path: '/admin/config/system/mailer/import-all' +symfony_mailer.override.status: + path: '/admin/config/system/mailer/override' defaults: - _controller: '\Drupal\symfony_mailer\Controller\SymfonyMailerController::importAll' - _title: 'Import all' + _controller: '\Drupal\symfony_mailer\Controller\SymfonyMailerController::overrideStatus' + _title: 'Override status' requirements: _permission: 'administer mailer' -symfony_mailer.import.import: - path: '/admin/config/system/mailer/import/{id}' +symfony_mailer.override.action: + path: '/admin/config/system/mailer/override/{id}/{action}' defaults: - _controller: '\Drupal\symfony_mailer\Controller\SymfonyMailerController::import' - _title: 'Import' - requirements: - _permission: 'administer mailer' - -symfony_mailer.import.skip: - path: '/admin/config/system/mailer/import/{id}/skip' - defaults: - _controller: '\Drupal\symfony_mailer\Controller\SymfonyMailerController::skip' - _title: 'Skip' + _form: '\Drupal\symfony_mailer\Form\OverrideActionForm' + _title: 'Override action' requirements: _permission: 'administer mailer' diff --git a/symfony_mailer.services.yml b/symfony_mailer.services.yml index 3d1c1ca..6114b9a 100644 --- a/symfony_mailer.services.yml +++ b/symfony_mailer.services.yml @@ -7,8 +7,10 @@ services: arguments: ['@plugin.manager.email_builder', '@plugin.manager.email_adjuster'] plugin.manager.email_builder: class: Drupal\symfony_mailer\Processor\EmailBuilderManager - arguments: ['@entity_type.manager', '@keyvalue'] + arguments: ['@entity_type.manager'] parent: default_plugin_manager + tags: + - {name: config.factory.override} plugin.manager.email_adjuster: class: Drupal\symfony_mailer\Processor\EmailAdjusterManager parent: default_plugin_manager @@ -21,6 +23,9 @@ services: symfony_mailer.legacy_helper: class: Drupal\symfony_mailer\LegacyMailerHelper arguments: ['@symfony_mailer.helper'] + symfony_mailer.override_manager: + class: Drupal\symfony_mailer\Processor\OverrideManager + arguments: ['@plugin.manager.email_builder', '@config.manager', '@config.storage'] symfony_mailer.transport_factory_manager: class: Drupal\symfony_mailer\TransportFactoryManager tags: diff --git a/tests/src/Functional/OverrideTest.php b/tests/src/Functional/OverrideTest.php new file mode 100644 index 0000000..27b919d --- /dev/null +++ b/tests/src/Functional/OverrideTest.php @@ -0,0 +1,107 @@ +assertSession(); + $this->drupalLogin($this->adminUser); + + // Check the override info page with defaults. + $expected = [ + ['Contact form', 'Disabled', 'Contact form recipients', 'Enable & import'], + ['Personal contact form', 'Disabled', '', 'Enable'], + ['User', 'Disabled', 'Update notification addresses', "User email settings\nWarning: This overrides the default HTML messages with imported plain text versions"], + ['*All*', '', '', 'Enable & import'], + ]; + $this->drupalGet(self::OVERRIDE_INFO); + $this->checkOverrideInfo($expected); + $session->linkByHrefExists(self::IMPORT_ALL); + + // Import all. + $this->drupalGet(self::IMPORT_ALL); + $session->pageTextContains('Import unavailable for Personal contact form'); + $session->pageTextContains('Import skipped for User: This overrides the default HTML messages with imported plain text versions'); + $session->pageTextContains('Run enable for override Personal contact form'); + $session->pageTextContains('Run import for override Contact form'); + $session->pageTextContains('Run enable for override User'); + $session->pageTextContains('Importing overwrites existing policy.'); + $this->submitForm([], 'Enable & import'); + + // Check the override info page again. + $expected[0][1] = 'Enabled & Imported'; + $expected[0][3] = 'Re-import'; + $expected[1][1] = $expected[2][1] = 'Enabled'; + $expected[1][3] = $expected[2][3] = 'Reset'; + $session->pageTextContains('Completed Enable & import for all overrides'); + $this->checkOverrideInfo($expected); + + // Import all again - nothing to do. + $this->drupalGet(self::IMPORT_ALL); + $session->pageTextContains('No available actions'); + $button = $this->getSession()->getPage()->findButton('Enable & import'); + $this->assertTrue($button->hasAttribute('disabled')); + $this->clickLink('Cancel'); + + // Force import the user override. + $session->linkByHrefExists(self::IMPORT_USER); + $this->drupalGet(self::IMPORT_USER); + $session->pageTextContains('This overrides the default HTML messages with imported plain text versions'); + $this->submitForm([], 'Import'); + + // Check the override info page again. + $expected[2][1] = 'Enabled & Imported'; + $expected[2][3] = 'Re-import'; + $session->pageTextContains('Completed import for override User'); + $this->checkOverrideInfo($expected); + } + + /** + * Checks the override info page. + * + * @param array $expected + * Array of expected table cell contents. + */ + protected function checkOverrideInfo(array $expected) { + $this->assertSession()->addressEquals(self::OVERRIDE_INFO); + foreach ($this->xpath('//tbody/tr') as $row) { + $expected_row = array_pop($expected); + foreach ($row->find('xpath', '/td') as $cell) { + $expected_cell = array_pop($expected_row); + $this->assertEquals($expected_cell, $cell->getText()); + } + } + } + +} diff --git a/tests/src/Functional/SymfonyMailerTestBase.php b/tests/src/Functional/SymfonyMailerTestBase.php index 5a87771..883b678 100644 --- a/tests/src/Functional/SymfonyMailerTestBase.php +++ b/tests/src/Functional/SymfonyMailerTestBase.php @@ -12,6 +12,9 @@ abstract class SymfonyMailerTestBase extends BrowserTestBase { use MailerTestTrait; + /** + * Human-readable string representing 'all'. + */ protected const TYPE_ALL = '*All*'; /**