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