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