From 5b75cd1f5c479ada468fbf62a733c54edad152f1 Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Tue, 5 Jan 2010 14:14:00 +0100 Subject: New mail subscription with digest --- inc/subscription.php | 342 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 inc/subscription.php (limited to 'inc/subscription.php') diff --git a/inc/subscription.php b/inc/subscription.php new file mode 100644 index 000000000..1dcecf6f5 --- /dev/null +++ b/inc/subscription.php @@ -0,0 +1,342 @@ + + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + */ + +require_once DOKU_INC.'/inc/pageutils.php'; + +/** + * Get the name of the metafile tracking subscriptions to target page or + * namespace + * + * @param string $id The target page or namespace, specified by id; Namespaces + * are identified by appending a colon. + * + * @author Adrian Lang + */ +function subscription_filename($id) { + $meta_fname = '.mlist'; + if ((substr($id, -1, 1) === ':')) { + $meta_froot = getNS($id); + if ($meta_froot === false) { + $meta_fname = '/' . $meta_fname; + } + } else { + $meta_froot = $id; + } + return metaFN($meta_froot, $meta_fname); +} + +/** + * Set subscription information + * + * Allows to set subscription informations for permanent storage in meta files. + * Subscriptions consist of a target object, a subscribing user, a subscribe + * style and optional data. + * A subscription may be deleted by specifying an empty subscribe style. + * Only one subscription per target and user is allowed. + * The function returns false on error, otherwise true. Note that no error is + * returned if a subscription should be deleted but the user is not subscribed + * and the subscription meta file exists. + * + * @param string $page The target object (page or namespace), specified by + * id; Namespaces are identified by a trailing colon. + * @param string $user The user + * @param string $style The subscribe style; DokuWiki currently implements + * “every”, “digest”, and “list”. + * @param bool $overwrite Whether an existing subscription may be overwritten + * @param string $data An optional data blob; For list and digest, this + * defaults to time(). + * + * @author Adrian Lang + */ +function subscription_set($page, $user, $style, $overwrite = false, $data = null) { + global $lang; + if (is_null($style)) { + // Delete subscription. + $file = subscription_filename($page); + if (!@file_exists($file)) { + msg(sprintf($lang['subscr_not_subscribed'], $user, + prettyprint_id($page)), -1); + return false; + } + + // io_deleteFromFile does not return false if no line matched. + return io_deleteFromFile($file, + subscription_regex(array('user' => $user)), + true); + } + + // Delete subscription if one exists and $overwrite is true. If $overwrite + // is false, fail. + $subs = subscription_find($page, array('user' => $user)); + if (count($subs) > 0 && array_pop(array_keys($subs)) === $page) { + if (!$overwrite) { + msg(sprintf($lang['subscr_already_subscribed'], $user, + prettyprint_id($page)), -1); + return false; + } + // Fail if deletion failed, else continue. + if (!subscription_set($page, $user, null)) { + return false; + } + } + + $file = subscription_filename($page); + $content = auth_nameencode($user) . ' ' . $style; + if (in_array($style, array('list', 'digest'))) { + $content .= ' ' . (!is_null($data) ? $data : time()); + } + return io_saveFile($file, $content . "\n", true); +} + +/** + * Recursively search for matching subscriptions + * + * This function searches all relevant subscription files for a page or + * namespace. + * + * @param string $page The target object’s (namespace or page) id + * @param array $pre A hash of predefined values + * + * @see function subscription_regex for $pre documentation + * + * @author Adrian Lang + */ +function subscription_find($page, $pre) { + // Construct list of files which may contain relevant subscriptions. + $filenames = array(); + do { + $filenames[$page] = subscription_filename($page); + $page = getNS(rtrim($page, ':')) . ':'; + } while ($page !== ':'); + + // Handle files. + $matches = array(); + foreach ($filenames as $cur_page => $filename) { + if (!@file_exists($filename)) { + continue; + } + $subscriptions = file($filename); + foreach ($subscriptions as $subscription) { + if (strpos($subscription, ' ') === false) { + // This is an old subscription file. + $subscription = trim($subscription) . " every\n"; + } + if (preg_match(subscription_regex($pre), $subscription, + &$line_matches) === 0) { + continue; + } + $match = array_slice($line_matches, 1); + if (!isset($matches[$cur_page])) { + $matches[$cur_page] = array(); + } + $matches[$cur_page][] = $match; + } + } + return array_reverse($matches); +} + +/** + * Get data for $INFO['subscribed'] + * + * $INFO['subscribed'] is either false if no subscription for the current page + * and user is in effect. Else it contains an array of arrays with the fields + * “target”, “style”, and optionally “data”. + * + * @author Adrian Lang + */ +function get_info_subscribed() { + global $ID; + global $conf; + if (!$conf['subscribers']) { + return false; + } + + $subs = subscription_find($ID, array('user' => $_SERVER['REMOTE_USER'])); + if (count($subs) === 0) { + return false; + } + + $_ret = array(); + foreach ($subs as $target => $subs_data) { + $new = array('target' => $target, + 'style' => $subs_data[0][0]); + if (count($subs_data[0]) > 1) { + $new['data'] = $subs_data[0][1]; + } + $_ret[] = $new; + } + + return $_ret; +} + +/** + * Construct a regular expression parsing a subscription definition line + * + * @param array $pre A hash of predefined values; “user”, “style”, and + * “data” may be set to limit the results to + * subscriptions matching these parameters. If + * “escaped” is true, these fields are inserted into the + * regular expression without escaping. + * + * @author Adrian Lang + */ +function subscription_regex($pre = array()) { + if (!isset($pre['escaped']) || $pre['escaped'] === false) { + $pre = array_map('preg_quote_cb', $pre); + } + foreach (array('user', 'style', 'data') as $key) { + if (!isset($pre[$key])) { + $pre[$key] = '(\S+)'; + } + } + return '/^' . $pre['user'] . '(?: ' . $pre['style'] . + '(?: ' . $pre['data'] . ')?)?$/'; +} + +/** + * Return a string with the email addresses of all the + * users subscribed to a page + * + * @param string $id The id of the changed page + * @param bool $self Whether a notice should be sent to the editor if he is + * subscribed + * + * @author Steven Danz + */ +function subscription_addresslist($id, $self=true){ + global $conf; + global $auth; + + if (!$conf['subscribers']) { + return ''; + } + $pres = array('style' => 'every', 'escaped' => true); + if (!$self && isset($_SERVER['REMOTE_USER'])) { + $pres['user'] = '((?:(?!' . preg_quote_cb($_SERVER['REMOTE_USER']) . + ')\S?)+)'; + } + $subs = subscription_find($id, $pres); + $emails = array(); + foreach ($subs as $by_targets) { + foreach ($by_targets as $sub) { + $info = $auth->getUserData($sub[0]); + if ($info === false) continue; + $level = auth_aclcheck($id, $sub[0], $info['grps']); + if ($level >= AUTH_READ) { + if (strcasecmp($info['mail'], $conf['notify']) != 0) { + $emails[$sub[0]] = $info['mail']; + } + } + } + } + return implode(',', $emails); +} + +/** + * Send a digest mail + * + * Sends a digest mail showing a bunch of changes. + * + * @param string $subscriber_mail The target mail address + * @param array $change The newest change + * @param int $lastupdate Time of the last notification + * + * @author Adrian Lang + */ +function subscription_send_digest($subscriber_mail, $change, $lastupdate) { + $id = $change['id']; + $n = 0; + do { + $rev = getRevisions($id, $n++, 1); + $rev = (count($rev) > 0) ? $rev[0] : null; + } while (!is_null($rev) && $rev > $lastupdate); + + $ip = $change['ip']; + $replaces = array('NEWPAGE' => wl($id, '', true, '&'), + 'SUBSCRIBE' => wl($id, array('do' => 'subscribe'), true, '&')); + if (!is_null($rev)) { + $subject = 'changed'; + $replaces['OLDPAGE'] = wl($id, "rev=$rev", true, '&'); + require_once DOKU_INC.'inc/DifferenceEngine.php'; + $df = new Diff(explode("\n", rawWiki($id, $rev)), + explode("\n", rawWiki($id))); + $dformat = new UnifiedDiffFormatter(); + $replaces['DIFF'] = $dformat->format($df); + } else { + $subject = 'newpage'; + $replaces['OLDPAGE'] = 'none'; + $replaces['DIFF'] = rawWiki($id); + } + subscription_send($subscriber_mail, $replaces, $subject, $id, + 'subscr_digest'); +} + +/** + * Send a list mail + * + * Sends a list mail showing a list of changed pages. + * + * @param string $subscriber_mail The target mail address + * @param array $changes Array of changes + * @param string $id The id of the namespace + * + * @author Adrian Lang + */ +function subscription_send_list($subscriber_mail, $changes, $id) { + $list = ''; + foreach ($changes as $change) { + $list .= '* ' . $change['id'] . NL; + } + subscription_send($subscriber_mail, + array('DIFF' => rtrim($list), + 'SUBSCRIBE' => wl($changes[0]['id'], + array('do' => 'subscribe'), + true, '&')), + 'subscribe_list', + prettyprint_id($id), + 'subscr_list'); +} + +/** + * Helper function for sending a mail + * + * @param string $subscriber_mail The target mail address + * @param array $replaces Predefined parameters used to parse the + * template + * @param string $subject The lang id of the mail subject (without the + * prefix “mail_”) + * @param string $id The page or namespace id + * @param string $template The name of the mail template + * + * @author Adrian Lang + */ +function subscription_send($subscriber_mail, $replaces, $subject, $id, $template) { + global $conf; + + $text = rawLocale($template); + $replaces = array_merge($replaces, array('TITLE' => $conf['title'], + 'DOKUWIKIURL' => DOKU_URL, + 'PAGE' => $id)); + + foreach ($replaces as $key => $substitution) { + $text = str_replace('@'.strtoupper($key).'@', $substitution, $text); + } + + global $lang; + $subject = $lang['mail_' . $subject] . ' ' . $id; + mail_send('', '['.$conf['title'].'] '. $subject, $text, + $conf['mailfrom'], '', $subscriber_mail); +} -- cgit v1.2.3 From 8881fcc99a05f20da8fdd0f1c52f801fd84a8bb7 Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Thu, 19 Nov 2009 15:25:25 +0100 Subject: Add events to subscription. --- inc/subscription.php | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) (limited to 'inc/subscription.php') diff --git a/inc/subscription.php b/inc/subscription.php index 1dcecf6f5..12f0a4b82 100644 --- a/inc/subscription.php +++ b/inc/subscription.php @@ -50,18 +50,18 @@ function subscription_filename($id) { * returned if a subscription should be deleted but the user is not subscribed * and the subscription meta file exists. * + * @param string $user The subscriber or unsubscriber * @param string $page The target object (page or namespace), specified by * id; Namespaces are identified by a trailing colon. - * @param string $user The user * @param string $style The subscribe style; DokuWiki currently implements * “every”, “digest”, and “list”. + * @param string $data An optional data blob * @param bool $overwrite Whether an existing subscription may be overwritten - * @param string $data An optional data blob; For list and digest, this - * defaults to time(). * * @author Adrian Lang */ -function subscription_set($page, $user, $style, $overwrite = false, $data = null) { +function subscription_set($user, $page, $style, $data = null, + $overwrite = false) { global $lang; if (is_null($style)) { // Delete subscription. @@ -88,15 +88,15 @@ function subscription_set($page, $user, $style, $overwrite = false, $data = null return false; } // Fail if deletion failed, else continue. - if (!subscription_set($page, $user, null)) { + if (!subscription_set($user, $page, null)) { return false; } } $file = subscription_filename($page); $content = auth_nameencode($user) . ' ' . $style; - if (in_array($style, array('list', 'digest'))) { - $content .= ' ' . (!is_null($data) ? $data : time()); + if (!is_null($data)) { + $content .= ' ' . $data; } return io_saveFile($file, $content . "\n", true); } @@ -116,7 +116,7 @@ function subscription_set($page, $user, $style, $overwrite = false, $data = null */ function subscription_find($page, $pre) { // Construct list of files which may contain relevant subscriptions. - $filenames = array(); + $filenames = array(':' => subscription_filename(':')); do { $filenames[$page] = subscription_filename($page); $page = getNS(rtrim($page, ':')) . ':'; @@ -210,16 +210,23 @@ function subscription_regex($pre = array()) { * Return a string with the email addresses of all the * users subscribed to a page * - * @param string $id The id of the changed page - * @param bool $self Whether a notice should be sent to the editor if he is - * subscribed + * This is the default action for COMMON_NOTIFY_ADDRESSLIST. + * + * @param array $data Containing $id (the page id), $self (whether the author + * should be notified, $addresslist (current email address + list) * * @author Steven Danz + * @author Adrian Lang */ -function subscription_addresslist($id, $self=true){ +function subscription_addresslist($data){ global $conf; global $auth; + $id = $data['id']; + $self = $data['self']; + $addresslist = $data['addresslist']; + if (!$conf['subscribers']) { return ''; } @@ -242,7 +249,7 @@ function subscription_addresslist($id, $self=true){ } } } - return implode(',', $emails); + $data['addresslist'] = trim($addresslist . ',' . implode(',', $emails), ','); } /** -- cgit v1.2.3 From f5b787850258b8f9d343ed55434dbec1631f3092 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 29 Jan 2010 14:33:20 +0100 Subject: more code cleanup --- inc/subscription.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'inc/subscription.php') diff --git a/inc/subscription.php b/inc/subscription.php index 12f0a4b82..69e153cf5 100644 --- a/inc/subscription.php +++ b/inc/subscription.php @@ -214,7 +214,7 @@ function subscription_regex($pre = array()) { * * @param array $data Containing $id (the page id), $self (whether the author * should be notified, $addresslist (current email address - list) + * list) * * @author Steven Danz * @author Adrian Lang -- cgit v1.2.3 From 46d41845ed1f3e722a11e31b866023973545fd7c Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sun, 31 Jan 2010 17:57:35 +0100 Subject: fixed call-time pass-by-reference warning --- inc/subscription.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'inc/subscription.php') diff --git a/inc/subscription.php b/inc/subscription.php index 69e153cf5..f7614014f 100644 --- a/inc/subscription.php +++ b/inc/subscription.php @@ -135,7 +135,7 @@ function subscription_find($page, $pre) { $subscription = trim($subscription) . " every\n"; } if (preg_match(subscription_regex($pre), $subscription, - &$line_matches) === 0) { + $line_matches) === 0) { continue; } $match = array_slice($line_matches, 1); -- cgit v1.2.3 From 16905344219a6293705b71cd526fad3ba07b04eb Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sun, 31 Jan 2010 19:02:14 +0100 Subject: first attempt to centralize all include loading Classes are loaded throug PHP5's class autoloader, all other includes are just loaded by default. This skips a lot of require_once calls. Parser and Plugin stuff isn't handled by the class loader yet. --- inc/subscription.php | 2 -- 1 file changed, 2 deletions(-) (limited to 'inc/subscription.php') diff --git a/inc/subscription.php b/inc/subscription.php index f7614014f..e98129b77 100644 --- a/inc/subscription.php +++ b/inc/subscription.php @@ -14,8 +14,6 @@ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) */ -require_once DOKU_INC.'/inc/pageutils.php'; - /** * Get the name of the metafile tracking subscriptions to target page or * namespace -- cgit v1.2.3 From 0af14a6e25ba35e88d96762bc73325838868e3fe Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Mon, 1 Feb 2010 15:38:41 +0100 Subject: removed more unneeded require_once() calls --- inc/subscription.php | 1 - 1 file changed, 1 deletion(-) (limited to 'inc/subscription.php') diff --git a/inc/subscription.php b/inc/subscription.php index e98129b77..280da225d 100644 --- a/inc/subscription.php +++ b/inc/subscription.php @@ -275,7 +275,6 @@ function subscription_send_digest($subscriber_mail, $change, $lastupdate) { if (!is_null($rev)) { $subject = 'changed'; $replaces['OLDPAGE'] = wl($id, "rev=$rev", true, '&'); - require_once DOKU_INC.'inc/DifferenceEngine.php'; $df = new Diff(explode("\n", rawWiki($id, $rev)), explode("\n", rawWiki($id))); $dformat = new UnifiedDiffFormatter(); -- cgit v1.2.3 From 599e75de5d1510a4124739fb5e8c5397f5dfa69b Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Mon, 8 Feb 2010 15:43:45 +0100 Subject: Receive event data by ref to change it --- inc/subscription.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'inc/subscription.php') diff --git a/inc/subscription.php b/inc/subscription.php index f7614014f..ae326c894 100644 --- a/inc/subscription.php +++ b/inc/subscription.php @@ -219,7 +219,7 @@ function subscription_regex($pre = array()) { * @author Steven Danz * @author Adrian Lang */ -function subscription_addresslist($data){ +function subscription_addresslist(&$data){ global $conf; global $auth; -- cgit v1.2.3 From 25624f5a1298c401be5c26959027cbe4569ac35a Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Tue, 9 Feb 2010 10:50:38 +0100 Subject: Nicer list subscription mail --- inc/subscription.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'inc/subscription.php') diff --git a/inc/subscription.php b/inc/subscription.php index ae326c894..f844fea1d 100644 --- a/inc/subscription.php +++ b/inc/subscription.php @@ -303,13 +303,14 @@ function subscription_send_digest($subscriber_mail, $change, $lastupdate) { * @author Adrian Lang */ function subscription_send_list($subscriber_mail, $changes, $id) { + global $conf; $list = ''; foreach ($changes as $change) { - $list .= '* ' . $change['id'] . NL; + $list .= '* ' . wl($change['id'], array(), true) . NL; } subscription_send($subscriber_mail, array('DIFF' => rtrim($list), - 'SUBSCRIBE' => wl($changes[0]['id'], + 'SUBSCRIBE' => wl($id . $conf['start'], array('do' => 'subscribe'), true, '&')), 'subscribe_list', -- cgit v1.2.3 From 3e0c7aa328ac721b3bcf17822f9ed3659ad93d14 Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Tue, 4 May 2010 12:07:35 +0200 Subject: Add locking for indexer-based notifications --- inc/subscription.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'inc/subscription.php') diff --git a/inc/subscription.php b/inc/subscription.php index e5938d9bd..ce5da4cd4 100644 --- a/inc/subscription.php +++ b/inc/subscription.php @@ -9,6 +9,8 @@ * - subscription_set * - get_info_subscribed * - subscription_addresslist + * - subscription_lock + * - subscription_unlock * * @author Adrian Lang * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) @@ -36,6 +38,32 @@ function subscription_filename($id) { return metaFN($meta_froot, $meta_fname); } +/** + * Lock subscription info for an ID + * + * @param string $id The target page or namespace, specified by id; Namespaces + * are identified by appending a colon. + * + * @author Adrian Lang + */ +function subscription_lock($id) { + $lockf = subscription_filename($id) . '.lock'; + return !file_exists($lockf) && touch($lockf); +} + +/** + * Unlock subscription info for an ID + * + * @param string $id The target page or namespace, specified by id; Namespaces + * are identified by appending a colon. + * + * @author Adrian Lang + */ +function subscription_unlock($id) { + $lockf = subscription_filename($id) . '.lock'; + return file_exists($lockf) && unlink($lockf); +} + /** * Set subscription information * -- cgit v1.2.3 From 41c141178e9733bbf38f8e937d3dea63058af0dc Mon Sep 17 00:00:00 2001 From: Adrian Lang Date: Tue, 10 Aug 2010 12:26:19 +0200 Subject: Ignore small & own changes in digest & list mails --- inc/subscription.php | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'inc/subscription.php') diff --git a/inc/subscription.php b/inc/subscription.php index ce5da4cd4..22d8fccd5 100644 --- a/inc/subscription.php +++ b/inc/subscription.php @@ -284,20 +284,18 @@ function subscription_addresslist(&$data){ * Sends a digest mail showing a bunch of changes. * * @param string $subscriber_mail The target mail address - * @param array $change The newest change + * @param array $id The ID * @param int $lastupdate Time of the last notification * * @author Adrian Lang */ -function subscription_send_digest($subscriber_mail, $change, $lastupdate) { - $id = $change['id']; +function subscription_send_digest($subscriber_mail, $id, $lastupdate) { $n = 0; do { $rev = getRevisions($id, $n++, 1); $rev = (count($rev) > 0) ? $rev[0] : null; } while (!is_null($rev) && $rev > $lastupdate); - $ip = $change['ip']; $replaces = array('NEWPAGE' => wl($id, '', true, '&'), 'SUBSCRIBE' => wl($id, array('do' => 'subscribe'), true, '&')); if (!is_null($rev)) { @@ -322,24 +320,25 @@ function subscription_send_digest($subscriber_mail, $change, $lastupdate) { * Sends a list mail showing a list of changed pages. * * @param string $subscriber_mail The target mail address - * @param array $changes Array of changes - * @param string $id The id of the namespace + * @param array $ids Array of ids + * @param string $ns_id The id of the namespace * * @author Adrian Lang */ -function subscription_send_list($subscriber_mail, $changes, $id) { +function subscription_send_list($subscriber_mail, $ids, $ns_id) { + if (count($ids) === 0) return; global $conf; $list = ''; - foreach ($changes as $change) { - $list .= '* ' . wl($change['id'], array(), true) . NL; + foreach ($ids as $id) { + $list .= '* ' . wl($id, array(), true) . NL; } subscription_send($subscriber_mail, array('DIFF' => rtrim($list), - 'SUBSCRIBE' => wl($id . $conf['start'], + 'SUBSCRIBE' => wl($ns_id . $conf['start'], array('do' => 'subscribe'), true, '&')), 'subscribe_list', - prettyprint_id($id), + prettyprint_id($ns_id), 'subscr_list'); } -- cgit v1.2.3 From 06853c0e060769b5fa39cdf608b1926785c9c557 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sun, 29 Aug 2010 13:31:02 +0200 Subject: fixed namespace subscription file location FS#2013 If you subscribed to namespaces other than the root namespace using the new develonly subscription feature you'll need to renew your subscriptions. --- inc/subscription.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'inc/subscription.php') diff --git a/inc/subscription.php b/inc/subscription.php index 22d8fccd5..f39b87eb5 100644 --- a/inc/subscription.php +++ b/inc/subscription.php @@ -29,13 +29,11 @@ function subscription_filename($id) { $meta_fname = '.mlist'; if ((substr($id, -1, 1) === ':')) { $meta_froot = getNS($id); - if ($meta_froot === false) { - $meta_fname = '/' . $meta_fname; - } + $meta_fname = '/' . $meta_fname; } else { $meta_froot = $id; } - return metaFN($meta_froot, $meta_fname); + return metaFN((string) $meta_froot, $meta_fname); } /** -- cgit v1.2.3