xref: /plugin/structpublish/meta/Assignments.php (revision 8b0ba635402b9a3b9717898c9e08f6ff65eb73f7)
1<?php
2
3namespace dokuwiki\plugin\structpublish\meta;
4
5/**
6 * Class Assignments
7 *
8 * Manages the assignment of users to pages and namespaces
9 * This is a singleton. Assignment data is only loaded once per request.
10 *
11 * @see \dokuwiki\plugin\struct\meta\Assignments
12 */
13class Assignments
14{
15    /** @var \helper_plugin_sqlite|null */
16    protected $sqlite;
17
18    /** @var  array All the assignments patterns */
19    protected $patterns;
20
21    /** @var Assignments */
22    protected static $instance = null;
23
24    /**
25     * Get the singleton instance of the Assignments
26     *
27     * @param bool $forcereload create a new instace to reload the assignment data
28     * @return Assignments
29     */
30    public static function getInstance($forcereload = false)
31    {
32        if (is_null(self::$instance) or $forcereload) {
33            $class = get_called_class();
34            self::$instance = new $class();
35        }
36        return self::$instance;
37    }
38
39    /**
40     * Assignments constructor.
41     *
42     * Not public. Use Assignments::getInstance() instead
43     */
44    protected function __construct()
45    {
46        /** @var \helper_plugin_structpublish_db $helper */
47        $helper = plugin_load('helper', 'struct_db');
48        $this->sqlite = $helper->getDB();
49
50        $this->loadPatterns();
51    }
52
53    /**
54     * Load existing assignment patterns
55     */
56    protected function loadPatterns()
57    {
58        $sql = 'SELECT * FROM structpublish_assignments_patterns ORDER BY pattern';
59        $res = $this->sqlite->query($sql);
60        $this->patterns = $this->sqlite->res2arr($res);
61        $this->sqlite->res_close($res);
62    }
63
64    /**
65     * Add a new assignment pattern to the pattern table
66     *
67     * @param string $pattern
68     * @param string $user
69     * @param string $status
70     * @return bool
71     */
72    public function addPattern($pattern, $user, $status)
73    {
74        // add the pattern
75        $sql = 'REPLACE INTO structpublish_assignments_patterns (pattern, user, status) VALUES (?,?,?)';
76        $ok = (bool) $this->sqlite->query($sql, [$pattern, $user, $status]);
77
78        // reload patterns
79        $this->loadPatterns();
80
81        // update assignments
82        // fetch known pages
83        /** @var \helper_plugin_structpublish_db $dbHelper */
84        $dbHelper = plugin_load('helper', 'structpublish_db');
85        $pids = $dbHelper->getPages();
86
87        foreach ($pids as $pid) {
88            $this->updatePageAssignments($pid);
89        }
90
91        return $ok;
92    }
93
94    /**
95     * Remove an existing assignment pattern from the pattern table
96     *
97     * @param string $pattern
98     * @param string $user
99     * @param string $status
100     * @return bool
101     */
102    public function removePattern($pattern, $user, $status)
103    {
104        // remove the pattern
105        $sql = 'DELETE FROM structpublish_assignments_patterns WHERE pattern = ? AND user = ? AND status = ?';
106        $ok = (bool) $this->sqlite->query($sql, [$pattern, $user, $status]);
107
108        // reload patterns
109        $this->loadPatterns();
110
111        // fetch possibly affected pages
112        $sql = 'SELECT pid FROM structpublish_assignments WHERE user = ? AND status = ?';
113        $res = $this->sqlite->query($sql, [$user, $status]);
114        $pagerows = $this->sqlite->res2arr($res);
115        $this->sqlite->res_close($res);
116
117        // reevalute the pages and unassign when needed
118        foreach ($pagerows as $row) {
119            $rules = $this->getPageAssignments($row['pid'], false);
120            // remove assignments matching the rule
121            foreach ($rules as $status => $users) {
122                foreach ($users as $user) {
123                    $this->deassignPage($row['pid'], $user, $status);
124                }
125            }
126        }
127
128        return $ok;
129    }
130
131    /**
132     * Updates all assignments of a given page against the current patterns
133     *
134     * @param string $pid
135     */
136    public function updatePageAssignments($pid, $reload = false)
137    {
138        if ($reload) {
139            $this->loadPatterns();
140        }
141        $rules = $this->getPageAssignments($pid, true);
142
143        foreach ($rules as $status => $users) {
144            foreach ($users as $user) {
145                $this->assignPage($pid, $user, $status);
146            }
147        }
148
149        // FIXME reevalute existing assignments
150    }
151
152    /**
153     * Clear all patterns - deassigns all pages
154     *
155     * This is mostly useful for testing and not used in the interface currently
156     *
157     * @param bool $full fully delete all previous assignments
158     * @return bool
159     */
160    public function clear($full = false)
161    {
162        $sql = 'DELETE FROM structpublish_assignments_patterns';
163        $ok = (bool) $this->sqlite->query($sql);
164
165        if ($full) {
166            $sql = 'DELETE FROM structpublish_assignments';
167        } else {
168            $sql = 'UPDATE structpublish_assignments SET assigned = 0';
169        }
170        $ok = $ok && (bool) $this->sqlite->query($sql);
171
172        // reload patterns
173        $this->loadPatterns();
174
175        return $ok;
176    }
177
178    /**
179     * Add page to assignments
180     *
181     * @param string $page
182     * @param string $user
183     * @param string $status
184     * @return bool
185     */
186    public function assignPage($page, $user = null, $status = null)
187    {
188        $sql = 'REPLACE INTO structpublish_assignments (pid, user, status, assigned) VALUES (?, ?, ?, 1)';
189        return (bool) $this->sqlite->query($sql, [$page, $user, $status]);
190    }
191
192    /**
193     * Remove page from assignments
194     *
195     * @param string $page
196     * @param string $user
197     * @return bool
198     */
199    public function deassignPage($page, $user, $status)
200    {
201        $sql = 'REPLACE INTO structpublish_assignments (pid, user, status, assigned) VALUES (?, ?, ?, 0)';
202        return (bool) $this->sqlite->query($sql, [$page, $user, $status]);
203    }
204
205    /**
206     * Get the whole pattern table
207     *
208     * @return array
209     */
210    public function getAllPatterns()
211    {
212        return $this->patterns;
213    }
214
215    /**
216     * Returns a list of user/group string lists per status assigned to the given page
217     *
218     * @param string $page
219     * @param bool $checkpatterns Should the current patterns be re-evaluated?
220     * @return array users assigned [role => [user, ...], ...]
221     */
222    public function getPageAssignments($page, $checkpatterns = true)
223    {
224        $rules = [];
225        $page = cleanID($page);
226
227        if ($checkpatterns) {
228            $helper = plugin_load('helper', 'structpublish_assignments');
229            // evaluate patterns
230            $pns = ':' . getNS($page) . ':';
231            foreach ($this->patterns as $row) {
232                if ($helper->matchPagePattern($row['pattern'], $page, $pns)) {
233                    $rules[$row['status']][] = $row['user'];
234                }
235            }
236        } else {
237            // just select
238            $sql = 'SELECT user, status FROM structpublish_assignments WHERE pid = ? AND assigned = 1';
239            $res = $this->sqlite->query($sql, [$page]);
240            $list = $this->sqlite->res2arr($res);
241            $this->sqlite->res_close($res);
242            foreach ($list as $row) {
243                $rules[$row['status']][] = $row['user'];
244            }
245        }
246
247        return $rules;
248    }
249
250    /**
251     * Get the pages known to struct and their assignment state
252     *
253     * @param bool $assignedonly limit results to currently assigned only
254     * @return array
255     */
256    public function getPages($assignedOnly = false)
257    {
258        $sql = 'SELECT pid, user, status, assigned FROM structpublish_assignments WHERE 1=1';
259
260        $opts = array();
261
262        if ($assignedOnly) {
263            $sql .= ' AND assigned = 1';
264        }
265
266        $sql .= ' ORDER BY pid, user, status';
267
268        $res = $this->sqlite->query($sql, $opts);
269        $list = $this->sqlite->res2arr($res);
270        $this->sqlite->res_close($res);
271
272        $result = array();
273        foreach ($list as $row) {
274            $pid = $row['pid'];
275            $user = $row['user'];
276            $status = $row['status'];
277            if (!isset($result[$pid])) {
278                $result[$pid] = array();
279            }
280            $result[$pid][$user][$status] = (bool) $row['assigned'];
281        }
282
283        return $result;
284    }
285}
286