3 * Utilities for handling (email) subscriptions
5 * The public interface of this file consists of the functions
7 * - subscription_send_digest
8 * - subscription_send_list
10 * - get_info_subscribed
11 * - subscription_addresslist
13 * @author Adrian Lang <lang@cosmocode.de>
14 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
17 require_once DOKU_INC
.'/inc/pageutils.php';
20 * Get the name of the metafile tracking subscriptions to target page or
23 * @param string $id The target page or namespace, specified by id; Namespaces
24 * are identified by appending a colon.
26 * @author Adrian Lang <lang@cosmocode.de>
28 function subscription_filename($id) {
29 $meta_fname = '.mlist';
30 if ((substr($id, -1, 1) === ':')) {
31 $meta_froot = getNS($id);
32 if ($meta_froot === false) {
33 $meta_fname = '/' . $meta_fname;
38 return metaFN($meta_froot, $meta_fname);
42 * Set subscription information
44 * Allows to set subscription informations for permanent storage in meta files.
45 * Subscriptions consist of a target object, a subscribing user, a subscribe
46 * style and optional data.
47 * A subscription may be deleted by specifying an empty subscribe style.
48 * Only one subscription per target and user is allowed.
49 * The function returns false on error, otherwise true. Note that no error is
50 * returned if a subscription should be deleted but the user is not subscribed
51 * and the subscription meta file exists.
53 * @param string $user The subscriber or unsubscriber
54 * @param string $page The target object (page or namespace), specified by
55 * id; Namespaces are identified by a trailing colon.
56 * @param string $style The subscribe style; DokuWiki currently implements
57 * “every”, “digest”, and “list”.
58 * @param string $data An optional data blob
59 * @param bool $overwrite Whether an existing subscription may be overwritten
61 * @author Adrian Lang <lang@cosmocode.de>
63 function subscription_set($user, $page, $style, $data = null,
66 if (is_null($style)) {
67 // Delete subscription.
68 $file = subscription_filename($page);
69 if (!@file_exists
($file)) {
70 msg(sprintf($lang['subscr_not_subscribed'], $user,
71 prettyprint_id($page)), -1);
75 // io_deleteFromFile does not return false if no line matched.
76 return io_deleteFromFile($file,
77 subscription_regex(array('user' => $user)),
81 // Delete subscription if one exists and $overwrite is true. If $overwrite
83 $subs = subscription_find($page, array('user' => $user));
84 if (count($subs) > 0 && array_pop(array_keys($subs)) === $page) {
86 msg(sprintf($lang['subscr_already_subscribed'], $user,
87 prettyprint_id($page)), -1);
90 // Fail if deletion failed, else continue.
91 if (!subscription_set($user, $page, null)) {
96 $file = subscription_filename($page);
97 $content = auth_nameencode($user) . ' ' . $style;
98 if (!is_null($data)) {
99 $content .= ' ' . $data;
101 return io_saveFile($file, $content . "\n", true);
105 * Recursively search for matching subscriptions
107 * This function searches all relevant subscription files for a page or
110 * @param string $page The target object’s (namespace or page) id
111 * @param array $pre A hash of predefined values
113 * @see function subscription_regex for $pre documentation
115 * @author Adrian Lang <lang@cosmocode.de>
117 function subscription_find($page, $pre) {
118 // Construct list of files which may contain relevant subscriptions.
119 $filenames = array(':' => subscription_filename(':'));
121 $filenames[$page] = subscription_filename($page);
122 $page = getNS(rtrim($page, ':')) . ':';
123 } while ($page !== ':');
127 foreach ($filenames as $cur_page => $filename) {
128 if (!@file_exists
($filename)) {
131 $subscriptions = file($filename);
132 foreach ($subscriptions as $subscription) {
133 if (strpos($subscription, ' ') === false) {
134 // This is an old subscription file.
135 $subscription = trim($subscription) . " every\n";
137 if (preg_match(subscription_regex($pre), $subscription,
138 $line_matches) === 0) {
141 $match = array_slice($line_matches, 1);
142 if (!isset($matches[$cur_page])) {
143 $matches[$cur_page] = array();
145 $matches[$cur_page][] = $match;
148 return array_reverse($matches);
152 * Get data for $INFO['subscribed']
154 * $INFO['subscribed'] is either false if no subscription for the current page
155 * and user is in effect. Else it contains an array of arrays with the fields
156 * “target”, “style”, and optionally “data”.
158 * @author Adrian Lang <lang@cosmocode.de>
160 function get_info_subscribed() {
163 if (!$conf['subscribers']) {
167 $subs = subscription_find($ID, array('user' => $_SERVER['REMOTE_USER']));
168 if (count($subs) === 0) {
173 foreach ($subs as $target => $subs_data) {
174 $new = array('target' => $target,
175 'style' => $subs_data[0][0]);
176 if (count($subs_data[0]) > 1) {
177 $new['data'] = $subs_data[0][1];
186 * Construct a regular expression parsing a subscription definition line
188 * @param array $pre A hash of predefined values; “user”, “style”, and
189 * “data” may be set to limit the results to
190 * subscriptions matching these parameters. If
191 * “escaped” is true, these fields are inserted into the
192 * regular expression without escaping.
194 * @author Adrian Lang <lang@cosmocode.de>
196 function subscription_regex($pre = array()) {
197 if (!isset($pre['escaped']) ||
$pre['escaped'] === false) {
198 $pre = array_map('preg_quote_cb', $pre);
200 foreach (array('user', 'style', 'data') as $key) {
201 if (!isset($pre[$key])) {
202 $pre[$key] = '(\S+)';
205 return '/^' . $pre['user'] . '(?: ' . $pre['style'] .
206 '(?: ' . $pre['data'] . ')?)?$/';
210 * Return a string with the email addresses of all the
211 * users subscribed to a page
213 * This is the default action for COMMON_NOTIFY_ADDRESSLIST.
215 * @param array $data Containing $id (the page id), $self (whether the author
216 * should be notified, $addresslist (current email address
219 * @author Steven Danz <steven-danz@kc.rr.com>
220 * @author Adrian Lang <lang@cosmocode.de>
222 function subscription_addresslist(&$data){
227 $self = $data['self'];
228 $addresslist = $data['addresslist'];
230 if (!$conf['subscribers']) {
233 $pres = array('style' => 'every', 'escaped' => true);
234 if (!$self && isset($_SERVER['REMOTE_USER'])) {
235 $pres['user'] = '((?:(?!' . preg_quote_cb($_SERVER['REMOTE_USER']) .
238 $subs = subscription_find($id, $pres);
240 foreach ($subs as $by_targets) {
241 foreach ($by_targets as $sub) {
242 $info = $auth->getUserData($sub[0]);
243 if ($info === false) continue;
244 $level = auth_aclcheck($id, $sub[0], $info['grps']);
245 if ($level >= AUTH_READ
) {
246 if (strcasecmp($info['mail'], $conf['notify']) != 0) {
247 $emails[$sub[0]] = $info['mail'];
252 $data['addresslist'] = trim($addresslist . ',' . implode(',', $emails), ',');
258 * Sends a digest mail showing a bunch of changes.
260 * @param string $subscriber_mail The target mail address
261 * @param array $change The newest change
262 * @param int $lastupdate Time of the last notification
264 * @author Adrian Lang <lang@cosmocode.de>
266 function subscription_send_digest($subscriber_mail, $change, $lastupdate) {
270 $rev = getRevisions($id, $n++
, 1);
271 $rev = (count($rev) > 0) ?
$rev[0] : null;
272 } while (!is_null($rev) && $rev > $lastupdate);
275 $replaces = array('NEWPAGE' => wl($id, '', true, '&'),
276 'SUBSCRIBE' => wl($id, array('do' => 'subscribe'), true, '&'));
277 if (!is_null($rev)) {
278 $subject = 'changed';
279 $replaces['OLDPAGE'] = wl($id, "rev=$rev", true, '&');
280 require_once DOKU_INC
.'inc/DifferenceEngine.php';
281 $df = new Diff(explode("\n", rawWiki($id, $rev)),
282 explode("\n", rawWiki($id)));
283 $dformat = new UnifiedDiffFormatter();
284 $replaces['DIFF'] = $dformat->format($df);
286 $subject = 'newpage';
287 $replaces['OLDPAGE'] = 'none';
288 $replaces['DIFF'] = rawWiki($id);
290 subscription_send($subscriber_mail, $replaces, $subject, $id,
297 * Sends a list mail showing a list of changed pages.
299 * @param string $subscriber_mail The target mail address
300 * @param array $changes Array of changes
301 * @param string $id The id of the namespace
303 * @author Adrian Lang <lang@cosmocode.de>
305 function subscription_send_list($subscriber_mail, $changes, $id) {
307 foreach ($changes as $change) {
308 $list .= '* ' . $change['id'] . NL
;
310 subscription_send($subscriber_mail,
311 array('DIFF' => rtrim($list),
312 'SUBSCRIBE' => wl($changes[0]['id'],
313 array('do' => 'subscribe'),
321 * Helper function for sending a mail
323 * @param string $subscriber_mail The target mail address
324 * @param array $replaces Predefined parameters used to parse the
326 * @param string $subject The lang id of the mail subject (without the
328 * @param string $id The page or namespace id
329 * @param string $template The name of the mail template
331 * @author Adrian Lang <lang@cosmocode.de>
333 function subscription_send($subscriber_mail, $replaces, $subject, $id, $template) {
336 $text = rawLocale($template);
337 $replaces = array_merge($replaces, array('TITLE' => $conf['title'],
338 'DOKUWIKIURL' => DOKU_URL
,
341 foreach ($replaces as $key => $substitution) {
342 $text = str_replace('@'.strtoupper($key).'@', $substitution, $text);
346 $subject = $lang['mail_' . $subject] . ' ' . $id;
347 mail_send('', '['.$conf['title'].'] '. $subject, $text,
348 $conf['mailfrom'], '', $subscriber_mail);