diff --git a/administrator/components/com_admin/sql/updates/mysql/4.2.0-2022-05-22.sql b/administrator/components/com_admin/sql/updates/mysql/4.2.0-2022-05-22.sql
new file mode 100644
index 0000000000000..d9465dbcad1c3
--- /dev/null
+++ b/administrator/components/com_admin/sql/updates/mysql/4.2.0-2022-05-22.sql
@@ -0,0 +1,2 @@
+INSERT IGNORE INTO `#__mail_templates` (`template_id`, `extension`, `language`, `subject`, `body`, `htmlbody`, `attachments`, `params`) VALUES
+('plg_workflow_notification.mail', 'plg_workflow_notification', '', 'PLG_WORKFLOW_NOTIFICATION_EMAIL_ON_TRANSITION_SUBJECT', 'PLG_WORKFLOW_NOTIFICATION_EMAIL_ON_TRANSITION_MSG', '', '', '{"tags":["siteurl","title","user","transitionname","tostage","extratext"]}');
diff --git a/administrator/components/com_admin/sql/updates/postgresql/4.2.0-2022-05-22.sql b/administrator/components/com_admin/sql/updates/postgresql/4.2.0-2022-05-22.sql
new file mode 100644
index 0000000000000..67d73cb2b15e0
--- /dev/null
+++ b/administrator/components/com_admin/sql/updates/postgresql/4.2.0-2022-05-22.sql
@@ -0,0 +1,3 @@
+INSERT INTO "#__mail_templates" ("template_id", "extension", "language", "subject", "body", "htmlbody", "attachments", "params") VALUES
+('plg_workflow_notification.mail', 'plg_workflow_notification', '', 'PLG_WORKFLOW_NOTIFICATION_EMAIL_ON_TRANSITION_SUBJECT', 'PLG_WORKFLOW_NOTIFICATION_EMAIL_ON_TRANSITION_MSG', '', '', '{"tags":["siteurl","title","user","transitionname","tostage","extratext"]}')
+ON CONFLICT DO NOTHING;
diff --git a/administrator/language/en-GB/plg_workflow_notification.ini b/administrator/language/en-GB/plg_workflow_notification.ini
index f6ee4e71faeb7..2d89b085c83c3 100644
--- a/administrator/language/en-GB/plg_workflow_notification.ini
+++ b/administrator/language/en-GB/plg_workflow_notification.ini
@@ -9,6 +9,13 @@ PLG_WORKFLOW_NOTIFICATION="Workflow - Notification"
PLG_WORKFLOW_NOTIFICATION_ADDTEXT="The stage has changed"
PLG_WORKFLOW_NOTIFICATION_ADDTEXT_DESC="This text will be sent: Title [title], changed by [user], new state: [state]. You can add your own text to this message and you can localise the text by using a language key and creating a language override."
PLG_WORKFLOW_NOTIFICATION_ADDTEXT_LABEL="Additional Message Text"
+PLG_WORKFLOW_NOTIFICATION_EMAIL_ON_TRANSITION_MSG="Title: {TITLE}. Transition {TRANSITIONNAME} performed by {USER}. New state: {TOSTAGE}. {EXTRATEXT}"
+PLG_WORKFLOW_NOTIFICATION_EMAIL_ON_TRANSITION_SUBJECT="The status of {TITLE} has been changed"
+PLG_WORKFLOW_NOTIFICATION_MAIL_MAIL_DESC="Mail sent to the selected usergroup and/or users set for the transition"
+PLG_WORKFLOW_NOTIFICATION_MAIL_MAIL_TITLE="Workflow: Transition Mail"
+PLG_WORKFLOW_NOTIFICATION_NO_NOTIFICATION_TYPE_SELECTED="At least 1 notification type should be selected"
+PLG_WORKFLOW_NOTIFICATION_NO_RECIPIENTS_SELECTED="No users or usergroups have been selected as recipients."
+PLG_WORKFLOW_NOTIFICATION_NO_TITLE="Unknown title"
PLG_WORKFLOW_NOTIFICATION_ON_TRANSITION_MSG="Title: %1$s. Transition \"%2$s\" performed by %3$s. New state: %4$s."
PLG_WORKFLOW_NOTIFICATION_ON_TRANSITION_SUBJECT="The status of \"%s\" has been changed"
PLG_WORKFLOW_NOTIFICATION_NO_RECEIVER="No notifications sent as there are no users to send this message to."
@@ -16,6 +23,10 @@ PLG_WORKFLOW_NOTIFICATION_NO_TITLE="Unknown title"
PLG_WORKFLOW_NOTIFICATION_RECEIVERS_LABEL="Users"
PLG_WORKFLOW_NOTIFICATION_SENDMAIL_LABEL="Send Notification"
PLG_WORKFLOW_NOTIFICATION_SENT="Notifications sent"
+PLG_WORKFLOW_NOTIFICATION_TYPE_DESC="The private messages option will only notify users who have administrator access."
+PLG_WORKFLOW_NOTIFICATION_TYPE_EMAIL="Email"
+PLG_WORKFLOW_NOTIFICATION_TYPE_LABEL="Notification Type"
+PLG_WORKFLOW_NOTIFICATION_TYPE_MESSAGES="Private Messages"
PLG_WORKFLOW_NOTIFICATION_USERGROUP_DESC="The users in this usergroup get a notification if this transition has been performed."
PLG_WORKFLOW_NOTIFICATION_USERGROUP_LABEL="Usergroups"
PLG_WORKFLOW_NOTIFICATION_XML_DESCRIPTION="Send notification if a transition has been performed in a workflow."
diff --git a/installation/sql/mysql/supports.sql b/installation/sql/mysql/supports.sql
index 9632026c26087..3694900bdd80d 100644
--- a/installation/sql/mysql/supports.sql
+++ b/installation/sql/mysql/supports.sql
@@ -439,4 +439,5 @@ INSERT INTO `#__mail_templates` (`template_id`, `extension`, `language`, `subjec
('plg_system_tasknotification.fatal_recovery_mail', 'plg_system_tasknotification', '', 'PLG_SYSTEM_TASK_NOTIFICATION_FATAL_MAIL_SUBJECT', 'PLG_SYSTEM_TASK_NOTIFICATION_FATAL_MAIL_BODY', '', '', '{"tags": ["task_id", "task_title"]}'),
('plg_system_tasknotification.orphan_mail', 'plg_system_tasknotification', '', 'PLG_SYSTEM_TASK_NOTIFICATION_ORPHAN_MAIL_SUBJECT', 'PLG_SYSTEM_TASK_NOTIFICATION_ORPHAN_MAIL_BODY', '', '', '{"tags": ["task_id", "task_title"]}'),
('plg_system_tasknotification.success_mail', 'plg_system_tasknotification', '', 'PLG_SYSTEM_TASK_NOTIFICATION_SUCCESS_MAIL_SUBJECT', 'PLG_SYSTEM_TASK_NOTIFICATION_SUCCESS_MAIL_BODY', '', '', '{"tags":["task_id", "task_title", "exec_data_time", "task_output"]}'),
-('plg_multifactorauth_email.mail', 'plg_multifactorauth_email', '', 'PLG_MULTIFACTORAUTH_EMAIL_EMAIL_SUBJECT', 'PLG_MULTIFACTORAUTH_EMAIL_EMAIL_BODY', '', '', '{"tags":["code","sitename","siteurl","username","email","fullname"]}');
+('plg_multifactorauth_email.mail', 'plg_multifactorauth_email', '', 'PLG_MULTIFACTORAUTH_EMAIL_EMAIL_SUBJECT', 'PLG_MULTIFACTORAUTH_EMAIL_EMAIL_BODY', '', '', '{"tags":["code","sitename","siteurl","username","email","fullname"]}'),
+('plg_workflow_notification.mail', 'plg_workflow_notification', '', 'PLG_WORKFLOW_NOTIFICATION_EMAIL_ON_TRANSITION_SUBJECT', 'PLG_WORKFLOW_NOTIFICATION_EMAIL_ON_TRANSITION_MSG', '', '', '{"tags":["siteurl","title","user","transitionname","tostage","extratext"]}');
diff --git a/installation/sql/postgresql/supports.sql b/installation/sql/postgresql/supports.sql
index 9187e94e804f3..c38b8f595caac 100644
--- a/installation/sql/postgresql/supports.sql
+++ b/installation/sql/postgresql/supports.sql
@@ -450,4 +450,5 @@ INSERT INTO "#__mail_templates" ("template_id", "extension", "language", "subjec
('plg_system_tasknotification.fatal_recovery_mail', 'plg_system_tasknotification', '', 'PLG_SYSTEM_TASK_NOTIFICATION_FATAL_MAIL_SUBJECT', 'PLG_SYSTEM_TASK_NOTIFICATION_FATAL_MAIL_BODY', '', '', '{"tags": ["task_id", "task_title"]}'),
('plg_system_tasknotification.orphan_mail', 'plg_system_tasknotification', '', 'PLG_SYSTEM_TASK_NOTIFICATION_ORPHAN_MAIL_SUBJECT', 'PLG_SYSTEM_TASK_NOTIFICATION_ORPHAN_MAIL_BODY', '', '', '{"tags": ["task_id", "task_title"]}'),
('plg_system_tasknotification.success_mail', 'plg_system_tasknotification', '', 'PLG_SYSTEM_TASK_NOTIFICATION_SUCCESS_MAIL_SUBJECT', 'PLG_SYSTEM_TASK_NOTIFICATION_SUCCESS_MAIL_BODY', '', '', '{"tags":["task_id", "task_title", "exec_data_time", "task_output"]}'),
-('plg_multifactorauth_email.mail', 'plg_multifactorauth_email', '', 'PLG_MULTIFACTORAUTH_EMAIL_EMAIL_SUBJECT', 'PLG_MULTIFACTORAUTH_EMAIL_EMAIL_BODY', '', '', '{"tags":["code","sitename","siteurl","username","email","fullname"]}');
+('plg_multifactorauth_email.mail', 'plg_multifactorauth_email', '', 'PLG_MULTIFACTORAUTH_EMAIL_EMAIL_SUBJECT', 'PLG_MULTIFACTORAUTH_EMAIL_EMAIL_BODY', '', '', '{"tags":["code","sitename","siteurl","username","email","fullname"]}'),
+('plg_workflow_notification.mail', 'plg_workflow_notification', '', 'PLG_WORKFLOW_NOTIFICATION_EMAIL_ON_TRANSITION_SUBJECT', 'PLG_WORKFLOW_NOTIFICATION_EMAIL_ON_TRANSITION_MSG', '', '', '{"tags":["siteurl","title","user","transitionname","tostage","extratext"]}');
diff --git a/plugins/workflow/notification/forms/action.xml b/plugins/workflow/notification/forms/action.xml
index 9387471d98026..6fce82a3f6209 100644
--- a/plugins/workflow/notification/forms/action.xml
+++ b/plugins/workflow/notification/forms/action.xml
@@ -13,6 +13,21 @@
+
+
+
+
+
+
'onContentPrepareForm',
- 'onWorkflowAfterTransition' => 'onWorkflowAfterTransition',
+ 'onContentPrepareForm' => 'onContentPrepareForm',
+ 'onWorkflowAfterTransition' => 'onWorkflowAfterTransition',
+ 'onContentBeforeSave' => 'onContentBeforeSave',
];
}
/**
- * The form event.
+ * Check if a transition is being saved and all fields are set correctly.
*
- * @param Form $form The form
- * @param stdClass $data The data
+ * @param Event $data The event data ($context, $table, $isNew, $data)
*
- * @return boolean
+ * @return boolean True if all necessary fields are filled.
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ */
+ public function onContentBeforeSave(Event $data): bool
+ {
+ $context = $data->getArgument(0);
+
+ if ($context !== 'com_workflow.transition') {
+ return true;
+ }
+
+ $values = $data->getArgument(3);
+
+ if ($values['options']['notification_send_mail'] === false) {
+ return true;
+ }
+
+ if (empty($values['options']['notification_type'])) {
+ throw new InvalidArgumentException(Text::_('PLG_WORKFLOW_NOTIFICATION_NO_NOTIFICATION_TYPE_SELECTED'));
+ }
+
+ if (
+ !isset($values['options']['notification_receivers']) && !isset($values['options']['notification_groups'])
+ || (empty($values['options']['notification_receivers']) && empty($values['options']['notification_groups']))
+ ) {
+ throw new InvalidArgumentException(Text::_('PLG_WORKFLOW_NOTIFICATION_NO_RECIPIENTS_SELECTED'));
+ }
+
+ return true;
+ }
+
+ /**
+ * The event contains two arguments:
+ *
+ * Form $form The form
+ * stdClass $data The data
+ *
+ * @param EventInterface $event The event data
+ *
+ * @return boolean
*
* @since 4.0.0
*/
- public function onContentPrepareForm(EventInterface $event)
+ public function onContentPrepareForm(EventInterface $event): bool
{
$form = $event->getArgument('0');
$data = $event->getArgument('1');
@@ -104,15 +149,23 @@ public function onContentPrepareForm(EventInterface $event)
/**
* Send a Notification to defined users a transition is performed
*
- * @param string $context The context for the content passed to the plugin.
- * @param array $pks A list of primary key ids of the content that has changed stage.
- * @param object $data Object containing data about the transition
+ * The event contains several arguments:
*
- * @return boolean
+ * string $context The context for the content passed to the plugin.
+ * string $extensionName The extension name throwing the trigger event
+ * array $pks A list of primary key ids of the content that has changed stage.
+ * object $data Object containing data about the transition
+ *
+ * @param WorkflowTransitionEvent $event The event data
+ *
+ * @return void
*
* @since 4.0.0
+ * @throws Exception
+ * @see sendMessages()
+ * @see sendEmail()
*/
- public function onWorkflowAfterTransition(WorkflowTransitionEvent $event)
+ public function onWorkflowAfterTransition(WorkflowTransitionEvent $event): void
{
$context = $event->getArgument('extension');
$extensionName = $event->getArgument('extensionName');
@@ -145,10 +198,11 @@ public function onWorkflowAfterTransition(WorkflowTransitionEvent $event)
// Prepare Language for messages
$defaultLanguage = ComponentHelper::getParams('com_languages')->get('administrator');
- $debug = $this->app->get('debug_lang');
+ $debug = $this->app->get('debug_lang');
$modelName = $component->getModelName($context);
- $model = $component->getMVCFactory()->createModel($modelName, $this->app->getName(), ['ignore_request' => true]);
+ $model = $component->getMVCFactory()
+ ->createModel($modelName, $this->app->getName(), ['ignore_request' => true]);
// Don't send the notification to the active user
$key = array_search($user->id, $userIds);
@@ -157,11 +211,6 @@ public function onWorkflowAfterTransition(WorkflowTransitionEvent $event)
unset($userIds[$key]);
}
- // Remove users with locked input box from the list of receivers
- if (!empty($userIds)) {
- $userIds = $this->removeLocked($userIds);
- }
-
// If there are no receivers, stop here
if (empty($userIds)) {
$this->app->enqueueMessage(Text::_('PLG_WORKFLOW_NOTIFICATION_NO_RECEIVER'), 'error');
@@ -169,63 +218,65 @@ public function onWorkflowAfterTransition(WorkflowTransitionEvent $event)
return;
}
- // Get the model for private messages
- $model_message = $this->app->bootComponent('com_messages')
- ->getMVCFactory()->createModel('Message', 'Administrator');
-
// Get the title of the stage
- $model_stage = $this->app->bootComponent('com_workflow')
+ $modelStage = $this->app->bootComponent('com_workflow')
->getMVCFactory()->createModel('Stage', 'Administrator');
- $toStage = $model_stage->getItem($transition->to_stage_id)->title;
+ $toStage = $modelStage->getItem($transition->to_stage_id)->title;
// Get the name of the transition
- $model_transition = $this->app->bootComponent('com_workflow')
+ $modelTransition = $this->app->bootComponent('com_workflow')
->getMVCFactory()->createModel('Transition', 'Administrator');
- $transitionName = $model_transition->getItem($transition->id)->title;
+ $transitionName = $modelTransition->getItem($transition->id)->title;
$hasGetItem = method_exists($model, 'getItem');
- $container = Factory::getContainer();
+ $container = Factory::getContainer();
+
+ // Load language for messaging
+ $lang = $container->get(LanguageFactoryInterface::class)
+ ->createLanguage($user->getParam('admin_language', $defaultLanguage), $debug);
+ $lang->load('plg_workflow_notification');
foreach ($pks as $pk) {
// Get the title of the item which has changed, unknown as fallback
$title = Text::_('PLG_WORKFLOW_NOTIFICATION_NO_TITLE');
if ($hasGetItem) {
- $item = $model->getItem($pk);
+ $item = $model->getItem($pk);
$title = !empty($item->title) ? $item->title : $title;
}
+ $notificationTypes = $transition->options['notification_type'] ?? [];
+
+ if (!is_array($notificationTypes)) {
+ $notificationTypes = (array) $notificationTypes;
+ }
+
// Send Email to receivers
- foreach ($userIds as $user_id) {
- $receiver = $container->get(UserFactoryInterface::class)->loadUserById($user_id);
-
- if ($receiver->authorise('core.manage', 'com_message')) {
- // Load language for messaging
- $lang = $container->get(LanguageFactoryInterface::class)
- ->createLanguage($user->getParam('admin_language', $defaultLanguage), $debug);
- $lang->load('plg_workflow_notification');
- $messageText = sprintf(
- $lang->_('PLG_WORKFLOW_NOTIFICATION_ON_TRANSITION_MSG'),
- $title,
- $lang->_($transitionName),
- $user->name,
- $lang->_($toStage)
- );
-
- if (!empty($transition->options['notification_text'])) {
- $messageText .= '
' . htmlspecialchars($lang->_($transition->options['notification_text']));
- }
+ foreach ($userIds as $userId) {
+ $recipient = $container->get(UserFactoryInterface::class)->loadUserById($userId);
+ $extraText = '';
- $message = [
- 'id' => 0,
- 'user_id_to' => $receiver->id,
- 'subject' => sprintf($lang->_('PLG_WORKFLOW_NOTIFICATION_ON_TRANSITION_SUBJECT'), $title),
- 'message' => $messageText,
- ];
+ if (!empty($transition->options['notification_text'])) {
+ $extraText = htmlspecialchars($lang->_($transition->options['notification_text']));
+ }
- $model_message->save($message);
+ foreach ($notificationTypes as $type) {
+ try {
+ $functionName = 'send' . ucfirst($type);
+ $this->$functionName(
+ $recipient,
+ $user->name,
+ $title,
+ $lang->_($transitionName),
+ $lang->_($toStage),
+ $lang,
+ $extraText
+ );
+ } catch (Exception $exception) {
+ $this->app->enqueueMessage($exception->getMessage(), 'error');
+ }
}
}
}
@@ -233,16 +284,111 @@ public function onWorkflowAfterTransition(WorkflowTransitionEvent $event)
$this->app->enqueueMessage(Text::_('PLG_WORKFLOW_NOTIFICATION_SENT'), 'message');
}
+ /**
+ * Send a message to com_messages when a stage changes transition.
+ *
+ * @param \Joomla\CMS\User\User $recipient The user receiving the message
+ * @param string $user The user making the transition
+ * @param string $title The title of the item transitioned
+ * @param string $transitionName The name of the transition executed
+ * @param string $toStage The stage moving to
+ * @param Language $language The language to use for translating the message
+ * @param string $extraText The additional text to add to the end of the message
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function sendMessages(
+ \Joomla\CMS\User\User $recipient,
+ string $user,
+ string $title,
+ string $transitionName,
+ string $toStage,
+ Language $language,
+ string $extraText
+ ): void {
+ if ($recipient->authorise('core.manage', 'com_message')) {
+ // Get the model for private messages
+ $modelMessage = $this->app->bootComponent('com_messages')
+ ->getMVCFactory()->createModel('Message', 'Administrator');
+
+ // Remove users with locked input box from the list of receivers
+ if ($this->isMessageBoxLocked($recipient->id)) {
+ return;
+ }
+
+ $subject = sprintf($language->_('PLG_WORKFLOW_NOTIFICATION_ON_TRANSITION_SUBJECT'), $title);
+ $messageText = sprintf(
+ $language->_('PLG_WORKFLOW_NOTIFICATION_ON_TRANSITION_MSG'),
+ $title,
+ $transitionName,
+ $user,
+ $toStage
+ );
+ $messageText .= '
' . $extraText;
+
+ $message = [
+ 'id' => 0,
+ 'user_id_to' => $recipient->id,
+ 'subject' => $subject,
+ 'message' => $messageText,
+ ];
+
+ $modelMessage->save($message);
+ }
+ }
+
+ /**
+ * Send an email when a stage changes transition.
+ *
+ * @param \Joomla\CMS\User\User $recipient The user receiving the message
+ * @param string $user The user making the transition
+ * @param string $title The title of the item transitioned
+ * @param string $transitionName The name of the transition executed
+ * @param string $toStage The stage moving to
+ * @param Language $language The language to use for translating the message
+ * @param string $extraText The additional text to add to the end of the message
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ * @throws \PHPMailer\PHPMailer\Exception
+ */
+ private function sendEmail(
+ \Joomla\CMS\User\User $recipient,
+ string $user,
+ string $title,
+ string $transitionName,
+ string $toStage,
+ Language $language,
+ string $extraText
+ ): void {
+ $data = [];
+ $data['siteurl'] = Uri::base();
+ $data['title'] = $title;
+ $data['user'] = $user;
+ $data['transitionName'] = $transitionName;
+ $data['toStage'] = $toStage;
+ $data['extraText'] = $extraText;
+
+ $mailer = new MailTemplate('plg_workflow_notification.mail', $this->app->getLanguage()->getTag());
+ $mailer->addTemplateData($data);
+ $mailer->addRecipient($recipient->email);
+ $mailer->send();
+ }
+
/**
* Get user_ids of receivers
*
- * @param object $data Object containing data about the transition
+ * @param stdClass $data Object containing data about the transition
*
* @return array $userIds The receivers
*
* @since 4.0.0
+ * @throws Exception
*/
- private function getUsersFromGroup($data): array
+ private function getUsersFromGroup(stdClass $data): array
{
$users = [];
@@ -282,13 +428,13 @@ private function getUsersFromGroup($data): array
/**
* Check if the current plugin should execute workflow related activities
*
- * @param string $context
+ * @param string $context The context to validate
*
* @return boolean
*
* @since 4.0.0
*/
- protected function isSupported($context)
+ protected function isSupported(string $context): bool
{
if (!$this->checkAllowedAndForbiddenlist($context)) {
return false;
@@ -314,18 +460,18 @@ protected function isSupported($context)
}
/**
- * Remove receivers who have locked their message inputbox
+ * Check if the message box is locked
*
- * @param array $userIds The userIds which must be checked
+ * @param int $userId The user ID which must be checked
*
- * @return array users with active message input box
+ * @return boolean Return status of message box is locked
*
* @since 4.0.0
*/
- private function removeLocked(array $userIds): array
+ private function isMessageBoxLocked(int $userId): bool
{
- if (empty($userIds)) {
- return [];
+ if (empty($userId)) {
+ return false;
}
// Check for locked inboxes would be better to have _cdf settings in the user_object or a filter in users model
@@ -333,12 +479,10 @@ private function removeLocked(array $userIds): array
$query->select($this->db->quoteName('user_id'))
->from($this->db->quoteName('#__messages_cfg'))
- ->whereIn($this->db->quoteName('user_id'), $userIds)
+ ->where($this->db->quoteName('user_id') . ' = ' . $userId)
->where($this->db->quoteName('cfg_name') . ' = ' . $this->db->quote('locked'))
->where($this->db->quoteName('cfg_value') . ' = 1');
- $locked = $this->db->setQuery($query)->loadColumn();
-
- return array_diff($userIds, $locked);
+ return (int) $this->db->setQuery($query)->loadResult() === $userId;
}
}