xref: /dokuwiki/inc/Subscriptions/SubscriberManager.php (revision 9c22b77ce53fe9d4577eb121afa86dda93c3a0e4)
1479c05b1SMichael Große<?php
2479c05b1SMichael Große
3479c05b1SMichael Großenamespace dokuwiki\Subscriptions;
4479c05b1SMichael Große
5479c05b1SMichael Großeuse dokuwiki\Input\Input;
6479c05b1SMichael Großeuse DokuWiki_Auth_Plugin;
7479c05b1SMichael Großeuse Exception;
8479c05b1SMichael Große
9479c05b1SMichael Großeclass SubscriberManager
10479c05b1SMichael Große{
11479c05b1SMichael Große
12479c05b1SMichael Große    /**
13479c05b1SMichael Große     * Check if subscription system is enabled
14479c05b1SMichael Große     *
15479c05b1SMichael Große     * @return bool
16479c05b1SMichael Große     */
17*9c22b77cSMichael Große    public function isenabled()
18*9c22b77cSMichael Große    {
19479c05b1SMichael Große        return actionOK('subscribe');
20479c05b1SMichael Große    }
21479c05b1SMichael Große
22479c05b1SMichael Große    /**
23479c05b1SMichael Große     * Adds a new subscription for the given page or namespace
24479c05b1SMichael Große     *
25479c05b1SMichael Große     * This will automatically overwrite any existent subscription for the given user on this
26479c05b1SMichael Große     * *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces.
27479c05b1SMichael Große     *
28*9c22b77cSMichael Große     * @throws Exception when user or style is empty
29*9c22b77cSMichael Große     *
30479c05b1SMichael Große     * @param string $id The target page or namespace, specified by id; Namespaces
31479c05b1SMichael Große     *                   are identified by appending a colon.
32479c05b1SMichael Große     * @param string $user
33479c05b1SMichael Große     * @param string $style
34479c05b1SMichael Große     * @param string $data
35*9c22b77cSMichael Große     *
36479c05b1SMichael Große     * @return bool
37479c05b1SMichael Große     */
38*9c22b77cSMichael Große    public function add($id, $user, $style, $data = '')
39*9c22b77cSMichael Große    {
40*9c22b77cSMichael Große        if (!$this->isenabled()) {
41*9c22b77cSMichael Große            return false;
42*9c22b77cSMichael Große        }
43479c05b1SMichael Große
44479c05b1SMichael Große        // delete any existing subscription
45479c05b1SMichael Große        $this->remove($id, $user);
46479c05b1SMichael Große
47479c05b1SMichael Große        $user = auth_nameencode(trim($user));
48479c05b1SMichael Große        $style = trim($style);
49479c05b1SMichael Große        $data = trim($data);
50479c05b1SMichael Große
51*9c22b77cSMichael Große        if (!$user) {
52*9c22b77cSMichael Große            throw new Exception('no subscription user given');
53*9c22b77cSMichael Große        }
54*9c22b77cSMichael Große        if (!$style) {
55*9c22b77cSMichael Große            throw new Exception('no subscription style given');
56*9c22b77cSMichael Große        }
57*9c22b77cSMichael Große        if (!$data) {
58*9c22b77cSMichael Große            $data = time();
59*9c22b77cSMichael Große        } //always add current time for new subscriptions
60479c05b1SMichael Große
61479c05b1SMichael Große        $line = "$user $style $data\n";
62479c05b1SMichael Große        $file = $this->file($id);
63479c05b1SMichael Große        return io_saveFile($file, $line, true);
64479c05b1SMichael Große    }
65479c05b1SMichael Große
66479c05b1SMichael Große
67479c05b1SMichael Große    /**
68479c05b1SMichael Große     * Removes a subscription for the given page or namespace
69479c05b1SMichael Große     *
70479c05b1SMichael Große     * This removes all subscriptions matching the given criteria on the given page or
71479c05b1SMichael Große     * namespace. It will *not* modify any subscriptions that may exist in higher
72479c05b1SMichael Große     * namespaces.
73479c05b1SMichael Große     *
74479c05b1SMichael Große     * @param string       $id The target object’s (namespace or page) id
75479c05b1SMichael Große     * @param string|array $user
76479c05b1SMichael Große     * @param string|array $style
77479c05b1SMichael Große     * @param string|array $data
78*9c22b77cSMichael Große     *
79479c05b1SMichael Große     * @return bool
80479c05b1SMichael Große     */
81*9c22b77cSMichael Große    public function remove($id, $user = null, $style = null, $data = null)
82*9c22b77cSMichael Große    {
83*9c22b77cSMichael Große        if (!$this->isenabled()) {
84*9c22b77cSMichael Große            return false;
85*9c22b77cSMichael Große        }
86479c05b1SMichael Große
87479c05b1SMichael Große        $file = $this->file($id);
88*9c22b77cSMichael Große        if (!file_exists($file)) {
89*9c22b77cSMichael Große            return true;
90*9c22b77cSMichael 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     *
104*9c22b77cSMichael Große     * @author Adrian Lang <lang@cosmocode.de>
105*9c22b77cSMichael Große     *
106479c05b1SMichael Große     * @param string $id   Page ID, defaults to global $ID
107479c05b1SMichael Große     * @param string $user User, defaults to $_SERVER['REMOTE_USER']
108*9c22b77cSMichael Große     *
109479c05b1SMichael Große     * @return array|false
110479c05b1SMichael Große     */
111*9c22b77cSMichael Große    public function userSubscription($id = '', $user = '')
112*9c22b77cSMichael Große    {
113*9c22b77cSMichael Große        if (!$this->isenabled()) {
114*9c22b77cSMichael Große            return false;
115*9c22b77cSMichael Große        }
116479c05b1SMichael Große
117479c05b1SMichael Große        global $ID;
118479c05b1SMichael Große        /** @var Input $INPUT */
119479c05b1SMichael Große        global $INPUT;
120*9c22b77cSMichael Große        if (!$id) {
121*9c22b77cSMichael Große            $id = $ID;
122*9c22b77cSMichael Große        }
123*9c22b77cSMichael Große        if (!$user) {
124*9c22b77cSMichael Große            $user = $INPUT->server->str('REMOTE_USER');
125*9c22b77cSMichael Große        }
126479c05b1SMichael Große
127479c05b1SMichael Große        $subs = $this->subscribers($id, $user);
128*9c22b77cSMichael Große        if (!count($subs)) {
129*9c22b77cSMichael Große            return false;
130*9c22b77cSMichael Große        }
131479c05b1SMichael Große
132*9c22b77cSMichael Große        $result = [];
133479c05b1SMichael Große        foreach ($subs as $target => $info) {
134*9c22b77cSMichael Große            $result[] = [
135479c05b1SMichael Große                'target' => $target,
136479c05b1SMichael Große                'style' => $info[$user][0],
137*9c22b77cSMichael Große                'data' => $info[$user][1],
138*9c22b77cSMichael Große            ];
139479c05b1SMichael Große        }
140479c05b1SMichael Große
141479c05b1SMichael Große        return $result;
142479c05b1SMichael Große    }
143479c05b1SMichael Große
144479c05b1SMichael Große    /**
145479c05b1SMichael Große     * Recursively search for matching subscriptions
146479c05b1SMichael Große     *
147479c05b1SMichael Große     * This function searches all relevant subscription files for a page or
148479c05b1SMichael Große     * namespace.
149479c05b1SMichael Große     *
150479c05b1SMichael Große     * @author Adrian Lang <lang@cosmocode.de>
151479c05b1SMichael Große     *
152479c05b1SMichael Große     * @param string       $page The target object’s (namespace or page) id
153479c05b1SMichael Große     * @param string|array $user
154479c05b1SMichael Große     * @param string|array $style
155479c05b1SMichael Große     * @param string|array $data
156*9c22b77cSMichael Große     *
157479c05b1SMichael Große     * @return array
158479c05b1SMichael Große     */
159*9c22b77cSMichael Große    public function subscribers($page, $user = null, $style = null, $data = null)
160*9c22b77cSMichael Große    {
161*9c22b77cSMichael Große        if (!$this->isenabled()) {
162*9c22b77cSMichael Große            return [];
163*9c22b77cSMichael Große        }
164479c05b1SMichael Große
165479c05b1SMichael Große        // Construct list of files which may contain relevant subscriptions.
166*9c22b77cSMichael Große        $files = [':' => $this->file(':')];
167479c05b1SMichael Große        do {
168479c05b1SMichael Große            $files[$page] = $this->file($page);
169479c05b1SMichael Große            $page = getNS(rtrim($page, ':')) . ':';
170479c05b1SMichael Große        } while ($page !== ':');
171479c05b1SMichael Große
172479c05b1SMichael Große        $regexBuilder = new SubscriberRegexBuilder();
173479c05b1SMichael Große        $re = $regexBuilder->buildRegex($user, $style, $data);
174479c05b1SMichael Große
175479c05b1SMichael Große        // Handle files.
176*9c22b77cSMichael Große        $result = [];
177479c05b1SMichael Große        foreach ($files as $target => $file) {
178*9c22b77cSMichael Große            if (!file_exists($file)) {
179*9c22b77cSMichael Große                continue;
180*9c22b77cSMichael Große            }
181479c05b1SMichael Große
182479c05b1SMichael Große            $lines = file($file);
183479c05b1SMichael Große            foreach ($lines as $line) {
184479c05b1SMichael Große                // fix old style subscription files
185*9c22b77cSMichael Große                if (strpos($line, ' ') === false) {
186*9c22b77cSMichael Große                    $line = trim($line) . " every\n";
187*9c22b77cSMichael Große                }
188479c05b1SMichael Große
189479c05b1SMichael Große                // check for matching entries
190*9c22b77cSMichael Große                if (!preg_match($re, $line, $m)) {
191*9c22b77cSMichael Große                    continue;
192*9c22b77cSMichael Große                }
193479c05b1SMichael Große
194479c05b1SMichael Große                $u = rawurldecode($m[1]); // decode the user name
195*9c22b77cSMichael Große                if (!isset($result[$target])) {
196*9c22b77cSMichael Große                    $result[$target] = [];
197*9c22b77cSMichael Große                }
198*9c22b77cSMichael Große                $result[$target][$u] = [$m[2], $m[3]]; // add to result
199479c05b1SMichael Große            }
200479c05b1SMichael Große        }
201479c05b1SMichael Große        return array_reverse($result);
202479c05b1SMichael Große    }
203479c05b1SMichael Große
204479c05b1SMichael Große    /**
205479c05b1SMichael Große     * Default callback for COMMON_NOTIFY_ADDRESSLIST
206479c05b1SMichael Große     *
207479c05b1SMichael Große     * Aggregates all email addresses of user who have subscribed the given page with 'every' style
208479c05b1SMichael Große     *
209479c05b1SMichael Große     * @author Adrian Lang <lang@cosmocode.de>
210*9c22b77cSMichael Große     * @author Steven Danz <steven-danz@kc.rr.com>
211479c05b1SMichael Große     *
212479c05b1SMichael Große     * @todo   move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead,
213479c05b1SMichael Große     *         use an array for the addresses within it
214479c05b1SMichael Große     *
215479c05b1SMichael Große     * @param array &$data Containing the entries:
216479c05b1SMichael Große     *                     - $id (the page id),
217479c05b1SMichael Große     *                     - $self (whether the author should be notified,
218479c05b1SMichael Große     *                     - $addresslist (current email address list)
219479c05b1SMichael Große     *                     - $replacements (array of additional string substitutions, @KEY@ to be replaced by value)
220479c05b1SMichael Große     */
221*9c22b77cSMichael Große    public function notifyAddresses(&$data)
222*9c22b77cSMichael Große    {
223*9c22b77cSMichael Große        if (!$this->isenabled()) {
224*9c22b77cSMichael Große            return;
225*9c22b77cSMichael Große        }
226479c05b1SMichael Große
227479c05b1SMichael Große        /** @var DokuWiki_Auth_Plugin $auth */
228479c05b1SMichael Große        global $auth;
229479c05b1SMichael Große        global $conf;
230479c05b1SMichael Große        /** @var \Input $INPUT */
231479c05b1SMichael Große        global $INPUT;
232479c05b1SMichael Große
233479c05b1SMichael Große        $id = $data['id'];
234479c05b1SMichael Große        $self = $data['self'];
235479c05b1SMichael Große        $addresslist = $data['addresslist'];
236479c05b1SMichael Große
237479c05b1SMichael Große        $subscriptions = $this->subscribers($id, null, 'every');
238479c05b1SMichael Große
239*9c22b77cSMichael Große        $result = [];
240479c05b1SMichael Große        foreach ($subscriptions as $target => $users) {
241479c05b1SMichael Große            foreach ($users as $user => $info) {
242479c05b1SMichael Große                $userinfo = $auth->getUserData($user);
243*9c22b77cSMichael Große                if ($userinfo === false) {
244*9c22b77cSMichael Große                    continue;
245*9c22b77cSMichael Große                }
246*9c22b77cSMichael Große                if (!$userinfo['mail']) {
247*9c22b77cSMichael Große                    continue;
248*9c22b77cSMichael Große                }
249*9c22b77cSMichael Große                if (!$self && $user == $INPUT->server->str('REMOTE_USER')) {
250*9c22b77cSMichael Große                    continue;
251*9c22b77cSMichael Große                } //skip our own changes
252479c05b1SMichael Große
253479c05b1SMichael Große                $level = auth_aclcheck($id, $user, $userinfo['grps']);
254479c05b1SMichael Große                if ($level >= AUTH_READ) {
255479c05b1SMichael Große                    if (strcasecmp($userinfo['mail'], $conf['notify']) != 0) { //skip user who get notified elsewhere
256479c05b1SMichael Große                        $result[$user] = $userinfo['mail'];
257479c05b1SMichael Große                    }
258479c05b1SMichael Große                }
259479c05b1SMichael Große            }
260479c05b1SMichael Große        }
261479c05b1SMichael Große        $data['addresslist'] = trim($addresslist . ',' . implode(',', $result), ',');
262479c05b1SMichael Große    }
263479c05b1SMichael Große
264479c05b1SMichael Große    /**
265479c05b1SMichael Große     * Return the subscription meta file for the given ID
266479c05b1SMichael Große     *
267479c05b1SMichael Große     * @author Adrian Lang <lang@cosmocode.de>
268479c05b1SMichael Große     *
269479c05b1SMichael Große     * @param string $id The target page or namespace, specified by id; Namespaces
270479c05b1SMichael Große     *                   are identified by appending a colon.
271*9c22b77cSMichael Große     *
272479c05b1SMichael Große     * @return string
273479c05b1SMichael Große     */
274*9c22b77cSMichael Große    protected function file($id)
275*9c22b77cSMichael Große    {
276479c05b1SMichael Große        $meta_fname = '.mlist';
277479c05b1SMichael Große        if ((substr($id, -1, 1) === ':')) {
278479c05b1SMichael Große            $meta_froot = getNS($id);
279479c05b1SMichael Große            $meta_fname = '/' . $meta_fname;
280479c05b1SMichael Große        } else {
281479c05b1SMichael Große            $meta_froot = $id;
282479c05b1SMichael Große        }
283479c05b1SMichael Große        return metaFN((string)$meta_froot, $meta_fname);
284479c05b1SMichael Große    }
285479c05b1SMichael Große}
286