← Index
NYTProf Performance Profile   « line view »
For ./view
  Run on Fri Jul 31 18:42:36 2015
Reported on Fri Jul 31 18:48:15 2015

Filename/var/www/foswikidev/core/lib/Foswiki/Contrib/MailerContrib.pm
StatementsExecuted 924 statements in 5.30ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
1112.14ms4.43msFoswiki::Contrib::MailerContrib::::BEGIN@27Foswiki::Contrib::MailerContrib::BEGIN@27
64221.71ms74.7msFoswiki::Contrib::MailerContrib::::parsePageListFoswiki::Contrib::MailerContrib::parsePageList (recurses: max depth 1, inclusive time 3.88ms)
1111.62ms2.65msFoswiki::Contrib::MailerContrib::::BEGIN@18Foswiki::Contrib::MailerContrib::BEGIN@18
1111.08ms1.18msFoswiki::Contrib::MailerContrib::::BEGIN@28Foswiki::Contrib::MailerContrib::BEGIN@28
111239µs284µsFoswiki::Contrib::MailerContrib::::BEGIN@29Foswiki::Contrib::MailerContrib::BEGIN@29
211102µs74.8msFoswiki::Contrib::MailerContrib::::isSubscribedToFoswiki::Contrib::MailerContrib::isSubscribedTo
21173µs74.7msFoswiki::Contrib::MailerContrib::::_isSubscribedToTopicFoswiki::Contrib::MailerContrib::_isSubscribedToTopic
11115µs30µsFoswiki::Contrib::MailerContrib::::BEGIN@15Foswiki::Contrib::MailerContrib::BEGIN@15
11111µs40µsFoswiki::Contrib::MailerContrib::::BEGIN@21Foswiki::Contrib::MailerContrib::BEGIN@21
11110µs16µsFoswiki::Contrib::MailerContrib::::BEGIN@16Foswiki::Contrib::MailerContrib::BEGIN@16
21110µs10µsFoswiki::Contrib::MailerContrib::::initContribFoswiki::Contrib::MailerContrib::initContrib
1117µs7µsFoswiki::Contrib::MailerContrib::::BEGIN@26Foswiki::Contrib::MailerContrib::BEGIN@26
1116µs6µsFoswiki::Contrib::MailerContrib::::BEGIN@19Foswiki::Contrib::MailerContrib::BEGIN@19
1115µs5µsFoswiki::Contrib::MailerContrib::::BEGIN@23Foswiki::Contrib::MailerContrib::BEGIN@23
1114µs4µsFoswiki::Contrib::MailerContrib::::BEGIN@25Foswiki::Contrib::MailerContrib::BEGIN@25
1114µs4µsFoswiki::Contrib::MailerContrib::::BEGIN@24Foswiki::Contrib::MailerContrib::BEGIN@24
0000s0sFoswiki::Contrib::MailerContrib::::_generateChangeDetailFoswiki::Contrib::MailerContrib::_generateChangeDetail
0000s0sFoswiki::Contrib::MailerContrib::::_loadUserPreferencesFoswiki::Contrib::MailerContrib::_loadUserPreferences
0000s0sFoswiki::Contrib::MailerContrib::::_processSubscriptionsFoswiki::Contrib::MailerContrib::_processSubscriptions
0000s0sFoswiki::Contrib::MailerContrib::::_processWebFoswiki::Contrib::MailerContrib::_processWeb
0000s0sFoswiki::Contrib::MailerContrib::::_restorePreferencesFoswiki::Contrib::MailerContrib::_restorePreferences
0000s0sFoswiki::Contrib::MailerContrib::::_sendChangesMailsFoswiki::Contrib::MailerContrib::_sendChangesMails
0000s0sFoswiki::Contrib::MailerContrib::::_sendNewsletterMailFoswiki::Contrib::MailerContrib::_sendNewsletterMail
0000s0sFoswiki::Contrib::MailerContrib::::_sendNewsletterMailsFoswiki::Contrib::MailerContrib::_sendNewsletterMails
0000s0sFoswiki::Contrib::MailerContrib::::_stompI18NFoswiki::Contrib::MailerContrib::_stompI18N
0000s0sFoswiki::Contrib::MailerContrib::::changeSubscriptionFoswiki::Contrib::MailerContrib::changeSubscription
0000s0sFoswiki::Contrib::MailerContrib::::mailNotifyFoswiki::Contrib::MailerContrib::mailNotify
0000s0sFoswiki::Contrib::MailerContrib::::relativeURLFoswiki::Contrib::MailerContrib::relativeURL
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1# See bottom of file for license and copyright information
2
3=begin TML
4
5---+ package Foswiki::Contrib::MailerContrib
6
7Package of support for extended Web<nop>Notify notification, supporting per-topic notification and notification of changes to children.
8
9Also supported is a simple API that can be used to change the Web<nop>Notify topic from other code.
10
11=cut
12
13package Foswiki::Contrib::MailerContrib;
14
15230µs244µs
# spent 30µs (15+14) within Foswiki::Contrib::MailerContrib::BEGIN@15 which was called: # once (15µs+14µs) by Foswiki::Plugins::SubscribePlugin::_SUBSCRIBE at line 15
use strict;
# spent 30µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@15 # spent 14µs making 1 call to strict::import
16227µs222µs
# spent 16µs (10+6) within Foswiki::Contrib::MailerContrib::BEGIN@16 which was called: # once (10µs+6µs) by Foswiki::Plugins::SubscribePlugin::_SUBSCRIBE at line 16
use warnings;
# spent 16µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@16 # spent 6µs making 1 call to warnings::import
17
182105µs12.65ms
# spent 2.65ms (1.62+1.03) within Foswiki::Contrib::MailerContrib::BEGIN@18 which was called: # once (1.62ms+1.03ms) by Foswiki::Plugins::SubscribePlugin::_SUBSCRIBE at line 18
use URI ();
# spent 2.65ms making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@18
19225µs16µs
# spent 6µs within Foswiki::Contrib::MailerContrib::BEGIN@19 which was called: # once (6µs+0s) by Foswiki::Plugins::SubscribePlugin::_SUBSCRIBE at line 19
use CGI ();
# spent 6µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@19
20
21228µs269µs
# spent 40µs (11+29) within Foswiki::Contrib::MailerContrib::BEGIN@21 which was called: # once (11µs+29µs) by Foswiki::Plugins::SubscribePlugin::_SUBSCRIBE at line 21
use Assert;
# spent 40µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@21 # spent 29µs making 1 call to Exporter::import
22
23220µs15µs
# spent 5µs within Foswiki::Contrib::MailerContrib::BEGIN@23 which was called: # once (5µs+0s) by Foswiki::Plugins::SubscribePlugin::_SUBSCRIBE at line 23
use Foswiki ();
# spent 5µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@23
24219µs14µs
# spent 4µs within Foswiki::Contrib::MailerContrib::BEGIN@24 which was called: # once (4µs+0s) by Foswiki::Plugins::SubscribePlugin::_SUBSCRIBE at line 24
use Foswiki::Plugins ();
# spent 4µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@24
25222µs14µs
# spent 4µs within Foswiki::Contrib::MailerContrib::BEGIN@25 which was called: # once (4µs+0s) by Foswiki::Plugins::SubscribePlugin::_SUBSCRIBE at line 25
use Foswiki::Time ();
# spent 4µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@25
26220µs17µs
# spent 7µs within Foswiki::Contrib::MailerContrib::BEGIN@26 which was called: # once (7µs+0s) by Foswiki::Plugins::SubscribePlugin::_SUBSCRIBE at line 26
use Foswiki::Func ();
# spent 7µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@26
272115µs14.43ms
# spent 4.43ms (2.14+2.29) within Foswiki::Contrib::MailerContrib::BEGIN@27 which was called: # once (2.14ms+2.29ms) by Foswiki::Plugins::SubscribePlugin::_SUBSCRIBE at line 27
use Foswiki::Contrib::MailerContrib::WebNotify ();
# spent 4.43ms making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@27
282103µs11.18ms
# spent 1.18ms (1.08+99µs) within Foswiki::Contrib::MailerContrib::BEGIN@28 which was called: # once (1.08ms+99µs) by Foswiki::Plugins::SubscribePlugin::_SUBSCRIBE at line 28
use Foswiki::Contrib::MailerContrib::Change ();
# spent 1.18ms making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@28
2922.99ms1284µs
# spent 284µs (239+45) within Foswiki::Contrib::MailerContrib::BEGIN@29 which was called: # once (239µs+45µs) by Foswiki::Plugins::SubscribePlugin::_SUBSCRIBE at line 29
use Foswiki::Contrib::MailerContrib::UpData ();
# spent 284µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@29
30
311700nsour $VERSION = '2.82';
321200nsour $RELEASE = '2.82';
331200nsour $SHORTDESCRIPTION = 'Supports email notification of changes';
34
35# PROTECTED STATIC ensure the contrib is internally initialised
36
# spent 10µs within Foswiki::Contrib::MailerContrib::initContrib which was called 2 times, avg 5µs/call: # 2 times (10µs+0s) by Foswiki::Contrib::MailerContrib::WebNotify::new at line 48 of /var/www/foswikidev/core/lib/Foswiki/Contrib/MailerContrib/WebNotify.pm, avg 5µs/call
sub initContrib {
3724µs $Foswiki::cfg{MailerContrib}{EmailFilterIn} ||=
38 $Foswiki::regex{emailAddrRegex};
3929µs $Foswiki::cfg{MailerContrib}{RespectUserPrefs} ||= 'LANGUAGE';
40}
41
42=begin TML
43
44---++ StaticMethod mailNotify($webs, $exwebs)
45 * =$webs= - filter list of names webs to process. Wildcards (*) may be used.
46 * =$exwebs= - filter list of webs to exclude.
47 * =%options%
48 * =verbose= - true to get verbose (debug) output.
49 * =news= - true to process news
50 * =changes= - true to process changes
51 * =reset= - true to reset the clock after processing
52 * =mail= - true to send emails from this run
53Main entry point.
54
55Process the Web<nop>Notify topics in each web and generate and issue
56notification mails. Designed to be invoked from the command line; should
57only be called by =mailnotify= scripts.
58
59=cut
60
61sub mailNotify {
62 my ( $webs, $exwebs, %options ) = @_;
63
64 my $webstr;
65 if ( defined($webs) ) {
66 $webstr = join( '|', @$webs );
67 }
68 $webstr = '*' unless ($webstr);
69 $webstr =~ s/\*/\.\*/g;
70
71 my $exwebstr = '';
72 if ( defined($exwebs) ) {
73 $exwebstr = join( '|', @$exwebs );
74 }
75 $exwebstr =~ s/\*/\.\*/g;
76
77 my $context = Foswiki::Func::getContext();
78
79 $context->{command_line} = 1;
80
81 # absolute URL context for email generation
82 $context->{absolute_urls} = 1;
83
84 initContrib();
85
86 foreach my $web ( Foswiki::Func::getListOfWebs('user ') ) {
87 if ( $web =~ m/^($webstr)$/ && $web !~ /^($exwebstr)$/ ) {
88 _processWeb( $web, \%options );
89 }
90 }
91
92 $context->{absolute_urls} = 0;
93}
94
95=begin TML
96
97---++ StaticMethod changeSubscription($web, $who, $topicList, $unsubscribe)
98
99Modify a user's subscription in =WebNotify= for a web.
100 * =$web= - web to edit the WebNotify for
101 * =$who= - the user's wikiname
102 * =$topicList= - list of topics to (un)subscribe to(from)
103 * =$unsubscribe= - false to subscribe, true to unsubscribe
104
105The current user must be able to modify WebNotify, or an access control
106violation will be thrown.
107
108=cut
109
110sub changeSubscription {
111 my ( $defaultWeb, $who, $topicList, $unsubscribe ) = @_;
112
113 # we can get away with a normalise on a list of topics, so long as
114 # the list starts with a topic
115 my ( $web, $t ) =
116 Foswiki::Func::normalizeWebTopicName( $defaultWeb, $topicList );
117
118 #TODO: this limits us to subscribing to one web.
119 my $wn =
120 Foswiki::Contrib::MailerContrib::WebNotify->new( $web,
121 $Foswiki::cfg{NotifyTopicName}, 1 );
122 $wn->parsePageSubscriptions( $who, $topicList, $unsubscribe );
123 $wn->writeWebNotify();
124 return;
125}
126
127=begin TML
128
129---++ isSubscribedTo ($web, $who, $topicList) -> boolean
130
131Returns true if all topics mentioned in the =$topicList= are subscribed to by =$who=.
132
133Can ignore all valid special characters that can be used on the WebNotify topic
134such as NewsTopic! , TopicAndChildren (2)
135
136=cut
137
138
# spent 74.8ms (102µs+74.7) within Foswiki::Contrib::MailerContrib::isSubscribedTo which was called 2 times, avg 37.4ms/call: # 2 times (102µs+74.7ms) by Foswiki::Plugins::SubscribePlugin::_SUBSCRIBE at line 86 of /var/www/foswikidev/core/lib/Foswiki/Plugins/SubscribePlugin.pm, avg 37.4ms/call
sub isSubscribedTo {
13924µs my ( $defaultWeb, $who, $topicList ) = @_;
140
14128µs my $subscribed = {
142 currentWeb => $defaultWeb,
143 topicSub => \&_isSubscribedToTopic,
144 webs => {}
145 };
146
14725µs274.7ms my $ret = parsePageList( $subscribed, $who, $topicList );
# spent 74.7ms making 2 calls to Foswiki::Contrib::MailerContrib::parsePageList, avg 37.4ms/call
148
149286µs return ( !defined( $subscribed->{not_subscribed} )
150 || ( 0 == scalar( $subscribed->{not_subscribed} ) ) );
151}
152
153
# spent 74.7ms (73µs+74.6) within Foswiki::Contrib::MailerContrib::_isSubscribedToTopic which was called 2 times, avg 37.3ms/call: # 2 times (73µs+74.6ms) by Foswiki::Contrib::MailerContrib::parsePageList at line 212, avg 37.3ms/call
sub _isSubscribedToTopic {
15423µs my ( $subscribed, $who, $unsubscribe, $topic, $options, $childDepth ) = @_;
15521µs require Foswiki::Contrib::MailerContrib::WebNotify;
15627µs227µs my ( $sweb, $stopic ) =
# spent 27µs making 2 calls to Foswiki::Func::normalizeWebTopicName, avg 14µs/call
157 Foswiki::Func::normalizeWebTopicName( $subscribed->{currentWeb}, $topic );
158
159218µs274.5ms $subscribed->{webs}->{$sweb} ||=
# spent 74.5ms making 2 calls to Foswiki::Contrib::MailerContrib::WebNotify::new, avg 37.3ms/call
160 Foswiki::Contrib::MailerContrib::WebNotify->new( $sweb,
161 $Foswiki::cfg{NotifyTopicName}, 0 );
162
16322µs my $wn = $subscribed->{webs}->{$sweb};
16424µs223µs my $subscriber = $wn->getSubscriber($who);
# spent 23µs making 2 calls to Foswiki::Contrib::MailerContrib::WebNotify::getSubscriber, avg 12µs/call
165211µs216µs my $db = Foswiki::Contrib::MailerContrib::UpData->new($sweb);
# spent 16µs making 2 calls to Foswiki::Contrib::MailerContrib::UpData::new, avg 8µs/call
166
167 #TODO: need to check $childDepth topics too (somehow)
168214µs210µs if ( $subscriber->isSubscribedTo( $stopic, $db )
# spent 10µs making 2 calls to Foswiki::Contrib::MailerContrib::Subscriber::isSubscribedTo, avg 5µs/call
169 && ( !$subscriber->isUnsubscribedFrom( $stopic, $db ) ) )
170 {
171 push( @{ $subscribed->{subscribed} }, $stopic );
172 }
173 else {
17424µs push( @{ $subscribed->{not_subscribed} }, $stopic );
175 }
176}
177
178=begin TML
179
180---++ parsePageList ( $object, $who, $spec, $unsubscribe ) -> unprocessable remainder of =$spec= line
181Calls =$object->{topicSub}= once per identified topic entry.
182 * =$object= (a hashref) may be a hashref that has the field =topicSub=,
183 which _may_ be a sub ref as follows:
184 =&topicSub($object, $who, $unsubscribe, $webTopic, $options, $childDepth)=
185 * =$unsubscribe= can be set to '-' to force an unsubscription
186 (used by SubscribePlugin)
187
188=cut
189
190
# spent 74.7ms (1.71+73.0) within Foswiki::Contrib::MailerContrib::parsePageList which was called 64 times, avg 1.17ms/call: # 62 times (1.67ms+-1.67ms) by Foswiki::Contrib::MailerContrib::WebNotify::parsePageSubscriptions at line 478 of /var/www/foswikidev/core/lib/Foswiki/Contrib/MailerContrib/WebNotify.pm, avg 0s/call # 2 times (41µs+74.7ms) by Foswiki::Contrib::MailerContrib::isSubscribedTo at line 147, avg 37.4ms/call
sub parsePageList {
1916433µs my ( $object, $who, $spec, $unsubscribe ) = @_;
192
193 #ASSERT(defined($object->{topicSub}));
194
1956427µs return $spec if ( !$object || !defined( $object->{topicSub} ) );
196
1976433µs $spec =~ s/,/ /g;
198
199 # $1: + or -, optional
200 # $2: the wildcarded topic specifier (may be quoted)
201 # TODO: refine the $2 regex to be proper web.topic/topic/* style..
202 # $3: options
203 # $4: child depth
204
20564328µs154µs while ( $spec =~
# spent 54µs making 1 call to utf8::SWASHNEW
206s/^\s*([-+])?\s*((?:[[:alnum:]]|[*.])+|'.*?'|".*?")([!?]?)\s*(?:\((\d+)\))?//
207 )
208 {
209182260µs my ( $us, $webTopic, $options, $childDepth ) =
210 ( $unsubscribe || $1 || '+', $2, $3, $4 || 0 );
211182139µs $webTopic =~ s/^(['"])(.*)\1$/$2/; # remove quotes
212182654µs18276.8ms &{ $object->{topicSub} }
# spent 74.7ms making 2 calls to Foswiki::Contrib::MailerContrib::_isSubscribedToTopic, avg 37.3ms/call # spent 2.15ms making 180 calls to Foswiki::Contrib::MailerContrib::WebNotify::_subscribeTopic, avg 12µs/call
213 ( $object, $who, $us, $webTopic, $options, $childDepth );
214 }
21564140µs return $spec;
216}
217
218# PRIVATE: Read the webnotify, and notify changes
219sub _processWeb {
220 my ( $web, $options ) = @_;
221
222 if ( !Foswiki::Func::webExists($web) ) {
223
224 # print STDERR "**** ERROR mailnotifier cannot find web $web\n";
225 return '';
226 }
227
228 print "Processing $web\n" if $options->{verbose};
229
230 # Read the webnotify and load subscriptions
231 my $wn =
232 Foswiki::Contrib::MailerContrib::WebNotify->new( $web,
233 $Foswiki::cfg{NotifyTopicName}, 0 );
234 if ( $wn->isEmpty() ) {
235 print "\t$web has no subscribers\n" if $options->{verbose};
236 }
237 else {
238
239 # create a DB object for parent pointers
240 print $wn->stringify(1) if $options->{verbose};
241 my $db = Foswiki::Contrib::MailerContrib::UpData->new($web);
242 _processSubscriptions( $web, $wn, $db, $options );
243 }
244}
245
246# Process subscriptions in $notify
247sub _processSubscriptions {
248 my ( $web, $notify, $db, $options ) = @_;
249
250 my $metadir = Foswiki::Func::getWorkArea('MailerContrib');
251 my $notmeta = $web;
252 $notmeta =~ s#/#.#g;
253 $notmeta = "$metadir/$notmeta";
254
255 my $timeOfLastNotify = 0;
256 if ( open( F, '<', $notmeta ) ) {
257 local $/ = undef;
258 $timeOfLastNotify = <F>;
259 close(F);
260 }
261
262 if ( $options->{verbose} ) {
263 print "\tLast notification was at "
264 . Foswiki::Time::formatTime( $timeOfLastNotify, 'iso' ) . "\n";
265 }
266
267 my $timeOfLastChange = 0;
268
269 # Hash indexed on name&email address, each entry contains a hash
270 # of topics already processed in the change set for this name&email.
271 # Each subhash maps the topic name to the index of the change
272 # record for this topic in the array of Change objects for this
273 # name&email in %changeset.
274 my %seenset;
275
276 # Hash indexed on name&email address, each entry contains an array
277 # indexed by the index stored in %seenSet. Each entry in the array
278 # is a ref to a Change object.
279 my %changeset;
280
281 # Hash indexed on topic name, mapping to name&email address, used to
282 # record simple newsletter subscriptions.
283 my %allSet;
284
285 # + 1 because the 'since' check is >=
286 my $it = Foswiki::Func::eachChangeSince( $web, $timeOfLastNotify + 1 );
287 while ( $it->hasNext() ) {
288 my $change = $it->next();
289 next if $change->{minor};
290 next if $change->{more} && $change->{more} =~ m/minor/;
291
292 next unless Foswiki::Func::topicExists( $web, $change->{topic} );
293
294 $timeOfLastChange = $change->{time} unless ($timeOfLastChange);
295
296 print "\tChange to $change->{topic} at "
297 . Foswiki::Time::formatTime( $change->{time}, 'iso' )
298 . ". New revision is $change->{revision}\n"
299 if ( $options->{verbose} );
300
301 # Formulate a change record, irrespective of
302 # whether any subscriber is interested
303 $change =
304 Foswiki::Contrib::MailerContrib::Change->new( $web, $change->{topic},
305 $change->{user}, $change->{time}, $change->{revision} );
306
307 # Now, find subscribers to this change and extend the change set
308 $notify->processChange( $change, $db, \%changeset, \%seenset,
309 \%allSet );
310 }
311
312 # For each topic, see if there's a compulsory subscription independent
313 # of the time since last notify
314 foreach my $topic ( Foswiki::Func::getTopicList($web) ) {
315 $notify->processCompulsory( $topic, $db, \%allSet );
316 }
317
318 # Now generate emails for each recipient
319 my %email2meta;
320 if ( $options->{changes} && scalar( keys %changeset ) ) {
321 _sendChangesMails( $web, \%changeset,
322 Foswiki::Time::formatTime($timeOfLastNotify),
323 \%email2meta, $options );
324 }
325
326 if ( $options->{news} ) {
327 _sendNewsletterMails( $web, \%allSet, \%email2meta, $options );
328 }
329
330 if ( $options->{reset} && $timeOfLastChange != 0 ) {
331 if ( open( F, '>', $notmeta ) ) {
332 print F $timeOfLastChange;
333 close(F);
334 }
335 }
336}
337
338# i18N doesn't change when we change LANGUAGE, so we have to stomp it.
339sub _stompI18N {
340 if ( $Foswiki::Plugins::SESSION->can('reset_i18n') ) {
341 $Foswiki::Plugins::SESSION->reset_i18n();
342 }
343 elsif ( $Foswiki::Plugins::SESSION->{i18n} ) {
344
345 # Muddy boots.
346 $Foswiki::Plugins::SESSION->i18n->finish();
347 undef $Foswiki::Plugins::SESSION->{i18n};
348 }
349}
350
351sub _loadUserPreferences {
352 my ( $name, $email, $email2meta, $oldPrefs ) = @_;
353
354 my $meta = $email2meta->{$email};
355 unless ( defined $meta ) {
356 my @wn = Foswiki::Func::emailToWikiNames($email);
357
358 # If the email maps to a single user, we can use their
359 # preferences.
360 # First check sanity of mappings.
361 if ( scalar(@wn) == 1 ) {
362 if ( $wn[0] ne $name ) {
363 my $mess = 'MailerContrib Warning: surprising mapping'
364 . " $email => $wn[0] != $name";
365 Foswiki::Func::writeDebug($mess);
366 }
367 $name = $wn[0];
368 }
369 elsif ( !grep { /^$name$/ } @wn ) {
370 my $mess =
371 'MailerContrib Warning: missing mapping'
372 . " $email => ("
373 . join( ',', @wn )
374 . ") != $name";
375 Foswiki::Func::writeDebug($mess);
376 }
377 my ( $uw, $ut ) =
378 Foswiki::Func::normalizeWebTopicName( $Foswiki::cfg{UsersWebName},
379 $name );
380 $meta = Foswiki::Meta->new( $Foswiki::Plugins::SESSION, $uw, $ut );
381 $email2meta->{$email} = $meta;
382 }
383 if ($meta) {
384 foreach my $k (
385 split( /[ ,]+/, $Foswiki::cfg{MailerContrib}{RespectUserPrefs} ) )
386 {
387
388 my $ov = Foswiki::Func::getPreferencesValue($k);
389 my $nv = $meta->getPreference($k);
390 if ( ( $nv || '' ) ne ( $ov || '' ) ) {
391 $oldPrefs->{$k} = $ov;
392 Foswiki::Func::setPreferencesValue( $k, $nv );
393 _stompI18N() if ( $k eq 'LANGUAGE' );
394 }
395 }
396 }
397}
398
399sub _restorePreferences {
400 my ($oldPrefs) = @_;
401
402 while ( my ( $k, $v ) = each %$oldPrefs ) {
403
404 # Really we'd like to clear the session pref, but there's
405 # no API to do that :-(
406 Foswiki::Func::setPreferencesValue( $k, $v );
407 _stompI18N() if ( $k eq 'LANGUAGE' );
408 }
409}
410
411# PRIVATE generate and send an email for each user
412sub _sendChangesMails {
413 my ( $web, $changeset, $lastTime, $email2meta, $options ) = @_;
414
415 # We read the mailnotify template in the context (skin and web) of the
416 # WebNotify topic we are currently processing
417 Foswiki::Func::pushTopicContext( $web, $Foswiki::cfg{NotifyTopicName} );
418 my $skin = Foswiki::Func::getSkin();
419 my $template = Foswiki::Func::readTemplate( 'mailnotify', $skin );
420 Foswiki::Func::popTopicContext();
421
422 my $sentMails = 0;
423
424 foreach my $name_email ( keys %{$changeset} ) {
425 my ( $name, $email ) = split( '&', $name_email, 2 );
426
427 my %oldPrefs;
428 _loadUserPreferences( $name, $email, $email2meta, \%oldPrefs );
429
430 my $mail =
431 Foswiki::Func::expandCommonVariables(
432 Foswiki::Func::expandTemplate('MailNotifyBody'),
433 $Foswiki::cfg{HomeTopicName}, $web );
434
435 if ( $Foswiki::cfg{MailerContrib}{RemoveImgInMailnotify} ) {
436
437 # change images to [alt] text if there, else remove image
438 $mail =~ s/<img\s[^>]*\balt=\"([^\"]+)[^>]*>/[$1]/gi;
439 $mail =~ s/<img\s[^>]*\bsrc=.*?[^>]>//gi;
440 }
441
442 $mail =~ s/%EMAILTO%/$email/g;
443 $mail =~ s/%(HTML|PLAIN)_TEXT%/
444 _generateChangeDetail($name_email, $changeset, $1, $web)/ge;
445 $mail =~ s/%LASTDATE%/$lastTime/ge;
446
447 my $base = $Foswiki::cfg{DefaultUrlHost} . $Foswiki::cfg{ScriptUrlPath};
448 $mail =~ s/(href=\")([^"]+)/$1.relativeURL($base,$2)/gei;
449 $mail =~ s/(action=\")([^"]+)/$1.relativeURL($base,$2)/gei;
450
451 # remove <nop> and <noautolink> tags
452 $mail =~ s/( ?) *<\/?(nop|noautolink)\/?>\n?/$1/gis;
453
454 _restorePreferences( \%oldPrefs );
455
456 my $error;
457 if ( $options->{mail} ) {
458 $error = Foswiki::Func::sendEmail( $mail, 5 );
459 }
460 else {
461 print $mail if $options->{verbose};
462 }
463
464 if ($error) {
465 print STDERR "Error sending mail for $web: $error\n";
466 print "$error\n";
467 }
468 else {
469 print "Notified $email of changes in $web\n" if $options->{verbose};
470 $sentMails++;
471 }
472 }
473 print "\t$sentMails change notifications from $web\n"
474 if $options->{verbose};
475}
476
477sub _generateChangeDetail {
478 my ( $name_email, $changeset, $style, $web ) = @_;
479
480 my @ep = ( $Foswiki::cfg{HomeTopicName}, $web );
481
482 my $template = Foswiki::Func::expandTemplate( $style . ':middle' );
483 my $diff_tmpl;
484 my $text = '';
485 my ( $name, $email ) = split( '&', $name_email, 2 );
486 foreach my $change ( sort { $a->{TIME} cmp $b->{TIME} }
487 @{ $changeset->{$name_email} } )
488 {
489 if ( $style eq 'HTML' ) {
490 $text .= Foswiki::Func::expandCommonVariables(
491 $change->expandHTML( $template, $name ), @ep );
492 }
493 elsif ( $style eq 'PLAIN' ) {
494 $text .= Foswiki::Func::expandCommonVariables(
495 $change->expandPlain( $template, $name ), @ep );
496 }
497
498 if ( $text =~ m/%DIFF_TEXT%/ ) {
499 $diff_tmpl ||= Foswiki::Func::expandTemplate( $style . ':diff' );
500
501 # Note: no macro expansion; this is a verbatim format
502 $text =~ s/%DIFF_TEXT%/$change->expandDiff($diff_tmpl)/ge;
503 }
504 }
505 return Foswiki::Func::expandCommonVariables(
506 Foswiki::Func::expandTemplate( $style . ':before' ), @ep )
507 . $text
508 . Foswiki::Func::expandCommonVariables(
509 Foswiki::Func::expandTemplate( $style . ':after' ), @ep );
510}
511
512sub relativeURL {
513 my ( $base, $link ) = @_;
514 if ( $link =~ "^#" ) {
515 return $link;
516 }
517 else {
518 return URI->new_abs( $link, URI->new($base) )->as_string;
519 }
520}
521
522sub _sendNewsletterMails {
523 my ( $web, $allSet, $email2meta, $options ) = @_;
524
525 foreach my $topic ( keys %$allSet ) {
526 _sendNewsletterMail( $web, $topic, $allSet->{$topic}, $email2meta,
527 $options );
528 }
529}
530
531sub _sendNewsletterMail {
532 my ( $web, $topic, $name_emails, $email2meta, $options ) = @_;
533 my $wikiName = Foswiki::Func::getWikiName();
534
535 # SMELL: this code is almost identical to PublishContrib
536
537 # Read topic data.
538 my ( $meta, $text ) = Foswiki::Func::readTopic( $web, $topic );
539
540 # SMELL: Have to hack into the core to set internal preferences :-(
541 my %old =
542 map { $_ => undef } qw(BASEWEB BASETOPIC INCLUDINGWEB INCLUDINGTOPIC);
543 if ( defined $Foswiki::Plugins::SESSION->{SESSION_TAGS} ) {
544
545 # In 1.0.6 and earlier, have to handle some session tags ourselves
546 # because pushTopicContext doesn't do it. **
547 foreach my $macro ( keys %old ) {
548 $old{$macro} = Foswiki::Func::getPreferencesValue($macro);
549 }
550 }
551 Foswiki::Func::pushTopicContext( $web, $topic );
552
553 # See ** above
554 if ( defined $Foswiki::Plugins::SESSION->{SESSION_TAGS} ) {
555 my $stags = $Foswiki::Plugins::SESSION->{SESSION_TAGS};
556 $stags->{BASEWEB} = $web;
557 $stags->{BASETOPIC} = $topic;
558 $stags->{INCLUDINGWEB} = $web;
559 $stags->{INCLUDINGTOPIC} = $topic;
560 }
561
562 # Only required pre-1.1
563 Foswiki::Func::getContext()->{can_render_meta} = $meta;
564
565 # Get the skin for this topic
566 my $skin = Foswiki::Func::getSkin();
567 Foswiki::Func::readTemplate( 'newsletter', $skin );
568 my $h_tmpl = Foswiki::Func::expandTemplate('NEWS:header');
569 my $body = Foswiki::Func::expandTemplate('NEWS:body');
570 my $footer = Foswiki::Func::expandTemplate('NEWS:footer');
571
572 my ( $revdate, $revuser, $maxrev );
573 ( $revdate, $revuser, $maxrev ) = $meta->getRevisionInfo();
574
575 # Handle standard formatting.
576 $body =~ s/%TEXT%/$text/g;
577
578 my $sentMails = 0;
579
580 foreach my $name_email (@$name_emails) {
581
582 my ( $name, $email ) = split( '&', $name_email, 2 );
583
584 # Set up user prefs
585 my %oldPrefs;
586 _loadUserPreferences( $name, $email, $email2meta, \%oldPrefs );
587
588 # Don't render the header, it is preformatted
589 my $header =
590 Foswiki::Func::expandCommonVariables( $h_tmpl, $topic, $web );
591 my $mail = "$body\n$footer";
592 $mail = Foswiki::Func::expandCommonVariables( $mail, $topic, $web );
593 $mail = Foswiki::Func::renderText( $mail, "", $meta );
594
595 # REFACTOR OPPORTUNITY: stop factor me into getTWikiRendering()
596 # SMELL: this code is identical to PublishContrib!
597
598 # New tags
599 my $newTmpl = '';
600 my $tagSeen = 0;
601 my $publish = 1;
602 foreach my $s ( split( /(%STARTPUBLISH%|%STOPPUBLISH%)/, $mail ) ) {
603 if ( $s eq '%STARTPUBLISH%' ) {
604 $publish = 1;
605 $newTmpl = '' unless ($tagSeen);
606 $tagSeen = 1;
607 }
608 elsif ( $s eq '%STOPPUBLISH%' ) {
609 $publish = 0;
610 $tagSeen = 1;
611 }
612 elsif ($publish) {
613 $newTmpl .= $s;
614 }
615 }
616 $mail = $header . $newTmpl;
617 $mail =~ s/.*?<\/nopublish>//gs;
618 $mail =~ s/%MAXREV%/$maxrev/g;
619 $mail =~ s/%CURRREV%/$maxrev/g;
620 $mail =~ s/%REVTITLE%//g;
621 $mail =~ s|( ?) *</*nop/*>\n?|$1|gis;
622
623 # Remove <base.../> tag
624 $mail =~ s/<base[^>]+\/>//;
625
626 # Remove <base...>...</base> tag
627 $mail =~ s/<base[^>]+>.*?<\/base>//;
628
629 # Rewrite absolute URLs
630 my $base =
631 $Foswiki::cfg{DefaultUrlHost}
632 . $Foswiki::cfg{ScriptUrlPath}
633 . "/view/"
634 . $web . "/"
635 . $topic;
636 $mail =~ s/(href=\")([^"]+)/$1.relativeURL($base,$2)/gei;
637 $mail =~ s/(action=\")([^"]+)/$1.relativeURL($base,$2)/gei;
638 $mail =~ s/%EMAILTO%/$email/g;
639
640 # remove <nop> and <noautolink> tags
641 $mail =~ s/( ?) *<\/?(nop|noautolink)\/?>\n?/$1/gis;
642
643 _restorePreferences( \%oldPrefs );
644
645 my $error;
646 if ( $options->{mail} ) {
647 $error = Foswiki::Func::sendEmail( $mail, 5 );
648 }
649 else {
650 print $mail if $options->{verbose};
651 }
652
653 if ($error) {
654 print STDERR "Error sending mail for $web: $error\n";
655 print "$error\n";
656 }
657 else {
658 print "Sent newsletter $web.$topic to $email\n"
659 if $options->{verbose};
660 $sentMails++;
661 }
662 }
663 print "\t$sentMails newsletters from $web\n";
664
665 Foswiki::Func::popTopicContext();
666
667 # SMELL: See ** above
668 if ( defined $Foswiki::Plugins::SESSION->{SESSION_TAGS} ) {
669
670 # In 1.0.6 and earlier, have to handle some session tags ourselves
671 # because pushTopicContext doesn't do it. **
672 foreach my $macro ( keys %old ) {
673 $Foswiki::Plugins::SESSION->{SESSION_TAGS}{$macro} = $old{$macro};
674 }
675 }
676}
677
67814µs1;
679__END__