Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
53.23% covered (warning)
53.23%
66 / 124
36.36% covered (danger)
36.36%
4 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
base
53.23% covered (warning)
53.23%
66 / 124
36.36% covered (danger)
36.36%
4 / 11
152.30
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 get_id
n/a
0 / 0
n/a
0 / 0
0
 is_enabled
n/a
0 / 0
n/a
0 / 0
0
 set_use_queue
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 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
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 send
n/a
0 / 0
n/a
0 / 0
0
 process_queue
n/a
0 / 0
n/a
0 / 0
0
 template
56.10% covered (warning)
56.10%
23 / 41
0.00% covered (danger)
0.00%
0 / 1
21.24
 assign_vars
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 assign_block_vars
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 prepare_message
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
56
 error
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 save_queue
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 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
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 $ext_manager = null,
123        ?log_interface $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 get_id(): int;
148
149    /**
150     * {@inheritdoc}
151     */
152    abstract public function is_enabled(): bool;
153
154    /**
155     * Sets the use of messenger queue flag
156     *
157     * @param bool $use_queue Flag indicating if cached queue to be used
158     *
159     * @return void
160     */
161    public function set_use_queue(bool $use_queue = true): void
162    {
163        $this->use_queue = $use_queue;
164    }
165
166    /**
167     * Initializes all the data (address, template file, etc) or resets to default
168     *
169     * @return void
170     */
171    abstract public function init(): void;
172
173    /**
174     * Set addresses for to/im as available
175     *
176     * @param array $user_row User row
177     *
178     * @return void
179     */
180    abstract public function set_addresses(array $user_row): void;
181
182    /**
183     * Get messenger method fie queue object name
184     *
185     * @return string
186     */
187    abstract public function get_queue_object_name(): string;
188
189    /**
190     * {@inheritdoc}
191     */
192    public function subject(string $subject = ''): void
193    {
194        $this->subject = $subject;
195    }
196
197    /**
198     * {@inheritdoc}
199     */
200    abstract public function send(): bool;
201
202    /**
203     * Send messages from the queue
204     *
205     * @param array $queue_data Queue data array
206     *
207     * @return void
208     */
209    abstract public function process_queue(array &$queue_data): void;
210
211    /**
212     * Set email template to use
213     *
214     * @param string    $template_file            Email template file name
215     * @param string    $template_lang            Email template language
216     * @param string    $template_path            Email template path
217     * @param string    $template_dir_prefix    Email template directory prefix
218     *
219     * @return bool
220     */
221    public function template(string $template_file, string $template_lang = '', string $template_path = '', string $template_dir_prefix = ''): bool
222    {
223        $template_dir_prefix = (!$template_dir_prefix || $template_dir_prefix[0] === '/') ? $template_dir_prefix : '/' . $template_dir_prefix;
224
225        $this->setup_template();
226
227        if (!trim($template_lang))
228        {
229            // fall back to board default language if the user's language is
230            // missing $template_file.  If this does not exist either,
231            // $this->template->set_filenames will do a trigger_error
232            $template_lang = basename($this->config['default_lang']);
233        }
234
235        $ext_template_paths = [
236            [
237                'name'         => $template_lang . '_email',
238                'ext_path'     => 'language/' . $template_lang . '/email' . $template_dir_prefix,
239            ],
240        ];
241
242        if ($template_path)
243        {
244            $template_paths = [
245                $template_path . $template_dir_prefix,
246            ];
247        }
248        else
249        {
250            $template_path = (!empty($this->user->lang_path)) ? $this->user->lang_path : $this->root_path . 'language/';
251            $template_path .= $template_lang . '/email';
252
253            $template_paths = [
254                $template_path . $template_dir_prefix,
255            ];
256
257            $board_language = basename($this->config['default_lang']);
258
259            // we can only specify default language fallback when the path is not a custom one for which we
260            // do not know the default language alternative
261            if ($template_lang !== $board_language)
262            {
263                $fallback_template_path = (!empty($this->user->lang_path)) ? $this->user->lang_path : $this->root_path . 'language/';
264                $fallback_template_path .= $board_language . '/email';
265
266                $template_paths[] = $fallback_template_path . $template_dir_prefix;
267
268                $ext_template_paths[] = [
269                    'name'        => $board_language . '_email',
270                    'ext_path'    => 'language/' . $board_language . '/email' . $template_dir_prefix,
271                ];
272            }
273            // If everything fails just fall back to en template
274            if ($template_lang !== 'en' && $board_language !== 'en')
275            {
276                $fallback_template_path = (!empty($this->user->lang_path)) ? $this->user->lang_path : $this->root_path . 'language/';
277                $fallback_template_path .= 'en/email';
278
279                $template_paths[] = $fallback_template_path . $template_dir_prefix;
280
281                $ext_template_paths[] = [
282                    'name'        => 'en_email',
283                    'ext_path'    => 'language/en/email' . $template_dir_prefix,
284                ];
285            }
286        }
287
288        $this->set_template_paths($ext_template_paths, $template_paths);
289
290        $this->template->set_filenames([
291            'body'        => $template_file . '.txt',
292        ]);
293
294        return true;
295    }
296
297    /**
298     * Assign variables to email template
299     *
300     * @param array    $vars    Array of VAR => VALUE to assign to email template
301     *
302     * @return void
303     */
304    public function assign_vars(array $vars): void
305    {
306        $this->setup_template();
307        $this->template->assign_vars($vars);
308    }
309
310    /**
311     * Assign block of variables to email template
312     *
313     * @param string    $blockname    Template block name
314     * @param array        $vars        Array of VAR => VALUE to assign to email template block
315     *
316     * @return void
317     */
318    public function assign_block_vars(string $blockname, array $vars): void
319    {
320        $this->setup_template();
321
322        $this->template->assign_block_vars($blockname, $vars);
323    }
324
325    /**
326     * Prepare message before sending out to the recipients
327     *
328     * @return void
329     */
330    public function prepare_message(): void
331    {
332        // We add some standard variables we always use, no need to specify them always
333        $this->assign_vars([
334            'U_BOARD'    => generate_board_url(),
335            'EMAIL_SIG'    => str_replace('<br />', "\n", "-- \n" . html_entity_decode($this->config['board_email_sig'], ENT_COMPAT)),
336            'SITENAME'    => html_entity_decode($this->config['sitename'], ENT_COMPAT),
337        ]);
338
339        $subject = $this->subject;
340        $template = $this->template;
341        /**
342         * Event to modify the template before parsing
343         *
344         * @event core.modify_notification_template
345         * @var    string    subject        The message subject
346         * @var string    template    The (readonly) template object
347         * @since 3.2.4-RC1
348         * @changed 4.0.0-a1 Removed vars: method, break.
349         */
350        $vars = ['subject', 'template'];
351        extract($this->dispatcher->trigger_event('core.modify_notification_template', compact($vars)));
352
353        // Parse message through template
354        $message = trim($this->template->assign_display('body'));
355
356        /**
357         * Event to modify notification message text after parsing
358         *
359         * @event core.modify_notification_message
360         * @var    string    message    The message text
361         * @var    string    subject    The message subject
362         * @since 3.1.11-RC1
363         * @changed 4.0.0-a1 Removed vars: method, break.
364         */
365        $vars = ['message', 'subject'];
366        extract($this->dispatcher->trigger_event('core.modify_notification_message', compact($vars)));
367
368        $this->subject = $subject;
369        $this->msg = $message;
370        unset($subject, $message, $template);
371
372        // 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
373        $this->msg = str_replace("\r\n", "\n", $this->msg);
374
375        // We now try and pull a subject from the email body ... if it exists,
376        // do this here because the subject may contain a variable
377        $drop_header = '';
378        $match = [];
379        if (preg_match('#^(Subject):(.*?)$#m', $this->msg, $match))
380        {
381            $this->subject = (trim($match[2]) != '') ? trim($match[2]) : (($this->subject != '') ? $this->subject : $this->language->lang('NO_EMAIL_SUBJECT'));
382            $drop_header .= '[\r\n]*?' . preg_quote($match[0], '#');
383        }
384        else
385        {
386            $this->subject = (($this->subject != '') ? $this->subject : $this->language->lang('NO_EMAIL_SUBJECT'));
387        }
388
389        if (preg_match('#^(List-Unsubscribe):(.*?)$#m', $this->msg, $match))
390        {
391            $drop_header .= '[\r\n]*?' . preg_quote($match[0], '#');
392            $this->additional_headers[$match[1]] = trim($match[2]);
393        }
394
395        if ($drop_header)
396        {
397            $this->msg = trim(preg_replace('#' . $drop_header . '#s', '', $this->msg));
398        }
399    }
400
401    /**
402     * {@inheritdoc}
403     */
404    public function error(string $msg): void
405    {
406        // Session doesn't exist, create it
407        if (!isset($this->user->session_id) || $this->user->session_id === '')
408        {
409            $this->user->session_begin();
410        }
411
412        $type = strtoupper($this->get_queue_object_name());
413        $calling_page = html_entity_decode($this->request->server('PHP_SELF'), ENT_COMPAT);
414        $message = '<strong>' . $type . '</strong><br><em>' . htmlspecialchars($calling_page, ENT_COMPAT) . '</em><br><br>' . $msg . '<br>';
415        if ($this->log)
416        {
417            $this->log->add('critical', $this->user->data['user_id'], $this->user->ip, 'LOG_ERROR_' . $type, false, [$message]);
418        }
419    }
420
421    /**
422     * Save message data to the messenger file queue
423     *
424     * @return void
425     */
426    public function save_queue(): void
427    {
428        if ($this->use_queue && !empty($this->queue))
429        {
430            $this->queue->save();
431        }
432    }
433
434    /**
435     * Setup template engine
436     *
437     * @return void
438     */
439    protected function setup_template(): void
440    {
441        if (isset($this->template) && $this->template instanceof \phpbb\template\template)
442        {
443            return;
444        }
445
446        $template_environment = new \phpbb\template\twig\environment(
447            $this->assets_bag,
448            $this->config,
449            new \phpbb\filesystem\filesystem(),
450            $this->path_helper,
451            $this->template_cache_path,
452            $this->ext_manager,
453            new \phpbb\template\twig\loader(),
454            $this->dispatcher,
455            []
456        );
457        $template_environment->setLexer($this->twig_lexer);
458
459        $this->template = new \phpbb\template\twig\twig(
460            $this->path_helper,
461            $this->config,
462            new \phpbb\template\context(),
463            $template_environment,
464            $this->template_cache_path,
465            $this->user,
466            $this->twig_extensions_collection,
467            $this->ext_manager
468        );
469    }
470
471    /**
472     * Set template paths to load
473     *
474     * @param string|array $path_name    Email template path name
475     * @param string|array $paths        Email template paths
476     *
477     * @return void
478     */
479    protected function set_template_paths(string|array $path_name, string|array $paths): void
480    {
481        $this->setup_template();
482        $this->template->set_custom_style($path_name, $paths);
483    }
484}