Failed Conditions
Push — psr2 ( 9ddafc...b78f68 )
by Andreas
11:21 queued 07:11
created

inc/subscription.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
use dokuwiki\ChangeLog\PageChangeLog;
0 ignored issues
show
This use statement conflicts with another class in this namespace, PageChangeLog.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
4
5
/**
6
 * Class for handling (email) subscriptions
7
 *
8
 * @author  Adrian Lang <[email protected]>
9
 * @author  Andreas Gohr <[email protected]>
10
 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
11
 */
12
class Subscription {
13
14
    /**
15
     * Check if subscription system is enabled
16
     *
17
     * @return bool
18
     */
19
    public function isenabled() {
20
        return actionOK('subscribe');
21
    }
22
23
    /**
24
     * Return the subscription meta file for the given ID
25
     *
26
     * @author Adrian Lang <[email protected]>
27
     *
28
     * @param string $id The target page or namespace, specified by id; Namespaces
29
     *                   are identified by appending a colon.
30
     * @return string
31
     */
32
    protected function file($id) {
33
        $meta_fname = '.mlist';
34
        if((substr($id, -1, 1) === ':')) {
35
            $meta_froot = getNS($id);
36
            $meta_fname = '/'.$meta_fname;
37
        } else {
38
            $meta_froot = $id;
39
        }
40
        return metaFN((string) $meta_froot, $meta_fname);
41
    }
42
43
    /**
44
     * Lock subscription info
45
     *
46
     * We don't use io_lock() her because we do not wait for the lock and use a larger stale time
47
     *
48
     * @author Adrian Lang <[email protected]>
49
     * @param string $id The target page or namespace, specified by id; Namespaces
50
     *                   are identified by appending a colon.
51
     * @return bool true, if you got a succesful lock
52
     */
53
    protected function lock($id) {
54
        global $conf;
55
56
        $lock = $conf['lockdir'].'/_subscr_'.md5($id).'.lock';
57
58
        if(is_dir($lock) && time() - @filemtime($lock) > 60 * 5) {
59
            // looks like a stale lock - remove it
60
            @rmdir($lock);
61
        }
62
63
        // try creating the lock directory
64
        if(!@mkdir($lock, $conf['dmode'])) {
65
            return false;
66
        }
67
68
        if(!empty($conf['dperm'])) chmod($lock, $conf['dperm']);
69
        return true;
70
    }
71
72
    /**
73
     * Unlock subscription info
74
     *
75
     * @author Adrian Lang <[email protected]>
76
     * @param string $id The target page or namespace, specified by id; Namespaces
77
     *                   are identified by appending a colon.
78
     * @return bool
79
     */
80
    protected function unlock($id) {
81
        global $conf;
82
        $lock = $conf['lockdir'].'/_subscr_'.md5($id).'.lock';
83
        return @rmdir($lock);
84
    }
85
86
    /**
87
     * Construct a regular expression for parsing a subscription definition line
88
     *
89
     * @author Andreas Gohr <[email protected]>
90
     *
91
     * @param string|array $user
92
     * @param string|array $style
93
     * @param string|array $data
94
     * @return string complete regexp including delimiters
95
     * @throws Exception when no data is passed
96
     */
97
    protected function buildregex($user = null, $style = null, $data = null) {
98
        // always work with arrays
99
        $user = (array) $user;
100
        $style = (array) $style;
101
        $data = (array) $data;
102
103
        // clean
104
        $user = array_filter(array_map('trim', $user));
105
        $style = array_filter(array_map('trim', $style));
106
        $data = array_filter(array_map('trim', $data));
107
108
        // user names are encoded
109
        $user = array_map('auth_nameencode', $user);
110
111
        // quote
112
        $user = array_map('preg_quote_cb', $user);
113
        $style = array_map('preg_quote_cb', $style);
114
        $data = array_map('preg_quote_cb', $data);
115
116
        // join
117
        $user = join('|', $user);
118
        $style = join('|', $style);
119
        $data = join('|', $data);
120
121
        // any data at all?
122
        if($user.$style.$data === '') throw new Exception('no data passed');
123
124
        // replace empty values, set which ones are optional
125
        $sopt = '';
126
        $dopt = '';
127
        if($user === '') {
128
            $user = '\S+';
129
        }
130
        if($style === '') {
131
            $style = '\S+';
132
            $sopt = '?';
133
        }
134
        if($data === '') {
135
            $data = '\S+';
136
            $dopt = '?';
137
        }
138
139
        // assemble
140
        return "/^($user)(?:\\s+($style))$sopt(?:\\s+($data))$dopt$/";
141
    }
142
143
    /**
144
     * Recursively search for matching subscriptions
145
     *
146
     * This function searches all relevant subscription files for a page or
147
     * namespace.
148
     *
149
     * @author Adrian Lang <[email protected]>
150
     *
151
     * @param string         $page The target object’s (namespace or page) id
152
     * @param string|array   $user
153
     * @param string|array   $style
154
     * @param string|array   $data
155
     * @return array
156
     */
157
    public function subscribers($page, $user = null, $style = null, $data = null) {
158
        if(!$this->isenabled()) return array();
159
160
        // Construct list of files which may contain relevant subscriptions.
161
        $files = array(':' => $this->file(':'));
162
        do {
163
            $files[$page] = $this->file($page);
164
            $page = getNS(rtrim($page, ':')).':';
165
        } while($page !== ':');
166
167
        $re = $this->buildregex($user, $style, $data);
168
169
        // Handle files.
170
        $result = array();
171
        foreach($files as $target => $file) {
172
            if(!file_exists($file)) continue;
173
174
            $lines = file($file);
175
            foreach($lines as $line) {
176
                // fix old style subscription files
177
                if(strpos($line, ' ') === false) $line = trim($line)." every\n";
178
179
                // check for matching entries
180
                if(!preg_match($re, $line, $m)) continue;
181
182
                $u = rawurldecode($m[1]); // decode the user name
183
                if(!isset($result[$target])) $result[$target] = array();
184
                $result[$target][$u] = array($m[2], $m[3]); // add to result
185
            }
186
        }
187
        return array_reverse($result);
188
    }
189
190
    /**
191
     * Adds a new subscription for the given page or namespace
192
     *
193
     * This will automatically overwrite any existent subscription for the given user on this
194
     * *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces.
195
     *
196
     * @param string $id The target page or namespace, specified by id; Namespaces
197
     *                   are identified by appending a colon.
198
     * @param string $user
199
     * @param string $style
200
     * @param string $data
201
     * @throws Exception when user or style is empty
202
     * @return bool
203
     */
204
    public function add($id, $user, $style, $data = '') {
205
        if(!$this->isenabled()) return false;
206
207
        // delete any existing subscription
208
        $this->remove($id, $user);
209
210
        $user  = auth_nameencode(trim($user));
211
        $style = trim($style);
212
        $data  = trim($data);
213
214
        if(!$user) throw new Exception('no subscription user given');
215
        if(!$style) throw new Exception('no subscription style given');
216
        if(!$data) $data = time(); //always add current time for new subscriptions
217
218
        $line = "$user $style $data\n";
219
        $file = $this->file($id);
220
        return io_saveFile($file, $line, true);
221
    }
222
223
    /**
224
     * Removes a subscription for the given page or namespace
225
     *
226
     * This removes all subscriptions matching the given criteria on the given page or
227
     * namespace. It will *not* modify any subscriptions that may exist in higher
228
     * namespaces.
229
     *
230
     * @param string         $id   The target object’s (namespace or page) id
231
     * @param string|array   $user
232
     * @param string|array   $style
233
     * @param string|array   $data
234
     * @return bool
235
     */
236
    public function remove($id, $user = null, $style = null, $data = null) {
237
        if(!$this->isenabled()) return false;
238
239
        $file = $this->file($id);
240
        if(!file_exists($file)) return true;
241
242
        $re = $this->buildregex($user, $style, $data);
243
        return io_deleteFromFile($file, $re, true);
244
    }
245
246
    /**
247
     * Get data for $INFO['subscribed']
248
     *
249
     * $INFO['subscribed'] is either false if no subscription for the current page
250
     * and user is in effect. Else it contains an array of arrays with the fields
251
     * “target”, “style”, and optionally “data”.
252
     *
253
     * @param string $id  Page ID, defaults to global $ID
254
     * @param string $user User, defaults to $_SERVER['REMOTE_USER']
255
     * @return array|false
256
     * @author Adrian Lang <[email protected]>
257
     */
258
    public function user_subscription($id = '', $user = '') {
259
        if(!$this->isenabled()) return false;
260
261
        global $ID;
262
        /** @var Input $INPUT */
263
        global $INPUT;
264
        if(!$id) $id = $ID;
265
        if(!$user) $user = $INPUT->server->str('REMOTE_USER');
266
267
        $subs = $this->subscribers($id, $user);
268
        if(!count($subs)) return false;
269
270
        $result = array();
271
        foreach($subs as $target => $info) {
272
            $result[] = array(
273
                'target' => $target,
274
                'style' => $info[$user][0],
275
                'data' => $info[$user][1]
276
            );
277
        }
278
279
        return $result;
280
    }
281
282
    /**
283
     * Send digest and list subscriptions
284
     *
285
     * This sends mails to all subscribers that have a subscription for namespaces above
286
     * the given page if the needed $conf['subscribe_time'] has passed already.
287
     *
288
     * This function is called form lib/exe/indexer.php
289
     *
290
     * @param string $page
291
     * @return int number of sent mails
292
     */
293
    public function send_bulk($page) {
294
        if(!$this->isenabled()) return 0;
295
296
        /** @var DokuWiki_Auth_Plugin $auth */
297
        global $auth;
298
        global $conf;
299
        global $USERINFO;
300
        /** @var Input $INPUT */
301
        global $INPUT;
302
        $count = 0;
303
304
        $subscriptions = $this->subscribers($page, null, array('digest', 'list'));
305
306
        // remember current user info
307
        $olduinfo = $USERINFO;
308
        $olduser = $INPUT->server->str('REMOTE_USER');
309
310
        foreach($subscriptions as $target => $users) {
311
            if(!$this->lock($target)) continue;
312
313
            foreach($users as $user => $info) {
314
                list($style, $lastupdate) = $info;
315
316
                $lastupdate = (int) $lastupdate;
317
                if($lastupdate + $conf['subscribe_time'] > time()) {
318
                    // Less than the configured time period passed since last
319
                    // update.
320
                    continue;
321
                }
322
323
                // Work as the user to make sure ACLs apply correctly
324
                $USERINFO = $auth->getUserData($user);
325
                $INPUT->server->set('REMOTE_USER',$user);
326
                if($USERINFO === false) continue;
327
                if(!$USERINFO['mail']) continue;
328
329
                if(substr($target, -1, 1) === ':') {
330
                    // subscription target is a namespace, get all changes within
331
                    $changes = getRecentsSince($lastupdate, null, getNS($target));
332
                } else {
333
                    // single page subscription, check ACL ourselves
334
                    if(auth_quickaclcheck($target) < AUTH_READ) continue;
335
                    $meta = p_get_metadata($target);
336
                    $changes = array($meta['last_change']);
337
                }
338
339
                // Filter out pages only changed in small and own edits
340
                $change_ids = array();
341
                foreach($changes as $rev) {
342
                    $n = 0;
343
                    while(!is_null($rev) && $rev['date'] >= $lastupdate &&
344
                        ($INPUT->server->str('REMOTE_USER') === $rev['user'] ||
345
                            $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)) {
346
                        $pagelog = new PageChangeLog($rev['id']);
347
                        $rev = $pagelog->getRevisions($n++, 1);
348
                        $rev = (count($rev) > 0) ? $rev[0] : null;
349
                    }
350
351
                    if(!is_null($rev) && $rev['date'] >= $lastupdate) {
352
                        // Some change was not a minor one and not by myself
353
                        $change_ids[] = $rev['id'];
354
                    }
355
                }
356
357
                // send it
358
                if($style === 'digest') {
359
                    foreach($change_ids as $change_id) {
360
                        $this->send_digest(
361
                            $USERINFO['mail'], $change_id,
362
                            $lastupdate
363
                        );
364
                        $count++;
365
                    }
366
                } elseif($style === 'list') {
367
                    $this->send_list($USERINFO['mail'], $change_ids, $target);
368
                    $count++;
369
                }
370
                // TODO: Handle duplicate subscriptions.
371
372
                // Update notification time.
373
                $this->add($target, $user, $style, time());
374
            }
375
            $this->unlock($target);
376
        }
377
378
        // restore current user info
379
        $USERINFO = $olduinfo;
380
        $INPUT->server->set('REMOTE_USER',$olduser);
381
        return $count;
382
    }
383
384
    /**
385
     * Send the diff for some page change
386
     *
387
     * @param string   $subscriber_mail The target mail address
388
     * @param string   $template        Mail template ('subscr_digest', 'subscr_single', 'mailtext', ...)
389
     * @param string   $id              Page for which the notification is
390
     * @param int|null $rev             Old revision if any
391
     * @param string   $summary         Change summary if any
392
     * @return bool                     true if successfully sent
393
     */
394
    public function send_diff($subscriber_mail, $template, $id, $rev = null, $summary = '') {
395
        global $DIFF_INLINESTYLES;
396
397
        // prepare replacements (keys not set in hrep will be taken from trep)
398
        $trep = array(
399
            'PAGE' => $id,
400
            'NEWPAGE' => wl($id, '', true, '&'),
401
            'SUMMARY' => $summary,
402
            'SUBSCRIBE' => wl($id, array('do' => 'subscribe'), true, '&')
403
        );
404
        $hrep = array();
405
406
        if($rev) {
407
            $subject = 'changed';
408
            $trep['OLDPAGE'] = wl($id, "rev=$rev", true, '&');
409
410
            $old_content = rawWiki($id, $rev);
411
            $new_content = rawWiki($id);
412
413
            $df = new Diff(explode("\n", $old_content),
414
                           explode("\n", $new_content));
415
            $dformat = new UnifiedDiffFormatter();
416
            $tdiff = $dformat->format($df);
417
418
            $DIFF_INLINESTYLES = true;
419
            $df = new Diff(explode("\n", $old_content),
420
                           explode("\n", $new_content));
421
            $dformat = new InlineDiffFormatter();
422
            $hdiff = $dformat->format($df);
423
            $hdiff = '<table>'.$hdiff.'</table>';
424
            $DIFF_INLINESTYLES = false;
425
        } else {
426
            $subject = 'newpage';
427
            $trep['OLDPAGE'] = '---';
428
            $tdiff = rawWiki($id);
429
            $hdiff = nl2br(hsc($tdiff));
430
        }
431
432
        $trep['DIFF'] = $tdiff;
433
        $hrep['DIFF'] = $hdiff;
434
435
        $headers = array('Message-Id' => $this->getMessageID($id));
436
        if ($rev) {
437
            $headers['In-Reply-To'] =  $this->getMessageID($id, $rev);
438
        }
439
440
        return $this->send(
441
            $subscriber_mail, $subject, $id,
442
            $template, $trep, $hrep, $headers
443
        );
444
    }
445
446
    /**
447
     * Send the diff for some media change
448
     *
449
     * @fixme this should embed thumbnails of images in HTML version
450
     *
451
     * @param string   $subscriber_mail The target mail address
452
     * @param string   $template        Mail template ('uploadmail', ...)
453
     * @param string   $id              Media file for which the notification is
454
     * @param int|bool $rev             Old revision if any
455
     */
456
    public function send_media_diff($subscriber_mail, $template, $id, $rev = false) {
457
        global $conf;
458
459
        $file = mediaFN($id);
460
        list($mime, /* $ext */) = mimetype($id);
461
462
        $trep = array(
463
            'MIME'  => $mime,
464
            'MEDIA' => ml($id,'',true,'&',true),
465
            'SIZE'  => filesize_h(filesize($file)),
466
        );
467
468
        if ($rev && $conf['mediarevisions']) {
469
            $trep['OLD'] = ml($id, "rev=$rev", true, '&', true);
470
        } else {
471
            $trep['OLD'] = '---';
472
        }
473
474
        $headers = array('Message-Id' => $this->getMessageID($id, @filemtime($file)));
475
        if ($rev) {
476
            $headers['In-Reply-To'] =  $this->getMessageID($id, $rev);
477
        }
478
479
        $this->send($subscriber_mail, 'upload', $id, $template, $trep, null, $headers);
480
481
    }
482
483
    /**
484
     * Send a notify mail on new registration
485
     *
486
     * @author Andreas Gohr <[email protected]>
487
     *
488
     * @param string $login    login name of the new user
489
     * @param string $fullname full name of the new user
490
     * @param string $email    email address of the new user
491
     * @return bool true if a mail was sent
492
     */
493
    public function send_register($login, $fullname, $email) {
494
        global $conf;
495
        if(empty($conf['registernotify'])) return false;
496
497
        $trep = array(
498
            'NEWUSER' => $login,
499
            'NEWNAME' => $fullname,
500
            'NEWEMAIL' => $email,
501
        );
502
503
        return $this->send(
504
            $conf['registernotify'],
505
            'new_user',
506
            $login,
507
            'registermail',
508
            $trep
509
        );
510
    }
511
512
    /**
513
     * Send a digest mail
514
     *
515
     * Sends a digest mail showing a bunch of changes of a single page. Basically the same as send_diff()
516
     * but determines the last known revision first
517
     *
518
     * @author Adrian Lang <[email protected]>
519
     *
520
     * @param string $subscriber_mail The target mail address
521
     * @param string $id              The ID
522
     * @param int    $lastupdate      Time of the last notification
523
     * @return bool
524
     */
525
    protected function send_digest($subscriber_mail, $id, $lastupdate) {
526
        $pagelog = new PageChangeLog($id);
527
        $n = 0;
528
        do {
529
            $rev = $pagelog->getRevisions($n++, 1);
530
            $rev = (count($rev) > 0) ? $rev[0] : null;
531
        } while(!is_null($rev) && $rev > $lastupdate);
532
533
        return $this->send_diff(
534
            $subscriber_mail,
535
            'subscr_digest',
536
            $id, $rev
537
        );
538
    }
539
540
    /**
541
     * Send a list mail
542
     *
543
     * Sends a list mail showing a list of changed pages.
544
     *
545
     * @author Adrian Lang <[email protected]>
546
     *
547
     * @param string $subscriber_mail The target mail address
548
     * @param array  $ids             Array of ids
549
     * @param string $ns_id           The id of the namespace
550
     * @return bool true if a mail was sent
551
     */
552
    protected function send_list($subscriber_mail, $ids, $ns_id) {
553
        if(count($ids) === 0) return false;
554
555
        $tlist = '';
556
        $hlist = '<ul>';
557
        foreach($ids as $id) {
558
            $link = wl($id, array(), true);
559
            $tlist .= '* '.$link.NL;
560
            $hlist .= '<li><a href="'.$link.'">'.hsc($id).'</a></li>'.NL;
561
        }
562
        $hlist .= '</ul>';
563
564
        $id = prettyprint_id($ns_id);
565
        $trep = array(
566
            'DIFF' => rtrim($tlist),
567
            'PAGE' => $id,
568
            'SUBSCRIBE' => wl($id, array('do' => 'subscribe'), true, '&')
569
        );
570
        $hrep = array(
571
            'DIFF' => $hlist
572
        );
573
574
        return $this->send(
575
            $subscriber_mail,
576
            'subscribe_list',
577
            $ns_id,
578
            'subscr_list', $trep, $hrep
579
        );
580
    }
581
582
    /**
583
     * Helper function for sending a mail
584
     *
585
     * @author Adrian Lang <[email protected]>
586
     *
587
     * @param string $subscriber_mail The target mail address
588
     * @param string $subject         The lang id of the mail subject (without the
589
     *                                prefix “mail_”)
590
     * @param string $context         The context of this mail, eg. page or namespace id
591
     * @param string $template        The name of the mail template
592
     * @param array  $trep            Predefined parameters used to parse the
593
     *                                template (in text format)
594
     * @param array  $hrep            Predefined parameters used to parse the
595
     *                                template (in HTML format), null to default to $trep
596
     * @param array  $headers         Additional mail headers in the form 'name' => 'value'
597
     * @return bool
598
     */
599
    protected function send($subscriber_mail, $subject, $context, $template, $trep, $hrep = null, $headers = array()) {
600
        global $lang;
601
        global $conf;
602
603
        $text = rawLocale($template);
604
        $subject = $lang['mail_'.$subject].' '.$context;
605
        $mail = new Mailer();
606
        $mail->bcc($subscriber_mail);
607
        $mail->subject($subject);
608
        $mail->setBody($text, $trep, $hrep);
609
        if(in_array($template, array('subscr_list', 'subscr_digest'))){
610
            $mail->from($conf['mailfromnobody']);
611
        }
612
        if(isset($trep['SUBSCRIBE'])) {
613
            $mail->setHeader('List-Unsubscribe', '<'.$trep['SUBSCRIBE'].'>', false);
614
        }
615
616
        foreach ($headers as $header => $value) {
617
            $mail->setHeader($header, $value);
618
        }
619
620
        return $mail->send();
621
    }
622
623
    /**
624
     * Get a valid message id for a certain $id and revision (or the current revision)
625
     *
626
     * @param string $id  The id of the page (or media file) the message id should be for
627
     * @param string $rev The revision of the page, set to the current revision of the page $id if not set
628
     * @return string
629
     */
630
    protected function getMessageID($id, $rev = null) {
631
        static $listid = null;
632
        if (is_null($listid)) {
633
            $server = parse_url(DOKU_URL, PHP_URL_HOST);
634
            $listid = join('.', array_reverse(explode('/', DOKU_BASE))).$server;
635
            $listid = urlencode($listid);
636
            $listid = strtolower(trim($listid, '.'));
637
        }
638
639
        if (is_null($rev)) {
640
            $rev = @filemtime(wikiFN($id));
641
        }
642
643
        return "<$id?rev=$rev@$listid>";
644
    }
645
646
    /**
647
     * Default callback for COMMON_NOTIFY_ADDRESSLIST
648
     *
649
     * Aggregates all email addresses of user who have subscribed the given page with 'every' style
650
     *
651
     * @author Steven Danz <[email protected]>
652
     * @author Adrian Lang <[email protected]>
653
     *
654
     * @todo move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead,
655
     *       use an array for the addresses within it
656
     *
657
     * @param array &$data Containing the entries:
658
     *    - $id (the page id),
659
     *    - $self (whether the author should be notified,
660
     *    - $addresslist (current email address list)
661
     *    - $replacements (array of additional string substitutions, @KEY@ to be replaced by value)
662
     */
663
    public function notifyaddresses(&$data) {
664
        if(!$this->isenabled()) return;
665
666
        /** @var DokuWiki_Auth_Plugin $auth */
667
        global $auth;
668
        global $conf;
669
        /** @var Input $INPUT */
670
        global $INPUT;
671
672
        $id = $data['id'];
673
        $self = $data['self'];
674
        $addresslist = $data['addresslist'];
675
676
        $subscriptions = $this->subscribers($id, null, 'every');
677
678
        $result = array();
679
        foreach($subscriptions as $target => $users) {
680
            foreach($users as $user => $info) {
681
                $userinfo = $auth->getUserData($user);
682
                if($userinfo === false) continue;
683
                if(!$userinfo['mail']) continue;
684
                if(!$self && $user == $INPUT->server->str('REMOTE_USER')) continue; //skip our own changes
685
686
                $level = auth_aclcheck($id, $user, $userinfo['grps']);
687
                if($level >= AUTH_READ) {
688
                    if(strcasecmp($userinfo['mail'], $conf['notify']) != 0) { //skip user who get notified elsewhere
689
                        $result[$user] = $userinfo['mail'];
690
                    }
691
                }
692
            }
693
        }
694
        $data['addresslist'] = trim($addresslist.','.implode(',', $result), ',');
695
    }
696
}
697