You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

260 lines
8.1 KiB

  1. <?php
  2. namespace dokuwiki\Subscriptions;
  3. use dokuwiki\ChangeLog\PageChangeLog;
  4. use dokuwiki\Extension\AuthPlugin;
  5. use dokuwiki\Input\Input;
  6. use Exception;
  7. class BulkSubscriptionSender extends SubscriptionSender
  8. {
  9. /**
  10. * Send digest and list subscriptions
  11. *
  12. * This sends mails to all subscribers that have a subscription for namespaces above
  13. * the given page if the needed $conf['subscribe_time'] has passed already.
  14. *
  15. * This function is called form lib/exe/indexer.php
  16. *
  17. * @param string $page
  18. * @return int number of sent mails
  19. * @throws Exception
  20. */
  21. public function sendBulk($page)
  22. {
  23. $subscriberManager = new SubscriberManager();
  24. if (!$subscriberManager->isenabled()) {
  25. return 0;
  26. }
  27. /** @var AuthPlugin $auth */
  28. global $auth;
  29. global $conf;
  30. global $USERINFO;
  31. /** @var Input $INPUT */
  32. global $INPUT;
  33. $count = 0;
  34. $subscriptions = $subscriberManager->subscribers($page, null, ['digest', 'list']);
  35. // remember current user info
  36. $olduinfo = $USERINFO;
  37. $olduser = $INPUT->server->str('REMOTE_USER');
  38. foreach ($subscriptions as $target => $users) {
  39. if (!$this->lock($target)) {
  40. continue;
  41. }
  42. foreach ($users as $user => $info) {
  43. [$style, $lastupdate] = $info;
  44. $lastupdate = (int)$lastupdate;
  45. if ($lastupdate + $conf['subscribe_time'] > time()) {
  46. // Less than the configured time period passed since last
  47. // update.
  48. continue;
  49. }
  50. // Work as the user to make sure ACLs apply correctly
  51. $USERINFO = $auth->getUserData($user);
  52. $INPUT->server->set('REMOTE_USER', $user);
  53. if ($USERINFO === false) {
  54. continue;
  55. }
  56. if (!$USERINFO['mail']) {
  57. continue;
  58. }
  59. if (str_ends_with($target, ':')) {
  60. // subscription target is a namespace, get all changes within
  61. $changes = getRecentsSince($lastupdate, null, getNS($target));
  62. } else {
  63. // single page subscription, check ACL ourselves
  64. if (auth_quickaclcheck($target) < AUTH_READ) {
  65. continue;
  66. }
  67. $meta = p_get_metadata($target);
  68. $changes = [$meta['last_change']];
  69. }
  70. // Filter out pages only changed in small and own edits
  71. $change_ids = [];
  72. foreach ($changes as $rev) {
  73. $n = 0;
  74. $pagelog = new PageChangeLog($rev['id']);
  75. while (
  76. !is_null($rev) && $rev['date'] >= $lastupdate &&
  77. ($INPUT->server->str('REMOTE_USER') === $rev['user'] ||
  78. $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)
  79. ) {
  80. $revisions = $pagelog->getRevisions($n++, 1);
  81. $rev = ($revisions !== []) ? $pagelog->getRevisionInfo($revisions[0]) : null;
  82. }
  83. if (!is_null($rev) && $rev['date'] >= $lastupdate) {
  84. // Some change was not a minor one and not by myself
  85. $change_ids[] = $rev['id'];
  86. }
  87. }
  88. // send it
  89. if ($style === 'digest') {
  90. foreach ($change_ids as $change_id) {
  91. $this->sendDigest(
  92. $USERINFO['mail'],
  93. $change_id,
  94. $lastupdate
  95. );
  96. $count++;
  97. }
  98. } elseif ($style === 'list') {
  99. $this->sendList($USERINFO['mail'], $change_ids, $target);
  100. $count++;
  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 = ($rev !== []) ? $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 ($ids === []) {
  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. }