1 | <?php
|
---|
2 | /**
|
---|
3 | * Utilities for handling (email) subscriptions
|
---|
4 | *
|
---|
5 | * The public interface of this file consists of the functions
|
---|
6 | * - subscription_find
|
---|
7 | * - subscription_send_digest
|
---|
8 | * - subscription_send_list
|
---|
9 | * - subscription_set
|
---|
10 | * - get_info_subscribed
|
---|
11 | * - subscription_addresslist
|
---|
12 | * - subscription_lock
|
---|
13 | * - subscription_unlock
|
---|
14 | *
|
---|
15 | * @author Adrian Lang <[email protected]>
|
---|
16 | * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
---|
17 | */
|
---|
18 |
|
---|
19 | /**
|
---|
20 | * Get the name of the metafile tracking subscriptions to target page or
|
---|
21 | * namespace
|
---|
22 | *
|
---|
23 | * @param string $id The target page or namespace, specified by id; Namespaces
|
---|
24 | * are identified by appending a colon.
|
---|
25 | *
|
---|
26 | * @author Adrian Lang <[email protected]>
|
---|
27 | */
|
---|
28 | function subscription_filename($id) {
|
---|
29 | $meta_fname = '.mlist';
|
---|
30 | if ((substr($id, -1, 1) === ':')) {
|
---|
31 | $meta_froot = getNS($id);
|
---|
32 | $meta_fname = '/' . $meta_fname;
|
---|
33 | } else {
|
---|
34 | $meta_froot = $id;
|
---|
35 | }
|
---|
36 | return metaFN((string) $meta_froot, $meta_fname);
|
---|
37 | }
|
---|
38 |
|
---|
39 | /**
|
---|
40 | * Lock subscription info for an ID
|
---|
41 | *
|
---|
42 | * @param string $id The target page or namespace, specified by id; Namespaces
|
---|
43 | * are identified by appending a colon.
|
---|
44 | *
|
---|
45 | * @author Adrian Lang <[email protected]>
|
---|
46 | */
|
---|
47 | function subscription_lock_filename ($id){
|
---|
48 | global $conf;
|
---|
49 | return $conf['lockdir'].'/_subscr_' . md5($id) . '.lock';
|
---|
50 | }
|
---|
51 |
|
---|
52 | function subscription_lock($id) {
|
---|
53 | global $conf;
|
---|
54 | $lock = subscription_lock_filename($id);
|
---|
55 |
|
---|
56 | if (is_dir($lock) && time()-@filemtime($lock) > 60*5) {
|
---|
57 | // looks like a stale lock - remove it
|
---|
58 | @rmdir($lock);
|
---|
59 | }
|
---|
60 |
|
---|
61 | // try creating the lock directory
|
---|
62 | if (!@mkdir($lock,$conf['dmode'])) {
|
---|
63 | return false;
|
---|
64 | }
|
---|
65 |
|
---|
66 | if($conf['dperm']) chmod($lock, $conf['dperm']);
|
---|
67 | return true;
|
---|
68 | }
|
---|
69 |
|
---|
70 | /**
|
---|
71 | * Unlock subscription info for an ID
|
---|
72 | *
|
---|
73 | * @param string $id The target page or namespace, specified by id; Namespaces
|
---|
74 | * are identified by appending a colon.
|
---|
75 | *
|
---|
76 | * @author Adrian Lang <[email protected]>
|
---|
77 | */
|
---|
78 | function subscription_unlock($id) {
|
---|
79 | $lockf = subscription_lock_filename($id);
|
---|
80 | return @rmdir($lockf);
|
---|
81 | }
|
---|
82 |
|
---|
83 | /**
|
---|
84 | * Set subscription information
|
---|
85 | *
|
---|
86 | * Allows to set subscription information for permanent storage in meta files.
|
---|
87 | * Subscriptions consist of a target object, a subscribing user, a subscribe
|
---|
88 | * style and optional data.
|
---|
89 | * A subscription may be deleted by specifying an empty subscribe style.
|
---|
90 | * Only one subscription per target and user is allowed.
|
---|
91 | * The function returns false on error, otherwise true. Note that no error is
|
---|
92 | * returned if a subscription should be deleted but the user is not subscribed
|
---|
93 | * and the subscription meta file exists.
|
---|
94 | *
|
---|
95 | * @param string $user The subscriber or unsubscriber
|
---|
96 | * @param string $page The target object (page or namespace), specified by
|
---|
97 | * id; Namespaces are identified by a trailing colon.
|
---|
98 | * @param string $style The subscribe style; DokuWiki currently implements
|
---|
99 | * âeveryâ, âdigestâ, and âlistâ.
|
---|
100 | * @param string $data An optional data blob
|
---|
101 | * @param bool $overwrite Whether an existing subscription may be overwritten
|
---|
102 | *
|
---|
103 | * @author Adrian Lang <[email protected]>
|
---|
104 | */
|
---|
105 | function subscription_set($user, $page, $style, $data = null,
|
---|
106 | $overwrite = false) {
|
---|
107 | global $lang;
|
---|
108 | if (is_null($style)) {
|
---|
109 | // Delete subscription.
|
---|
110 | $file = subscription_filename($page);
|
---|
111 | if (!@file_exists($file)) {
|
---|
112 | msg(sprintf($lang['subscr_not_subscribed'], $user,
|
---|
113 | prettyprint_id($page)), -1);
|
---|
114 | return false;
|
---|
115 | }
|
---|
116 |
|
---|
117 | // io_deleteFromFile does not return false if no line matched.
|
---|
118 | return io_deleteFromFile($file,
|
---|
119 | subscription_regex(array('user' => auth_nameencode($user))),
|
---|
120 | true);
|
---|
121 | }
|
---|
122 |
|
---|
123 | // Delete subscription if one exists and $overwrite is true. If $overwrite
|
---|
124 | // is false, fail.
|
---|
125 | $subs = subscription_find($page, array('user' => $user));
|
---|
126 | if (count($subs) > 0 && array_pop(array_keys($subs)) === $page) {
|
---|
127 | if (!$overwrite) {
|
---|
128 | msg(sprintf($lang['subscr_already_subscribed'], $user,
|
---|
129 | prettyprint_id($page)), -1);
|
---|
130 | return false;
|
---|
131 | }
|
---|
132 | // Fail if deletion failed, else continue.
|
---|
133 | if (!subscription_set($user, $page, null)) {
|
---|
134 | return false;
|
---|
135 | }
|
---|
136 | }
|
---|
137 |
|
---|
138 | $file = subscription_filename($page);
|
---|
139 | $content = auth_nameencode($user) . ' ' . $style;
|
---|
140 | if (!is_null($data)) {
|
---|
141 | $content .= ' ' . $data;
|
---|
142 | }
|
---|
143 | return io_saveFile($file, $content . "\n", true);
|
---|
144 | }
|
---|
145 |
|
---|
146 | /**
|
---|
147 | * Recursively search for matching subscriptions
|
---|
148 | *
|
---|
149 | * This function searches all relevant subscription files for a page or
|
---|
150 | * namespace.
|
---|
151 | *
|
---|
152 | * @param string $page The target objectâs (namespace or page) id
|
---|
153 | * @param array $pre A hash of predefined values
|
---|
154 | *
|
---|
155 | * @see function subscription_regex for $pre documentation
|
---|
156 | *
|
---|
157 | * @author Adrian Lang <[email protected]>
|
---|
158 | */
|
---|
159 | function subscription_find($page, $pre) {
|
---|
160 | // Construct list of files which may contain relevant subscriptions.
|
---|
161 | $filenames = array(':' => subscription_filename(':'));
|
---|
162 | do {
|
---|
163 | $filenames[$page] = subscription_filename($page);
|
---|
164 | $page = getNS(rtrim($page, ':')) . ':';
|
---|
165 | } while ($page !== ':');
|
---|
166 |
|
---|
167 | // Handle files.
|
---|
168 | $matches = array();
|
---|
169 | foreach ($filenames as $cur_page => $filename) {
|
---|
170 | if (!@file_exists($filename)) {
|
---|
171 | continue;
|
---|
172 | }
|
---|
173 | $subscriptions = file($filename);
|
---|
174 | foreach ($subscriptions as $subscription) {
|
---|
175 | if (strpos($subscription, ' ') === false) {
|
---|
176 | // This is an old subscription file.
|
---|
177 | $subscription = trim($subscription) . " every\n";
|
---|
178 | }
|
---|
179 |
|
---|
180 | list($user, $rest) = explode(' ', $subscription, 2);
|
---|
181 | $subscription = rawurldecode($user) . " " . $rest;
|
---|
182 |
|
---|
183 | if (preg_match(subscription_regex($pre), $subscription,
|
---|
184 | $line_matches) === 0) {
|
---|
185 | continue;
|
---|
186 | }
|
---|
187 | $match = array_slice($line_matches, 1);
|
---|
188 | if (!isset($matches[$cur_page])) {
|
---|
189 | $matches[$cur_page] = array();
|
---|
190 | }
|
---|
191 | $matches[$cur_page][] = $match;
|
---|
192 | }
|
---|
193 | }
|
---|
194 | return array_reverse($matches);
|
---|
195 | }
|
---|
196 |
|
---|
197 | /**
|
---|
198 | * Get data for $INFO['subscribed']
|
---|
199 | *
|
---|
200 | * $INFO['subscribed'] is either false if no subscription for the current page
|
---|
201 | * and user is in effect. Else it contains an array of arrays with the fields
|
---|
202 | * âtargetâ, âstyleâ, and optionally âdataâ.
|
---|
203 | *
|
---|
204 | * @author Adrian Lang <[email protected]>
|
---|
205 | */
|
---|
206 | function get_info_subscribed() {
|
---|
207 | global $ID;
|
---|
208 | global $conf;
|
---|
209 | if (!$conf['subscribers']) {
|
---|
210 | return false;
|
---|
211 | }
|
---|
212 |
|
---|
213 | $subs = subscription_find($ID, array('user' => $_SERVER['REMOTE_USER']));
|
---|
214 | if (count($subs) === 0) {
|
---|
215 | return false;
|
---|
216 | }
|
---|
217 |
|
---|
218 | $_ret = array();
|
---|
219 | foreach ($subs as $target => $subs_data) {
|
---|
220 | $new = array('target' => $target,
|
---|
221 | 'style' => $subs_data[0][0]);
|
---|
222 | if (count($subs_data[0]) > 1) {
|
---|
223 | $new['data'] = $subs_data[0][1];
|
---|
224 | }
|
---|
225 | $_ret[] = $new;
|
---|
226 | }
|
---|
227 |
|
---|
228 | return $_ret;
|
---|
229 | }
|
---|
230 |
|
---|
231 | /**
|
---|
232 | * Construct a regular expression parsing a subscription definition line
|
---|
233 | *
|
---|
234 | * @param array $pre A hash of predefined values; âuserâ, âstyleâ, and
|
---|
235 | * âdataâ may be set to limit the results to
|
---|
236 | * subscriptions matching these parameters. If
|
---|
237 | * âescapedâ is true, these fields are inserted into the
|
---|
238 | * regular expression without escaping.
|
---|
239 | *
|
---|
240 | * @author Adrian Lang <[email protected]>
|
---|
241 | */
|
---|
242 | function subscription_regex($pre = array()) {
|
---|
243 | if (!isset($pre['escaped']) || $pre['escaped'] === false) {
|
---|
244 | $pre = array_map('preg_quote_cb', $pre);
|
---|
245 | }
|
---|
246 | foreach (array('user', 'style', 'data') as $key) {
|
---|
247 | if (!isset($pre[$key])) {
|
---|
248 | $pre[$key] = '(\S+)';
|
---|
249 | }
|
---|
250 | }
|
---|
251 | return '/^' . $pre['user'] . '(?: ' . $pre['style'] .
|
---|
252 | '(?: ' . $pre['data'] . ')?)?$/';
|
---|
253 | }
|
---|
254 |
|
---|
255 | /**
|
---|
256 | * Return a string with the email addresses of all the
|
---|
257 | * users subscribed to a page
|
---|
258 | *
|
---|
259 | * This is the default action for COMMON_NOTIFY_ADDRESSLIST.
|
---|
260 | *
|
---|
261 | * @param array $data Containing $id (the page id), $self (whether the author
|
---|
262 | * should be notified, $addresslist (current email address
|
---|
263 | * list)
|
---|
264 | *
|
---|
265 | * @author Steven Danz <[email protected]>
|
---|
266 | * @author Adrian Lang <[email protected]>
|
---|
267 | */
|
---|
268 | function subscription_addresslist(&$data){
|
---|
269 | global $conf;
|
---|
270 | global $auth;
|
---|
271 |
|
---|
272 | $id = $data['id'];
|
---|
273 | $self = $data['self'];
|
---|
274 | $addresslist = $data['addresslist'];
|
---|
275 |
|
---|
276 | if (!$conf['subscribers'] || $auth === null) {
|
---|
277 | return '';
|
---|
278 | }
|
---|
279 | $pres = array('style' => 'every', 'escaped' => true);
|
---|
280 | if (!$self && isset($_SERVER['REMOTE_USER'])) {
|
---|
281 | $pres['user'] = '((?!' . preg_quote_cb($_SERVER['REMOTE_USER']) .
|
---|
282 | '(?: |$))\S+)';
|
---|
283 | }
|
---|
284 | $subs = subscription_find($id, $pres);
|
---|
285 | $emails = array();
|
---|
286 | foreach ($subs as $by_targets) {
|
---|
287 | foreach ($by_targets as $sub) {
|
---|
288 | $info = $auth->getUserData($sub[0]);
|
---|
289 | if ($info === false) continue;
|
---|
290 | $level = auth_aclcheck($id, $sub[0], $info['grps']);
|
---|
291 | if ($level >= AUTH_READ) {
|
---|
292 | if (strcasecmp($info['mail'], $conf['notify']) != 0) {
|
---|
293 | $emails[$sub[0]] = $info['mail'];
|
---|
294 | }
|
---|
295 | }
|
---|
296 | }
|
---|
297 | }
|
---|
298 | $data['addresslist'] = trim($addresslist . ',' . implode(',', $emails), ',');
|
---|
299 | }
|
---|
300 |
|
---|
301 | /**
|
---|
302 | * Send a digest mail
|
---|
303 | *
|
---|
304 | * Sends a digest mail showing a bunch of changes.
|
---|
305 | *
|
---|
306 | * @param string $subscriber_mail The target mail address
|
---|
307 | * @param array $id The ID
|
---|
308 | * @param int $lastupdate Time of the last notification
|
---|
309 | *
|
---|
310 | * @author Adrian Lang <[email protected]>
|
---|
311 | */
|
---|
312 | function subscription_send_digest($subscriber_mail, $id, $lastupdate) {
|
---|
313 | $n = 0;
|
---|
314 | do {
|
---|
315 | $rev = getRevisions($id, $n++, 1);
|
---|
316 | $rev = (count($rev) > 0) ? $rev[0] : null;
|
---|
317 | } while (!is_null($rev) && $rev > $lastupdate);
|
---|
318 |
|
---|
319 | $replaces = array('NEWPAGE' => wl($id, '', true, '&'),
|
---|
320 | 'SUBSCRIBE' => wl($id, array('do' => 'subscribe'), true, '&'));
|
---|
321 | if (!is_null($rev)) {
|
---|
322 | $subject = 'changed';
|
---|
323 | $replaces['OLDPAGE'] = wl($id, "rev=$rev", true, '&');
|
---|
324 | $df = new Diff(explode("\n", rawWiki($id, $rev)),
|
---|
325 | explode("\n", rawWiki($id)));
|
---|
326 | $dformat = new UnifiedDiffFormatter();
|
---|
327 | $replaces['DIFF'] = $dformat->format($df);
|
---|
328 | } else {
|
---|
329 | $subject = 'newpage';
|
---|
330 | $replaces['OLDPAGE'] = 'none';
|
---|
331 | $replaces['DIFF'] = rawWiki($id);
|
---|
332 | }
|
---|
333 | subscription_send($subscriber_mail, $replaces, $subject, $id,
|
---|
334 | 'subscr_digest');
|
---|
335 | }
|
---|
336 |
|
---|
337 | /**
|
---|
338 | * Send a list mail
|
---|
339 | *
|
---|
340 | * Sends a list mail showing a list of changed pages.
|
---|
341 | *
|
---|
342 | * @param string $subscriber_mail The target mail address
|
---|
343 | * @param array $ids Array of ids
|
---|
344 | * @param string $ns_id The id of the namespace
|
---|
345 | *
|
---|
346 | * @author Adrian Lang <[email protected]>
|
---|
347 | */
|
---|
348 | function subscription_send_list($subscriber_mail, $ids, $ns_id) {
|
---|
349 | if (count($ids) === 0) return;
|
---|
350 | global $conf;
|
---|
351 | $list = '';
|
---|
352 | foreach ($ids as $id) {
|
---|
353 | $list .= '* ' . wl($id, array(), true) . NL;
|
---|
354 | }
|
---|
355 | subscription_send($subscriber_mail,
|
---|
356 | array('DIFF' => rtrim($list),
|
---|
357 | 'SUBSCRIBE' => wl($ns_id . $conf['start'],
|
---|
358 | array('do' => 'subscribe'),
|
---|
359 | true, '&')),
|
---|
360 | 'subscribe_list',
|
---|
361 | prettyprint_id($ns_id),
|
---|
362 | 'subscr_list');
|
---|
363 | }
|
---|
364 |
|
---|
365 | /**
|
---|
366 | * Helper function for sending a mail
|
---|
367 | *
|
---|
368 | * @param string $subscriber_mail The target mail address
|
---|
369 | * @param array $replaces Predefined parameters used to parse the
|
---|
370 | * template
|
---|
371 | * @param string $subject The lang id of the mail subject (without the
|
---|
372 | * prefix âmail_â)
|
---|
373 | * @param string $id The page or namespace id
|
---|
374 | * @param string $template The name of the mail template
|
---|
375 | *
|
---|
376 | * @author Adrian Lang <[email protected]>
|
---|
377 | */
|
---|
378 | function subscription_send($subscriber_mail, $replaces, $subject, $id, $template) {
|
---|
379 | global $conf;
|
---|
380 |
|
---|
381 | $text = rawLocale($template);
|
---|
382 | $replaces = array_merge($replaces, array('TITLE' => $conf['title'],
|
---|
383 | 'DOKUWIKIURL' => DOKU_URL,
|
---|
384 | 'PAGE' => $id));
|
---|
385 |
|
---|
386 | foreach ($replaces as $key => $substitution) {
|
---|
387 | $text = str_replace('@'.strtoupper($key).'@', $substitution, $text);
|
---|
388 | }
|
---|
389 |
|
---|
390 | global $lang;
|
---|
391 | $subject = $lang['mail_' . $subject] . ' ' . $id;
|
---|
392 | mail_send('', '['.$conf['title'].'] '. $subject, $text,
|
---|
393 | $conf['mailfrom'], '', $subscriber_mail);
|
---|
394 | }
|
---|