1<?php
2
3use dokuwiki\Subscriptions\SubscriberManager;
4
5/**
6 * DokuWiki Plugin submgr (Helper Component)
7 *
8 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
9 * @author  Andreas Gohr <dokuwiki@cosmocode.de>
10 */
11class helper_plugin_submgr extends DokuWiki_Plugin
12{
13
14    /**
15     * @var array stores the current rules
16     */
17    protected $rules = array();
18
19    /**
20     * helper_plugin_submgr constructor.
21     */
22    public function __construct()
23    {
24        $this->loadRules();
25    }
26
27    /**
28     * Returns the currently defined rules
29     *
30     * item => (type, members)
31     *
32     * @return array
33     */
34    public function getRules()
35    {
36        return $this->rules;
37    }
38
39    /**
40     * Add a new rule
41     *
42     * @param string $item page or namespace
43     * @param string $type every|digest|list
44     * @param string $members user/group list
45     * @throws Exception
46     */
47    public function addRule($item, $type, $members)
48    {
49        $isns = $this->cleanItem($item);
50        $members = trim($members);
51        if (!in_array($type, array('every', 'digest', 'list'))) {
52            throw new Exception('bad subscription type');
53        }
54        if (!$isns && $type == 'list') {
55            throw new Exception('list subscription is not supported for single pages');
56        }
57        if (!$item) {
58            throw new Exception('no page or namespace given');
59        }
60        if (!$members) {
61            throw new Exception('no users or groups given');
62        }
63
64        // remove existing (will be ignored if doesn't exist)
65        $this->removeRule($item);
66
67        $this->rules[$item] = array($type, $members);
68        $this->writeRules();
69
70        $this->applyRule($item, $type, $members);
71    }
72
73    /**
74     * Removes an existing rule
75     *
76     * @param string $item page or namespace
77     * @throws Exception
78     */
79    public function removeRule($item)
80    {
81        if (!isset($this->rules[$item])) return;
82
83        list($type, $members) = $this->rules[$item];
84        unset($this->rules[$item]);
85        $this->writeRules();
86
87        $this->ceaseRule($item, $type, $members);
88    }
89
90    /**
91     * Applies all rules that match the given user/group combo
92     *
93     * @param string $user
94     * @param string[] $groups
95     * @throws Exception
96     */
97    public function runRules($user, $groups)
98    {
99        $sub = new SubscriberManager();
100
101        foreach ($this->rules as $item => $data) {
102            if (auth_isMember($data[1], $user, $groups)) {
103                $sub->add($item, $user, $data[0]);
104            }
105        }
106    }
107
108    /**
109     * loads the current rules
110     */
111    protected function loadRules()
112    {
113        $lines = confToHash(DOKU_CONF . 'subscription.rules');
114
115        foreach ($lines as $key => $val) {
116            $val = preg_split('/\s+/', $val, 2);
117            $val = array_map('trim', $val);
118            $val = array_filter($val);
119            $val = array_unique($val);
120            $lines[$key] = $val;
121        }
122
123        $this->rules = $lines;
124        ksort($this->rules);
125    }
126
127    /**
128     * saves the current rules
129     *
130     * @return bool true on success
131     */
132    protected function writeRules()
133    {
134        $out = "# auto subscription rules\n";
135        foreach ($this->rules as $item => $data) {
136            $out .= "$item\t$data[0]\t$data[1]\n";
137        }
138
139        return io_saveFile(DOKU_CONF . 'subscription.rules', $out);
140    }
141
142    /**
143     * Applies a rule by subscribing all affected users
144     *
145     * @param string $item page or namespace
146     * @param string $type every|digest|list
147     * @param string $members user/group list
148     * @throws Exception
149     */
150    protected function applyRule($item, $type, $members)
151    {
152        $users = $this->getAffectedUsers($members);
153        $sub = new SubscriberManager();
154        foreach ($users as $user) {
155            $sub->add($item, $user, $type);
156        }
157        msg(sprintf($this->getLang('appliedrule'), count($users)));
158    }
159
160    /**
161     * Removes a rule by unsubscribing all affected users
162     *
163     * @param string $item page or namespace
164     * @param string $type every|digest|list
165     * @param string $members user/group list
166     * @throws Exception
167     */
168    protected function ceaseRule($item, $type, $members)
169    {
170        $users = $this->getAffectedUsers($members);
171
172        $sub = new SubscriberManager();
173        foreach ($users as $user) {
174            $sub->remove($item, $user, $type);
175        }
176        msg(sprintf($this->getLang('removedrule'), count($users)));
177    }
178
179    /**
180     * Gets all users affected by a member string
181     *
182     * @param string $members comma separated list of users and groups
183     * @return array
184     */
185    protected function getAffectedUsers($members)
186    {
187        /** @var DokuWiki_Auth_Plugin $auth */
188        global $auth;
189
190        $members = explode(',', $members);
191        $members = array_map('trim', $members);
192        $members = array_filter($members);
193        $members = array_unique($members);
194
195        // get all users directly specified or from specified groups
196        $users = array();
197        foreach ($members as $one) {
198            if (substr($one, 0, 1) == '@') {
199                // passing 0 for all users is broken in some backends (#1630), limiting to 5000 should be good enough
200                $found = $auth->retrieveUsers(0, 5000, array('grps' => substr($one, 1)));
201                if ($found) {
202                    $users = array_merge($users, array_keys($found));
203                }
204            } else {
205                $users[] = $one;
206            }
207        }
208
209        $users = array_unique($users);
210
211        return $users;
212    }
213
214    /**
215     * Clean the item and return if it is a namespace
216     *
217     * @param string &$item reference to the item to clear
218     * @return bool is the given item a namespace? (trailing colon)
219     */
220    protected function cleanItem(&$item)
221    {
222        $isns = false;
223        $item = trim($item);
224        if (substr($item, -1) == ':') {
225            $isns = true;
226        }
227        $item = cleanID($item);
228        if ($isns) $item = "$item:";
229
230        return $isns;
231    }
232
233}
234