BulkSubscriptionSender.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <?php
  2. namespace dokuwiki\Subscriptions;
  3. use dokuwiki\ChangeLog\PageChangeLog;
  4. use dokuwiki\Input\Input;
  5. use DokuWiki_Auth_Plugin;
  6. class BulkSubscriptionSender extends SubscriptionSender
  7. {
  8. /**
  9. * Send digest and list subscriptions
  10. *
  11. * This sends mails to all subscribers that have a subscription for namespaces above
  12. * the given page if the needed $conf['subscribe_time'] has passed already.
  13. *
  14. * This function is called form lib/exe/indexer.php
  15. *
  16. * @param string $page
  17. *
  18. * @return int number of sent mails
  19. */
  20. public function sendBulk($page)
  21. {
  22. $subscriberManager = new SubscriberManager();
  23. if (!$subscriberManager->isenabled()) {
  24. return 0;
  25. }
  26. /** @var DokuWiki_Auth_Plugin $auth */
  27. global $auth;
  28. global $conf;
  29. global $USERINFO;
  30. /** @var Input $INPUT */
  31. global $INPUT;
  32. $count = 0;
  33. $subscriptions = $subscriberManager->subscribers($page, null, ['digest', 'list']);
  34. // remember current user info
  35. $olduinfo = $USERINFO;
  36. $olduser = $INPUT->server->str('REMOTE_USER');
  37. foreach ($subscriptions as $target => $users) {
  38. if (!$this->lock($target)) {
  39. continue;
  40. }
  41. foreach ($users as $user => $info) {
  42. list($style, $lastupdate) = $info;
  43. $lastupdate = (int)$lastupdate;
  44. if ($lastupdate + $conf['subscribe_time'] > time()) {
  45. // Less than the configured time period passed since last
  46. // update.
  47. continue;
  48. }
  49. // Work as the user to make sure ACLs apply correctly
  50. $USERINFO = $auth->getUserData($user);
  51. $INPUT->server->set('REMOTE_USER', $user);
  52. if ($USERINFO === false) {
  53. continue;
  54. }
  55. if (!$USERINFO['mail']) {
  56. continue;
  57. }
  58. if (substr($target, -1, 1) === ':') {
  59. // subscription target is a namespace, get all changes within
  60. $changes = getRecentsSince($lastupdate, null, getNS($target));
  61. } else {
  62. // single page subscription, check ACL ourselves
  63. if (auth_quickaclcheck($target) < AUTH_READ) {
  64. continue;
  65. }
  66. $meta = p_get_metadata($target);
  67. $changes = [$meta['last_change']];
  68. }
  69. // Filter out pages only changed in small and own edits
  70. $change_ids = [];
  71. foreach ($changes as $rev) {
  72. $n = 0;
  73. $pagelog = new PageChangeLog($rev['id']);
  74. while (!is_null($rev) && $rev['date'] >= $lastupdate &&
  75. ($INPUT->server->str('REMOTE_USER') === $rev['user'] ||
  76. $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)
  77. ) {
  78. $revisions = $pagelog->getRevisions($n++, 1);
  79. $rev = (count($revisions) > 0) ? $pagelog->getRevisionInfo($revisions[0]) : null;
  80. }
  81. if (!is_null($rev) && $rev['date'] >= $lastupdate) {
  82. // Some change was not a minor one and not by myself
  83. $change_ids[] = $rev['id'];
  84. }
  85. }
  86. // send it
  87. if ($style === 'digest') {
  88. foreach ($change_ids as $change_id) {
  89. $this->sendDigest(
  90. $USERINFO['mail'],
  91. $change_id,
  92. $lastupdate
  93. );
  94. $count++;
  95. }
  96. } else {
  97. if ($style === 'list') {
  98. $this->sendList($USERINFO['mail'], $change_ids, $target);
  99. $count++;
  100. }
  101. }
  102. // TODO: Handle duplicate subscriptions.
  103. // Update notification time.
  104. $subscriberManager->add($target, $user, $style, time());
  105. }
  106. $this->unlock($target);
  107. }
  108. // restore current user info
  109. $USERINFO = $olduinfo;
  110. $INPUT->server->set('REMOTE_USER', $olduser);
  111. return $count;
  112. }
  113. /**
  114. * Lock subscription info
  115. *
  116. * We don't use io_lock() her because we do not wait for the lock and use a larger stale time
  117. *
  118. * @param string $id The target page or namespace, specified by id; Namespaces
  119. * are identified by appending a colon.
  120. *
  121. * @return bool true, if you got a succesful lock
  122. * @author Adrian Lang <lang@cosmocode.de>
  123. */
  124. protected function lock($id)
  125. {
  126. global $conf;
  127. $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock';
  128. if (is_dir($lock) && time() - @filemtime($lock) > 60 * 5) {
  129. // looks like a stale lock - remove it
  130. @rmdir($lock);
  131. }
  132. // try creating the lock directory
  133. if (!@mkdir($lock)) {
  134. return false;
  135. }
  136. if ($conf['dperm']) {
  137. chmod($lock, $conf['dperm']);
  138. }
  139. return true;
  140. }
  141. /**
  142. * Unlock subscription info
  143. *
  144. * @param string $id The target page or namespace, specified by id; Namespaces
  145. * are identified by appending a colon.
  146. *
  147. * @return bool
  148. * @author Adrian Lang <lang@cosmocode.de>
  149. */
  150. protected function unlock($id)
  151. {
  152. global $conf;
  153. $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock';
  154. return @rmdir($lock);
  155. }
  156. /**
  157. * Send a digest mail
  158. *
  159. * Sends a digest mail showing a bunch of changes of a single page. Basically the same as sendPageDiff()
  160. * but determines the last known revision first
  161. *
  162. * @param string $subscriber_mail The target mail address
  163. * @param string $id The ID
  164. * @param int $lastupdate Time of the last notification
  165. *
  166. * @return bool
  167. * @author Adrian Lang <lang@cosmocode.de>
  168. *
  169. */
  170. protected function sendDigest($subscriber_mail, $id, $lastupdate)
  171. {
  172. $pagelog = new PageChangeLog($id);
  173. $n = 0;
  174. do {
  175. $rev = $pagelog->getRevisions($n++, 1);
  176. $rev = (count($rev) > 0) ? $rev[0] : null;
  177. } while (!is_null($rev) && $rev > $lastupdate);
  178. // TODO I'm not happy with the following line and passing $this->mailer around. Not sure how to solve it better
  179. $pageSubSender = new PageSubscriptionSender($this->mailer);
  180. return $pageSubSender->sendPageDiff(
  181. $subscriber_mail,
  182. 'subscr_digest',
  183. $id,
  184. $rev
  185. );
  186. }
  187. /**
  188. * Send a list mail
  189. *
  190. * Sends a list mail showing a list of changed pages.
  191. *
  192. * @param string $subscriber_mail The target mail address
  193. * @param array $ids Array of ids
  194. * @param string $ns_id The id of the namespace
  195. *
  196. * @return bool true if a mail was sent
  197. * @author Adrian Lang <lang@cosmocode.de>
  198. *
  199. */
  200. protected function sendList($subscriber_mail, $ids, $ns_id)
  201. {
  202. if (count($ids) === 0) {
  203. return false;
  204. }
  205. $tlist = '';
  206. $hlist = '<ul>';
  207. foreach ($ids as $id) {
  208. $link = wl($id, [], true);
  209. $tlist .= '* ' . $link . NL;
  210. $hlist .= '<li><a href="' . $link . '">' . hsc($id) . '</a></li>' . NL;
  211. }
  212. $hlist .= '</ul>';
  213. $id = prettyprint_id($ns_id);
  214. $trep = [
  215. 'DIFF' => rtrim($tlist),
  216. 'PAGE' => $id,
  217. 'SUBSCRIBE' => wl($id, ['do' => 'subscribe'], true, '&'),
  218. ];
  219. $hrep = [
  220. 'DIFF' => $hlist,
  221. ];
  222. return $this->send(
  223. $subscriber_mail,
  224. 'subscribe_list',
  225. $ns_id,
  226. 'subscr_list',
  227. $trep,
  228. $hrep
  229. );
  230. }
  231. }