Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
124 / 124
100.00% covered (success)
100.00%
12 / 12
CRAP
100.00% covered (success)
100.00%
1 / 1
base
100.00% covered (success)
100.00%
124 / 124
100.00% covered (success)
100.00%
12 / 12
34
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 is_enabled
n/a
0 / 0
n/a
0 / 0
0
 set_use_queue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 init
n/a
0 / 0
n/a
0 / 0
0
 set_addresses
n/a
0 / 0
n/a
0 / 0
0
 get_queue_object_name
n/a
0 / 0
n/a
0 / 0
0
 subject
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 send
n/a
0 / 0
n/a
0 / 0
0
 process_queue
n/a
0 / 0
n/a
0 / 0
0
 template
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
1 / 1
11
 assign_vars
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 assign_block_vars
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 prepare_message
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
7
 error
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 save_queue
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 setup_template
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
3
 set_template_paths
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 header
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 *
4 * This file is part of the phpBB Forum Software package.
5 *
6 * @copyright (c) phpBB Limited <https://www.phpbb.com>
7 * @license GNU General Public License, version 2 (GPL-2.0)
8 *
9 * For full copyright and license information, please see
10 * the docs/CREDITS.txt file.
11 *
12 */
13
14namespace phpbb\messenger\method;
15
16use phpbb\config\config;
17use phpbb\di\service_collection;
18use phpbb\event\dispatcher;
19use phpbb\extension\manager;
20use phpbb\language\language;
21use phpbb\log\log_interface;
22use phpbb\path_helper;
23use phpbb\request\request;
24use phpbb\messenger\queue;
25use phpbb\template\assets_bag;
26use phpbb\template\twig\lexer;
27use phpbb\user;
28
29/**
30 * Messenger base class
31 */
32abstract class base implements messenger_interface
33{
34    /** @var array */
35    protected $additional_headers = [];
36
37    /** @var assets_bag */
38    protected $assets_bag;
39
40    /** @var config */
41    protected $config;
42
43    /** @var dispatcher */
44    protected $dispatcher;
45
46    /** @var manager */
47    protected $ext_manager;
48
49    /** @var language */
50    protected $language;
51
52    /** @var log_interface */
53    protected $log;
54
55    /** @var string */
56    protected $msg = '';
57
58    /** @var queue */
59    protected $queue;
60
61    /** @var  path_helper */
62    protected $path_helper;
63
64    /** @var  request */
65    protected $request;
66
67    /** @var string */
68    protected $root_path;
69
70    /** @var string */
71    protected $subject = '';
72
73    /** @var \phpbb\template\template */
74    protected $template;
75
76    /** @var string */
77    protected $template_cache_path;
78
79    /** @var service_collection */
80    protected $twig_extensions_collection;
81
82    /** @var lexer */
83    protected $twig_lexer;
84
85    /** @var bool */
86    protected $use_queue = true;
87
88    /** @var user */
89    protected $user;
90
91    /**
92     * Messenger base class constructor
93     *
94     * @param assets_bag $assets_bag
95     * @param config $config
96     * @param dispatcher $dispatcher
97     * @param language $language
98     * @param queue $queue
99     * @param path_helper $path_helper
100     * @param request $request
101     * @param service_collection $twig_extensions_collection
102     * @param lexer $twig_lexer
103     * @param user $user
104     * @param string $phpbb_root_path
105     * @param string $template_cache_path
106     * @param manager $ext_manager
107     * @param log_interface $log
108     */
109    public function __construct(
110        assets_bag $assets_bag,
111        config $config,
112        dispatcher $dispatcher,
113        language $language,
114        queue $queue,
115        path_helper $path_helper,
116        request $request,
117        service_collection $twig_extensions_collection,
118        lexer $twig_lexer,
119        user $user,
120        string $phpbb_root_path,
121        string $template_cache_path,
122        manager|null $ext_manager = null,
123        log_interface|null $log = null
124    )
125    {
126        $this->assets_bag = $assets_bag;
127        $this->config = $config;
128        $this->dispatcher = $dispatcher;
129        $this->ext_manager = $ext_manager;
130        $this->language = $language;
131        $this->log = $log;
132        $this->queue = $queue;
133        $this->path_helper = $path_helper;
134        $this->request = $request;
135        $this->twig_extensions_collection = $twig_extensions_collection;
136        $this->twig_lexer = $twig_lexer;
137        $this->user = $user;
138        $this->root_path = $phpbb_root_path;
139        $this->template_cache_path = $template_cache_path;
140
141        $this->set_use_queue();
142    }
143
144    /**
145     * {@inheritdoc}
146     */
147    abstract public function is_enabled(): bool;
148
149    /**
150     * Sets the use of messenger queue flag
151     *
152     * @param bool $use_queue Flag indicating if cached queue to be used
153     *
154     * @return void
155     */
156    public function set_use_queue(bool $use_queue = true): void
157    {
158        $this->use_queue = $use_queue;
159    }
160
161    /**
162     * Initializes all the data (address, template file, etc) or resets to default
163     *
164     * @return void
165     */
166    abstract public function init(): void;
167
168    /**
169     * Set addresses for to/im as available
170     *
171     * @param array $user_row User row
172     *
173     * @return void
174     */
175    abstract public function set_addresses(array $user_row): void;
176
177    /**
178     * Get messenger method fie queue object name
179     *
180     * @return string
181     */
182    abstract public function get_queue_object_name(): string;
183
184    /**
185     * {@inheritdoc}
186     */
187    public function subject(string $subject = ''): void
188    {
189        $this->subject = $subject;
190    }
191
192    /**
193     * {@inheritdoc}
194     */
195    abstract public function send(): bool;
196
197    /**
198     * Send messages from the queue
199     *
200     * @param array $queue_data Queue data array
201     *
202     * @return void
203     */
204    abstract public function process_queue(array &$queue_data): void;
205
206    /**
207     * Set email template to use
208     *
209     * @param string    $template_file            Email template file name
210     * @param string    $template_lang            Email template language
211     * @param string    $template_path            Email template path
212     * @param string    $template_dir_prefix    Email template directory prefix
213     *
214     * @return void
215     */
216    public function template(string $template_file, string $template_lang = '', string $template_path = '', string $template_dir_prefix = ''): void
217    {
218        $template_dir_prefix = (!$template_dir_prefix || $template_dir_prefix[0] === '/') ? $template_dir_prefix : '/' . $template_dir_prefix;
219
220        $this->setup_template();
221
222        if (!trim($template_lang))
223        {
224            // fall back to board default language if the user's language is
225            // missing $template_file.  If this does not exist either,
226            // $this->template->set_filenames will do a trigger_error
227            $template_lang = basename($this->config['default_lang']);
228        }
229
230        $ext_template_paths = [
231            [
232                'name'         => $template_lang . '_email',
233                'ext_path'     => 'language/' . $template_lang . '/email' . $template_dir_prefix,
234            ],
235        ];
236
237        if ($template_path)
238        {
239            $template_paths = [
240                $template_path . $template_dir_prefix,
241            ];
242        }
243        else
244        {
245            $template_path = (!empty($this->user->lang_path)) ? $this->user->lang_path : $this->root_path . 'language/';
246            $template_path .= $template_lang . '/email';
247
248            $template_paths = [
249                $template_path . $template_dir_prefix,
250            ];
251
252            $board_language = basename($this->config['default_lang']);
253
254            // we can only specify default language fallback when the path is not a custom one for which we
255            // do not know the default language alternative
256            if ($template_lang !== $board_language)
257            {
258                $fallback_template_path = (!empty($this->user->lang_path)) ? $this->user->lang_path : $this->root_path . 'language/';
259                $fallback_template_path .= $board_language . '/email';
260
261                $template_paths[] = $fallback_template_path . $template_dir_prefix;
262
263                $ext_template_paths[] = [
264                    'name'        => $board_language . '_email',
265                    'ext_path'    => 'language/' . $board_language . '/email' . $template_dir_prefix,
266                ];
267            }
268            // If everything fails just fall back to en template
269            if ($template_lang !== 'en' && $board_language !== 'en')
270            {
271                $fallback_template_path = (!empty($this->user->lang_path)) ? $this->user->lang_path : $this->root_path . 'language/';
272                $fallback_template_path .= 'en/email';
273
274                $template_paths[] = $fallback_template_path . $template_dir_prefix;
275
276                $ext_template_paths[] = [
277                    'name'        => 'en_email',
278                    'ext_path'    => 'language/en/email' . $template_dir_prefix,
279                ];
280            }
281        }
282
283        $this->set_template_paths($ext_template_paths, $template_paths);
284
285        $this->template->set_filenames([
286            'body'        => $template_file . '.txt',
287        ]);
288    }
289
290    /**
291     * Assign variables to email template
292     *
293     * @param array    $vars    Array of VAR => VALUE to assign to email template
294     *
295     * @return void
296     */
297    public function assign_vars(array $vars): void
298    {
299        $this->setup_template();
300        $this->template->assign_vars($vars);
301    }
302
303    /**
304     * Assign block of variables to email template
305     *
306     * @param string    $blockname    Template block name
307     * @param array        $vars        Array of VAR => VALUE to assign to email template block
308     *
309     * @return void
310     */
311    public function assign_block_vars(string $blockname, array $vars): void
312    {
313        $this->setup_template();
314
315        $this->template->assign_block_vars($blockname, $vars);
316    }
317
318    /**
319     * Prepare message before sending out to the recipients
320     *
321     * @return void
322     */
323    public function prepare_message(): void
324    {
325        // We add some standard variables we always use, no need to specify them always
326        $this->assign_vars([
327            'U_BOARD'    => generate_board_url(),
328            'EMAIL_SIG'    => str_replace('<br />', "\n", "-- \n" . html_entity_decode($this->config['board_email_sig'], ENT_COMPAT)),
329            'SITENAME'    => html_entity_decode($this->config['sitename'], ENT_COMPAT),
330        ]);
331
332        $subject = $this->subject;
333        $template = $this->template;
334        /**
335         * Event to modify the template before parsing
336         *
337         * @event core.modify_notification_template
338         * @var    string    subject        The message subject
339         * @var string    template    The (readonly) template object
340         * @since 3.2.4-RC1
341         * @changed 4.0.0-a1 Removed vars: method, break.
342         */
343        $vars = ['subject', 'template'];
344        extract($this->dispatcher->trigger_event('core.modify_notification_template', compact($vars)));
345
346        // Parse message through template
347        $message = trim($this->template->assign_display('body'));
348
349        /**
350         * Event to modify notification message text after parsing
351         *
352         * @event core.modify_notification_message
353         * @var    string    message    The message text
354         * @var    string    subject    The message subject
355         * @since 3.1.11-RC1
356         * @changed 4.0.0-a1 Removed vars: method, break.
357         */
358        $vars = ['message', 'subject'];
359        extract($this->dispatcher->trigger_event('core.modify_notification_message', compact($vars)));
360
361        $this->subject = $subject;
362        $this->msg = $message;
363        unset($subject, $message, $template);
364
365        // 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
366        $this->msg = str_replace("\r\n", "\n", $this->msg);
367
368        // We now try and pull a subject from the email body ... if it exists,
369        // do this here because the subject may contain a variable
370        $drop_header = '';
371        $match = [];
372        if (preg_match('#^(Subject):(.*?)$#m', $this->msg, $match))
373        {
374            $this->subject = (trim($match[2]) != '') ? trim($match[2]) : (($this->subject != '') ? $this->subject : $this->language->lang('NO_EMAIL_SUBJECT'));
375            $drop_header .= '[\r\n]*?' . preg_quote($match[0], '#');
376        }
377        else
378        {
379            $this->subject = (($this->subject != '') ? $this->subject : $this->language->lang('NO_EMAIL_SUBJECT'));
380        }
381
382        if (preg_match('#^(List-Unsubscribe):(.*?)$#m', $this->msg, $match))
383        {
384            $drop_header .= '[\r\n]*?' . preg_quote($match[0], '#');
385            $this->additional_headers[$match[1]] = trim($match[2]);
386        }
387
388        if ($drop_header)
389        {
390            $this->msg = trim(preg_replace('#' . $drop_header . '#s', '', $this->msg));
391        }
392    }
393
394    /**
395     * {@inheritdoc}
396     */
397    public function error(string $msg): void
398    {
399        // Session doesn't exist, create it
400        if (!isset($this->user->session_id) || $this->user->session_id === '')
401        {
402            $this->user->session_begin();
403        }
404
405        $type = strtoupper($this->get_queue_object_name());
406        $calling_page = html_entity_decode($this->request->server('PHP_SELF'), ENT_COMPAT);
407        $message = '<strong>' . $type . '</strong><br><em>' . htmlspecialchars($calling_page, ENT_COMPAT) . '</em><br><br>' . $msg . '<br>';
408        if ($this->log)
409        {
410            $this->log->add('critical', $this->user->data['user_id'], $this->user->ip, 'LOG_ERROR_' . $type, false, [$message]);
411        }
412    }
413
414    /**
415     * Save message data to the messenger file queue
416     *
417     * @return void
418     */
419    public function save_queue(): void
420    {
421        if ($this->use_queue)
422        {
423            $this->queue->save();
424        }
425    }
426
427    /**
428     * Setup template engine
429     *
430     * @return void
431     */
432    protected function setup_template(): void
433    {
434        if (isset($this->template) && $this->template instanceof \phpbb\template\template)
435        {
436            return;
437        }
438
439        $template_environment = new \phpbb\template\twig\environment(
440            $this->assets_bag,
441            $this->config,
442            new \phpbb\filesystem\filesystem(),
443            $this->path_helper,
444            $this->template_cache_path,
445            $this->ext_manager,
446            new \phpbb\template\twig\loader(),
447            $this->dispatcher,
448            []
449        );
450        $template_environment->setLexer($this->twig_lexer);
451
452        $this->template = new \phpbb\template\twig\twig(
453            $this->path_helper,
454            $this->config,
455            new \phpbb\template\context(),
456            $template_environment,
457            $this->template_cache_path,
458            $this->user,
459            $this->twig_extensions_collection,
460            $this->ext_manager
461        );
462    }
463
464    /**
465     * Set template paths to load
466     *
467     * @param string|array $path_name    Email template path name
468     * @param string|array $paths        Email template paths
469     *
470     * @return void
471     */
472    protected function set_template_paths(array|string $path_name, array|string $paths): void
473    {
474        $this->setup_template();
475        $this->template->set_custom_style($path_name, $paths);
476    }
477
478    /**
479     * {@inheritdoc}
480     */
481    public function header(string $header_name, mixed $header_value): void
482    {
483    }
484}