summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrian Lang <lang@cosmocode.de>2010-01-05 14:14:00 +0100
committerAdrian Lang <lang@cosmocode.de>2010-01-20 10:53:18 +0100
commit5b75cd1f5c479ada468fbf62a733c54edad152f1 (patch)
tree7ca6012d892aaef60cee7bc86b2f62ade43e03ce
parentb5ee21aa65a2f380e3b99ff5ea6ced48c1cb720e (diff)
downloadrpg-5b75cd1f5c479ada468fbf62a733c54edad152f1.tar.gz
rpg-5b75cd1f5c479ada468fbf62a733c54edad152f1.tar.bz2
New mail subscription with digest
-rw-r--r--conf/dokuwiki.php2
-rw-r--r--inc/actions.php124
-rw-r--r--inc/common.php129
-rw-r--r--inc/confutils.php1
-rw-r--r--inc/form.php21
-rw-r--r--inc/lang/en/lang.php31
-rw-r--r--inc/lang/en/subscr_digest.txt20
-rw-r--r--inc/lang/en/subscr_form.txt3
-rw-r--r--inc/lang/en/subscr_list.txt17
-rw-r--r--inc/lang/en/subscr_single.txt (renamed from inc/lang/en/subscribermail.txt)0
-rw-r--r--inc/pageutils.php17
-rw-r--r--inc/subscription.php342
-rw-r--r--inc/template.php138
-rw-r--r--lib/exe/indexer.php70
-rw-r--r--lib/exe/js.php1
-rw-r--r--lib/scripts/subscriptions.js46
-rw-r--r--lib/tpl/default/design.css14
-rw-r--r--lib/tpl/default/main.php1
18 files changed, 735 insertions, 242 deletions
diff --git a/conf/dokuwiki.php b/conf/dokuwiki.php
index 74d95147e..e6a19e60b 100644
--- a/conf/dokuwiki.php
+++ b/conf/dokuwiki.php
@@ -103,6 +103,8 @@ $conf['gdlib'] = 2; //the GDlib version (0, 1 or 2) 2 tries
$conf['im_convert'] = ''; //path to ImageMagicks convert (will be used instead of GD)
$conf['jpg_quality'] = '70'; //quality of compression when scaling jpg images (0-100)
$conf['subscribers'] = 0; //enable change notice subscription support
+$conf['subscribe_time'] = 24 * 60 * 60; //Time after which digests / lists are sent (in sec, default 1 day)
+ //Should be larger than the time specified in recent_days
$conf['compress'] = 1; //Strip whitespaces and comments from Styles and JavaScript? 1|0
$conf['hidepages'] = ''; //Regexp for pages to be skipped from RSS, Search and Recent Changes
$conf['send404'] = 0; //Send a HTTP 404 status for non existing pages?
diff --git a/inc/actions.php b/inc/actions.php
index 92f817133..a856b7919 100644
--- a/inc/actions.php
+++ b/inc/actions.php
@@ -47,12 +47,13 @@ function act_dispatch(){
}
//check if user is asking to (un)subscribe a page
- if($ACT == 'subscribe' || $ACT == 'unsubscribe')
- $ACT = act_subscription($ACT);
-
- //check if user is asking to (un)subscribe a namespace
- if($ACT == 'subscribens' || $ACT == 'unsubscribens')
- $ACT = act_subscriptionns($ACT);
+ if($ACT == 'subscribe') {
+ try {
+ $ACT = act_subscription($ACT);
+ } catch (Exception $e) {
+ msg($e->getMessage(), -1);
+ }
+ }
//check permissions
$ACT = act_permcheck($ACT);
@@ -550,81 +551,68 @@ function act_export($act){
}
/**
- * Handle page 'subscribe', 'unsubscribe'
+ * Handle page 'subscribe'
+ *
+ * Throws exception on error.
*
- * @author Steven Danz <steven-danz@kc.rr.com>
- * @todo localize
+ * @author Adrian Lang <lang@cosmocode.de>
*/
function act_subscription($act){
- global $ID;
- global $INFO;
global $lang;
-
- $file=metaFN($ID,'.mlist');
- if ($act=='subscribe' && !$INFO['subscribed']){
- if ($INFO['userinfo']['mail']){
- if (io_saveFile($file,$_SERVER['REMOTE_USER']."\n",true)) {
- $INFO['subscribed'] = true;
- msg(sprintf($lang[$act.'_success'], $INFO['userinfo']['name'], $ID),1);
- } else {
- msg(sprintf($lang[$act.'_error'], $INFO['userinfo']['name'], $ID),1);
- }
- } else {
- msg($lang['subscribe_noaddress']);
- }
- } elseif ($act=='unsubscribe' && $INFO['subscribed']){
- if (io_deleteFromFile($file,$_SERVER['REMOTE_USER']."\n")) {
- $INFO['subscribed'] = false;
- msg(sprintf($lang[$act.'_success'], $INFO['userinfo']['name'], $ID),1);
- } else {
- msg(sprintf($lang[$act.'_error'], $INFO['userinfo']['name'], $ID),1);
- }
- }
-
- return 'show';
-}
-
-/**
- * Handle namespace 'subscribe', 'unsubscribe'
- *
- */
-function act_subscriptionns($act){
- global $ID;
global $INFO;
- global $lang;
- if(!getNS($ID)) {
- $file = metaFN(getNS($ID),'.mlist');
- $ns = "root";
- } else {
- $file = metaFN(getNS($ID),'/.mlist');
- $ns = getNS($ID);
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+ // No post to handle, let tpl_subscribe manage the request.
+ return $act;
}
- // reuse strings used to display the status of the subscribe action
- $act_msg = rtrim($act, 'ns');
-
- if ($act=='subscribens' && !$INFO['subscribedns']){
- if ($INFO['userinfo']['mail']){
- if (io_saveFile($file,$_SERVER['REMOTE_USER']."\n",true)) {
- $INFO['subscribedns'] = true;
- msg(sprintf($lang[$act_msg.'_success'], $INFO['userinfo']['name'], $ns),1);
- } else {
- msg(sprintf($lang[$act_msg.'_error'], $INFO['userinfo']['name'], $ns),1);
+ // Get and validate parameters.
+ if (!isset($_POST['subscribe_target'])) {
+ throw new Exception($lang['subscr_no_target']);
+ }
+ $target = $_POST['subscribe_target'];
+ $valid_styles = array('every', 'digest');
+ if (substr($target, -1, 1) === ':') {
+ // Allow “list” subscribe style since the target is a namespace.
+ $valid_styles[] = 'list';
+ }
+ $style = valid_input_set('subscribe_style', $valid_styles, $_POST,
+ $lang['subscr_invalid_style']);
+ $action = valid_input_set('subscribe_action', array('subscribe',
+ 'unsubscribe'),
+ $_POST, $lang['subscr_invalid_action']);
+
+ // Check other conditions.
+ if ($action === 'subscribe') {
+ if ($INFO['userinfo']['mail'] === '') {
+ throw new Exception($lang['subscr_subscribe_noaddress']);
+ }
+ } elseif ($action === 'unsubscribe') {
+ $is = false;
+ foreach($INFO['subscribed'] as $subscr) {
+ if ($subscr['target'] === $target) {
+ $is = true;
}
- } else {
- msg($lang['subscribe_noaddress']);
}
- } elseif ($act=='unsubscribens' && $INFO['subscribedns']){
- if (io_deleteFromFile($file,$_SERVER['REMOTE_USER']."\n")) {
- $INFO['subscribedns'] = false;
- msg(sprintf($lang[$act_msg.'_success'], $INFO['userinfo']['name'], $ns),1);
- } else {
- msg(sprintf($lang[$act_msg.'_error'], $INFO['userinfo']['name'], $ns),1);
+ if ($is === false) {
+ throw new Exception(sprintf($lang['subscr_not_subscribed_you'],
+ prettyprint_id($target)));
}
+ // subscription_set deletes a subscription if style = null.
+ $style = null;
}
- return 'show';
+ // Perform action.
+ require_once DOKU_INC . 'inc/subscription.php';
+ if (!subscription_set($target, $_SERVER['REMOTE_USER'], $style)) {
+ throw new Exception(sprintf($lang["subscr_{$action}_error"],
+ hsc($INFO['userinfo']['name']),
+ prettyprint_id($target)));
+ }
+ $INFO['subscribed'] = get_info_subscribed();
+ msg(sprintf($lang["subscr_{$action}_success"], hsc($INFO['userinfo']['name']),
+ prettyprint_id($target)), 1);
+ return $act;
}
//Setup VIM: ex: et ts=2 enc=utf-8 :
diff --git a/inc/common.php b/inc/common.php
index 85187f16d..2cc279844 100644
--- a/inc/common.php
+++ b/inc/common.php
@@ -13,6 +13,7 @@ require_once(DOKU_INC.'inc/utf8.php');
require_once(DOKU_INC.'inc/mail.php');
require_once(DOKU_INC.'inc/parserutils.php');
require_once(DOKU_INC.'inc/infoutils.php');
+require_once DOKU_INC.'inc/subscription.php';
/**
* These constants are used with the recents function
@@ -117,8 +118,7 @@ function pageinfo(){
if(isset($_SERVER['REMOTE_USER'])){
$info['userinfo'] = $USERINFO;
$info['perm'] = auth_quickaclcheck($ID);
- $info['subscribed'] = is_subscribed($ID,$_SERVER['REMOTE_USER'],false);
- $info['subscribedns'] = is_subscribed($ID,$_SERVER['REMOTE_USER'],true);
+ $info['subscribed'] = get_info_subscribed();
$info['client'] = $_SERVER['REMOTE_USER'];
if($info['perm'] == AUTH_ADMIN){
@@ -1061,10 +1061,10 @@ function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){
}elseif($who == 'subscribers'){
if(!$conf['subscribers']) return; //subscribers enabled?
if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return; //skip minors
- $bcc = subscriber_addresslist($id,false);
+ $bcc = subscription_addresslist($id,false);
if(empty($bcc)) return;
$to = '';
- $text = rawLocale('subscribermail');
+ $text = rawLocale('subscr_single');
}elseif($who == 'register'){
if(empty($conf['registernotify'])) return;
$text = rawLocale('registermail');
@@ -1097,7 +1097,7 @@ function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){
$text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true,'&'),$text);
require_once(DOKU_INC.'inc/DifferenceEngine.php');
$df = new Diff(explode("\n",rawWiki($id,$rev)),
- explode("\n",rawWiki($id)));
+ explode("\n",rawWiki($id)));
$dformat = new UnifiedDiffFormatter();
$diff = $dformat->format($df);
}else{
@@ -1273,97 +1273,6 @@ function obfuscate($email) {
}
/**
- * Let us know if a user is tracking a page or a namespace
- *
- * @author Andreas Gohr <andi@splitbrain.org>
- */
-function is_subscribed($id,$uid,$ns=false){
- if(!$ns) {
- $file=metaFN($id,'.mlist');
- } else {
- if(!getNS($id)) {
- $file = metaFN(getNS($id),'.mlist');
- } else {
- $file = metaFN(getNS($id),'/.mlist');
- }
- }
- if (@file_exists($file)) {
- $mlist = file($file);
- $pos = array_search($uid."\n",$mlist);
- return is_int($pos);
- }
-
- return false;
-}
-
-/**
- * Return a string with the email addresses of all the
- * users subscribed to a page
- *
- * @author Steven Danz <steven-danz@kc.rr.com>
- */
-function subscriber_addresslist($id,$self=true){
- global $conf;
- global $auth;
-
- if (!$conf['subscribers']) return '';
-
- $users = array();
- $emails = array();
-
- // load the page mlist file content
- $mlist = array();
- $file=metaFN($id,'.mlist');
- if (@file_exists($file)) {
- $mlist = file($file);
- foreach ($mlist as $who) {
- $who = rtrim($who);
- if(!$self && $who == $_SERVER['REMOTE_USER']) continue;
- $users[$who] = true;
- }
- }
-
- // load also the namespace mlist file content
- $ns = getNS($id);
- while ($ns) {
- $nsfile = metaFN($ns,'/.mlist');
- if (@file_exists($nsfile)) {
- $mlist = file($nsfile);
- foreach ($mlist as $who) {
- $who = rtrim($who);
- if(!$self && $who == $_SERVER['REMOTE_USER']) continue;
- $users[$who] = true;
- }
- }
- $ns = getNS($ns);
- }
- // root namespace
- $nsfile = metaFN('','.mlist');
- if (@file_exists($nsfile)) {
- $mlist = file($nsfile);
- foreach ($mlist as $who) {
- $who = rtrim($who);
- if(!$self && $who == $_SERVER['REMOTE_USER']) continue;
- $users[$who] = true;
- }
- }
- if(!empty($users)) {
- foreach (array_keys($users) as $who) {
- $info = $auth->getUserData($who);
- if($info === false) continue;
- $level = auth_aclcheck($id,$who,$info['grps']);
- if ($level >= AUTH_READ) {
- if (strcasecmp($info['mail'],$conf['notify']) != 0) {
- $emails[] = $info['mail'];
- }
- }
- }
- }
-
- return implode(',',$emails);
-}
-
-/**
* Removes quoting backslashes
*
* @author Andreas Gohr <andi@splitbrain.org>
@@ -1545,4 +1454,30 @@ function send_redirect($url){
exit;
}
-//Setup VIM: ex: et ts=4 enc=utf-8 :
+/**
+ * Validate a value using a set of valid values
+ *
+ * This function checks whether a specified value is set and in the array
+ * $valid_values. If not, the function returns a default value or, if no
+ * default is specified, throws an exception.
+ *
+ * @param string $param The name of the parameter
+ * @param array $valid_values A set of valid values; Optionally a default may
+ * be marked by the key “default”.
+ * @param array $array The array containing the value (typically $_POST
+ * or $_GET)
+ * @param string $exc The text of the raised exception
+ *
+ * @author Adrian Lang <lang@cosmocode.de>
+ */
+function valid_input_set($param, $valid_values, $array, $exc = '') {
+ if (isset($array[$param]) && in_array($array[$param], $valid_values)) {
+ return $array[$param];
+ } elseif (isset($valid_values['default'])) {
+ return $valid_values['default'];
+ } else {
+ throw new Exception($exc);
+ }
+}
+
+//Setup VIM: ex: et ts=2 enc=utf-8 :
diff --git a/inc/confutils.php b/inc/confutils.php
index abfde8a80..de63846de 100644
--- a/inc/confutils.php
+++ b/inc/confutils.php
@@ -248,7 +248,6 @@ function actionOK($action){
if(isset($conf['resendpasswd']) && !$conf['resendpasswd']) $disabled[] = 'resendpwd';
if(isset($conf['subscribers']) && !$conf['subscribers']) {
$disabled[] = 'subscribe';
- $disabled[] = 'subscribens';
}
$disabled = array_unique($disabled);
}
diff --git a/inc/form.php b/inc/form.php
index 6d496f414..0a6bc2bba 100644
--- a/inc/form.php
+++ b/inc/form.php
@@ -283,6 +283,27 @@ class Doku_Form {
echo $this->getForm();
}
+ /**
+ * Add a radio set
+ *
+ * This function adds a set of radio buttons to the form. If $_POST[$name]
+ * is set, this radio is preselected, else the first radio button.
+ *
+ * @param string $name The HTML field name
+ * @param array $entries An array of entries $value => $caption
+ *
+ * @author Adrian Lang <lang@cosmocode.de>
+ */
+
+ function addRadioSet($name, $entries) {
+ $value = (isset($_POST[$name]) && isset($entries[$_POST[$name]])) ?
+ $_POST[$name] : key($entries);
+ foreach($entries as $val => $cap) {
+ $data = ($value === $val) ? array('checked' => 'checked') : array();
+ $this->addElement(form_makeRadioField($name, $val, $cap, '', '', $data));
+ }
+ }
+
}
/**
diff --git a/inc/lang/en/lang.php b/inc/lang/en/lang.php
index cf5173d05..728d7823b 100644
--- a/inc/lang/en/lang.php
+++ b/inc/lang/en/lang.php
@@ -39,10 +39,7 @@ $lang['btn_delete'] = 'Delete';
$lang['btn_back'] = 'Back';
$lang['btn_backlink'] = "Backlinks";
$lang['btn_backtomedia'] = 'Back to Mediafile Selection';
-$lang['btn_subscribe'] = 'Subscribe Page Changes';
-$lang['btn_unsubscribe'] = 'Unsubscribe Page Changes';
-$lang['btn_subscribens'] = 'Subscribe Namespace Changes';
-$lang['btn_unsubscribens'] = 'Unsubscribe Namespace Changes';
+$lang['btn_subscribe'] = 'Manage Subscriptions';
$lang['btn_profile'] = 'Update Profile';
$lang['btn_reset'] = 'Reset';
$lang['btn_resendpwd'] = 'Send new password';
@@ -158,6 +155,7 @@ $lang['download'] = 'Download Snippet';
$lang['mail_newpage'] = 'page added:';
$lang['mail_changed'] = 'page changed:';
+$lang['mail_subscribe_list'] = 'pages changed in namespace:';
$lang['mail_new_user'] = 'new user:';
$lang['mail_upload'] = 'file uploaded:';
@@ -212,11 +210,26 @@ $lang['img_format'] = 'Format';
$lang['img_camera'] = 'Camera';
$lang['img_keywords']= 'Keywords';
-$lang['subscribe_success'] = 'Added %s to subscription list for %s';
-$lang['subscribe_error'] = 'Error adding %s to subscription list for %s';
-$lang['subscribe_noaddress']= 'There is no address associated with your login, you cannot be added to the subscription list';
-$lang['unsubscribe_success']= 'Removed %s from subscription list for %s';
-$lang['unsubscribe_error'] = 'Error removing %s from subscription list for %s';
+$lang['subscr_subscribe_success'] = 'Added %s to subscription list for %s';
+$lang['subscr_subscribe_error'] = 'Error adding %s to subscription list for %s';
+$lang['subscr_subscribe_noaddress']= 'There is no address associated with your login, you cannot be added to the subscription list';
+$lang['subscr_unsubscribe_success']= 'Removed %s from subscription list for %s';
+$lang['subscr_unsubscribe_error'] = 'Error removing %s from subscription list for %s';
+$lang['subscr_no_target'] = 'No subscription target';
+$lang['subscr_invalid_style'] = 'Invalid subscription style';
+$lang['subscr_invalid_action'] = 'Invalid subscription action';
+$lang['subscr_already_subscribed'] = '%s is already subscribed to %s';
+$lang['subscr_not_subscribed'] = '%s is not subscribed to %s';
+$lang['subscr_not_subscribed_you'] = 'You are not subscribed to %s';
+// Manage page for subscriptions
+$lang['subscr_m_current_header'] = 'Current subscriptions';
+$lang['subscr_m_current'] = 'Your current subscriptions:';
+$lang['subscr_m_entry'] = 'Subscribed to %s receiving %s.';
+$lang['subscr_m_delete'] = 'Delete';
+$lang['subscr_m_not_subscribed'] = 'You are currently not subscribed to this page.';
+$lang['subscr_m_new_header'] = 'Add subscription';
+$lang['subscr_m_noemail'] = 'You did not set an email address.';
+$lang['subscr_m_subscribe'] = 'Subscribe';
/* auth.class language support */
$lang['authmodfailed'] = 'Bad user authentication configuration. Please inform your Wiki Admin.';
diff --git a/inc/lang/en/subscr_digest.txt b/inc/lang/en/subscr_digest.txt
new file mode 100644
index 000000000..35011b6e6
--- /dev/null
+++ b/inc/lang/en/subscr_digest.txt
@@ -0,0 +1,20 @@
+Hello!
+
+The page @PAGE@ in the @TITLE@ wiki changed.
+Here are the changes:
+
+--------------------------------------------------------
+@DIFF@
+--------------------------------------------------------
+
+Old Revision: @OLDPAGE@
+New Revision: @NEWPAGE@
+
+To cancel the page notifications, log into the wiki at
+@DOKUWIKIURL@ then visit
+@SUBSCRIBE@
+and unsubscribe page and/or namespace changes.
+
+--
+This mail was generated by DokuWiki at
+@DOKUWIKIURL@
diff --git a/inc/lang/en/subscr_form.txt b/inc/lang/en/subscr_form.txt
new file mode 100644
index 000000000..94b75258c
--- /dev/null
+++ b/inc/lang/en/subscr_form.txt
@@ -0,0 +1,3 @@
+====== Manage subscriptions ======
+
+This form allows you to manage your subscriptions for the current page.
diff --git a/inc/lang/en/subscr_list.txt b/inc/lang/en/subscr_list.txt
new file mode 100644
index 000000000..efe27d866
--- /dev/null
+++ b/inc/lang/en/subscr_list.txt
@@ -0,0 +1,17 @@
+Hello!
+
+Pages in the namespace @PAGE@ of the @TITLE@ wiki changed.
+Here are the changed pages:
+
+--------------------------------------------------------
+@DIFF@
+--------------------------------------------------------
+
+To cancel the page notifications, log into the wiki at
+@DOKUWIKIURL@ then visit
+@SUBSCRIBE@
+and unsubscribe page and/or namespace changes.
+
+--
+This mail was generated by DokuWiki at
+@DOKUWIKIURL@
diff --git a/inc/lang/en/subscribermail.txt b/inc/lang/en/subscr_single.txt
index 673c4c32a..673c4c32a 100644
--- a/inc/lang/en/subscribermail.txt
+++ b/inc/lang/en/subscr_single.txt
diff --git a/inc/pageutils.php b/inc/pageutils.php
index 9c192e5e6..239ff41c5 100644
--- a/inc/pageutils.php
+++ b/inc/pageutils.php
@@ -534,4 +534,21 @@ function isVisiblePage($id){
return !isHiddenPage($id);
}
+/**
+ * Format an id for output to a user
+ *
+ * Namespaces are denoted by a trailing “:*”. The root namespace is
+ * “*”. Output is escaped.
+ *
+ * @author Adrian Lang <lang@cosmocode.de>
+ */
+function prettyprint_id($id) {
+ if (!$id || $id === ':') {
+ return '*';
+ }
+ if ((substr($id, -1, 1) === ':')) {
+ $id .= '*';
+ }
+ return hsc($id);
+}
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 @@
+<?php
+/**
+ * Utilities for handling (email) subscriptions
+ *
+ * The public interface of this file consists of the functions
+ * - subscription_find
+ * - subscription_send_digest
+ * - subscription_send_list
+ * - subscription_set
+ * - get_info_subscribed
+ * - subscription_addresslist
+ *
+ * @author Adrian Lang <lang@cosmocode.de>
+ * @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 <lang@cosmocode.de>
+ */
+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 <lang@cosmocode.de>
+ */
+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 <lang@cosmocode.de>
+ */
+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 <lang@cosmocode.de>
+ */
+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 <lang@cosmocode.de>
+ */
+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 <steven-danz@kc.rr.com>
+ */
+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 <lang@cosmocode.de>
+ */
+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 <lang@cosmocode.de>
+ */
+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 <lang@cosmocode.de>
+ */
+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);
+}
diff --git a/inc/template.php b/inc/template.php
index 4681300eb..9b738bf8f 100644
--- a/inc/template.php
+++ b/inc/template.php
@@ -126,6 +126,9 @@ function tpl_content_core(){
case 'admin':
tpl_admin();
break;
+ case 'subscribe':
+ tpl_subscribe();
+ break;
default:
$evt = new Doku_Event('TPL_ACT_UNKNOWN',$ACT);
if ($evt->advise_before())
@@ -540,31 +543,10 @@ function tpl_button($type,$return=false){
}
break;
case 'subscribe':
- case 'subscription':
- if($conf['useacl'] && $auth && $ACT == 'show' && $conf['subscribers'] == 1){
- if($_SERVER['REMOTE_USER']){
- if($INFO['subscribed']){
- if(actionOK('unsubscribe'))
- $out .= html_btn('unsubscribe',$ID,'',array('do' => 'unsubscribe',));
- } else {
- if(actionOK('subscribe'))
- $out .= html_btn('subscribe',$ID,'',array('do' => 'subscribe',));
- }
- }
- }
- if($type == 'subscribe') break;
- // else: fall through for backward compatibility
- case 'subscribens':
- if($conf['useacl'] && $auth && $ACT == 'show' && $conf['subscribers'] == 1){
- if($_SERVER['REMOTE_USER']){
- if($INFO['subscribedns']){
- if(actionOK('unsubscribens'))
- $out .= html_btn('unsubscribens',$ID,'',array('do' => 'unsubscribens',));
- } else {
- if(actionOK('subscribens'))
- $out .= html_btn('subscribens',$ID,'',array('do' => 'subscribens',));
- }
- }
+ if ($conf['useacl'] && $auth && $ACT == 'show' &&
+ $conf['subscribers'] && isset($_SERVER['REMOTE_USER']) &&
+ actionOK('subscribe')) {
+ $out .= html_btn('subscribe',$ID,'',array('do' => 'subscribe',));
}
break;
case 'backlink':
@@ -712,37 +694,12 @@ function tpl_actionlink($type,$pre='',$suf='',$inner='',$return=false){
break;
case 'subscribe':
case 'subscription':
- if($conf['useacl'] && $auth && $ACT == 'show' && $conf['subscribers'] == 1){
+ if($conf['useacl'] && $auth && $ACT == 'show' && $conf['subscribers']) {
if($_SERVER['REMOTE_USER']){
- if($INFO['subscribed']) {
- if(actionOK('unsubscribe'))
- $out .= tpl_link(wl($ID,'do=unsubscribe'),
- $pre.(($inner)?$inner:$lang['btn_unsubscribe']).$suf,
- 'class="action unsubscribe" rel="nofollow"',1);
- } else {
if(actionOK('subscribe'))
$out .= tpl_link(wl($ID,'do=subscribe'),
$pre.(($inner)?$inner:$lang['btn_subscribe']).$suf,
'class="action subscribe" rel="nofollow"',1);
- }
- }
- }
- if($type == 'subscribe') break;
- // else: fall through for backward compatibility
- case 'subscribens':
- if($conf['useacl'] && $auth && $ACT == 'show' && $conf['subscribers'] == 1){
- if($_SERVER['REMOTE_USER']){
- if($INFO['subscribedns']) {
- if(actionOK('unsubscribens'))
- $out .= tpl_link(wl($ID,'do=unsubscribens'),
- $pre.(($inner)?$inner:$lang['btn_unsubscribens']).$suf,
- 'class="action unsubscribens" rel="nofollow"',1);
- } else {
- if(actionOK('subscribens'))
- $out .= tpl_link(wl($ID,'do=subscribens'),
- $pre.(($inner)?$inner:$lang['btn_subscribens']).$suf,
- 'class="action subscribens" rel="nofollow"',1);
- }
}
}
break;
@@ -1323,23 +1280,9 @@ function tpl_actiondropdown($empty='',$button='&gt;'){
echo '<option value="profile">'.$lang['btn_profile'].'</option>';
}
- if($conf['useacl'] && $auth && $ACT == 'show' && $conf['subscribers'] == 1){
+ if($conf['useacl'] && $auth && $ACT == 'show' && $conf['subscribers']){
if($_SERVER['REMOTE_USER']){
- if($INFO['subscribed']) {
- echo '<option value="unsubscribe">'.$lang['btn_unsubscribe'].'</option>';
- } else {
echo '<option value="subscribe">'.$lang['btn_subscribe'].'</option>';
- }
- }
- }
-
- if($conf['useacl'] && $auth && $ACT == 'show' && $conf['subscribers'] == 1){
- if($_SERVER['REMOTE_USER']){
- if($INFO['subscribedns']) {
- echo '<option value="unsubscribens">'.$lang['btn_unsubscribens'].'</option>';
- } else {
- echo '<option value="subscribens">'.$lang['btn_subscribens'].'</option>';
- }
}
}
@@ -1406,5 +1349,68 @@ function tpl_include_page($pageid,$print=true){
echo $html;
}
+/**
+ * Display the subscribe form
+ *
+ * @author Adrian Lang <lang@cosmocode.de>
+ */
+
+function tpl_subscribe() {
+ global $INFO;
+ global $ID;
+ global $lang;
+ $targets = array($ID => 'the current page',
+ 'namespace' => 'the namespace “%s”');
+ $styles = array('every' => 'a notice on every change',
+ 'digest' => 'a digest for each changed page*',
+ 'list' => 'a list of changed pages*');
+
+ echo p_locale_xhtml('subscr_form');
+
+ echo '<h2>' . $lang['subscr_m_current_header'] . '</h2>';
+ if ($INFO['subscribed'] === false) {
+ echo '<p>' . $lang['subscr_m_not_subscribed'] . '</p>';
+ } else {
+ echo '<p>' . $lang['subscr_m_current'] . '</p>';
+ echo '<ul>';
+ foreach($INFO['subscribed'] as $sub) {
+ $form = new Doku_Form(array('class' => 'unsubscribe'));
+ if ($sub['target'] !== $ID) {
+ $str = sprintf($targets['namespace'], prettyprint_id($sub['target']));
+ } else {
+ $str = $targets[$ID];
+ }
+ $form->addElement('<li><div class="li">' .
+ sprintf($lang['subscr_m_entry'], $str,
+ $styles[$sub['style']]));
+ $form->addElement(form_makeButton('submit', 'subscribe', $lang['subscr_m_delete']));
+ $form->addHidden('subscribe_target', $sub['target']);
+ $form->addHidden('subscribe_style', $sub['style']);
+ $form->addHidden('subscribe_action', 'unsubscribe');
+ $form->addElement('</div></li>');
+ html_form('UNSUBSCRIBE', $form);
+ }
+ echo '</ul>';
+ }
+
+ echo '<h2>' . $lang['subscr_m_new_header'] . '</h2>';
+ if ($INFO['userinfo']['mail'] === '') {
+ echo $lang['subscr_m_noemail'];
+ return;
+ }
+ $styles['list'] = $styles['list'] . ' (Not allowed for single pages)';
+ $form = new Doku_Form(array('id' => 'subscribe'));
+ $ns = getNS($ID). ':';
+ $targets[$ns] = sprintf($targets['namespace'], prettyprint_id($ns));
+ unset($targets['namespace']);
+ $form->addElement('<p>' . 'Subscribe to' . '</p>');
+ $form->addRadioSet('subscribe_target', $targets);
+ $form->addElement('<p>' . 'Receive' . '</p>');
+ $form->addRadioSet('subscribe_style', $styles);
+ $form->addHidden('subscribe_action', 'subscribe');
+ $form->addElement(form_makeButton('submit', 'subscribe', $lang['subscr_m_subscribe']));
+ html_form('SUBSCRIBE', $form);
+}
+
//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/lib/exe/indexer.php b/lib/exe/indexer.php
index 1c4128eb7..eb1556e1d 100644
--- a/lib/exe/indexer.php
+++ b/lib/exe/indexer.php
@@ -37,6 +37,7 @@ if ($evt->advise_before()) {
runIndexer() or
metaUpdate() or
runSitemapper() or
+ sendDigest() or
runTrimRecentChanges() or
runTrimRecentChanges(true) or
$evt->advise_after();
@@ -335,6 +336,75 @@ function runSitemapper(){
}
/**
+ * Send digest and list mails for all subscriptions which are in effect for the
+ * current page
+ *
+ * @author Adrian Lang <lang@cosmocode.de>
+ */
+function sendDigest() {
+ require_once DOKU_INC . 'inc/subscription.php';
+ echo 'sendDigest(): start'.NL;
+ global $ID;
+ global $conf;
+ if (!$conf['subscribers']) {
+ return;
+ }
+
+ $subscriptions = subscription_find($ID, array('style' => '(digest|list)',
+ 'escaped' => true));
+ global $auth;
+ global $lang;
+ global $conf;
+ foreach($subscriptions as $id => $users) {
+ foreach($users as $data) {
+ list($user, $style, $lastupdate) = $data;
+ $lastupdate = (int) $lastupdate;
+ if ($lastupdate + $conf['subscribe_interval'] > time()) {
+ // Less than a day passed since last update.
+ continue;
+ }
+ // TODO: Does that suffice for namespaces?
+ $info = $auth->getUserData($user);
+ if ($info === false) {
+ continue;
+ }
+ $level = auth_aclcheck($id, $user, $info['grps']);
+ if ($level < AUTH_READ) {
+ continue;
+ }
+
+ if (substr($id, -1, 1) === ':') {
+ // The subscription target is a namespace
+ $changes = getRecentsSince($lastupdate, null, getNS($id));
+ if (count($changes) === 0) {
+ continue;
+ }
+ if ($style === 'digest') {
+ foreach($changes as $change) {
+ subscription_send_digest($info['mail'], $change,
+ $lastupdate);
+ }
+ } elseif ($style === 'list') {
+ subscription_send_list($info['mail'], $changes, $id);
+ }
+ // TODO: Handle duplicate subscriptions.
+ } else {
+ $meta = p_get_metadata($id);
+ $rev = $meta['last_change']['date'];
+ if ($rev < $lastupdate) {
+ // There is no new revision.
+ continue;
+ }
+ subscription_send_digest($info['mail'], $meta['last_change'],
+ $lastupdate);
+ }
+ // Update notification time.
+ subscription_set($id, $user, $style, true);
+ }
+ }
+}
+
+/**
* Formats a timestamp as ISO 8601 date
*
* @author <ungu at terong dot com>
diff --git a/lib/exe/js.php b/lib/exe/js.php
index 38fda1789..8648bf18f 100644
--- a/lib/exe/js.php
+++ b/lib/exe/js.php
@@ -53,6 +53,7 @@ function js_out(){
DOKU_INC.'lib/scripts/edit.js',
DOKU_INC.'lib/scripts/linkwiz.js',
DOKU_INC.'lib/scripts/media.js',
+ DOKU_INC.'lib/scripts/subscriptions.js',
DOKU_TPLINC.'script.js',
);
diff --git a/lib/scripts/subscriptions.js b/lib/scripts/subscriptions.js
new file mode 100644
index 000000000..9f602dde8
--- /dev/null
+++ b/lib/scripts/subscriptions.js
@@ -0,0 +1,46 @@
+/**
+ * Hide list subscription style if target is a page
+ *
+ * @author Adrian Lang <lang@cosmocode.de>
+ */
+
+addInitEvent(function () {
+ var form = $('subscribe');
+ if (!form) {
+ return;
+ }
+
+ var styleradios = {};
+
+ function update_state() {
+ if (!this.checked) {
+ return;
+ }
+ if (this.value.match(/:$/)) {
+ styleradios.list.parentNode.style.display = '';
+ } else {
+ styleradios.list.parentNode.style.display = 'none';
+ if (styleradios.list.checked) {
+ styleradios.digest.checked = 'checked';
+ }
+ }
+ }
+
+ var cur_sel = null;
+
+ var inputs = form.getElementsByTagName('input');
+ for (var i = 0; i < inputs.length ; ++i) {
+ switch (inputs[i].name) {
+ case 'subscribe_target':
+ inputs[i].addEventListener('change', update_state, false);
+ if (inputs[i].checked) {
+ cur_sel = inputs[i];
+ }
+ break;
+ case 'subscribe_style':
+ styleradios[inputs[i].value] = inputs[i];
+ break;
+ }
+ }
+ update_state.call(cur_sel);
+});
diff --git a/lib/tpl/default/design.css b/lib/tpl/default/design.css
index 4830a9e2c..02804256c 100644
--- a/lib/tpl/default/design.css
+++ b/lib/tpl/default/design.css
@@ -833,3 +833,17 @@ div.dokuwiki div.imagemeta img.thumb {
float: left;
margin-right: 0.1em;
}
+
+form#subscribe p {
+ margin: 0.5em 0;
+}
+
+form#subscribe label {
+ display:block;
+ margin: 0 0.5em 0.5em;
+}
+
+form#subscribe fieldset, form#unsubscribe fieldset {
+ text-align:inherit;
+ margin: 0;
+}
diff --git a/lib/tpl/default/main.php b/lib/tpl/default/main.php
index 67c3a00ba..b5717c009 100644
--- a/lib/tpl/default/main.php
+++ b/lib/tpl/default/main.php
@@ -117,7 +117,6 @@ if (!defined('DOKU_INC')) die();
</div>
<div class="bar-right" id="bar__bottomright">
<?php tpl_button('subscribe')?>
- <?php tpl_button('subscribens')?>
<?php tpl_button('admin')?>
<?php tpl_button('profile')?>
<?php tpl_button('login')?>