SubscriberManager.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. <?php
  2. namespace dokuwiki\Subscriptions;
  3. use dokuwiki\Input\Input;
  4. use DokuWiki_Auth_Plugin;
  5. use Exception;
  6. class SubscriberManager
  7. {
  8. /**
  9. * Check if subscription system is enabled
  10. *
  11. * @return bool
  12. */
  13. public function isenabled()
  14. {
  15. return actionOK('subscribe');
  16. }
  17. /**
  18. * Adds a new subscription for the given page or namespace
  19. *
  20. * This will automatically overwrite any existent subscription for the given user on this
  21. * *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces.
  22. *
  23. * @throws Exception when user or style is empty
  24. *
  25. * @param string $id The target page or namespace, specified by id; Namespaces
  26. * are identified by appending a colon.
  27. * @param string $user
  28. * @param string $style
  29. * @param string $data
  30. *
  31. * @return bool
  32. */
  33. public function add($id, $user, $style, $data = '')
  34. {
  35. if (!$this->isenabled()) {
  36. return false;
  37. }
  38. // delete any existing subscription
  39. $this->remove($id, $user);
  40. $user = auth_nameencode(trim($user));
  41. $style = trim($style);
  42. $data = trim($data);
  43. if (!$user) {
  44. throw new Exception('no subscription user given');
  45. }
  46. if (!$style) {
  47. throw new Exception('no subscription style given');
  48. }
  49. if (!$data) {
  50. $data = time();
  51. } //always add current time for new subscriptions
  52. $line = "$user $style $data\n";
  53. $file = $this->file($id);
  54. return io_saveFile($file, $line, true);
  55. }
  56. /**
  57. * Removes a subscription for the given page or namespace
  58. *
  59. * This removes all subscriptions matching the given criteria on the given page or
  60. * namespace. It will *not* modify any subscriptions that may exist in higher
  61. * namespaces.
  62. *
  63. * @param string $id The target object’s (namespace or page) id
  64. * @param string|array $user
  65. * @param string|array $style
  66. * @param string|array $data
  67. *
  68. * @return bool
  69. */
  70. public function remove($id, $user = null, $style = null, $data = null)
  71. {
  72. if (!$this->isenabled()) {
  73. return false;
  74. }
  75. $file = $this->file($id);
  76. if (!file_exists($file)) {
  77. return true;
  78. }
  79. $regexBuilder = new SubscriberRegexBuilder();
  80. $re = $regexBuilder->buildRegex($user, $style, $data);
  81. return io_deleteFromFile($file, $re, true);
  82. }
  83. /**
  84. * Get data for $INFO['subscribed']
  85. *
  86. * $INFO['subscribed'] is either false if no subscription for the current page
  87. * and user is in effect. Else it contains an array of arrays with the fields
  88. * “target”, “style”, and optionally “data”.
  89. *
  90. * @author Adrian Lang <lang@cosmocode.de>
  91. *
  92. * @param string $id Page ID, defaults to global $ID
  93. * @param string $user User, defaults to $_SERVER['REMOTE_USER']
  94. *
  95. * @return array|false
  96. */
  97. public function userSubscription($id = '', $user = '')
  98. {
  99. if (!$this->isenabled()) {
  100. return false;
  101. }
  102. global $ID;
  103. /** @var Input $INPUT */
  104. global $INPUT;
  105. if (!$id) {
  106. $id = $ID;
  107. }
  108. if (!$user) {
  109. $user = $INPUT->server->str('REMOTE_USER');
  110. }
  111. if (empty($user)) {
  112. // not logged in
  113. return false;
  114. }
  115. $subs = $this->subscribers($id, $user);
  116. if (!count($subs)) {
  117. return false;
  118. }
  119. $result = [];
  120. foreach ($subs as $target => $info) {
  121. $result[] = [
  122. 'target' => $target,
  123. 'style' => $info[$user][0],
  124. 'data' => $info[$user][1],
  125. ];
  126. }
  127. return $result;
  128. }
  129. /**
  130. * Recursively search for matching subscriptions
  131. *
  132. * This function searches all relevant subscription files for a page or
  133. * namespace.
  134. *
  135. * @author Adrian Lang <lang@cosmocode.de>
  136. *
  137. * @param string $page The target object’s (namespace or page) id
  138. * @param string|array $user
  139. * @param string|array $style
  140. * @param string|array $data
  141. *
  142. * @return array
  143. */
  144. public function subscribers($page, $user = null, $style = null, $data = null)
  145. {
  146. if (!$this->isenabled()) {
  147. return [];
  148. }
  149. // Construct list of files which may contain relevant subscriptions.
  150. $files = [':' => $this->file(':')];
  151. do {
  152. $files[$page] = $this->file($page);
  153. $page = getNS(rtrim($page, ':')) . ':';
  154. } while ($page !== ':');
  155. $regexBuilder = new SubscriberRegexBuilder();
  156. $re = $regexBuilder->buildRegex($user, $style, $data);
  157. // Handle files.
  158. $result = [];
  159. foreach ($files as $target => $file) {
  160. if (!file_exists($file)) {
  161. continue;
  162. }
  163. $lines = file($file);
  164. foreach ($lines as $line) {
  165. // fix old style subscription files
  166. if (strpos($line, ' ') === false) {
  167. $line = trim($line) . " every\n";
  168. }
  169. // check for matching entries
  170. if (!preg_match($re, $line, $m)) {
  171. continue;
  172. }
  173. // if no last sent is set, use 0
  174. if (!isset($m[3])) {
  175. $m[3] = 0;
  176. }
  177. $u = rawurldecode($m[1]); // decode the user name
  178. if (!isset($result[$target])) {
  179. $result[$target] = [];
  180. }
  181. $result[$target][$u] = [$m[2], $m[3]]; // add to result
  182. }
  183. }
  184. return array_reverse($result);
  185. }
  186. /**
  187. * Default callback for COMMON_NOTIFY_ADDRESSLIST
  188. *
  189. * Aggregates all email addresses of user who have subscribed the given page with 'every' style
  190. *
  191. * @author Adrian Lang <lang@cosmocode.de>
  192. * @author Steven Danz <steven-danz@kc.rr.com>
  193. *
  194. * @todo move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead,
  195. * use an array for the addresses within it
  196. *
  197. * @param array &$data Containing the entries:
  198. * - $id (the page id),
  199. * - $self (whether the author should be notified,
  200. * - $addresslist (current email address list)
  201. * - $replacements (array of additional string substitutions, @KEY@ to be replaced by value)
  202. */
  203. public function notifyAddresses(&$data)
  204. {
  205. if (!$this->isenabled()) {
  206. return;
  207. }
  208. /** @var DokuWiki_Auth_Plugin $auth */
  209. global $auth;
  210. global $conf;
  211. /** @var \Input $INPUT */
  212. global $INPUT;
  213. $id = $data['id'];
  214. $self = $data['self'];
  215. $addresslist = $data['addresslist'];
  216. $subscriptions = $this->subscribers($id, null, 'every');
  217. $result = [];
  218. foreach ($subscriptions as $target => $users) {
  219. foreach ($users as $user => $info) {
  220. $userinfo = $auth->getUserData($user);
  221. if ($userinfo === false) {
  222. continue;
  223. }
  224. if (!$userinfo['mail']) {
  225. continue;
  226. }
  227. if (!$self && $user == $INPUT->server->str('REMOTE_USER')) {
  228. continue;
  229. } //skip our own changes
  230. $level = auth_aclcheck($id, $user, $userinfo['grps']);
  231. if ($level >= AUTH_READ) {
  232. if (strcasecmp($userinfo['mail'], $conf['notify']) != 0) { //skip user who get notified elsewhere
  233. $result[$user] = $userinfo['mail'];
  234. }
  235. }
  236. }
  237. }
  238. $data['addresslist'] = trim($addresslist . ',' . implode(',', $result), ',');
  239. }
  240. /**
  241. * Return the subscription meta file for the given ID
  242. *
  243. * @author Adrian Lang <lang@cosmocode.de>
  244. *
  245. * @param string $id The target page or namespace, specified by id; Namespaces
  246. * are identified by appending a colon.
  247. *
  248. * @return string
  249. */
  250. protected function file($id)
  251. {
  252. $meta_fname = '.mlist';
  253. if ((substr($id, -1, 1) === ':')) {
  254. $meta_froot = getNS($id);
  255. $meta_fname = '/' . $meta_fname;
  256. } else {
  257. $meta_froot = $id;
  258. }
  259. return metaFN((string)$meta_froot, $meta_fname);
  260. }
  261. }