xref: /plugin/structpublish/meta/Assignments.php (revision 503721892780ab5c218318b62d80320b321d5a93)
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        // wrap in transaction
88        $this->sqlite->query('BEGIN TRANSACTION');
89
90        foreach ($pids as $pid) {
91            $this->updatePageAssignments($pid);
92        }
93
94        $ok = $ok && $this->sqlite->query('COMMIT TRANSACTION');
95        if (!$ok) {
96            $this->sqlite->query('ROLLBACK TRANSACTION');
97        }
98
99        return $ok;
100    }
101
102    /**
103     * Remove an existing assignment pattern from the pattern table
104     *
105     * @param string $pattern
106     * @param string $user
107     * @param string $status
108     * @return bool
109     */
110    public function removePattern($pattern, $user, $status)
111    {
112        // remove the pattern
113        $sql = 'DELETE FROM structpublish_assignments_patterns WHERE pattern = ? AND user = ? AND status = ?';
114        $ok = (bool) $this->sqlite->query($sql, [$pattern, $user, $status]);
115
116        // reload patterns
117        $this->loadPatterns();
118
119        // fetch possibly affected pages
120        $sql = 'SELECT pid FROM structpublish_assignments WHERE user = ? AND status = ?';
121        $res = $this->sqlite->query($sql, [$user, $status]);
122        $pagerows = $this->sqlite->res2arr($res);
123        $this->sqlite->res_close($res);
124
125        // reevalute the pages and unassign when needed
126        foreach ($pagerows as $row) {
127            $rules = $this->getPageAssignments($row['pid'], false);
128            // remove assignments matching the rule
129            foreach ($rules as $status => $users) {
130                foreach ($users as $user) {
131                    $this->deassignPage($row['pid'], $user, $status);
132                }
133            }
134        }
135
136        return $ok;
137    }
138
139    /**
140     * Updates all assignments of a given page against the current patterns
141     *
142     * @param string $pid
143     */
144    public function updatePageAssignments($pid, $reload = false)
145    {
146        if ($reload) {
147            $this->loadPatterns();
148        }
149        $rules = $this->getPageAssignments($pid, true);
150
151        foreach ($rules as $status => $users) {
152            foreach ($users as $user) {
153                $this->assignPage($pid, $user, $status);
154            }
155        }
156
157        // FIXME reevalute existing assignments for exclusion
158    }
159
160    /**
161     * Clear all patterns - deassigns all pages
162     *
163     * This is mostly useful for testing and not used in the interface currently
164     *
165     * @param bool $full fully delete all previous assignments
166     * @return bool
167     */
168    public function clear($full = false)
169    {
170        $sql = 'DELETE FROM structpublish_assignments_patterns';
171        $ok = (bool) $this->sqlite->query($sql);
172
173        if ($full) {
174            $sql = 'DELETE FROM structpublish_assignments';
175        } else {
176            $sql = 'UPDATE structpublish_assignments SET assigned = 0';
177        }
178        $ok = $ok && (bool) $this->sqlite->query($sql);
179
180        // reload patterns
181        $this->loadPatterns();
182
183        return $ok;
184    }
185
186    /**
187     * Add page to assignments
188     *
189     * @param string $page
190     * @param string $user
191     * @param string $status
192     * @return bool
193     */
194    public function assignPage($page, $user = null, $status = null)
195    {
196        $sql = 'REPLACE INTO structpublish_assignments (pid, user, status, assigned) VALUES (?, ?, ?, 1)';
197        return (bool) $this->sqlite->query($sql, [$page, $user, $status]);
198    }
199
200    /**
201     * Remove page from assignments
202     *
203     * @param string $page
204     * @param string $user
205     * @return bool
206     */
207    public function deassignPage($page, $user, $status)
208    {
209        $sql = 'REPLACE INTO structpublish_assignments (pid, user, status, assigned) VALUES (?, ?, ?, 0)';
210        return (bool) $this->sqlite->query($sql, [$page, $user, $status]);
211    }
212
213    /**
214     * Get the whole pattern table
215     *
216     * @return array
217     */
218    public function getAllPatterns()
219    {
220        return $this->patterns;
221    }
222
223    /**
224     * Returns a list of user/group string lists per status assigned to the given page
225     *
226     * @param string $page
227     * @param bool $checkpatterns Should the current patterns be re-evaluated?
228     * @return array users assigned [role => [user, ...], ...]
229     */
230    public function getPageAssignments($page, $checkpatterns = true)
231    {
232        $rules = [];
233        $page = cleanID($page);
234
235        if ($checkpatterns) {
236            $helper = plugin_load('helper', 'structpublish_assignments');
237            // evaluate patterns
238            $pns = ':' . getNS($page) . ':';
239            foreach ($this->patterns as $row) {
240                if ($helper->matchPagePattern($row['pattern'], $page, $pns)) {
241                    $rules[$row['status']][] = $row['user'];
242                }
243            }
244        } else {
245            // just select
246            $sql = 'SELECT user, status FROM structpublish_assignments WHERE pid = ? AND assigned = 1';
247            $res = $this->sqlite->query($sql, [$page]);
248            $list = $this->sqlite->res2arr($res);
249            $this->sqlite->res_close($res);
250            foreach ($list as $row) {
251                $rules[$row['status']][] = $row['user'];
252            }
253        }
254
255        return $rules;
256    }
257
258    /**
259     * Get the pages known to struct and their assignment state
260     *
261     * @param bool $assignedonly limit results to currently assigned only
262     * @return array
263     */
264    public function getPages($assignedOnly = false)
265    {
266        $sql = 'SELECT pid, user, status, assigned FROM structpublish_assignments WHERE 1=1';
267
268        $opts = array();
269
270        if ($assignedOnly) {
271            $sql .= ' AND assigned = 1';
272        }
273
274        $sql .= ' ORDER BY pid, user, status';
275
276        $res = $this->sqlite->query($sql, $opts);
277        $list = $this->sqlite->res2arr($res);
278        $this->sqlite->res_close($res);
279
280        $result = array();
281        foreach ($list as $row) {
282            $pid = $row['pid'];
283            $user = $row['user'];
284            $status = $row['status'];
285            if (!isset($result[$pid])) {
286                $result[$pid] = array();
287            }
288            $result[$pid][$user][$status] = (bool) $row['assigned'];
289        }
290
291        return $result;
292    }
293
294    /**
295     * @return \helper_plugin_sqlite|null
296     */
297    public function getSqlite()
298    {
299        return $this->sqlite;
300    }
301}
302