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