Filename | /var/www/foswikidev/core/lib/Foswiki/Contrib/MailerContrib.pm |
Statements | Executed 924 statements in 5.30ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 2.14ms | 4.43ms | BEGIN@27 | Foswiki::Contrib::MailerContrib::
64 | 2 | 2 | 1.71ms | 74.7ms | parsePageList (recurses: max depth 1, inclusive time 3.88ms) | Foswiki::Contrib::MailerContrib::
1 | 1 | 1 | 1.62ms | 2.65ms | BEGIN@18 | Foswiki::Contrib::MailerContrib::
1 | 1 | 1 | 1.08ms | 1.18ms | BEGIN@28 | Foswiki::Contrib::MailerContrib::
1 | 1 | 1 | 239µs | 284µs | BEGIN@29 | Foswiki::Contrib::MailerContrib::
2 | 1 | 1 | 102µs | 74.8ms | isSubscribedTo | Foswiki::Contrib::MailerContrib::
2 | 1 | 1 | 73µs | 74.7ms | _isSubscribedToTopic | Foswiki::Contrib::MailerContrib::
1 | 1 | 1 | 15µs | 30µs | BEGIN@15 | Foswiki::Contrib::MailerContrib::
1 | 1 | 1 | 11µs | 40µs | BEGIN@21 | Foswiki::Contrib::MailerContrib::
1 | 1 | 1 | 10µs | 16µs | BEGIN@16 | Foswiki::Contrib::MailerContrib::
2 | 1 | 1 | 10µs | 10µs | initContrib | Foswiki::Contrib::MailerContrib::
1 | 1 | 1 | 7µs | 7µs | BEGIN@26 | Foswiki::Contrib::MailerContrib::
1 | 1 | 1 | 6µs | 6µs | BEGIN@19 | Foswiki::Contrib::MailerContrib::
1 | 1 | 1 | 5µs | 5µs | BEGIN@23 | Foswiki::Contrib::MailerContrib::
1 | 1 | 1 | 4µs | 4µs | BEGIN@25 | Foswiki::Contrib::MailerContrib::
1 | 1 | 1 | 4µs | 4µs | BEGIN@24 | Foswiki::Contrib::MailerContrib::
0 | 0 | 0 | 0s | 0s | _generateChangeDetail | Foswiki::Contrib::MailerContrib::
0 | 0 | 0 | 0s | 0s | _loadUserPreferences | Foswiki::Contrib::MailerContrib::
0 | 0 | 0 | 0s | 0s | _processSubscriptions | Foswiki::Contrib::MailerContrib::
0 | 0 | 0 | 0s | 0s | _processWeb | Foswiki::Contrib::MailerContrib::
0 | 0 | 0 | 0s | 0s | _restorePreferences | Foswiki::Contrib::MailerContrib::
0 | 0 | 0 | 0s | 0s | _sendChangesMails | Foswiki::Contrib::MailerContrib::
0 | 0 | 0 | 0s | 0s | _sendNewsletterMail | Foswiki::Contrib::MailerContrib::
0 | 0 | 0 | 0s | 0s | _sendNewsletterMails | Foswiki::Contrib::MailerContrib::
0 | 0 | 0 | 0s | 0s | _stompI18N | Foswiki::Contrib::MailerContrib::
0 | 0 | 0 | 0s | 0s | changeSubscription | Foswiki::Contrib::MailerContrib::
0 | 0 | 0 | 0s | 0s | mailNotify | Foswiki::Contrib::MailerContrib::
0 | 0 | 0 | 0s | 0s | relativeURL | Foswiki::Contrib::MailerContrib::
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 | |||||
7 | Package of support for extended Web<nop>Notify notification, supporting per-topic notification and notification of changes to children. | ||||
8 | |||||
9 | Also supported is a simple API that can be used to change the Web<nop>Notify topic from other code. | ||||
10 | |||||
11 | =cut | ||||
12 | |||||
13 | package Foswiki::Contrib::MailerContrib; | ||||
14 | |||||
15 | 2 | 30µs | 2 | 44µ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 # spent 30µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@15
# spent 14µs making 1 call to strict::import |
16 | 2 | 27µs | 2 | 22µ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 # spent 16µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@16
# spent 6µs making 1 call to warnings::import |
17 | |||||
18 | 2 | 105µs | 1 | 2.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 # spent 2.65ms making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@18 |
19 | 2 | 25µs | 1 | 6µ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 # spent 6µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@19 |
20 | |||||
21 | 2 | 28µs | 2 | 69µ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 # spent 40µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@21
# spent 29µs making 1 call to Exporter::import |
22 | |||||
23 | 2 | 20µs | 1 | 5µ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 # spent 5µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@23 |
24 | 2 | 19µs | 1 | 4µ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 # spent 4µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@24 |
25 | 2 | 22µs | 1 | 4µ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 # spent 4µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@25 |
26 | 2 | 20µs | 1 | 7µ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 # spent 7µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@26 |
27 | 2 | 115µs | 1 | 4.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 # spent 4.43ms making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@27 |
28 | 2 | 103µs | 1 | 1.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 # spent 1.18ms making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@28 |
29 | 2 | 2.99ms | 1 | 284µ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 # spent 284µs making 1 call to Foswiki::Contrib::MailerContrib::BEGIN@29 |
30 | |||||
31 | 1 | 700ns | our $VERSION = '2.82'; | ||
32 | 1 | 200ns | our $RELEASE = '2.82'; | ||
33 | 1 | 200ns | our $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 | ||||
37 | 2 | 4µs | $Foswiki::cfg{MailerContrib}{EmailFilterIn} ||= | ||
38 | $Foswiki::regex{emailAddrRegex}; | ||||
39 | 2 | 9µ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 | ||||
53 | Main entry point. | ||||
54 | |||||
55 | Process the Web<nop>Notify topics in each web and generate and issue | ||||
56 | notification mails. Designed to be invoked from the command line; should | ||||
57 | only be called by =mailnotify= scripts. | ||||
58 | |||||
59 | =cut | ||||
60 | |||||
61 | sub 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 | |||||
99 | Modify 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 | |||||
105 | The current user must be able to modify WebNotify, or an access control | ||||
106 | violation will be thrown. | ||||
107 | |||||
108 | =cut | ||||
109 | |||||
110 | sub 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 | |||||
131 | Returns true if all topics mentioned in the =$topicList= are subscribed to by =$who=. | ||||
132 | |||||
133 | Can ignore all valid special characters that can be used on the WebNotify topic | ||||
134 | such 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 | ||||
139 | 2 | 4µs | my ( $defaultWeb, $who, $topicList ) = @_; | ||
140 | |||||
141 | 2 | 8µs | my $subscribed = { | ||
142 | currentWeb => $defaultWeb, | ||||
143 | topicSub => \&_isSubscribedToTopic, | ||||
144 | webs => {} | ||||
145 | }; | ||||
146 | |||||
147 | 2 | 5µs | 2 | 74.7ms | my $ret = parsePageList( $subscribed, $who, $topicList ); # spent 74.7ms making 2 calls to Foswiki::Contrib::MailerContrib::parsePageList, avg 37.4ms/call |
148 | |||||
149 | 2 | 86µ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 | ||||
154 | 2 | 3µs | my ( $subscribed, $who, $unsubscribe, $topic, $options, $childDepth ) = @_; | ||
155 | 2 | 1µs | require Foswiki::Contrib::MailerContrib::WebNotify; | ||
156 | 2 | 7µs | 2 | 27µ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 | |||||
159 | 2 | 18µs | 2 | 74.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 | |||||
163 | 2 | 2µs | my $wn = $subscribed->{webs}->{$sweb}; | ||
164 | 2 | 4µs | 2 | 23µs | my $subscriber = $wn->getSubscriber($who); # spent 23µs making 2 calls to Foswiki::Contrib::MailerContrib::WebNotify::getSubscriber, avg 12µs/call |
165 | 2 | 11µs | 2 | 16µ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) | ||||
168 | 2 | 14µs | 2 | 10µ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 { | ||||
174 | 2 | 4µs | push( @{ $subscribed->{not_subscribed} }, $stopic ); | ||
175 | } | ||||
176 | } | ||||
177 | |||||
178 | =begin TML | ||||
179 | |||||
180 | ---++ parsePageList ( $object, $who, $spec, $unsubscribe ) -> unprocessable remainder of =$spec= line | ||||
181 | Calls =$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 | ||||
191 | 64 | 33µs | my ( $object, $who, $spec, $unsubscribe ) = @_; | ||
192 | |||||
193 | #ASSERT(defined($object->{topicSub})); | ||||
194 | |||||
195 | 64 | 27µs | return $spec if ( !$object || !defined( $object->{topicSub} ) ); | ||
196 | |||||
197 | 64 | 33µ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 | |||||
205 | 64 | 328µs | 1 | 54µs | while ( $spec =~ # spent 54µs making 1 call to utf8::SWASHNEW |
206 | s/^\s*([-+])?\s*((?:[[:alnum:]]|[*.])+|'.*?'|".*?")([!?]?)\s*(?:\((\d+)\))?// | ||||
207 | ) | ||||
208 | { | ||||
209 | 182 | 260µs | my ( $us, $webTopic, $options, $childDepth ) = | ||
210 | ( $unsubscribe || $1 || '+', $2, $3, $4 || 0 ); | ||||
211 | 182 | 139µs | $webTopic =~ s/^(['"])(.*)\1$/$2/; # remove quotes | ||
212 | 182 | 654µs | 182 | 76.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 | } | ||||
215 | 64 | 140µs | return $spec; | ||
216 | } | ||||
217 | |||||
218 | # PRIVATE: Read the webnotify, and notify changes | ||||
219 | sub _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 | ||||
247 | sub _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. | ||||
339 | sub _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 | |||||
351 | sub _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 | |||||
399 | sub _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 | ||||
412 | sub _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 | |||||
477 | sub _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 | |||||
512 | sub 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 | |||||
522 | sub _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 | |||||
531 | sub _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 | |||||
678 | 1 | 4µs | 1; | ||
679 | __END__ |