From df5b7fd66e9da5964683b51069d15fc7dc5219ff Mon Sep 17 00:00:00 2001 From: rxu Date: Mon, 5 Jun 2023 22:47:14 +0700 Subject: [PATCH] [ticket/17135] Refactor messenger code to services [ci skip] PHPBB3-17135 --- phpBB/config/default/container/services.yml | 1 + .../default/container/services_messenger.yml | 46 ++ phpBB/phpbb/messenger/method/base.php | 408 +++++++++++++ phpBB/phpbb/messenger/method/email.php | 576 ++++++++++++++++++ .../messenger/method/jabber.php} | 247 ++++++-- 5 files changed, 1213 insertions(+), 65 deletions(-) create mode 100644 phpBB/config/default/container/services_messenger.yml create mode 100644 phpBB/phpbb/messenger/method/base.php create mode 100644 phpBB/phpbb/messenger/method/email.php rename phpBB/{includes/functions_jabber.php => phpbb/messenger/method/jabber.php} (79%) diff --git a/phpBB/config/default/container/services.yml b/phpBB/config/default/container/services.yml index 8a4336e832..c09f21b5c6 100644 --- a/phpBB/config/default/container/services.yml +++ b/phpBB/config/default/container/services.yml @@ -19,6 +19,7 @@ imports: - { resource: services_http.yml } - { resource: services_language.yml } - { resource: services_mention.yml } + - { resource: services_messenger.yml } - { resource: services_migrator.yml } - { resource: services_mimetype_guesser.yml } - { resource: services_module.yml } diff --git a/phpBB/config/default/container/services_messenger.yml b/phpBB/config/default/container/services_messenger.yml new file mode 100644 index 0000000000..659a4fa558 --- /dev/null +++ b/phpBB/config/default/container/services_messenger.yml @@ -0,0 +1,46 @@ +parameters: + core.messenger_queue_file: '%core.cache_dir%queue.%core.php_ext%' + +services: + messenger.method_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: messenger.method, class_name_aware: true } + + messenger.method_base: + class: phpbb\messenger\method\base + shared: false + arguments: + - '@config' + - '@dispatcher' + - '@language' + - '@log' + - '@request' + - '@user' + - '@messenger.queue' + + messenger.method_email: + class: phpbb\messenger\method\email + shared: false + parent: messenger.method_base + calls: + - [init, []] + - [set_transport, []] + + messenger.method_jabber: + class: phpbb\messenger\method\jabber + shared: false + parent: messenger.method_base + calls: + - [init, []] + + messenger.queue: + class: phpbb\messenger\queue + shared: false + arguments: + - '@config' + - '@dispatcher' + - '@messenger.method_collection' + - '%core.messenger_queue_file%' diff --git a/phpBB/phpbb/messenger/method/base.php b/phpBB/phpbb/messenger/method/base.php new file mode 100644 index 0000000000..507b5beea8 --- /dev/null +++ b/phpBB/phpbb/messenger/method/base.php @@ -0,0 +1,408 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\messenger\method; + +use phpbb\config\config; +use phpbb\event\dispatcher; +use phpbb\language\language; +use phpbb\log\log_interface; +use phpbb\request\request; +use phpbb\messenger\queue; +use phpbb\template\template; +use phpbb\user; + +/** + * Messenger base class + */ +class base +{ + /** @var array */ + protected $additional_headers = []; + + /** @var array */ + protected $addresses = []; + + /** @var config */ + protected $config; + + /** @var dispatcher */ + protected $dispatcher; + + /** @var language */ + protected $language; + + /** @var log_interface */ + protected $log; + + /** @var string */ + protected $msg = ''; + + /** @var queue */ + protected $queue; + + /** @var request */ + protected $request; + + /** @var string */ + protected $subject = ''; + + /** @var template */ + protected $template; + + /** @var bool */ + protected $use_queue = true; + + /** @var user */ + protected $user; + + /** + * Messenger base class constructor + * + * @param config $config + * @param dispatcher $dispatcher + * @param language $language + * @param log_interface $log + * @param request $request + * @param user $user + * @param queue $queue + */ + function __construct(config $config, dispatcher $dispatcher, language $language, log_interface $log, request $request, user $user, queue $queue) + { + $this->config = $config; + $this->dispatcher = $dispatcher; + $this->language = $language; + $this->log = $log; + $this->request = $request; + $this->user = $user; + $this->queue = $queue; + + $this->set_use_queue(); + } + + /** + * Sets the use of messenger queue flag + * + * @return void + */ + public function set_use_queue($use_queue = true) + { + $this->use_queue = $use_queue; + } + + /** + * Resets all the data (address, template file, etc etc) to default + * + * @return void + */ + public function reset() + { + $this->addresses = []; + $this->msg = ''; + } + + /** + * Set addresses for to/im as available + * + * @param array $user User row + * @return void + */ + public function set_addresses($user) + { + } + + /** + * Set up subject for mail + * + * @param string $subject Email subject + * @return void + */ + public function subject($subject = '') + { + $this->subject = $subject; + } + + /** + * Set email template to use + * + * @param string $template_file Email template file name + * @param string $template_lang Email template language + * @param string $template_path Email template path + * @param string $template_dir_prefix Email template directory prefix + * + * @return bool + */ + public function template($template_file, $template_lang = '', $template_path = '', $template_dir_prefix = '') + { + $template_dir_prefix = (!$template_dir_prefix || $template_dir_prefix[0] === '/') ? $template_dir_prefix : '/' . $template_dir_prefix; + + $this->setup_template(); + + if (!trim($template_file)) + { + trigger_error('No template file for emailing set.', E_USER_ERROR); + } + + if (!trim($template_lang)) + { + // fall back to board default language if the user's language is + // missing $template_file. If this does not exist either, + // $this->template->set_filenames will do a trigger_error + $template_lang = basename($this->config['default_lang']); + } + + $ext_template_paths = [ + [ + 'name' => $template_lang . '_email', + 'ext_path' => 'language/' . $template_lang . '/email' . $template_dir_prefix, + ], + ]; + + if ($template_path) + { + $template_paths = [ + $template_path . $template_dir_prefix, + ]; + } + else + { + $template_path = (!empty($this->user->lang_path)) ? $this->user->lang_path : $this->root_path . 'language/'; + $template_path .= $template_lang . '/email'; + + $template_paths = [ + $template_path . $template_dir_prefix, + ]; + + $board_language = basename($this->config['default_lang']); + + // we can only specify default language fallback when the path is not a custom one for which we + // do not know the default language alternative + if ($template_lang !== $board_language) + { + $fallback_template_path = (!empty($this->user->lang_path)) ? $this->user->lang_path : $this->root_path . 'language/'; + $fallback_template_path .= $board_language . '/email'; + + $template_paths[] = $fallback_template_path . $template_dir_prefix; + + $ext_template_paths[] = [ + 'name' => $board_language . '_email', + 'ext_path' => 'language/' . $board_language . '/email' . $template_dir_prefix, + ]; + } + // If everything fails just fall back to en template + if ($template_lang !== 'en' && $board_language !== 'en') + { + $fallback_template_path = (!empty($this->user->lang_path)) ? $this->user->lang_path : $this->root_path . 'language/'; + $fallback_template_path .= 'en/email'; + + $template_paths[] = $fallback_template_path . $template_dir_prefix; + + $ext_template_paths[] = [ + 'name' => 'en_email', + 'ext_path' => 'language/en/email' . $template_dir_prefix, + ]; + } + } + + $this->set_template_paths($ext_template_paths, $template_paths); + + $this->template->set_filenames([ + 'body' => $template_file . '.txt', + ]); + + return true; + } + + /** + * Assign variables to email template + * + * @param array $vars Array of VAR => VALUE to assign to email template + * @return void + */ + public function assign_vars($vars) + { + $this->setup_template(); + + $this->template->assign_vars($vars); + } + + /** + * Assign block of variables to email template + * + * @param string $blockname Template block name + * @param array $vars Array of VAR => VALUE to assign to email template block + * @return void + */ + public function assign_block_vars($blockname, $vars) + { + $this->setup_template(); + + $this->template->assign_block_vars($blockname, $vars); + } + + /** + * Prepare message before sending out to the recipients + * + * @return void + */ + public function prepare_message() + { + // We add some standard variables we always use, no need to specify them always + $this->assign_vars([ + 'U_BOARD' => generate_board_url(), + 'EMAIL_SIG' => str_replace('
', "\n", "-- \n" . html_entity_decode($this->config['board_email_sig'], ENT_COMPAT)), + 'SITENAME' => html_entity_decode($this->config['sitename'], ENT_COMPAT), + ]); + + $subject = $this->email->getSubject(); + $template = $this->template; + /** + * Event to modify the template before parsing + * + * @event core.modify_notification_template + * @var string subject The message subject + * @var \phpbb\template\template template The (readonly) template object + * @since 3.2.4-RC1 + * @changed 4.0.0-a1 Added vars: email. Removed vars: method, break. + */ + $vars = ['subject', 'template']; + extract($this->dispatcher->trigger_event('core.modify_notification_template', compact($vars))); + + // Parse message through template + $message = trim($this->template->assign_display('body')); + + /** + * Event to modify notification message text after parsing + * + * @event core.modify_notification_message + * @var string message The message text + * @var string subject The message subject + * @since 3.1.11-RC1 + * @changed 4.0.0-a1 Removed vars: method, break. + */ + $vars = ['message', 'subject']; + extract($this->dispatcher->trigger_event('core.modify_notification_message', compact($vars))); + + $this->subject = $subject; + $this->msg = $message; + unset($subject, $message, $template); + + // Because we use \n for newlines in the body message we need to fix line encoding errors for those admins who uploaded email template files in the wrong encoding + $this->msg = str_replace("\r\n", "\n", $this->msg); + + // We now try and pull a subject from the email body ... if it exists, + // do this here because the subject may contain a variable + $drop_header = ''; + $match = []; + if (preg_match('#^(Subject):(.*?)$#m', $this->msg, $match)) + { + $this->subject = (trim($match[2]) != '') ? trim($match[2]) : (($this->subject != '') ? $this->subject : $this->language->lang('NO_EMAIL_SUBJECT')); + $drop_header .= '[\r\n]*?' . preg_quote($match[0], '#'); + } + else + { + $this->subject = (($this->subject != '') ? $this->subject : $this->language->lang('NO_EMAIL_SUBJECT')); + } + + if (preg_match('#^(List-Unsubscribe):(.*?)$#m', $this->msg, $match)) + { + $drop_header .= '[\r\n]*?' . preg_quote($match[0], '#'); + $this->additional_headers[$match[1]] = trim($match[2]); + } + + if ($drop_header) + { + $this->msg = trim(preg_replace('#' . $drop_header . '#s', '', $this->msg)); + } + } + + /** + * Add error message to log + * + * @param string $type Error type: EMAIL / etc + * @param string $msg Error message text + * @return void + */ + public function error($type, $msg) + { + // Session doesn't exist, create it + if (!isset($this->user->session_id) || $this->user->session_id === '') + { + $this->user->session_begin(); + } + + $calling_page = html_entity_decode($this->request->server('PHP_SELF'), ENT_COMPAT); + $message = '' . $type . '
' . htmlspecialchars($calling_page, ENT_COMPAT) . '

' . $msg . '
'; + $this->log->add('critical', $this->user->data['user_id'], $this->user->ip, 'LOG_ERROR_' . $type, false, [$message]); + } + + /** + * Save message data to the messemger file queue + * @return void + */ + public function save_queue() + { + $this->queue->save(); + return; + } + + /** + * Setup template engine + * + * @return void + */ + protected function setup_template() + { + if ($this->template instanceof \phpbb\template\template) + { + return; + } + + $template_environment = new \phpbb\template\twig\environment( + $this->config, + $this->phpbb_container->get('filesystem'), + $this->phpbb_container->get('path_helper'), + $this->phpbb_container->getParameter('core.template.cache_path'), + $this->phpbb_container->get('ext.manager'), + new \phpbb\template\twig\loader(), + $this->dispatcher, + [] + ); + $template_environment->setLexer($this->phpbb_container->get('template.twig.lexer')); + + $this->template = new \phpbb\template\twig\twig( + $this->phpbb_container->get('path_helper'), + $this->config, + new \phpbb\template\context(), + $template_environment, + $this->phpbb_container->getParameter('core.template.cache_path'), + $this->user, + $this->phpbb_container->get('template.twig.extensions.collection'), + $this->phpbb_container->get('ext.manager') + ); + } + + /** + * Set template paths to load + * + * @param string $path_name Email template path name + * @param string $paths Email template paths + * @return void + */ + protected function set_template_paths($path_name, $paths) + { + $this->setup_template(); + + $this->template->set_custom_style($path_name, $paths); + } +} diff --git a/phpBB/phpbb/messenger/method/email.php b/phpBB/phpbb/messenger/method/email.php new file mode 100644 index 0000000000..039591feb7 --- /dev/null +++ b/phpBB/phpbb/messenger/method/email.php @@ -0,0 +1,576 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\messenger\method; + +use Symfony\Component\Mailer\Transport; +use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Header\Headers; + +/** + * Messenger class + */ +class email extends base +{ + /** @var array */ + private const PRIORITY_MAP = [ + Email::PRIORITY_HIGHEST => 'Highest', + Email::PRIORITY_HIGH => 'High', + Email::PRIORITY_NORMAL => 'Normal', + Email::PRIORITY_LOW => 'Low', + Email::PRIORITY_LOWEST => 'Lowest', + ]; + + /** @var array */ + private const HEADER_CLASS_MAP = [ + 'date' => DateHeader::class, + 'from' => MailboxListHeader::class, + 'sender' => MailboxHeader::class, + 'reply-to' => MailboxListHeader::class, + 'to' => MailboxListHeader::class, + 'cc' => MailboxListHeader::class, + 'bcc' => MailboxListHeader::class, + 'message-id' => IdentificationHeader::class, + 'in-reply-to' => UnstructuredHeader::class, + 'references' => UnstructuredHeader::class, + 'return-path' => PathHeader::class, + ]; + + /** + * @var string + * + * Symfony Mailer transport DSN + */ + protected $dsn = ''; + + /** @var string */ + protected $from; + + /** @var Symfony\Component\Mime\Header\Headers */ + protected $headers; + + /** + * @var int + * + * Possible values are: + * Email::PRIORITY_HIGHEST + * Email::PRIORITY_HIGH + * Email::PRIORITY_NORMAL + * Email::PRIORITY_LOW + * Email::PRIORITY_LOWEST + */ + protected $mail_priority = Email::PRIORITY_NORMAL; + + /** @var queue */ + protected $queue; + + /** @var string */ + protected $replyto = ''; + + /** @var Symfony\Component\Mailer\Transport */ + protected $transport; + + /** + * Check if the messenger method is enabled + * @return void + */ + public function is_enabled() + { + return (bool) $this->config['email_enable']; + } + + /** + * Inits/resets the data to default + * + * @return void + */ + public function init() + { + $this->email = new Email(); + $this->headers = $this->email->getHeaders(); + $this->msg = $this->replyto = $this->from = ''; + $this->mail_priority = Email::PRIORITY_NORMAL; + } + + /** + * Sets the use of messenger queue flag + * + * @return void + */ + public function set_use_queue($use_queue = true) + { + $this->use_queue = !$this->config['email_package_size'] ? false : $use_queue; + } + + /** + * Set address as available + * + * @param array $user User row + * @return void + */ + public function set_addresses($user) + { + if (isset($user['user_email']) && $user['user_email']) + { + $this->to($user['user_email'], $user['username'] ?: ''); + } + } + + /** + * Sets email address to send to + * + * @param string $address Email "To" recipient address + * @param string $realname Email "To" recipient name + * @return void + */ + public function to($address, $realname = '') + { + if (!$address = trim($address)) + { + return; + } + + // If empty sendmail_path on windows, PHP changes the to line + $windows_empty_sendmail_path = !$this->config['smtp_delivery'] && DIRECTORY_SEPARATOR == '\\'; + + $to = new Address($address, $windows_empty_sendmail_path ? '' : trim($realname)); + $this->email->getTo() ? $this->email->addTo($to) : $this->email->to($to); + } + + /** + * Sets cc address to send to + * + * @param string $address Email carbon copy recipient address + * @param string $realname Email carbon copy recipient name + * @return void + */ + public function cc($address, $realname = '') + { + if (!$address = trim($address)) + { + return; + } + + $cc = new Address($address, trim($realname)); + $this->email->getCc() ? $this->email->addCc($to) : $this->email->cc($to); + } + + /** + * Sets bcc address to send to + * + * @param string $address Email black carbon copy recipient address + * @param string $realname Email black carbon copy recipient name + * @return void + */ + public function bcc($address, $realname = '') + { + if (!$address = trim($address)) + { + return; + } + + $bcc = new Address($address, trim($realname)); + $this->email->getBcc() ? $this->email->addBcc($to) : $this->email->bcc($to); + } + + /** + * Set the reply to address + * + * @param string $address Email "Reply to" address + * @return void + */ + public function replyto($address) + { + $this->replyto = new Address(trim($address)); + $this->email->getReplyTo() ? $this->email->addReplyTo($this->replyto) : $this->email->replyTo($this->replyto); + } + + /** + * Set the from address + * + * @param string $address Email "from" address + * @return void + */ + public function from($address) + { + $this->from = new Address(trim($address)); + $this->email->getFrom() ? $this->email->addFrom($this->from) : $this->email->from($this->from); + } + + /** + * Set up subject for mail + * + * @param string $subject Email subject + * @return void + */ + public function subject($subject = '') + { + parent::subject(trim($subject)); + $this->email->subject($this->subject); + } + + /** + * Set up extra mail headers + * + * @param string $header_name Email header name + * @param string $header_value Email header body + * @return void + */ + public function header($header_name, $header_value) + { + $header_name = trim($header_name); + $header_value = trim($header_value); + + // addMailboxListHeader() requires value to be array + if ($this->get_header_method($header_name) == 'addMailboxListHeader') + { + $header_value = [$header_value]; + } + $this->headers->addHeader($header_name, $header_value); + } + + /** + * Adds X-AntiAbuse headers + * + * @param \phpbb\config\config $config Config object + * @param \phpbb\user $user User object + * @return void + */ + public function anti_abuse_headers($config, $user) + { + $this->header('X-AntiAbuse', 'Board servername - ' . $config['server_name']); + $this->header('X-AntiAbuse', 'User_id - ' . $user->data['user_id']); + $this->header('X-AntiAbuse', 'Username - ' . $user->data['username']); + $this->header('X-AntiAbuse', 'User IP - ' . $user->ip); + } + + /** + * Set the email priority + * + * Possible values are: + * Email::PRIORITY_HIGHEST = 1 + * Email::PRIORITY_HIGH = 2 + * Email::PRIORITY_NORMAL = 3 + * Email::PRIORITY_LOW = 4 + * Email::PRIORITY_LOWEST = 5 + * + * @param int $priority Email priority level + * @return void + */ + public function set_mail_priority($priority = Email::PRIORITY_NORMAL) + { + $this->email->priority($priority); + } + + /** + * Add error message to log + * + * @param string $msg Error message text + * @return void + */ + public function error($msg) + { + $type = 'EMAIL/' . ($this->config['smtp_delivery']) ? 'SMTP' : 'PHP/mail()'; + parent::error($type, $msg); + } + + /** + * Save message data to the messenger file queue + * @return void + */ + public function save_queue() + { + if ($this->config['email_package_size'] && $this->use_queue && !empty($this->queue)) + { + $this->queue->save(); + return; + } + } + + /** + * Detect proper Header class method to add header + * + * @param string $name Email header name + * @return string + */ + protected function get_header_method(string $name) + { + $parts = explode('\\', self::HEADER_CLASS_MAP[strtolower($name)] ?? UnstructuredHeader::class); + $method = 'add'.ucfirst(array_pop($parts)); + if ('addUnstructuredHeader' === $method) + { + $method = 'addTextHeader'; + } + else if ('addIdentificationHeader' === $method) + { + $method = 'addIdHeader'; + } + + return $method; + } + + /** + * Set email headers + * + * @return bool + */ + protected function build_header() + { + $headers = []; + + $board_contact = $this->config['board_contact']; + if (empty($this->email->getReplyTo())) + { + $this->replyto($board_contact); + $headers['Reply-To'] = $this->replyto; + } + + if (empty($this->email->getFrom())) + { + $this->from($board_contact); + $headers['From'] = $this->from; + } + + $headers['Return-Path'] = new Address($this->config['board_email']); + $headers['Sender'] = new Address($this->config['board_email']); + $headers['X-Priority'] = sprintf('%d (%s)', $this->mail_priority, self::PRIORITY_MAP[$this->mail_priority]); + $headers['X-MSMail-Priority'] = self::PRIORITY_MAP[$this->mail_priority]; + $headers['X-Mailer'] = 'phpBB3'; + $headers['X-MimeOLE'] = 'phpBB3'; + $headers['X-phpBB-Origin'] = 'phpbb://' . str_replace(['http://', 'https://'], ['', ''], generate_board_url()); + + /** + * Event to modify email header entries + * + * @event core.modify_email_headers + * @var array headers Array containing email header entries + * @since 3.1.11-RC1 + */ + $vars = ['headers']; + extract($this->dispatcher->trigger_event('core.modify_email_headers', compact($vars))); + + foreach ($headers as $header => $value) + { + $this->header($header, $value); + } + + return true; + } + + /** + * Generates valid DSN for Symfony Mailer transport + * + * @param string $dsn Symfony Mailer transport DSN + * @return void + */ + public function set_dsn($dsn = '') + { + if (!empty($dsn)) + { + $this->dsn = $dsn; + } + else if ($this->config['smtp_delivery']) + { + if (empty($this->config['smtp_host'])) + { + $this->dsn = 'null://null'; + } + else + { + $user = urlencode($this->config['smtp_username']); + $password = urlencode($this->config['smtp_password']); + $smtp_host = urlencode($this->config['smtp_host']); + $smtp_port = $this->config['smtp_port']; + + $this->dsn = "smtp://$user:$password@$smtp_host:$smtp_port"; + } + } + else + { + $this->dsn = 'sendmail://default'; + } + } + + /** + * Get Symfony Mailer transport DSN + * + * @return void + */ + public function get_dsn() + { + return $this->dsn; + } + + /** + * Generates a valid transport to send email + * + * @return void + */ + public function set_transport() + { + if (empty($this->dsn)) + { + $this->set_dsn(); + } + + $this->transport = Transport::fromDsn($this->dsn); + + if ($this->config['smtp_delivery'] && !in_array($this->dsn, ['null://null', 'sendmail://default'])) + { + // Set ssl context options, see http://php.net/manual/en/context.ssl.php + $options['ssl'] = [ + 'verify_peer' => (bool) $this->config['smtp_verify_peer'], + 'verify_peer_name' => (bool) $this->config['smtp_verify_peer_name'], + 'allow_self_signed' => (bool) $this->config['smtp_allow_self_signed'], + ]; + $this->transport->getStream()->setStreamOptions($options); + } + } + + /** + * Get mailer transport object + * + * @return Symfony\Component\Mailer\Transport Symfony Mailer transport object + */ + public function get_transport() + { + return $this->transport; + } + + /** + * Send out emails + * + * @return bool + */ + protected function send() + { + $this->prepare_message(); + + $contact_name = html_entity_decode($this->config['board_contact_name'], ENT_COMPAT); + $board_contact = trim($this->config['board_contact']); + + $this->email->subject($this->subject); + $this->email->text($this->msg); + + $subject = $this->subject; + $msg = $this->msg; + $email = $this->email; + /** + * Event to send message via external transport + * + * @event core.notification_message_email + * @var string subject The message subject + * @var string msg The message text + * @var Symfony\Component\Mime\Email email The Symfony Email object + * @since 3.2.4-RC1 + * @changed 4.0.0-a1 Added vars: email. Removed vars: addresses, break + */ + $vars = [ + 'subject', + 'msg', + 'email', + ]; + extract($this->dispatcher->trigger_event('core.notification_message_email', compact($vars))); + + if (empty($this->email->getReplyto())) + { + $this->replyto($board_contact); + } + + if (empty($this->email->getFrom())) + { + $this->from($board_contact); + } + + // Build headers + foreach ($this->additional_headers as $header_name => $header_value) + { + $this->header($header_name, $header_value); + } + $this->build_header(); + + // Send message ... + if (!$this->use_queue) + { + $mailer = new Mailer($this->transport); + + $subject = $this->subject; + $msg = $this->msg; + $headers = $this->headers; + $email = $this->email; + /** + * Modify data before sending out emails with PHP's mail function + * + * @event core.phpbb_mail_before + * @var Symfony\Component\Mime\Email email The Symfony Email object + * @var string subject The message subject + * @var string msg The message text + * @var string headers The email headers + * @since 3.3.6-RC1 + * @changed 4.0.0-a1 Added vars: email. Removed vars: to, eol, additional_parameters. + */ + $vars = [ + 'email', + 'subject', + 'msg', + 'headers', + ]; + extract($this->dispatcher->trigger_event('core.phpbb_mail_before', compact($vars))); + + $this->subject = $subject; + $this->msg = $msg; + $this->headers = $headers; + $this->email = $email; + + try + { + $mailer->send($this->email); + } + catch (TransportExceptionInterface $e) + { + $this->error($e->getDebug()); + return false; + } + + /** + * Execute code after sending out emails with PHP's mail function + * + * @event core.phpbb_mail_after + * @var Symfony\Component\Mime\Email email The Symfony Email object + * @var string subject The message subject + * @var string msg The message text + * @var string headers The email headers + * @since 3.3.6-RC1 + * @changed 4.0.0-a1 Added vars: email. Removed vars: to, eol, additional_parameters, $result. + */ + $vars = [ + 'email', + 'subject', + 'msg', + 'headers', + ]; + extract($this->dispatcher->trigger_event('core.phpbb_mail_after', compact($vars))); + } + else + { + $this->queue->init('email', $this->config['email_package_size']); + $this->queue->put('email', [ + 'email' => $this->email, + ]); + } + + return true; + } +} diff --git a/phpBB/includes/functions_jabber.php b/phpBB/phpbb/messenger/method/jabber.php similarity index 79% rename from phpBB/includes/functions_jabber.php rename to phpBB/phpbb/messenger/method/jabber.php index 282e6105a2..2e7f2040da 100644 --- a/phpBB/includes/functions_jabber.php +++ b/phpBB/phpbb/messenger/method/jabber.php @@ -1,35 +1,30 @@ -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\messenger\method; /** -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ - exit; -} - -/** -* -* Jabber class from Flyspray project -* -* @version class.jabber2.php 1595 2008-09-19 (0.9.9) -* @copyright 2006 Flyspray.org -* @author Florian Schmitz (floele) -* -* Only slightly modified by Acyd Burn -*/ -class jabber + * + * Based on Jabber class from Flyspray project + * + * @version class.jabber2.php 1595 2008-09-19 (0.9.9) + * @copyright 2006 Flyspray.org + * @author Florian Schmitz (floele) + * + * Slightly modified by Acyd Burn (2006) + * Refactored to a service (2023) + */ +class jabber extends base { /** @var string */ protected $connect_server; @@ -70,6 +65,9 @@ class jabber /** @var int */ protected $timeout = 10; + /** @var array */ + protected $to = []; + /** @var bool */ protected $use_ssl = false; @@ -80,25 +78,49 @@ class jabber private const STREAM_CLOSE_HANDSHAKE = ''; /** - * Jabber class constructor + * Set initial parameter values + * To init correctly, username() call should go before server() + * and ssl() call should go before port() and stream_options() calls. * - * Use (username() call should go before server() - * and ssl() call should go before port() and stream_options()): - * - * new jabber() - * -> username($username) - * -> password($password) - * -> ssl($use_ssl) - * -> server($server) - * -> port($port) - * -> stream_options( + * Example: + * $this->username($username) + * ->password($password) + * ->ssl($use_ssl) + * ->server($server) + * ->port($port) + * ->stream_options( * 'verify_peer' => true, * 'verify_peer_name' => true, * 'allow_self_signed' => false, * ); + * + * @return void */ - function __construct() + public function init() { + $this->username($this->config['jab_username']) + ->password($this->config['jab_password']) + ->ssl((bool) $this->config['jab_use_ssl']) + ->server($this->config['jab_host']) + ->port($this->config['jab_port']) + ->stream_options['ssl'] = [ + 'verify_peer' => $this->config['jab_verify_peer'], + 'verify_peer_name' => $this->config['jab_verify_peer_name'], + 'allow_self_signed' => $this->config['jab_allow_self_signed'], + ]; + } + + /** + * Check if the messenger method is enabled + * @return void + */ + public function is_enabled() + { + return + empty($this->config['jab_enable']) || + empty($this->config['jab_host']) || + empty($this->config['jab_username']) || + empty($this->config['jab_password']); } /** @@ -112,12 +134,8 @@ class jabber { if ($this->use_ssl) { - // Set default stream options and change it if needed - $this->stream_options['ssl'] = array_merge([ - 'verify_peer' => true, - 'verify_peer_name' => true, - 'allow_self_signed' => false, - ], $options); + // Change default stream options if needed + $this->stream_options['ssl'] = array_merge($this->stream_options['ssl'], $options); } return $this; @@ -131,7 +149,7 @@ class jabber */ public function password($password = '') { - $this->password = $password; + $this->password = html_entity_decode($password, ENT_COMPAT); return $this; } @@ -263,8 +281,8 @@ class jabber if ($this->open_socket($this->connect_server, $this->port)) { - $this->send("\n"); - $this->send("\n"); + $this->send_xml("\n"); + $this->send_xml("\n"); } else { @@ -331,6 +349,105 @@ class jabber return $this->response($this->features); } + /** + * Set address as available + * + * @param array $user User row + * @return void + */ + public function set_addresses($user) + { + if (isset($user['user_jabber']) && $user['user_jabber']) + { + $this->to($user['user_jabber'], (isset($user['username']) ? $user['username'] : '')); + } + } + + /** + * Sets jabber contact to send message to + * + * @param string $address Jabber "To" recipient address + * @param string $realname Jabber "To" recipient name + * @return void + */ + public function to($address, $realname = '') + { + // IM-Addresses could be empty + if (!trim($address)) + { + return; + } + + $pos = !empty($this->to) ? count($this->to) : 0; + $this->to[$pos]['uid'] = trim($address); + $this->to[$pos]['name'] = trim($realname); + } + + /** + * Sets the use of messenger queue flag + * + * @return void + */ + public function set_use_queue($use_queue = true) + { + $this->use_queue = !$this->config['jab_package_size'] ? false : $use_queue; + } + + /** + * Send jabber message out + */ + public function send() + { + $this->prepare_message(); + + if (empty($this->to)) + { + $this->add_to_log('Error: Could not send, recepient addresses undefined.'); + return false; + } + + $addresses = []; + foreach ($this->to as $uid_ary) + { + $addresses[] = $uid_ary['uid']; + } + $addresses = array_unique($addresses); + + if (!$this->use_queue) + { + if (!$this->connect()) + { + $this->error('JABBER', $this->user->lang['ERR_JAB_CONNECT'] . '
' . $this->get_log()); + return false; + } + + if (!$this->login()) + { + $this->error('JABBER', $this->user->lang['ERR_JAB_AUTH'] . '
' . $this->get_log()); + return false; + } + + foreach ($addresses as $address) + { + $this->send_message($address, $this->msg, $this->subject); + } + + $this->disconnect(); + } + else + { + $this->queue->init('jabber', $this->config['jab_package_size']); + $this->queue->put('jabber', array( + 'addresses' => $addresses, + 'subject' => $this->subject, + 'msg' => $this->msg) + ); + } + unset($addresses); + + return true; + } + /** * Send data to the Jabber server * @@ -338,7 +455,7 @@ class jabber * * @return bool */ - public function send($xml) + public function send_xml($xml) { if ($this->connected()) { @@ -459,7 +576,7 @@ class jabber return false; } - $this->send(""); + $this->send_xml(""); return $this->response($this->listen()); } @@ -488,7 +605,7 @@ class jabber $this->session['sent_presence'] = !$unavailable; - return $this->send("" . $type . $message . ''); + return $this->send_xml("" . $type . $message . ''); } /** @@ -567,7 +684,7 @@ class jabber // session required? $this->session['sess_required'] = isset($xml['stream:features'][0]['#']['session']); - $this->send(" + $this->send_xml(" " . utf8_htmlspecialchars($this->resource) . ' @@ -579,7 +696,7 @@ class jabber if (!$this->session['ssl'] && self::can_use_tls() && self::can_use_ssl() && isset($xml['stream:features'][0]['#']['starttls'])) { $this->add_to_log('Switching to TLS.'); - $this->send("\n"); + $this->send_xml("\n"); return $this->response($this->listen()); } @@ -605,18 +722,18 @@ class jabber if (in_array('DIGEST-MD5', $methods)) { - $this->send(""); + $this->send_xml(""); } else if (in_array('PLAIN', $methods) && ($this->session['ssl'] || !empty($this->session['tls']))) { // http://www.ietf.org/rfc/rfc4616.txt (PLAIN SASL Mechanism) - $this->send("" + $this->send_xml("" . base64_encode($this->username . '@' . $this->server . chr(0) . $this->username . chr(0) . $this->password) . ''); } else if (in_array('ANONYMOUS', $methods)) { - $this->send(""); + $this->send_xml(""); } else { @@ -653,7 +770,7 @@ class jabber // second challenge? if (isset($decoded['rspauth'])) { - $this->send(""); + $this->send_xml(""); } else { @@ -680,7 +797,7 @@ class jabber } } - $this->send("" . base64_encode($this->implode_data($response)) . ''); + $this->send_xml("" . base64_encode($this->implode_data($response)) . ''); } return $this->response($this->listen()); @@ -707,15 +824,15 @@ class jabber $this->session['tls'] = true; // new stream - $this->send("\n"); - $this->send("\n"); + $this->send_xml("\n"); + $this->send_xml("\n"); return $this->response($this->listen()); break; case 'success': // Yay, authentication successful. - $this->send("\n"); + $this->send_xml("\n"); $this->session['authenticated'] = true; // we have to wait for another response @@ -738,7 +855,7 @@ class jabber // and (maybe) yet another request to be able to send messages *finally* if ($this->session['sess_required']) { - $this->send(" + $this->send_xml(" "); return $this->response($this->listen()); @@ -752,7 +869,7 @@ class jabber break; case 'reg_1': - $this->send(" + $this->send_xml(" " . utf8_htmlspecialchars($this->username) . " " . utf8_htmlspecialchars($this->password) . " @@ -829,7 +946,7 @@ class jabber $type = 'normal'; } - return $this->send(" + return $this->send_xml(" " . utf8_htmlspecialchars($subject) . " " . utf8_htmlspecialchars($text) . " "