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