1<?php
2
3namespace dokuwiki\plugin\virtualgroup;
4
5use dokuwiki\Logger;
6
7class VirtualGroups
8{
9    public const CONFIG_FILE = DOKU_CONF . 'virtualgroup.conf';
10
11    /**
12     * Get the configuration by user
13     *
14     * @return array [user => [group1, group2, ...], ...]
15     */
16    public function getUserStructure()
17    {
18        $config = $this->loadConfig();
19        ksort($config);
20        return $config;
21    }
22
23    /**
24     * Get the groups for a user
25     *
26     * @param string $user
27     * @return string[]
28     */
29    public function getUserGroups($user)
30    {
31        $config = $this->loadConfig();
32        return $config[$user] ?? [];
33    }
34
35    /**
36     * Get all users in a group
37     *
38     * @param string $group
39     * @return string[]
40     */
41    public function getGroupUsers($group)
42    {
43        $config = $this->loadConfig();
44        $users = [];
45        foreach ($config as $user => $groups) {
46            if (in_array($group, $groups)) {
47                $users[] = $user;
48            }
49        }
50        return $users;
51    }
52
53
54    /**
55     * Get the configuration by group
56     *
57     * @return array [group => [user1, user2, ...], ...]
58     */
59    public function getGroupStructure()
60    {
61        $config = $this->loadConfig();
62        $groups = [];
63        foreach ($config as $user => $usergroups) {
64            foreach ($usergroups as $group) {
65                if (!isset($groups[$group])) {
66                    $groups[$group] = [];
67                }
68                $groups[$group][] = $user;
69            }
70        }
71        ksort($groups);
72        return $groups;
73    }
74
75    // region individual user/group management
76
77    /**
78     * Remove a user from all groups
79     *
80     * @param string $user
81     * @return void
82     */
83    public function removeUser($user)
84    {
85        $config = $this->loadConfig();
86        if (isset($config[$user])) unset($config[$user]);
87        $this->saveConfig($config);
88    }
89
90    /**
91     * Add a user to one or more groups
92     *
93     * @param string $user
94     * @param string[] $groups
95     * @return void
96     */
97    public function addGroupsToUser($user, $groups)
98    {
99        $config = $this->loadConfig();
100        if (!isset($config[$user])) {
101            $config[$user] = [];
102        }
103        $config[$user] = array_filter(array_unique(array_merge($config[$user], $groups)));
104        $this->saveConfig($config);
105    }
106
107    /**
108     * Set the groups for a user
109     *
110     * @param string $user
111     * @param string[] $groups
112     * @return void
113     */
114    public function setUserGroups($user, $groups)
115    {
116        $config = $this->loadConfig();
117        $config[$user] = array_filter($groups);
118        if ($config[$user] === []) {
119            unset($config[$user]);
120        }
121        $this->saveConfig($config);
122    }
123
124    /**
125     * Remove a group from all users
126     *
127     * @param string $group
128     * @return void
129     */
130    public function removeGroup($group)
131    {
132        $config = $this->loadConfig();
133        foreach ($config as $user => $groups) {
134            if (($key = array_search($group, $groups)) !== false) {
135                unset($config[$user][$key]);
136            }
137        }
138        $this->saveConfig($config);
139    }
140
141    /**
142     * Add one or more users to a group
143     *
144     * @param string $group
145     * @param string[] $users
146     * @return void
147     */
148    public function addUsersToGroup($group, $users)
149    {
150        $config = $this->loadConfig();
151        foreach ($users as $user) {
152            if (!isset($config[$user])) {
153                $config[$user] = [];
154            }
155            $config[$user][] = $group;
156            $config[$user] = array_filter(array_unique($config[$user]));
157        }
158        $this->saveConfig($config);
159    }
160
161    public function setGroupUsers($group, $users)
162    {
163        $config = $this->loadConfig();
164        foreach ($users as $user) {
165            if (!isset($config[$user])) {
166                $config[$user] = [];
167            }
168            $config[$user][] = $group;
169            $config[$user] = array_filter(array_unique($config[$user]));
170            if ($config[$user] === []) {
171                unset($config[$user]);
172            }
173        }
174        $this->saveConfig($config);
175    }
176
177    // endregion
178
179    // region file management
180
181    /**
182     * Load the configuration
183     *
184     * @return array [user => [group1, group2, ...], ...]
185     */
186    protected function loadConfig()
187    {
188        if (!file_exists(self::CONFIG_FILE)) return $this->loadLegacyConfig();
189
190        $config = [];
191        $raw = linesToHash(file(self::CONFIG_FILE));
192        foreach ($raw as $key => $value) {
193            $user = rawurldecode($key);
194            $groups = array_map(static fn($group) => rawurldecode(trim($group)), explode(',', $value));
195            $config[$user] = $groups;
196        }
197
198        return $config;
199    }
200
201    /**
202     * Save the configuration
203     *
204     * @param array $config [user => [group1, group2, ...], ...]
205     * @return boolean
206     */
207    protected function saveConfig($config)
208    {
209        global $INPUT;
210
211        $lines = [
212            '# This file is managed by the virtualgroup plugin',
213            '# Last saved by ' . $INPUT->server->str('REMOTE_USER') . ' on ' . date('Y-m-d H:i:s'),
214            ''
215        ];
216        foreach ($config as $user => $groups) {
217            $lines[] = auth_nameencode($user) . "\t" .
218                implode(',', array_map(static fn($group) => auth_nameencode($group), $groups));
219        }
220
221        $ok = file_put_contents(self::CONFIG_FILE, implode("\n", $lines));
222        if ($ok === false) {
223            msg('Failed to save virtual group configuration', -1);
224        }
225        return (bool)$ok;
226    }
227
228    /**
229     * Load the legacy configuration
230     *
231     * @return array [user => [group1, group2, ...], ...]
232     * @deprecated
233     */
234    protected function loadLegacyConfig()
235    {
236        global $conf;
237        // determine the path to the data
238        $userFile = $conf['savedir'] . '/virtualgrp.php';
239
240        // if there is no file we hava no data ;-)
241        if (!is_file($userFile)) return [];
242
243        // read the file
244        $content = trim(file_get_contents($userFile));
245
246        // if its empty we have no data also
247        if (empty($content)) return [];
248
249        $users = unserialize($content);
250        // check for invalid data
251        if ($users === false) {
252            Logger::error('Failed to parse virtualgrp.php configuration file. File will be deleted.');
253            @unlink($userFile);
254            return [];
255        }
256
257        // save in new format
258        $ok = $this->saveConfig($users);
259        if ($ok) {
260            @unlink($userFile);
261        }
262
263        return $users;
264    }
265
266    // endregion
267}
268