PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/wiki/inc/subscription.php

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