~kosova/+junk/tuxfamily-twiki

« back to all changes in this revision

Viewing changes to foswiki/lib/Foswiki/Contrib/MailerContrib/WebNotify.pm

  • Committer: James Michael DuPont
  • Date: 2009-07-18 19:58:49 UTC
  • Revision ID: jamesmikedupont@gmail.com-20090718195849-vgbmaht2ys791uo2
added foswiki

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/
 
2
#
 
3
# Copyright (C) 2004 Wind River Systems Inc.
 
4
# Copyright (C) 1999-2007 Foswiki Contributors.
 
5
# All Rights Reserved. Foswiki Contributors
 
6
# are listed in the AUTHORS file in the root of this distribution.
 
7
# NOTE: Please extend that file, not this notice.
 
8
#
 
9
# This program is free software; you can redistribute it and/or
 
10
# modify it under the terms of the GNU General Public License
 
11
# as published by the Free Software Foundation; either version 2
 
12
# of the License, or (at your option) any later version. For
 
13
# more details read LICENSE in the root of this distribution.
 
14
#
 
15
# This program is distributed in the hope that it will be useful,
 
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
18
#
 
19
# As per the GPL, removal of this notice is prohibited.
 
20
 
 
21
=pod
 
22
 
 
23
---+ package Foswiki::Contrib::MailerContrib::WebNotify
 
24
Object that represents the contents of a %NOTIFYTOPIC% topic in a Foswiki web.
 
25
 
 
26
Note that =$Foswiki::Plugins::SESSION= is used to find the Foswiki session, and
 
27
must be set up before this class is used.
 
28
 
 
29
=cut
 
30
 
 
31
package Foswiki::Contrib::MailerContrib::WebNotify;
 
32
 
 
33
use strict;
 
34
use locale; # required for matching \w with international characters
 
35
 
 
36
use Assert;
 
37
 
 
38
require Foswiki::Func;
 
39
require Foswiki::Contrib::MailerContrib;
 
40
require Foswiki::Contrib::MailerContrib::Subscriber;
 
41
require Foswiki::Contrib::MailerContrib::Subscription;
 
42
 
 
43
=pod
 
44
 
 
45
---++ new($session, $web, $topic)
 
46
   * =$session= - Foswiki object
 
47
   * =$web= - web name
 
48
   * =$topic= - topic name
 
49
   * =$noexpandgroups= - True will prevent expansion of  group subscriptions
 
50
          (False is best for checking subscriptions, but True is best for
 
51
          writing results back to $topic)
 
52
Create a new object by parsing the content of the given topic in the
 
53
given web. This is the normal way to load a %NOTIFYTOPIC% topic. If the
 
54
topic does not exist, it will create an empty object.
 
55
 
 
56
=cut
 
57
 
 
58
sub new {
 
59
    my ( $class, $session, $web, $topic, $noexpandgroups ) = @_;
 
60
 
 
61
    my $this = bless( {}, $class );
 
62
 
 
63
    # Ensure the contrib is initialised
 
64
    Foswiki::Contrib::MailerContrib::initContrib();
 
65
 
 
66
    $this->{web} = $web;
 
67
    $this->{topic} = $topic || $Foswiki::cfg{NotifyTopicName} || 'WebNotify';
 
68
    $this->{pretext} = '';
 
69
    $this->{posttext} = '';
 
70
    $this->{session} = $session;
 
71
    $this->{noexpandgroups} = $noexpandgroups;
 
72
 
 
73
    if( Foswiki::Func::topicExists( $web, $topic )) {
 
74
        $this->_load();
 
75
    }
 
76
 
 
77
    return $this;
 
78
}
 
79
 
 
80
=pod
 
81
 
 
82
---++ writeWebNotify()
 
83
Write the object to the %NOTIFYTOPIC% topic it was read from.
 
84
If there is a problem writing the topic (e.g. it is locked),
 
85
the method will throw an exception.
 
86
 
 
87
=cut
 
88
 
 
89
sub writeWebNotify {
 
90
    my $this = shift;
 
91
    Foswiki::Func::saveTopicText(
 
92
        $this->{web},
 
93
        $this->{topic},
 
94
        $this->stringify(),
 
95
        1, 1);
 
96
}
 
97
 
 
98
=pod
 
99
 
 
100
---++ getSubscriber($name, $noAdd)
 
101
   * =$name= - Name of subscriber (wikiname with no web or email address)
 
102
   * =$noAdd= - If false or undef, a new subscriber will be created for this name
 
103
Get a subscriber from the list of subscribers, and return a reference
 
104
to the Subscriber object. If $noAdd is true, and the subscriber is not
 
105
found, undef will be returned. Otherwise a new Subscriber object will
 
106
be added if necessary.
 
107
 
 
108
=cut
 
109
 
 
110
sub getSubscriber {
 
111
    my ( $this, $name, $noAdd ) = @_;
 
112
 
 
113
    my $subscriber = $this->{subscribers}{$name};
 
114
    unless ( $noAdd || defined( $subscriber )) {
 
115
        $subscriber =
 
116
          new Foswiki::Contrib::MailerContrib::Subscriber( $name );
 
117
        $this->{subscribers}{$name} = $subscriber;
 
118
    }
 
119
    return $subscriber;
 
120
}
 
121
 
 
122
=pod
 
123
 
 
124
---++ getSubscribers()
 
125
Get a list of all subscriber names (unsorted)
 
126
 
 
127
=cut
 
128
 
 
129
sub getSubscribers {
 
130
    my ( $this ) = @_;
 
131
 
 
132
    return keys %{$this->{subscribers}};
 
133
}
 
134
 
 
135
=pod
 
136
 
 
137
---++ subscribe($name, $topics, $depth, $options)
 
138
   * =$name= - Name of subscriber (wikiname with no web or email address)
 
139
   * =$topics= - wildcard expression giving topics to subscribe to
 
140
   * =$depth= - Child depth to scan (default 0)
 
141
   * =$options= - Bitmap of Mailer::Const options
 
142
Add a subscription, adding the subscriber if necessary.
 
143
 
 
144
=cut
 
145
 
 
146
sub subscribe {
 
147
    my ( $this, $name, $topics, $depth, $opts ) = @_;
 
148
 
 
149
    ASSERT(defined($opts) && $opts =~ /^\d*$/) if DEBUG;
 
150
 
 
151
    my @names = ($name);
 
152
    unless ($this->{noexpandgroups}) {
 
153
        if (defined &Foswiki::Func::eachGroupMember) {
 
154
            my $it = Foswiki::Func::eachGroupMember( $name );
 
155
            if( $it ) {
 
156
                @names = ();
 
157
                while( $it->hasNext() ) {
 
158
                    my $member = $it->next();
 
159
                    push( @names, $member );
 
160
                }
 
161
            }
 
162
        } else {
 
163
            my $user = Foswiki::User->new($this->{session}, '', $name);
 
164
            if ($user->isGroup) {
 
165
                @names = map {$_->wikiName} @{$user->groupMembers};
 
166
            }
 
167
        }
 
168
    }
 
169
 
 
170
    foreach my $n (@names) {
 
171
        my $subscriber = $this->getSubscriber( $n );
 
172
        my $sub = new Foswiki::Contrib::MailerContrib::Subscription(
 
173
            $topics, $depth, $opts );
 
174
        $subscriber->subscribe( $sub );
 
175
    }
 
176
}
 
177
 
 
178
=pod
 
179
 
 
180
---++ unsubscribe($name, $topics, $depth)
 
181
   * =$name= - Name of subscriber (wikiname with no web or email address)
 
182
   * =$topics= - wildcard expression giving topics to subscribe to
 
183
   * =$depth= - Child depth to scan (default 0)
 
184
Add an unsubscription, adding the subscriber if necessary. An unsubscription
 
185
is a specific request to ignore notifications for a topic for this
 
186
particular subscriber.
 
187
 
 
188
=cut
 
189
 
 
190
sub unsubscribe {
 
191
    my ( $this, $name, $topics, $depth ) = @_;
 
192
 
 
193
    my @names = ( $name );
 
194
    unless ($this->{noexpandgroups}) {
 
195
        if (defined &Foswiki::Func::eachGroupMember) {
 
196
            my $it = Foswiki::Func::eachGroupMember( $name );
 
197
            if( $it ) {
 
198
                @names = ();
 
199
                while( $it->hasNext() ) {
 
200
                    my $member = $it->next();
 
201
                    push( @names, $member );
 
202
                }
 
203
            }
 
204
        } else {
 
205
            my $user = Foswiki::User->new($this->{session}, '', $name);
 
206
            if ($user->isGroup) {
 
207
                @names = map {$_->wikiName} @{$user->groupMembers};
 
208
            }
 
209
        }
 
210
    }
 
211
 
 
212
    foreach my $n (@names) {
 
213
        my $subscriber = $this->getSubscriber( $n );
 
214
        my $sub = new Foswiki::Contrib::MailerContrib::Subscription(
 
215
            $topics, $depth, 0 );
 
216
        $subscriber->unsubscribe( $sub );
 
217
    }
 
218
}
 
219
 
 
220
=pod
 
221
 
 
222
---++ stringify() -> string
 
223
Return a string representation of this object, in %NOTIFYTOPIC% format.
 
224
 
 
225
Optional $subscribersOnly parameter to only print the parsed subscription list.
 
226
Used when running a mailnotify, where printing out the entire WebNotify topic is confusing,
 
227
as its different from the actual topic contents, but doesn't inform the user why.
 
228
 
 
229
=cut
 
230
 
 
231
sub stringify {
 
232
    my $this = shift;
 
233
    my $subscribersOnly = shift || 0;
 
234
 
 
235
    my $page = '';
 
236
 
 
237
    $page .= $this->{pretext} if (!$subscribersOnly);
 
238
    foreach my $name ( sort keys %{$this->{subscribers}} ) {
 
239
        my $subscriber = $this->{subscribers}{$name};
 
240
        $page .= $subscriber->stringify() . "\n";
 
241
    }
 
242
    $page .= $this->{posttext} if (!$subscribersOnly);
 
243
 
 
244
    return $page;
 
245
}
 
246
 
 
247
=pod
 
248
 
 
249
---++ processChange($change, $db, $changeSet, $seenSet, $allSet)
 
250
   * =$change= - ref of a Foswiki::Contrib::Mailer::Change
 
251
   * =$db= - Foswiki::Contrib::MailerContrib::UpData database of parent references
 
252
   * =$changeSet= - ref of a hash mapping emails to sets of changes
 
253
   * =$seenSet= - ref of a hash recording indices of topics already seen
 
254
   * =$allSet= - ref of a hash that maps topics to email addresses for news subscriptions
 
255
Find all subscribers that are interested in the given change. Only the most
 
256
recent change to each topic listed in the .changes file is retained. This
 
257
method does _not_ change this object.
 
258
 
 
259
=cut
 
260
 
 
261
sub processChange {
 
262
    my ( $this, $change, $db, $changeSet, $seenSet, $allSet ) = @_;
 
263
 
 
264
    my $topic = $change->{TOPIC};
 
265
    my $web = $change->{WEB};
 
266
    my %authors = map { $_ => 1 }
 
267
      @{Foswiki::Contrib::MailerContrib::Subscriber::getEmailAddressesForUser(
 
268
          $change->{author})};
 
269
 
 
270
    foreach my $name ( keys %{$this->{subscribers}} ) {
 
271
        my $subscriber = $this->{subscribers}{$name};
 
272
        my $subs = $subscriber->isSubscribedTo( $topic, $db );
 
273
        if ($subs && !$subscriber->isUnsubscribedFrom( $topic, $db )) {
 
274
 
 
275
            next unless Foswiki::Func::checkAccessPermission(
 
276
                'VIEW', $name, undef, $topic, $this->{web}, undef );
 
277
 
 
278
            my $emails = $subscriber->getEmailAddresses();
 
279
            if( $emails && scalar( @$emails )) {
 
280
                foreach my $email ( @$emails ) {
 
281
                    # Skip this change if the subscriber is the author
 
282
                    # of the change, and we are not always sending
 
283
                    next if (!($subs->{options} & $MailerConst::ALWAYS)
 
284
                               && $authors{$email});
 
285
 
 
286
                    if ($subs->{options} & $MailerConst::FULL_TOPIC) {
 
287
                        push( @{$allSet->{$topic}}, $email );
 
288
                    } else {
 
289
                        my $at = $seenSet->{$email}{$topic};
 
290
                        if ( $at ) {
 
291
                            $changeSet->{$email}[$at - 1]->merge( $change );
 
292
                        } else {
 
293
                            $seenSet->{$email}{$topic} =
 
294
                              push( @{$changeSet->{$email}}, $change );
 
295
                        }
 
296
                    }
 
297
                }
 
298
            } else {
 
299
                $this->_emailWarn($subscriber,$name,$web);
 
300
            }
 
301
        }
 
302
    }
 
303
}
 
304
 
 
305
=pod
 
306
 
 
307
---++ processCompulsory($topic, $db, \%allSet)
 
308
   * =$topic= - topic name
 
309
   * =$db= - Foswiki::Contrib::MailerContrib::UpData database of parent references
 
310
   * =\%allSet= - ref of a hash that maps topics to email addresses for news subscriptions
 
311
 
 
312
=cut
 
313
 
 
314
sub processCompulsory {
 
315
    my ($this, $topic, $db, $allSet) = @_;
 
316
 
 
317
    foreach my $name ( keys %{$this->{subscribers}} ) {
 
318
        my $subscriber = $this->{subscribers}{$name};
 
319
        my $subs = $subscriber->isSubscribedTo( $topic, $db );
 
320
        next unless $subs;
 
321
        next unless ($subs->{options} & $MailerConst::ALWAYS);
 
322
        unless( $subscriber->isUnsubscribedFrom( $topic, $db )) {
 
323
            my $emails = $subscriber->getEmailAddresses();
 
324
            if( $emails ) {
 
325
                foreach my $address (@$emails) {
 
326
                    push( @{$allSet->{$topic}}, $address );
 
327
                }
 
328
            }
 
329
        }
 
330
    }
 
331
}
 
332
 
 
333
=pod
 
334
 
 
335
---++ isEmpty() -> boolean
 
336
Return true if there are no subscribers
 
337
 
 
338
=cut
 
339
 
 
340
sub isEmpty {
 
341
    my $this = shift;
 
342
    return ( scalar( keys %{$this->{subscribers}} ) == 0 );
 
343
}
 
344
 
 
345
# PRIVATE parse a topic extracting formatted lines
 
346
sub _load {
 
347
    my $this = shift;
 
348
 
 
349
    my ( $meta, $text ) = Foswiki::Func::readTopic(
 
350
        $this->{web}, $this->{topic} );
 
351
    my $in_pre = 1;
 
352
    $this->{pretext} = '';
 
353
    $this->{posttext} = '';
 
354
    $this->{meta} = $meta;
 
355
    # join \ terminated lines
 
356
    $text =~ s/\\\r?\n//gs;
 
357
    my $webRE = qr/(?:$Foswiki::cfg{UsersWebName}\.)?/o;
 
358
    foreach my $baseline ( split ( /\r?\n/, $text )) {
 
359
        my $line = Foswiki::Func::expandCommonVariables(
 
360
            $baseline, $this->{topic}, $this->{web}, $meta);
 
361
        if( $line =~ /^\s+\*\s$webRE($Foswiki::regex{wikiWordRegex})\s+\-\s+($Foswiki::cfg{MailerContrib}{EmailFilterIn}+)\s*$/o
 
362
              && $1 ne $Foswiki::cfg{DefaultUserWikiName}) {
 
363
            # Main.WikiName - email@domain (legacy format)
 
364
            $this->subscribe( $2, '*', 0, 0 );
 
365
            $in_pre = 0;
 
366
        }
 
367
        elsif ( $line =~ /^\s+\*\s$webRE($Foswiki::regex{wikiWordRegex}|'.*?'|".*?"|$Foswiki::cfg{MailerContrib}{EmailFilterIn})\s*(:.*)?$/o
 
368
                  && $1 ne $Foswiki::cfg{DefaultUserWikiName}) {
 
369
            my $subscriber = $1;
 
370
            # Get the topic list from the last bracket matched. Have to do it
 
371
            # this awkward way because the email filter may contain braces
 
372
            my $topics = $+;
 
373
            # email addresses can't start with :
 
374
            $topics = undef unless ($topics =~ s/^://);
 
375
            $subscriber =~ s/^(['"])(.*)\1$/$2/; # remove quotes
 
376
            if ($topics) {
 
377
                $this->parsePageSubscriptions( $subscriber, $topics );
 
378
            } else {
 
379
                $this->subscribe($subscriber, '*', 0, 0 );
 
380
            }
 
381
            $in_pre = 0;
 
382
        }
 
383
        else {
 
384
            if( $in_pre ) {
 
385
                $this->{pretext} .= "$baseline\n";
 
386
            } else {
 
387
                $this->{posttext} .=  "$baseline\n";
 
388
            }
 
389
        }
 
390
    }
 
391
}
 
392
 
 
393
# parse a pages list, adding subscriptions as appropriate
 
394
# $unsubscribe is set to '-' by SubscribePlugin to force a '-' operation
 
395
sub parsePageSubscriptions {
 
396
    my ( $this, $who, $spec, $unsubscribe ) = @_;
 
397
    
 
398
    $this->{topicSub} = \&_subscribeTopic;
 
399
    
 
400
    my $ret = Foswiki::Contrib::MailerContrib::parsePageList($this, $who, $spec, $unsubscribe);
 
401
    if ( $ret =~ m/\S/ ) {
 
402
        Foswiki::Func::writeWarning(
 
403
            "Badly formatted page list at $who: $spec");
 
404
        return -1;
 
405
    }
 
406
    return;
 
407
}
 
408
 
 
409
sub _subscribeTopic {
 
410
    my ( $this, $who, $unsubscribe, $webTopic, $options, $childDepth ) = @_;
 
411
    
 
412
    my ($web, $topic) = Foswiki::Func::normalizeWebTopicName($this->{web}, $webTopic);
 
413
    
 
414
#print STDERR "_subscribeTopic($topic)\n";
 
415
    my $opts = 0;
 
416
    if ($options) {
 
417
        $opts |= $MailerConst::FULL_TOPIC;
 
418
        if ($options =~ /!/) {
 
419
            $opts |= $MailerConst::ALWAYS;
 
420
        }
 
421
    }
 
422
    my $kids = $childDepth or 0;
 
423
    if ( $unsubscribe && $unsubscribe eq '-') {
 
424
        $this->unsubscribe( $who, $topic, $kids );
 
425
    } else {
 
426
        $this->subscribe( $who, $topic, $kids, $opts );
 
427
    }
 
428
    #TODO: howto find & report errors?
 
429
    return '';
 
430
}
 
431
 
 
432
# PRIVATE emailWarn to warn when an email address cannot be found
 
433
# for a subscriber.
 
434
sub _emailWarn {
 
435
    my ($this, $subscriber, $name, $web) = @_;
 
436
 
 
437
    # Make sure we only warn once. Don't want to see this for every
 
438
    # Topic we are notifying on.
 
439
    unless (defined $this->{nomail}{$name}) {
 
440
        $this->{nomail}{$name} = 1;
 
441
        Foswiki::Func::writeWarning(
 
442
            "Failed to find permitted email for '".
 
443
              $subscriber->stringify()."' when processing web '$web'");
 
444
    }
 
445
}
 
446
 
 
447
1;