summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--conf/dokuwiki.php2
-rw-r--r--inc/actions.php138
-rw-r--r--inc/common.php132
-rw-r--r--inc/confutils.php1
-rw-r--r--inc/form.php21
-rw-r--r--inc/html.php61
-rw-r--r--inc/lang/de/lang.php24
-rw-r--r--inc/lang/en/lang.php29
-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/parser/handler.php5
-rw-r--r--inc/parser/renderer.php2
-rw-r--r--inc/parser/xhtml.php3
-rw-r--r--inc/subscription.php349
-rw-r--r--inc/template.php140
-rw-r--r--lib/exe/indexer.php79
-rw-r--r--lib/exe/js.php1
-rw-r--r--lib/plugins/config/lang/en/lang.php1
-rw-r--r--lib/plugins/config/settings/config.metadata.php1
-rw-r--r--lib/scripts/drag.js55
-rw-r--r--lib/scripts/edit.js5
-rw-r--r--lib/scripts/script.js41
-rw-r--r--lib/scripts/subscriptions.js46
-rw-r--r--lib/scripts/toolbar.js4
-rw-r--r--lib/tpl/default/_subscription.css21
-rw-r--r--lib/tpl/default/design.css5
-rw-r--r--lib/tpl/default/main.php1
-rw-r--r--lib/tpl/default/style.ini7
31 files changed, 928 insertions, 303 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..1fda0584e 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,102 @@ function act_export($act){
}
/**
- * Handle page 'subscribe', 'unsubscribe'
+ * Handle page 'subscribe'
*
- * @author Steven Danz <steven-danz@kc.rr.com>
- * @todo localize
+ * Throws exception on error.
+ *
+ * @author Adrian Lang <lang@cosmocode.de>
*/
function act_subscription($act){
- global $ID;
- global $INFO;
global $lang;
+ global $INFO;
+ global $ID;
- $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);
+ // get and preprocess data.
+ $params = array();
+ foreach(array('target', 'style', 'action') as $param) {
+ if (isset($_REQUEST["sub_$param"])) {
+ $params[$param] = $_REQUEST["sub_$param"];
}
}
+ // any action given? if not just return and show the subscription page
+ if(!$params['action']) return $act;
+
+ // Handle POST data, may throw exception.
+ trigger_event('ACTION_HANDLE_SUBSCRIBE', $params, 'subscription_handle_post');
+
+ $target = $params['target'];
+ $style = $params['style'];
+ $data = $params['data'];
+ $action = $params['action'];
+
+ // Perform action.
+ require_once DOKU_INC . 'inc/subscription.php';
+ if (!subscription_set($_SERVER['REMOTE_USER'], $target, $style, $data)) {
+ throw new Exception(sprintf($lang["subscr_{$action}_error"],
+ hsc($INFO['userinfo']['name']),
+ prettyprint_id($target)));
+ }
+ msg(sprintf($lang["subscr_{$action}_success"], hsc($INFO['userinfo']['name']),
+ prettyprint_id($target)), 1);
+ act_redirect($ID, $act);
+
+ // Assure that we have valid data if act_redirect somehow fails.
+ $INFO['subscribed'] = get_info_subscribed();
return 'show';
}
/**
- * Handle namespace 'subscribe', 'unsubscribe'
+ * Validate POST data
+ *
+ * Validates POST data for a subscribe or unsubscribe request. This is the
+ * default action for the event ACTION_HANDLE_SUBSCRIBE.
*
+ * @author Adrian Lang <lang@cosmocode.de>
*/
-function act_subscriptionns($act){
- global $ID;
+function subscription_handle_post(&$params) {
global $INFO;
global $lang;
- if(!getNS($ID)) {
- $file = metaFN(getNS($ID),'.mlist');
- $ns = "root";
- } else {
- $file = metaFN(getNS($ID),'/.mlist');
- $ns = getNS($ID);
+ // Get and validate parameters.
+ if (!isset($params['target'])) {
+ throw new Exception('no subscription target given');
}
-
- // 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);
+ $target = $params['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('style', $valid_styles, $params,
+ 'invalid subscription style given');
+ $action = valid_input_set('action', array('subscribe', 'unsubscribe'),
+ $params, 'invalid subscription action given');
+
+ // 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'],
+ $_SERVER['REMOTE_USER'],
+ prettyprint_id($target)));
}
+ // subscription_set deletes a subscription if style = null.
+ $style = null;
}
- return 'show';
+ $data = in_array($style, array('list', 'digest')) ? time() : null;
+ $params = compact('target', 'style', 'data', 'action');
}
//Setup VIM: ex: et ts=2 enc=utf-8 :
diff --git a/inc/common.php b/inc/common.php
index 85187f16d..9cadb56fd 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,13 @@ 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);
+ $data = array('id' => $id, 'addresslist' => '', 'self' => false);
+ trigger_event('COMMON_NOTIFY_ADDRESSLIST', $data,
+ 'subscription_addresslist');
+ $bcc = $data['addresslist'];
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 +1100,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 +1276,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 +1457,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 5384593c7..7850682ba 100644
--- a/inc/confutils.php
+++ b/inc/confutils.php
@@ -249,7 +249,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/html.php b/inc/html.php
index 29dddbe74..8a215f440 100644
--- a/inc/html.php
+++ b/inc/html.php
@@ -91,14 +91,18 @@ function html_secedit_button($matches){
global $ID;
global $INFO;
- $section = $matches[2];
- $name = $matches[1];
+ $edittarget = ($matches[1] === 'SECTION') ? 'plain' :
+ strtolower($matches[1]);
+
+ $section = $matches[3];
+ $name = $matches[2];
$secedit = '';
- $secedit .= '<div class="secedit">';
+ $secedit .= '<div class="secedit editbutton_' . $edittarget . '">';
$secedit .= html_btn('secedit',$ID,'',
array('do' => 'edit',
- 'lines' => "$section",
+ 'lines' => $section,
+ 'edittarget' => $edittarget,
'rev' => $INFO['lastmod']),
'post', $name);
$secedit .= '</div>';
@@ -113,11 +117,13 @@ function html_secedit_button($matches){
function html_secedit($text,$show=true){
global $INFO;
+ $regexp = '#<!-- ([A-Z]+) (?:"(.*)" )?\[(\d+-\d*)\] -->#';
+
if($INFO['writable'] && $show && !$INFO['rev']){
- $text = preg_replace_callback('#<!-- SECTION "(.*?)" \[(\d+-\d*)\] -->#',
+ $text = preg_replace_callback($regexp,
'html_secedit_button', $text);
}else{
- $text = preg_replace('#<!-- SECTION "(.*?)" \[(\d+-\d*)\] -->#','',$text);
+ $text = preg_replace($regexp,'',$text);
}
return $text;
@@ -181,7 +187,7 @@ function html_btn($name,$id,$akey,$params,$method='get',$tooltip=''){
$tip = htmlspecialchars($label);
}
- $ret .= '<input type="submit" value="'.htmlspecialchars($label).'" class="button" ';
+ $ret .= '<input type="submit" value="'.hsc($label).'" class="button" ';
if($akey){
$tip .= ' ['.strtoupper($akey).']';
$ret .= 'accesskey="'.$akey.'" ';
@@ -1104,11 +1110,9 @@ function html_updateprofile(){
}
/**
- * This displays the edit form (lots of logic included)
+ * Preprocess edit form data
*
- * @fixme this is a huge lump of code and should be modularized
* @triggers HTML_PAGE_FROMTEMPLATE
- * @triggers HTML_EDITFORM_INJECTION
* @author Andreas Gohr <andi@splitbrain.org>
*/
function html_edit($text=null,$include='edit'){ //FIXME: include needed?
@@ -1122,7 +1126,6 @@ function html_edit($text=null,$include='edit'){ //FIXME: include needed?
global $SUM;
global $lang;
global $conf;
- global $license;
//set summary default
if(!$SUM){
@@ -1176,20 +1179,44 @@ function html_edit($text=null,$include='edit'){ //FIXME: include needed?
print p_locale_xhtml('read');
}
if(!$DATE) $DATE = $INFO['lastmod'];
- ?>
- <div style="width:99%;">
- <div class="toolbar">
- <div id="draft__status"><?php if(!empty($INFO['draft'])) echo $lang['draftdate'].' '.dformat();?></div>
- <div id="tool__bar"><?php if($wr){?><a href="<?php echo DOKU_BASE?>lib/exe/mediamanager.php?ns=<?php echo $INFO['namespace']?>"
- target="_blank"><?php echo $lang['mediaselect'] ?></a><?php }?></div>
+ $data = compact('wr', 'text', 'mod', 'check');
+ trigger_event('HTML_EDIT_FORMSELECTION', $data, 'html_edit_form', true);
+}
+/**
+ * Display the default edit form
+ *
+ * Is the default action for HTML_EDIT_FORMSELECTION.
+ *
+ * @triggers HTML_EDITFORM_OUTPUT
+ */
+function html_edit_form($param) {
+ extract($param);
+ global $conf;
+ global $license;
+ global $lang;
+ global $REV;
+ global $DATE;
+ global $PRE;
+ global $SUF;
+ global $INFO;
+ global $SUM;
+ global $ID;
+ ?>
<?php if($wr){?>
<script type="text/javascript" charset="utf-8"><!--//--><![CDATA[//><!--
<?php /* sets changed to true when previewed */?>
textChanged = <?php ($mod) ? print 'true' : print 'false' ?>;
//--><!]]></script>
<?php } ?>
+ <div style="width:99%;">
+
+ <div class="toolbar">
+ <div id="draft__status"><?php if(!empty($INFO['draft'])) echo $lang['draftdate'].' '.dformat();?></div>
+ <div id="tool__bar"><?php if($wr){?><a href="<?php echo DOKU_BASE?>lib/exe/mediamanager.php?ns=<?php echo $INFO['namespace']?>"
+ target="_blank"><?php echo $lang['mediaselect'] ?></a><?php }?></div>
+
</div>
<?php
$form = new Doku_Form(array('id' => 'dw__editform'));
diff --git a/inc/lang/de/lang.php b/inc/lang/de/lang.php
index d0c1f8b70..c3fefd926 100644
--- a/inc/lang/de/lang.php
+++ b/inc/lang/de/lang.php
@@ -203,11 +203,25 @@ $lang['img_copyr'] = 'Copyright';
$lang['img_format'] = 'Format';
$lang['img_camera'] = 'Kamera';
$lang['img_keywords'] = 'Schlagwörter';
-$lang['subscribe_success'] = '%s hat nun Änderungen der Seite %s abonniert';
-$lang['subscribe_error'] = '%s kann die Änderungen der Seite %s nicht abonnieren';
-$lang['subscribe_noaddress'] = 'Weil Ihre E-Mail-Adresse fehlt, können Sie das Thema nicht abonnieren';
-$lang['unsubscribe_success'] = 'Das Abonnement von %s für die Seite %s wurde aufgelöst';
-$lang['unsubscribe_error'] = 'Das Abonnement von %s für die Seite %s konnte nicht aufgelöst werden';
+
+$lang['subscr_subscribe_success'] = '%s hat nun Änderungen der Seite %s abonniert';
+$lang['subscr_subscribe_error'] = '%s kann die Änderungen der Seite %s nicht abonnieren';
+$lang['subscr_subscribe_noaddress']= 'Weil Ihre E-Mail-Adresse fehlt, können Sie das Thema nicht abonnieren';
+$lang['subscr_unsubscribe_success']= 'Das Abonnement von %s für die Seite %s wurde aufgelöst';
+$lang['subscr_unsubscribe_error'] = 'Das Abonnement von %s für die Seite %s konnte nicht aufgelöst werden';
+$lang['subscr_already_subscribed'] = '%s hat %s bereits abonniert';
+$lang['subscr_not_subscribed'] = '%s hat %s nicht abonniert';
+// Manage page for subscriptions
+$lang['subscr_m_not_subscribed'] = 'Sie haben die aktuelle Seite und ihre Namensräume nicht abonniert.';
+$lang['subscr_m_new_header'] = 'Abonnement hinzufügen';
+$lang['subscr_m_current_header'] = 'Aktuelle Abonnements';
+$lang['subscr_m_unsubscribe'] = 'Löschen';
+$lang['subscr_m_subscribe'] = 'Abonnieren';
+
+$lang['subscr_style_every'] = 'Email bei jeder Bearbeitung';
+$lang['subscr_style_digest'] = 'Übersichtsemail für jede veränderte Seite';
+$lang['subscr_style_list'] = 'Email mit Liste der geänderten Seiten';
+
$lang['authmodfailed'] = 'Benutzerüberprüfung nicht möglich. Bitte wenden Sie sich an den Systembetreuer.';
$lang['authtempfail'] = 'Benutzerüberprüfung momentan nicht möglich. Falls das Problem andauert, wenden Sie sich an den Systembetreuer.';
$lang['i_chooselang'] = 'Wählen Sie Ihre Sprache';
diff --git a/inc/lang/en/lang.php b/inc/lang/en/lang.php
index cf5173d05..98ded12ca 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,24 @@ $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_already_subscribed'] = '%s is already subscribed to %s';
+$lang['subscr_not_subscribed'] = '%s is not subscribed to %s';
+// Manage page for subscriptions
+$lang['subscr_m_not_subscribed'] = 'You are currently not subscribed to the current page or namespace.';
+$lang['subscr_m_new_header'] = 'Add subscription';
+$lang['subscr_m_current_header'] = 'Current subscriptions';
+$lang['subscr_m_unsubscribe'] = 'Unsubscribe';
+$lang['subscr_m_subscribe'] = 'Subscribe';
+$lang['subscr_m_receive'] = 'Receive';
+$lang['subscr_style_every'] = 'email on every change';
+$lang['subscr_style_digest'] = 'digest email of changes for each page';
+$lang['subscr_style_list'] = 'list of changed pages since last email';
+
/* 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..d606508c6
--- /dev/null
+++ b/inc/lang/en/subscr_form.txt
@@ -0,0 +1,3 @@
+====== Subscription Management ======
+
+This page allows you to manage your subscriptions for the current page and namespace.
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/parser/handler.php b/inc/parser/handler.php
index eaa42f9bf..ea5a69b35 100644
--- a/inc/parser/handler.php
+++ b/inc/parser/handler.php
@@ -589,10 +589,11 @@ class Doku_Handler {
} else {
$this->_addCall('tablecell', array(), $pos);
}
+ $this->status['table_begin'] = $pos;
break;
case DOKU_LEXER_EXIT:
- $this->_addCall('table_end', array(), $pos);
+ $this->_addCall('table_end', array($this->status['table_begin']+1, $pos), $pos);
$this->CallWriter->process();
$ReWriter = & $this->CallWriter;
$this->CallWriter = & $ReWriter->CallWriter;
@@ -1222,7 +1223,7 @@ class Doku_Handler_Table {
}
function tableEnd($call) {
- $this->tableCalls[] = array('table_close',array(),$call[2]);
+ $this->tableCalls[] = array('table_close',$call[1],$call[2]);
$this->finalizeTable();
}
diff --git a/inc/parser/renderer.php b/inc/parser/renderer.php
index 6082e935d..65dcaf8a1 100644
--- a/inc/parser/renderer.php
+++ b/inc/parser/renderer.php
@@ -233,7 +233,7 @@ class Doku_Renderer extends DokuWiki_Plugin {
function table_open($maxcols = NULL, $numrows = NULL){}
- function table_close(){}
+ function table_close($begin, $end){}
function tablerow_open(){}
diff --git a/inc/parser/xhtml.php b/inc/parser/xhtml.php
index 20acf4281..4e848ec1d 100644
--- a/inc/parser/xhtml.php
+++ b/inc/parser/xhtml.php
@@ -851,8 +851,9 @@ class Doku_Renderer_xhtml extends Doku_Renderer {
$this->doc .= '<table class="inline">'.DOKU_LF;
}
- function table_close(){
+ function table_close($begin, $end){
$this->doc .= '</table>'.DOKU_LF;
+ $this->doc .= '<!-- TABLE ['. $begin .'-'.$end.'] -->';
}
function tablerow_open(){
diff --git a/inc/subscription.php b/inc/subscription.php
new file mode 100644
index 000000000..12f0a4b82
--- /dev/null
+++ b/inc/subscription.php
@@ -0,0 +1,349 @@
+<?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 $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 $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
+ *
+ * @author Adrian Lang <lang@cosmocode.de>
+ */
+function subscription_set($user, $page, $style, $data = null,
+ $overwrite = false) {
+ 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($user, $page, null)) {
+ return false;
+ }
+ }
+
+ $file = subscription_filename($page);
+ $content = auth_nameencode($user) . ' ' . $style;
+ if (!is_null($data)) {
+ $content .= ' ' . $data;
+ }
+ 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(':' => subscription_filename(':'));
+ 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
+ *
+ * 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 <steven-danz@kc.rr.com>
+ * @author Adrian Lang <lang@cosmocode.de>
+ */
+function subscription_addresslist($data){
+ global $conf;
+ global $auth;
+
+ $id = $data['id'];
+ $self = $data['self'];
+ $addresslist = $data['addresslist'];
+
+ 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'];
+ }
+ }
+ }
+ }
+ $data['addresslist'] = trim($addresslist . ',' . 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..84fbda051 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,70 @@ 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;
+
+ echo p_locale_xhtml('subscr_form');
+ echo '<h2>' . $lang['subscr_m_current_header'] . '</h2>';
+ echo '<div class="level2">';
+ if ($INFO['subscribed'] === false) {
+ echo '<p>' . $lang['subscr_m_not_subscribed'] . '</p>';
+ } else {
+ echo '<ul>';
+ foreach($INFO['subscribed'] as $sub) {
+ echo '<li><div class="li">';
+ if ($sub['target'] !== $ID) {
+ echo '<code class="ns">'.hsc(prettyprint_id($sub['target'])).'</code>';
+ } else {
+ echo '<code class="page">'.hsc(prettyprint_id($sub['target'])).'</code>';
+ }
+ $sstl = $lang['subscr_style_'.$sub['style']];
+ if(!$sstl) $sstl = hsc($sub['style']);
+ echo ' ('.$sstl.') ';
+
+ echo '<a href="'.wl($ID,array('do'=>'subscribe','sub_target'=>$sub['target'],'sub_style'=>$sub['style'],'sub_action'=>'unsubscribe')).'" class="unsubscribe">'.$lang['subscr_m_unsubscribe'].'</a>';
+
+
+ echo '</div></li>';
+ }
+ echo '</ul>';
+ }
+ echo '</div>';
+
+ // Add new subscription form
+ echo '<h2>' . $lang['subscr_m_new_header'] . '</h2>';
+ echo '<div class="level2">';
+ $ns = getNS($ID).':';
+ $targets = array(
+ $ID => '<code class="page">'.prettyprint_id($ID).'</code>',
+ $ns => '<code class="ns">'.prettyprint_id($ns).'</code>',
+ );
+ $styles = array(
+ 'every' => $lang['subscr_style_every'],
+ 'digest' => $lang['subscr_style_digest'],
+ 'list' => $lang['subscr_style_list'],
+ );
+
+ $form = new Doku_Form(array('id' => 'subscribe__form'));
+ $form->startFieldset($lang['subscr_m_subscribe']);
+ $form->addRadioSet('sub_target', $targets);
+ $form->startFieldset($lang['subscr_m_receive']);
+ $form->addRadioSet('sub_style', $styles);
+ $form->addHidden('sub_action', 'subscribe');
+ $form->addHidden('do', 'subscribe');
+ $form->addHidden('id', $ID);
+ $form->endFieldset();
+ $form->addElement(form_makeButton('submit', 'subscribe', $lang['subscr_m_subscribe']));
+ html_form('SUBSCRIBE', $form);
+ echo '</div>';
+}
+
//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/lib/exe/indexer.php b/lib/exe/indexer.php
index 1c4128eb7..84eb9d482 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,84 @@ 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() {
+ echo 'sendDigest(): start'.NL;
+ global $ID;
+ global $conf;
+ if (!$conf['subscribers']) {
+ return;
+ }
+ require_once DOKU_INC . 'inc/subscription.php';
+ $subscriptions = subscription_find($ID, array('style' => '(digest|list)',
+ 'escaped' => true));
+ global $auth;
+ global $lang;
+ global $conf;
+ global $USERINFO;
+
+ // remember current user info
+ $olduinfo = $USERINFO;
+ $olduser = $_SERVER['REMOTE_USER'];
+
+ 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;
+ }
+
+ // Work as the user to make sure ACLs apply correctly
+ $USERINFO = $auth->getUserData($user);
+ $_SERVER['REMOTE_USER'] = $user;
+ if ($USERINFO === false) {
+ 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($USERINFO['mail'], $change,
+ $lastupdate);
+ }
+ } elseif ($style === 'list') {
+ subscription_send_list($USERINFO['mail'], $changes, $id);
+ }
+ // TODO: Handle duplicate subscriptions.
+ } else {
+ if(auth_quickaclcheck($id) < AUTH_READ) continue;
+
+ $meta = p_get_metadata($id);
+ $rev = $meta['last_change']['date'];
+ if ($rev < $lastupdate) {
+ // There is no new revision.
+ continue;
+ }
+ subscription_send_digest($USERINFO['mail'], $meta['last_change'],
+ $lastupdate);
+ }
+ // Update notification time.
+ subscription_set($user, $id, $style, time(), true);
+ }
+ }
+
+ // restore current user info
+ $USERINFO = $olduinfo;
+ $_SERVER['REMOTE_USER'] = $olduser;
+}
+
+/**
* 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/plugins/config/lang/en/lang.php b/lib/plugins/config/lang/en/lang.php
index 25103278c..45b653680 100644
--- a/lib/plugins/config/lang/en/lang.php
+++ b/lib/plugins/config/lang/en/lang.php
@@ -130,6 +130,7 @@ $lang['gdlib'] = 'GD Lib version';
$lang['im_convert'] = 'Path to ImageMagick\'s convert tool';
$lang['jpg_quality'] = 'JPG compression quality (0-100)';
$lang['subscribers'] = 'Enable page subscription support';
+$lang['subscribe_time'] = 'Time after which subscription lists and digests are sent (sec); This should be larger than the time specified in recent_days.';
$lang['compress'] = 'Compact CSS and javascript output';
$lang['hidepages'] = 'Hide matching pages (regular expressions)';
$lang['send404'] = 'Send "HTTP 404/Page Not Found" for non existing pages';
diff --git a/lib/plugins/config/settings/config.metadata.php b/lib/plugins/config/settings/config.metadata.php
index 742faf387..da2ba4aef 100644
--- a/lib/plugins/config/settings/config.metadata.php
+++ b/lib/plugins/config/settings/config.metadata.php
@@ -148,6 +148,7 @@ $meta['htmlok'] = array('onoff');
$meta['phpok'] = array('onoff');
$meta['notify'] = array('email', '_multiple' => true);
$meta['subscribers'] = array('onoff');
+$meta['subscribe_time'] = array('numeric');
$meta['locktime'] = array('numeric');
$meta['cachetime'] = array('numeric');
diff --git a/lib/scripts/drag.js b/lib/scripts/drag.js
index 4726b77de..fa249a996 100644
--- a/lib/scripts/drag.js
+++ b/lib/scripts/drag.js
@@ -1,8 +1,9 @@
/**
* Makes a DOM object draggable
*
- * This is currently for movable DOM dialogs only. If needed it could be
- * extended to execute callbacks on special events...
+ * If you just want to move objects around, use drag.attach. For full
+ * customization, drag can be used as a javascript prototype, it is
+ * inheritance-aware.
*
* @link http://nofunc.org/Drag_Drop/
*/
@@ -26,33 +27,36 @@ var drag = {
attach: function (obj,handle) {
if(handle){
handle.dragobject = obj;
- addEvent($(handle),'mousedown',drag.start);
}else{
- addEvent($(obj),'mousedown',drag.start);
+ handle = obj;
}
+ var _this = this;
+ addEvent($(handle),'mousedown',function (e) {return _this.start(e); });
},
/**
* Starts the dragging operation
*/
start: function (e){
- drag.handle = e.target;
- if(drag.handle.dragobject){
- drag.obj = drag.handle.dragobject;
+ this.handle = e.target;
+ if(this.handle.dragobject){
+ this.obj = this.handle.dragobject;
}else{
- drag.obj = drag.handle;
+ this.obj = this.handle;
}
- drag.handle.className += ' ondrag';
- drag.obj.className += ' ondrag';
+ this.handle.className += ' ondrag';
+ this.obj.className += ' ondrag';
- drag.oX = parseInt(drag.obj.style.left);
- drag.oY = parseInt(drag.obj.style.top);
- drag.eX = drag.evX(e);
- drag.eY = drag.evY(e);
+ this.oX = parseInt(this.obj.style.left);
+ this.oY = parseInt(this.obj.style.top);
+ this.eX = drag.evX(e);
+ this.eY = drag.evY(e);
- addEvent(document,'mousemove',drag.drag);
- addEvent(document,'mouseup',drag.stop);
+ var _this = this;
+ this.mousehandlers = [function (e) {return _this.drag(e);}, function (e) {return _this.stop(e);}];
+ addEvent(document,'mousemove', this.mousehandlers[0]);
+ addEvent(document,'mouseup', this.mousehandlers[1]);
e.preventDefault();
e.stopPropagation();
@@ -63,21 +67,21 @@ var drag = {
* Ends the dragging operation
*/
stop: function(){
- drag.handle.className = drag.handle.className.replace(/ ?ondrag/,'');
- drag.obj.className = drag.obj.className.replace(/ ?ondrag/,'');
- removeEvent(document,'mousemove',drag.drag);
- removeEvent(document,'mouseup',drag.stop);
- drag.obj = null;
- drag.handle = null;
+ this.handle.className = this.handle.className.replace(/ ?ondrag/,'');
+ this.obj.className = this.obj.className.replace(/ ?ondrag/,'');
+ removeEvent(document,'mousemove', this.mousehandlers[0]);
+ removeEvent(document,'mouseup', this.mousehandlers[1]);
+ this.obj = null;
+ this.handle = null;
},
/**
* Moves the object during the dragging operation
*/
drag: function(e) {
- if(drag.obj) {
- drag.obj.style.top = (drag.evY(e)+drag.oY-drag.eY+'px');
- drag.obj.style.left = (drag.evX(e)+drag.oX-drag.eX+'px');
+ if(this.obj) {
+ this.obj.style.top = (this.evY(e)+this.oY-this.eY+'px');
+ this.obj.style.left = (this.evX(e)+this.oX-this.eX+'px');
}
},
@@ -96,4 +100,3 @@ var drag = {
}
};
-
diff --git a/lib/scripts/edit.js b/lib/scripts/edit.js
index d7391b7e7..4d8ead858 100644
--- a/lib/scripts/edit.js
+++ b/lib/scripts/edit.js
@@ -12,12 +12,15 @@
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
-function createToolButton(icon,label,key,id){
+function createToolButton(icon,label,key,id,classname){
var btn = document.createElement('button');
var ico = document.createElement('img');
// preapare the basic button stuff
btn.className = 'toolbutton';
+ if(classname){
+ btn.className += ' '+classname;
+ }
btn.title = label;
if(key){
btn.title += ' ['+key.toUpperCase()+']';
diff --git a/lib/scripts/script.js b/lib/scripts/script.js
index ccba82144..b611f980a 100644
--- a/lib/scripts/script.js
+++ b/lib/scripts/script.js
@@ -538,24 +538,41 @@ addInitEvent(function(){
var break_classes = new RegExp('secedit|toc|page');
var btns = getElementsByClass('btn_secedit',document,'form');
for(var i=0; i<btns.length; i++){
- addEvent(btns[i],'mouseover',function(e){
- var tgt = e.target;
- if(tgt.form) tgt = tgt.form;
- tgt = tgt.parentNode.previousSibling;
- if(tgt.nodeName != "DIV") tgt = tgt.previousSibling;
- while(!break_classes.test(tgt.className)) {
- tgt.className += ' section_highlight';
- if (tgt.tagName == 'H1') break;
- tgt = (tgt.previousSibling != null) ? tgt.previousSibling : tgt.parentNode;
- }
- });
+ switch(btns[i].parentNode.className.match(/editbutton_(\w+)/)[1]) {
+ case 'plain':
+ addEvent(btns[i],'mouseover',function(e){
+ var tgt = e.target.form.parentNode;
+ do {
+ tgt = tgt.previousSibling;
+ } while (tgt && !tgt.tagName);
+ if (!tgt) return;
+ if(tgt.nodeName != "DIV") tgt = tgt.previousSibling;
+ while(!break_classes.test(tgt.className)) {
+ tgt.className += ' section_highlight';
+ if (tgt.tagName == 'H1') break;
+ tgt = (tgt.previousSibling != null) ? tgt.previousSibling : tgt.parentNode;
+ }
+ });
+ break;
+
+ case 'table':
+ addEvent(btns[i],'mouseover',function(e){
+ var tgt = e.target.form.parentNode;
+ do {
+ tgt = tgt.previousSibling;
+ } while (tgt && !tgt.tagName);
+ if (tgt && tgt.tagName === 'TABLE') {
+ tgt.className += ' section_highlight';
+ }
+ });
+ break;
+ }
addEvent(btns[i],'mouseout',function(e){
var secs = getElementsByClass('section_highlight');
for(var j=0; j<secs.length; j++){
secs[j].className = secs[j].className.replace(/section_highlight/,'');
}
- var secs = getElementsByClass('section_highlight');
});
}
});
diff --git a/lib/scripts/subscriptions.js b/lib/scripts/subscriptions.js
new file mode 100644
index 000000000..d701f258f
--- /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__form');
+ 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 'sub_target':
+ addEvent(inputs[i], 'click', update_state);
+ if (inputs[i].checked) {
+ cur_sel = inputs[i];
+ }
+ break;
+ case 'sub_style':
+ styleradios[inputs[i].value] = inputs[i];
+ break;
+ }
+ }
+ update_state.call(cur_sel);
+});
diff --git a/lib/scripts/toolbar.js b/lib/scripts/toolbar.js
index 1e4a91864..99ad1bb9c 100644
--- a/lib/scripts/toolbar.js
+++ b/lib/scripts/toolbar.js
@@ -27,7 +27,9 @@ function initToolbar(tbid,edid,tb){
// create new button
var btn = createToolButton(tb[i]['icon'],
tb[i]['title'],
- tb[i]['key']);
+ tb[i]['key'],
+ tb[i]['id'],
+ tb[i]['class']);
// type is a tb function -> assign it as onclick
diff --git a/lib/tpl/default/_subscription.css b/lib/tpl/default/_subscription.css
new file mode 100644
index 000000000..0792c8c21
--- /dev/null
+++ b/lib/tpl/default/_subscription.css
@@ -0,0 +1,21 @@
+/**
+ * Styles for the subscription page
+ */
+
+form#subscribe__form {
+ display: block;
+ width: 300px;
+ text-align: center;
+}
+
+form#subscribe__form fieldset {
+ text-align: left;
+ margin: 0.5em 0;
+}
+
+form#subscribe__form label {
+ display:block;
+ margin: 0 0.5em 0.5em;
+}
+
+
diff --git a/lib/tpl/default/design.css b/lib/tpl/default/design.css
index 4830a9e2c..cd4d03e43 100644
--- a/lib/tpl/default/design.css
+++ b/lib/tpl/default/design.css
@@ -741,8 +741,8 @@ div.dokuwiki ul.search_quickhits li {
width: 30%;
}
-div.dokuwiki div.section_highlight {
- background-color: __background_alt__;
+div.dokuwiki .section_highlight {
+ background-color: __background_alt__ !important;
}
/* ------------------ Additional ---------------------- */
@@ -833,3 +833,4 @@ div.dokuwiki div.imagemeta img.thumb {
float: left;
margin-right: 0.1em;
}
+
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')?>
diff --git a/lib/tpl/default/style.ini b/lib/tpl/default/style.ini
index dfd5500fa..84d04e743 100644
--- a/lib/tpl/default/style.ini
+++ b/lib/tpl/default/style.ini
@@ -10,9 +10,10 @@ layout.css = screen
design.css = screen
style.css = screen
-media.css = screen
-_admin.css = screen
-_linkwiz.css = screen
+media.css = screen
+_admin.css = screen
+_linkwiz.css = screen
+_subscription.css = screen
rtl.css = rtl
print.css = print