xref: /dokuwiki/inc/Subscriptions/SubscriberManager.php (revision 6c16a3a9aa602bb7e269fb6d5d18e1353e17f97f)
1479c05b1SMichael Große<?php
2479c05b1SMichael Große
3479c05b1SMichael Großenamespace dokuwiki\Subscriptions;
4479c05b1SMichael Große
551ee2399SGerrit Uitslaguse dokuwiki\Extension\AuthPlugin;
6479c05b1SMichael Großeuse dokuwiki\Input\Input;
7479c05b1SMichael Großeuse Exception;
8479c05b1SMichael Große
9479c05b1SMichael Großeclass SubscriberManager
10479c05b1SMichael Große{
11479c05b1SMichael Große    /**
12479c05b1SMichael Große     * Check if subscription system is enabled
13479c05b1SMichael Große     *
14479c05b1SMichael Große     * @return bool
15479c05b1SMichael Große     */
169c22b77cSMichael Große    public function isenabled()
179c22b77cSMichael Große    {
18479c05b1SMichael Große        return actionOK('subscribe');
19479c05b1SMichael Große    }
20479c05b1SMichael Große
21479c05b1SMichael Große    /**
22479c05b1SMichael Große     * Adds a new subscription for the given page or namespace
23479c05b1SMichael Große     *
24479c05b1SMichael Große     * This will automatically overwrite any existent subscription for the given user on this
25479c05b1SMichael Große     * *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces.
26479c05b1SMichael Große     *
279c22b77cSMichael Große     * @throws Exception when user or style is empty
289c22b77cSMichael Große     *
29479c05b1SMichael Große     * @param string $id The target page or namespace, specified by id; Namespaces
30479c05b1SMichael Große     *                   are identified by appending a colon.
31479c05b1SMichael Große     * @param string $user
32479c05b1SMichael Große     * @param string $style
33479c05b1SMichael Große     * @param string $data
349c22b77cSMichael Große     *
35479c05b1SMichael Große     * @return bool
36479c05b1SMichael Große     */
379c22b77cSMichael Große    public function add($id, $user, $style, $data = '')
389c22b77cSMichael Große    {
399c22b77cSMichael Große        if (!$this->isenabled()) {
409c22b77cSMichael Große            return false;
419c22b77cSMichael Große        }
42479c05b1SMichael Große
43479c05b1SMichael Große        // delete any existing subscription
44479c05b1SMichael Große        $this->remove($id, $user);
45479c05b1SMichael Große
46479c05b1SMichael Große        $user = auth_nameencode(trim($user));
47479c05b1SMichael Große        $style = trim($style);
48479c05b1SMichael Große        $data = trim($data);
49479c05b1SMichael Große
509c22b77cSMichael Große        if (!$user) {
519c22b77cSMichael Große            throw new Exception('no subscription user given');
529c22b77cSMichael Große        }
539c22b77cSMichael Große        if (!$style) {
549c22b77cSMichael Große            throw new Exception('no subscription style given');
559c22b77cSMichael Große        }
569c22b77cSMichael Große        if (!$data) {
579c22b77cSMichael Große            $data = time();
589c22b77cSMichael Große        } //always add current time for new subscriptions
59479c05b1SMichael Große
60479c05b1SMichael Große        $line = "$user $style $data\n";
61479c05b1SMichael Große        $file = $this->file($id);
62479c05b1SMichael Große        return io_saveFile($file, $line, true);
63479c05b1SMichael Große    }
64479c05b1SMichael Große
65479c05b1SMichael Große
66479c05b1SMichael Große    /**
67479c05b1SMichael Große     * Removes a subscription for the given page or namespace
68479c05b1SMichael Große     *
69479c05b1SMichael Große     * This removes all subscriptions matching the given criteria on the given page or
70479c05b1SMichael Große     * namespace. It will *not* modify any subscriptions that may exist in higher
71479c05b1SMichael Große     * namespaces.
72479c05b1SMichael Große     *
73479c05b1SMichael Große     * @param string $id The target object’s (namespace or page) id
74479c05b1SMichael Große     * @param string|array $user
75479c05b1SMichael Große     * @param string|array $style
76479c05b1SMichael Große     * @param string|array $data
779c22b77cSMichael Große     *
78479c05b1SMichael Große     * @return bool
7951ee2399SGerrit Uitslag     * @throws Exception
80479c05b1SMichael Große     */
819c22b77cSMichael Große    public function remove($id, $user = null, $style = null, $data = null)
829c22b77cSMichael Große    {
839c22b77cSMichael Große        if (!$this->isenabled()) {
849c22b77cSMichael Große            return false;
859c22b77cSMichael Große        }
86479c05b1SMichael Große
87479c05b1SMichael Große        $file = $this->file($id);
889c22b77cSMichael Große        if (!file_exists($file)) {
899c22b77cSMichael Große            return true;
909c22b77cSMichael Große        }
91479c05b1SMichael Große
92479c05b1SMichael Große        $regexBuilder = new SubscriberRegexBuilder();
93479c05b1SMichael Große        $re = $regexBuilder->buildRegex($user, $style, $data);
94479c05b1SMichael Große        return io_deleteFromFile($file, $re, true);
95479c05b1SMichael Große    }
96479c05b1SMichael Große
97479c05b1SMichael Große    /**
98479c05b1SMichael Große     * Get data for $INFO['subscribed']
99479c05b1SMichael Große     *
100479c05b1SMichael Große     * $INFO['subscribed'] is either false if no subscription for the current page
101479c05b1SMichael Große     * and user is in effect. Else it contains an array of arrays with the fields
102479c05b1SMichael Große     * “target”, “style”, and optionally “data”.
103479c05b1SMichael Große     *
104479c05b1SMichael Große     * @param string $id Page ID, defaults to global $ID
105479c05b1SMichael Große     * @param string $user User, defaults to $_SERVER['REMOTE_USER']
1069c22b77cSMichael Große     *
107479c05b1SMichael Große     * @return array|false
10851ee2399SGerrit Uitslag     * @throws Exception
10951ee2399SGerrit Uitslag     *
11051ee2399SGerrit Uitslag     * @author Adrian Lang <lang@cosmocode.de>
111479c05b1SMichael Große     */
1129c22b77cSMichael Große    public function userSubscription($id = '', $user = '')
1139c22b77cSMichael Große    {
1149c22b77cSMichael Große        if (!$this->isenabled()) {
1159c22b77cSMichael Große            return false;
1169c22b77cSMichael Große        }
117479c05b1SMichael Große
118479c05b1SMichael Große        global $ID;
119479c05b1SMichael Große        /** @var Input $INPUT */
120479c05b1SMichael Große        global $INPUT;
1219c22b77cSMichael Große        if (!$id) {
1229c22b77cSMichael Große            $id = $ID;
1239c22b77cSMichael Große        }
1249c22b77cSMichael Große        if (!$user) {
1259c22b77cSMichael Große            $user = $INPUT->server->str('REMOTE_USER');
1269c22b77cSMichael Große        }
127479c05b1SMichael Große
1289efb6f4aSPhy        if (empty($user)) {
1299efb6f4aSPhy            // not logged in
1309efb6f4aSPhy            return false;
1319efb6f4aSPhy        }
1329efb6f4aSPhy
133479c05b1SMichael Große        $subs = $this->subscribers($id, $user);
1345983e241SAndreas Gohr        if ($subs === []) {
1359c22b77cSMichael Große            return false;
1369c22b77cSMichael Große        }
137479c05b1SMichael Große
1389c22b77cSMichael Große        $result = [];
139479c05b1SMichael Große        foreach ($subs as $target => $info) {
1409c22b77cSMichael Große            $result[] = [
141479c05b1SMichael Große                'target' => $target,
142479c05b1SMichael Große                'style' => $info[$user][0],
1439c22b77cSMichael Große                'data' => $info[$user][1],
1449c22b77cSMichael Große            ];
145479c05b1SMichael Große        }
146479c05b1SMichael Große
147479c05b1SMichael Große        return $result;
148479c05b1SMichael Große    }
149479c05b1SMichael Große
150479c05b1SMichael Große    /**
151479c05b1SMichael Große     * Recursively search for matching subscriptions
152479c05b1SMichael Große     *
153479c05b1SMichael Große     * This function searches all relevant subscription files for a page or
154479c05b1SMichael Große     * namespace.
155479c05b1SMichael Große     *
156479c05b1SMichael Große     * @param string $page The target object’s (namespace or page) id
157479c05b1SMichael Große     * @param string|array $user
158479c05b1SMichael Große     * @param string|array $style
159479c05b1SMichael Große     * @param string|array $data
1609c22b77cSMichael Große     *
161479c05b1SMichael Große     * @return array
16251ee2399SGerrit Uitslag     * @throws Exception
16351ee2399SGerrit Uitslag     *
16451ee2399SGerrit Uitslag     * @author Adrian Lang <lang@cosmocode.de>
16551ee2399SGerrit Uitslag     *
166479c05b1SMichael Große     */
1679c22b77cSMichael Große    public function subscribers($page, $user = null, $style = null, $data = null)
1689c22b77cSMichael Große    {
1699c22b77cSMichael Große        if (!$this->isenabled()) {
1709c22b77cSMichael Große            return [];
1719c22b77cSMichael Große        }
172479c05b1SMichael Große
173479c05b1SMichael Große        // Construct list of files which may contain relevant subscriptions.
1749c22b77cSMichael Große        $files = [':' => $this->file(':')];
175479c05b1SMichael Große        do {
176479c05b1SMichael Große            $files[$page] = $this->file($page);
177479c05b1SMichael Große            $page = getNS(rtrim($page, ':')) . ':';
178479c05b1SMichael Große        } while ($page !== ':');
179479c05b1SMichael Große
180479c05b1SMichael Große        $regexBuilder = new SubscriberRegexBuilder();
181479c05b1SMichael Große        $re = $regexBuilder->buildRegex($user, $style, $data);
182479c05b1SMichael Große
183479c05b1SMichael Große        // Handle files.
1849c22b77cSMichael Große        $result = [];
185479c05b1SMichael Große        foreach ($files as $target => $file) {
1869c22b77cSMichael Große            if (!file_exists($file)) {
1879c22b77cSMichael Große                continue;
1889c22b77cSMichael Große            }
189479c05b1SMichael Große
190479c05b1SMichael Große            $lines = file($file);
191479c05b1SMichael Große            foreach ($lines as $line) {
192479c05b1SMichael Große                // fix old style subscription files
1939c22b77cSMichael Große                if (strpos($line, ' ') === false) {
1949c22b77cSMichael Große                    $line = trim($line) . " every\n";
1959c22b77cSMichael Große                }
196479c05b1SMichael Große
197479c05b1SMichael Große                // check for matching entries
1989c22b77cSMichael Große                if (!preg_match($re, $line, $m)) {
1999c22b77cSMichael Große                    continue;
2009c22b77cSMichael Große                }
201479c05b1SMichael Große
202062cf88bSAndreas Gohr                // if no last sent is set, use 0
203062cf88bSAndreas Gohr                if (!isset($m[3])) {
204062cf88bSAndreas Gohr                    $m[3] = 0;
205062cf88bSAndreas Gohr                }
206062cf88bSAndreas Gohr
207479c05b1SMichael Große                $u = rawurldecode($m[1]); // decode the user name
2089c22b77cSMichael Große                if (!isset($result[$target])) {
2099c22b77cSMichael Große                    $result[$target] = [];
2109c22b77cSMichael Große                }
2119c22b77cSMichael Große                $result[$target][$u] = [$m[2], $m[3]]; // add to result
212479c05b1SMichael Große            }
213479c05b1SMichael Große        }
214479c05b1SMichael Große        return array_reverse($result);
215479c05b1SMichael Große    }
216479c05b1SMichael Große
217479c05b1SMichael Große    /**
218479c05b1SMichael Große     * Default callback for COMMON_NOTIFY_ADDRESSLIST
219479c05b1SMichael Große     *
220479c05b1SMichael Große     * Aggregates all email addresses of user who have subscribed the given page with 'every' style
221479c05b1SMichael Große     *
222479c05b1SMichael Große     * @param array &$data Containing the entries:
223479c05b1SMichael Große     *                     - $id (the page id),
224479c05b1SMichael Große     *                     - $self (whether the author should be notified,
225479c05b1SMichael Große     *                     - $addresslist (current email address list)
226479c05b1SMichael Große     *                     - $replacements (array of additional string substitutions, @KEY@ to be replaced by value)
22751ee2399SGerrit Uitslag     * @throws Exception
22851ee2399SGerrit Uitslag     *
22951ee2399SGerrit Uitslag     * @author Adrian Lang <lang@cosmocode.de>
23051ee2399SGerrit Uitslag     * @author Steven Danz <steven-danz@kc.rr.com>
23151ee2399SGerrit Uitslag     *
23251ee2399SGerrit Uitslag     * @todo   move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead,
23351ee2399SGerrit Uitslag     *         use an array for the addresses within it
234479c05b1SMichael Große     */
2359c22b77cSMichael Große    public function notifyAddresses(&$data)
2369c22b77cSMichael Große    {
2379c22b77cSMichael Große        if (!$this->isenabled()) {
2389c22b77cSMichael Große            return;
2399c22b77cSMichael Große        }
240479c05b1SMichael Große
24151ee2399SGerrit Uitslag        /** @var AuthPlugin $auth */
242479c05b1SMichael Große        global $auth;
243479c05b1SMichael Große        global $conf;
244479c05b1SMichael Große        /** @var \Input $INPUT */
245479c05b1SMichael Große        global $INPUT;
246479c05b1SMichael Große
247479c05b1SMichael Große        $id = $data['id'];
248479c05b1SMichael Große        $self = $data['self'];
249479c05b1SMichael Große        $addresslist = $data['addresslist'];
250479c05b1SMichael Große
251479c05b1SMichael Große        $subscriptions = $this->subscribers($id, null, 'every');
252479c05b1SMichael Große
2539c22b77cSMichael Große        $result = [];
2545983e241SAndreas Gohr        foreach ($subscriptions as $users) {
255479c05b1SMichael Große            foreach ($users as $user => $info) {
256479c05b1SMichael Große                $userinfo = $auth->getUserData($user);
2579c22b77cSMichael Große                if ($userinfo === false) {
2589c22b77cSMichael Große                    continue;
2599c22b77cSMichael Große                }
2609c22b77cSMichael Große                if (!$userinfo['mail']) {
2619c22b77cSMichael Große                    continue;
2629c22b77cSMichael Große                }
2639c22b77cSMichael Große                if (!$self && $user == $INPUT->server->str('REMOTE_USER')) {
2649c22b77cSMichael Große                    continue;
2659c22b77cSMichael Große                } //skip our own changes
266479c05b1SMichael Große
267479c05b1SMichael Große                $level = auth_aclcheck($id, $user, $userinfo['grps']);
268479c05b1SMichael Große                if ($level >= AUTH_READ) {
269479c05b1SMichael Große                    if (strcasecmp($userinfo['mail'], $conf['notify']) != 0) { //skip user who get notified elsewhere
270479c05b1SMichael Große                        $result[$user] = $userinfo['mail'];
271479c05b1SMichael Große                    }
272479c05b1SMichael Große                }
273479c05b1SMichael Große            }
274479c05b1SMichael Große        }
275479c05b1SMichael Große        $data['addresslist'] = trim($addresslist . ',' . implode(',', $result), ',');
276479c05b1SMichael Große    }
277479c05b1SMichael Große
278479c05b1SMichael Große    /**
279479c05b1SMichael Große     * Return the subscription meta file for the given ID
280479c05b1SMichael Große     *
281479c05b1SMichael Große     * @author Adrian Lang <lang@cosmocode.de>
282479c05b1SMichael Große     *
283479c05b1SMichael Große     * @param string $id The target page or namespace, specified by id; Namespaces
284479c05b1SMichael Große     *                   are identified by appending a colon.
2859c22b77cSMichael Große     *
286479c05b1SMichael Große     * @return string
287479c05b1SMichael Große     */
2889c22b77cSMichael Große    protected function file($id)
2899c22b77cSMichael Große    {
290479c05b1SMichael Große        $meta_fname = '.mlist';
291*6c16a3a9Sfiwswe        if (str_ends_with($id, ':')) {
292479c05b1SMichael Große            $meta_froot = getNS($id);
293479c05b1SMichael Große            $meta_fname = '/' . $meta_fname;
294479c05b1SMichael Große        } else {
295479c05b1SMichael Große            $meta_froot = $id;
296479c05b1SMichael Große        }
297479c05b1SMichael Große        return metaFN((string)$meta_froot, $meta_fname);
298479c05b1SMichael Große    }
299479c05b1SMichael Große}
300