1
# Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/
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.
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.
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.
19
# As per the GPL, removal of this notice is prohibited.
23
---+ package Foswiki::Contrib::MailerContrib::WebNotify
24
Object that represents the contents of a %NOTIFYTOPIC% topic in a Foswiki web.
26
Note that =$Foswiki::Plugins::SESSION= is used to find the Foswiki session, and
27
must be set up before this class is used.
31
package Foswiki::Contrib::MailerContrib::WebNotify;
34
use locale; # required for matching \w with international characters
38
require Foswiki::Func;
39
require Foswiki::Contrib::MailerContrib;
40
require Foswiki::Contrib::MailerContrib::Subscriber;
41
require Foswiki::Contrib::MailerContrib::Subscription;
45
---++ new($session, $web, $topic)
46
* =$session= - Foswiki object
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.
59
my ( $class, $session, $web, $topic, $noexpandgroups ) = @_;
61
my $this = bless( {}, $class );
63
# Ensure the contrib is initialised
64
Foswiki::Contrib::MailerContrib::initContrib();
67
$this->{topic} = $topic || $Foswiki::cfg{NotifyTopicName} || 'WebNotify';
68
$this->{pretext} = '';
69
$this->{posttext} = '';
70
$this->{session} = $session;
71
$this->{noexpandgroups} = $noexpandgroups;
73
if( Foswiki::Func::topicExists( $web, $topic )) {
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.
91
Foswiki::Func::saveTopicText(
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.
111
my ( $this, $name, $noAdd ) = @_;
113
my $subscriber = $this->{subscribers}{$name};
114
unless ( $noAdd || defined( $subscriber )) {
116
new Foswiki::Contrib::MailerContrib::Subscriber( $name );
117
$this->{subscribers}{$name} = $subscriber;
124
---++ getSubscribers()
125
Get a list of all subscriber names (unsorted)
132
return keys %{$this->{subscribers}};
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.
147
my ( $this, $name, $topics, $depth, $opts ) = @_;
149
ASSERT(defined($opts) && $opts =~ /^\d*$/) if DEBUG;
152
unless ($this->{noexpandgroups}) {
153
if (defined &Foswiki::Func::eachGroupMember) {
154
my $it = Foswiki::Func::eachGroupMember( $name );
157
while( $it->hasNext() ) {
158
my $member = $it->next();
159
push( @names, $member );
163
my $user = Foswiki::User->new($this->{session}, '', $name);
164
if ($user->isGroup) {
165
@names = map {$_->wikiName} @{$user->groupMembers};
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 );
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.
191
my ( $this, $name, $topics, $depth ) = @_;
193
my @names = ( $name );
194
unless ($this->{noexpandgroups}) {
195
if (defined &Foswiki::Func::eachGroupMember) {
196
my $it = Foswiki::Func::eachGroupMember( $name );
199
while( $it->hasNext() ) {
200
my $member = $it->next();
201
push( @names, $member );
205
my $user = Foswiki::User->new($this->{session}, '', $name);
206
if ($user->isGroup) {
207
@names = map {$_->wikiName} @{$user->groupMembers};
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 );
222
---++ stringify() -> string
223
Return a string representation of this object, in %NOTIFYTOPIC% format.
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.
233
my $subscribersOnly = shift || 0;
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";
242
$page .= $this->{posttext} if (!$subscribersOnly);
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.
262
my ( $this, $change, $db, $changeSet, $seenSet, $allSet ) = @_;
264
my $topic = $change->{TOPIC};
265
my $web = $change->{WEB};
266
my %authors = map { $_ => 1 }
267
@{Foswiki::Contrib::MailerContrib::Subscriber::getEmailAddressesForUser(
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 )) {
275
next unless Foswiki::Func::checkAccessPermission(
276
'VIEW', $name, undef, $topic, $this->{web}, undef );
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});
286
if ($subs->{options} & $MailerConst::FULL_TOPIC) {
287
push( @{$allSet->{$topic}}, $email );
289
my $at = $seenSet->{$email}{$topic};
291
$changeSet->{$email}[$at - 1]->merge( $change );
293
$seenSet->{$email}{$topic} =
294
push( @{$changeSet->{$email}}, $change );
299
$this->_emailWarn($subscriber,$name,$web);
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
314
sub processCompulsory {
315
my ($this, $topic, $db, $allSet) = @_;
317
foreach my $name ( keys %{$this->{subscribers}} ) {
318
my $subscriber = $this->{subscribers}{$name};
319
my $subs = $subscriber->isSubscribedTo( $topic, $db );
321
next unless ($subs->{options} & $MailerConst::ALWAYS);
322
unless( $subscriber->isUnsubscribedFrom( $topic, $db )) {
323
my $emails = $subscriber->getEmailAddresses();
325
foreach my $address (@$emails) {
326
push( @{$allSet->{$topic}}, $address );
335
---++ isEmpty() -> boolean
336
Return true if there are no subscribers
342
return ( scalar( keys %{$this->{subscribers}} ) == 0 );
345
# PRIVATE parse a topic extracting formatted lines
349
my ( $meta, $text ) = Foswiki::Func::readTopic(
350
$this->{web}, $this->{topic} );
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 );
367
elsif ( $line =~ /^\s+\*\s$webRE($Foswiki::regex{wikiWordRegex}|'.*?'|".*?"|$Foswiki::cfg{MailerContrib}{EmailFilterIn})\s*(:.*)?$/o
368
&& $1 ne $Foswiki::cfg{DefaultUserWikiName}) {
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
373
# email addresses can't start with :
374
$topics = undef unless ($topics =~ s/^://);
375
$subscriber =~ s/^(['"])(.*)\1$/$2/; # remove quotes
377
$this->parsePageSubscriptions( $subscriber, $topics );
379
$this->subscribe($subscriber, '*', 0, 0 );
385
$this->{pretext} .= "$baseline\n";
387
$this->{posttext} .= "$baseline\n";
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 ) = @_;
398
$this->{topicSub} = \&_subscribeTopic;
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");
409
sub _subscribeTopic {
410
my ( $this, $who, $unsubscribe, $webTopic, $options, $childDepth ) = @_;
412
my ($web, $topic) = Foswiki::Func::normalizeWebTopicName($this->{web}, $webTopic);
414
#print STDERR "_subscribeTopic($topic)\n";
417
$opts |= $MailerConst::FULL_TOPIC;
418
if ($options =~ /!/) {
419
$opts |= $MailerConst::ALWAYS;
422
my $kids = $childDepth or 0;
423
if ( $unsubscribe && $unsubscribe eq '-') {
424
$this->unsubscribe( $who, $topic, $kids );
426
$this->subscribe( $who, $topic, $kids, $opts );
428
#TODO: howto find & report errors?
432
# PRIVATE emailWarn to warn when an email address cannot be found
435
my ($this, $subscriber, $name, $web) = @_;
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'");