OpenApi Gen: add toString method for easier testing
[dokuwiki.git] / inc / Subscriptions / SubscriberManager.php
blob8da1ac35d3a87136dfad54c807c45d0e2b8f4b80
1 <?php
3 namespace dokuwiki\Subscriptions;
5 use dokuwiki\Extension\AuthPlugin;
6 use dokuwiki\Input\Input;
7 use Exception;
9 class SubscriberManager
11 /**
12 * Check if subscription system is enabled
14 * @return bool
16 public function isenabled()
18 return actionOK('subscribe');
21 /**
22 * Adds a new subscription for the given page or namespace
24 * This will automatically overwrite any existent subscription for the given user on this
25 * *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces.
27 * @throws Exception when user or style is empty
29 * @param string $id The target page or namespace, specified by id; Namespaces
30 * are identified by appending a colon.
31 * @param string $user
32 * @param string $style
33 * @param string $data
35 * @return bool
37 public function add($id, $user, $style, $data = '')
39 if (!$this->isenabled()) {
40 return false;
43 // delete any existing subscription
44 $this->remove($id, $user);
46 $user = auth_nameencode(trim($user));
47 $style = trim($style);
48 $data = trim($data);
50 if (!$user) {
51 throw new Exception('no subscription user given');
53 if (!$style) {
54 throw new Exception('no subscription style given');
56 if (!$data) {
57 $data = time();
58 } //always add current time for new subscriptions
60 $line = "$user $style $data\n";
61 $file = $this->file($id);
62 return io_saveFile($file, $line, true);
66 /**
67 * Removes a subscription for the given page or namespace
69 * This removes all subscriptions matching the given criteria on the given page or
70 * namespace. It will *not* modify any subscriptions that may exist in higher
71 * namespaces.
73 * @param string $id The target object’s (namespace or page) id
74 * @param string|array $user
75 * @param string|array $style
76 * @param string|array $data
78 * @return bool
79 * @throws Exception
81 public function remove($id, $user = null, $style = null, $data = null)
83 if (!$this->isenabled()) {
84 return false;
87 $file = $this->file($id);
88 if (!file_exists($file)) {
89 return true;
92 $regexBuilder = new SubscriberRegexBuilder();
93 $re = $regexBuilder->buildRegex($user, $style, $data);
94 return io_deleteFromFile($file, $re, true);
97 /**
98 * Get data for $INFO['subscribed']
100 * $INFO['subscribed'] is either false if no subscription for the current page
101 * and user is in effect. Else it contains an array of arrays with the fields
102 * “target”, “style”, and optionally “data”.
104 * @param string $id Page ID, defaults to global $ID
105 * @param string $user User, defaults to $_SERVER['REMOTE_USER']
107 * @return array|false
108 * @throws Exception
110 * @author Adrian Lang <lang@cosmocode.de>
112 public function userSubscription($id = '', $user = '')
114 if (!$this->isenabled()) {
115 return false;
118 global $ID;
119 /** @var Input $INPUT */
120 global $INPUT;
121 if (!$id) {
122 $id = $ID;
124 if (!$user) {
125 $user = $INPUT->server->str('REMOTE_USER');
128 if (empty($user)) {
129 // not logged in
130 return false;
133 $subs = $this->subscribers($id, $user);
134 if ($subs === []) {
135 return false;
138 $result = [];
139 foreach ($subs as $target => $info) {
140 $result[] = [
141 'target' => $target,
142 'style' => $info[$user][0],
143 'data' => $info[$user][1],
147 return $result;
151 * Recursively search for matching subscriptions
153 * This function searches all relevant subscription files for a page or
154 * namespace.
156 * @param string $page The target object’s (namespace or page) id
157 * @param string|array $user
158 * @param string|array $style
159 * @param string|array $data
161 * @return array
162 * @throws Exception
164 * @author Adrian Lang <lang@cosmocode.de>
167 public function subscribers($page, $user = null, $style = null, $data = null)
169 if (!$this->isenabled()) {
170 return [];
173 // Construct list of files which may contain relevant subscriptions.
174 $files = [':' => $this->file(':')];
175 do {
176 $files[$page] = $this->file($page);
177 $page = getNS(rtrim($page, ':')) . ':';
178 } while ($page !== ':');
180 $regexBuilder = new SubscriberRegexBuilder();
181 $re = $regexBuilder->buildRegex($user, $style, $data);
183 // Handle files.
184 $result = [];
185 foreach ($files as $target => $file) {
186 if (!file_exists($file)) {
187 continue;
190 $lines = file($file);
191 foreach ($lines as $line) {
192 // fix old style subscription files
193 if (strpos($line, ' ') === false) {
194 $line = trim($line) . " every\n";
197 // check for matching entries
198 if (!preg_match($re, $line, $m)) {
199 continue;
202 // if no last sent is set, use 0
203 if (!isset($m[3])) {
204 $m[3] = 0;
207 $u = rawurldecode($m[1]); // decode the user name
208 if (!isset($result[$target])) {
209 $result[$target] = [];
211 $result[$target][$u] = [$m[2], $m[3]]; // add to result
214 return array_reverse($result);
218 * Default callback for COMMON_NOTIFY_ADDRESSLIST
220 * Aggregates all email addresses of user who have subscribed the given page with 'every' style
222 * @param array &$data Containing the entries:
223 * - $id (the page id),
224 * - $self (whether the author should be notified,
225 * - $addresslist (current email address list)
226 * - $replacements (array of additional string substitutions, @KEY@ to be replaced by value)
227 * @throws Exception
229 * @author Adrian Lang <lang@cosmocode.de>
230 * @author Steven Danz <steven-danz@kc.rr.com>
232 * @todo move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead,
233 * use an array for the addresses within it
235 public function notifyAddresses(&$data)
237 if (!$this->isenabled()) {
238 return;
241 /** @var AuthPlugin $auth */
242 global $auth;
243 global $conf;
244 /** @var \Input $INPUT */
245 global $INPUT;
247 $id = $data['id'];
248 $self = $data['self'];
249 $addresslist = $data['addresslist'];
251 $subscriptions = $this->subscribers($id, null, 'every');
253 $result = [];
254 foreach ($subscriptions as $users) {
255 foreach ($users as $user => $info) {
256 $userinfo = $auth->getUserData($user);
257 if ($userinfo === false) {
258 continue;
260 if (!$userinfo['mail']) {
261 continue;
263 if (!$self && $user == $INPUT->server->str('REMOTE_USER')) {
264 continue;
265 } //skip our own changes
267 $level = auth_aclcheck($id, $user, $userinfo['grps']);
268 if ($level >= AUTH_READ) {
269 if (strcasecmp($userinfo['mail'], $conf['notify']) != 0) { //skip user who get notified elsewhere
270 $result[$user] = $userinfo['mail'];
275 $data['addresslist'] = trim($addresslist . ',' . implode(',', $result), ',');
279 * Return the subscription meta file for the given ID
281 * @author Adrian Lang <lang@cosmocode.de>
283 * @param string $id The target page or namespace, specified by id; Namespaces
284 * are identified by appending a colon.
286 * @return string
288 protected function file($id)
290 $meta_fname = '.mlist';
291 if (str_ends_with($id, ':')) {
292 $meta_froot = getNS($id);
293 $meta_fname = '/' . $meta_fname;
294 } else {
295 $meta_froot = $id;
297 return metaFN((string)$meta_froot, $meta_fname);