xref: /plugin/struct/meta/Assignments.php (revision b25bb9fea2afa507f76055e97401dbceb446871e)
1fb31ca9fSAndreas Gohr<?php
2fb31ca9fSAndreas Gohr
3ba766201SAndreas Gohrnamespace dokuwiki\plugin\struct\meta;
4fb31ca9fSAndreas Gohr
51a8d1235SAndreas Gohr/**
61a8d1235SAndreas Gohr * Class Assignments
71a8d1235SAndreas Gohr *
81a8d1235SAndreas Gohr * Manages the assignment of schemas (table names) to pages and namespaces
91a8d1235SAndreas Gohr *
10ba766201SAndreas Gohr * @package dokuwiki\plugin\struct\meta
111a8d1235SAndreas Gohr */
12fb31ca9fSAndreas Gohrclass Assignments {
13fb31ca9fSAndreas Gohr
14fb31ca9fSAndreas Gohr    /** @var \helper_plugin_sqlite|null */
15fb31ca9fSAndreas Gohr    protected $sqlite;
16fb31ca9fSAndreas Gohr
1733d7be6aSAndreas Gohr    /** @var  array All the assignments patterns */
1849d38573SAndreas Gohr    protected $patterns;
19fb31ca9fSAndreas Gohr
20fb31ca9fSAndreas Gohr    /**
21fb31ca9fSAndreas Gohr     * Assignments constructor.
22fb31ca9fSAndreas Gohr     */
23fb31ca9fSAndreas Gohr    public function __construct() {
24fb31ca9fSAndreas Gohr        /** @var \helper_plugin_struct_db $helper */
25fb31ca9fSAndreas Gohr        $helper = plugin_load('helper', 'struct_db');
26fb31ca9fSAndreas Gohr        $this->sqlite = $helper->getDB();
27fb31ca9fSAndreas Gohr
2833d7be6aSAndreas Gohr        if($this->sqlite) $this->loadPatterns();
29fb31ca9fSAndreas Gohr    }
30fb31ca9fSAndreas Gohr
31fb31ca9fSAndreas Gohr    /**
3249d38573SAndreas Gohr     * Load existing assignment patterns
33fb31ca9fSAndreas Gohr     */
3433d7be6aSAndreas Gohr    protected function loadPatterns() {
3549d38573SAndreas Gohr        $sql = 'SELECT * FROM schema_assignments_patterns ORDER BY pattern';
36fb31ca9fSAndreas Gohr        $res = $this->sqlite->query($sql);
3749d38573SAndreas Gohr        $this->patterns = $this->sqlite->res2arr($res);
38fb31ca9fSAndreas Gohr        $this->sqlite->res_close($res);
39fb31ca9fSAndreas Gohr    }
40fb31ca9fSAndreas Gohr
41fb31ca9fSAndreas Gohr    /**
4249d38573SAndreas Gohr     * Add a new assignment pattern to the pattern table
431a8d1235SAndreas Gohr     *
4449d38573SAndreas Gohr     * @param string $pattern
451a8d1235SAndreas Gohr     * @param string $table
461a8d1235SAndreas Gohr     * @return bool
471a8d1235SAndreas Gohr     */
4833d7be6aSAndreas Gohr    public function addPattern($pattern, $table) {
49ed60c3b3SAndreas Gohr        // add the pattern
5049d38573SAndreas Gohr        $sql = 'REPLACE INTO schema_assignments_patterns (pattern, tbl) VALUES (?,?)';
51ed60c3b3SAndreas Gohr        $ok = (bool) $this->sqlite->query($sql, array($pattern, $table));
52ed60c3b3SAndreas Gohr
53ed60c3b3SAndreas Gohr        // reload patterns
54ed60c3b3SAndreas Gohr        $this->loadPatterns();
55*b25bb9feSMichael Grosse        $this->propagatePageAssignments($table);
56ed60c3b3SAndreas Gohr
57ed60c3b3SAndreas Gohr
58ed60c3b3SAndreas Gohr        return $ok;
591a8d1235SAndreas Gohr    }
601a8d1235SAndreas Gohr
611a8d1235SAndreas Gohr    /**
6249d38573SAndreas Gohr     * Remove an existing assignment pattern from the pattern table
631a8d1235SAndreas Gohr     *
6449d38573SAndreas Gohr     * @param string $pattern
651a8d1235SAndreas Gohr     * @param string $table
661a8d1235SAndreas Gohr     * @return bool
671a8d1235SAndreas Gohr     */
6833d7be6aSAndreas Gohr    public function removePattern($pattern, $table) {
69ed60c3b3SAndreas Gohr        // remove the pattern
7049d38573SAndreas Gohr        $sql = 'DELETE FROM schema_assignments_patterns WHERE pattern = ? AND tbl = ?';
71ed60c3b3SAndreas Gohr        $ok = (bool) $this->sqlite->query($sql, array($pattern, $table));
72ed60c3b3SAndreas Gohr
73ed60c3b3SAndreas Gohr        // reload patterns
74ed60c3b3SAndreas Gohr        $this->loadPatterns();
75ed60c3b3SAndreas Gohr
76ed60c3b3SAndreas Gohr        // fetch possibly affected pages
77ed60c3b3SAndreas Gohr        $sql = 'SELECT pid FROM schema_assignments WHERE tbl = ?';
78ed60c3b3SAndreas Gohr        $res = $this->sqlite->query($sql, $table);
790e9e058fSAndreas Gohr        $pagerows = $this->sqlite->res2arr($res);
80ed60c3b3SAndreas Gohr        $this->sqlite->res_close($res);
81ed60c3b3SAndreas Gohr
82ed60c3b3SAndreas Gohr        // reevalute the pages and unassign when needed
830e9e058fSAndreas Gohr        foreach($pagerows as $row) {
84be94e9d9SAndreas Gohr            $tables = $this->getPageAssignments($row['pid'], true);
85ed60c3b3SAndreas Gohr            if(!in_array($table, $tables)) {
86be94e9d9SAndreas Gohr                $this->deassignPageSchema($row['pid'], $table);
87ed60c3b3SAndreas Gohr            }
88ed60c3b3SAndreas Gohr        }
89ed60c3b3SAndreas Gohr
90ed60c3b3SAndreas Gohr        return $ok;
91ed60c3b3SAndreas Gohr    }
92ed60c3b3SAndreas Gohr
93ed60c3b3SAndreas Gohr    /**
940173e75dSAndreas Gohr     * Rechecks all assignments of a given page against the current patterns
950173e75dSAndreas Gohr     *
960173e75dSAndreas Gohr     * @param string $pid
970173e75dSAndreas Gohr     */
980173e75dSAndreas Gohr    public function reevaluatePageAssignments($pid) {
990173e75dSAndreas Gohr        // reload patterns
1000173e75dSAndreas Gohr        $this->loadPatterns();
1010173e75dSAndreas Gohr        $tables = $this->getPageAssignments($pid, true);
1020173e75dSAndreas Gohr
1030173e75dSAndreas Gohr        // fetch possibly affected tables
1040173e75dSAndreas Gohr        $sql = 'SELECT tbl FROM schema_assignments WHERE pid = ?';
1050173e75dSAndreas Gohr        $res = $this->sqlite->query($sql, $pid);
1060173e75dSAndreas Gohr        $tablerows = $this->sqlite->res2arr($res);
1070173e75dSAndreas Gohr        $this->sqlite->res_close($res);
1080173e75dSAndreas Gohr
1090173e75dSAndreas Gohr        // reevalute the tables and apply assignments
1100173e75dSAndreas Gohr        foreach($tablerows as $row) {
1110173e75dSAndreas Gohr            if(in_array($row['tbl'], $tables)) {
1120173e75dSAndreas Gohr                $this->assignPageSchema($pid, $row['tbl']);
1130173e75dSAndreas Gohr            } else {
1140173e75dSAndreas Gohr                $this->deassignPageSchema($pid, $row['tbl']);
1150173e75dSAndreas Gohr            }
1160173e75dSAndreas Gohr        }
1170173e75dSAndreas Gohr    }
1180173e75dSAndreas Gohr
1190173e75dSAndreas Gohr    /**
1200e9e058fSAndreas Gohr     * Clear all patterns - deassigns all pages
1210e9e058fSAndreas Gohr     *
1220e9e058fSAndreas Gohr     * This is mostly useful for testing and not used in the interface currently
1230e9e058fSAndreas Gohr     *
124153400c7SAndreas Gohr     * @param bool $full fully delete all previous assignments
1250e9e058fSAndreas Gohr     * @return bool
1260e9e058fSAndreas Gohr     */
127153400c7SAndreas Gohr    public function clear($full=false) {
1280e9e058fSAndreas Gohr        $sql = 'DELETE FROM schema_assignments_patterns';
1290e9e058fSAndreas Gohr        $ok = (bool) $this->sqlite->query($sql);
1300e9e058fSAndreas Gohr
131153400c7SAndreas Gohr        if($full) {
132153400c7SAndreas Gohr            $sql = 'DELETE FROM schema_assignments';
133153400c7SAndreas Gohr        } else {
1340e9e058fSAndreas Gohr            $sql = 'UPDATE schema_assignments SET assigned = 0';
135153400c7SAndreas Gohr        }
1360e9e058fSAndreas Gohr        $ok = $ok && (bool) $this->sqlite->query($sql);
1370e9e058fSAndreas Gohr
1380e9e058fSAndreas Gohr        // reload patterns
1390e9e058fSAndreas Gohr        $this->loadPatterns();
1400e9e058fSAndreas Gohr
1410e9e058fSAndreas Gohr        return $ok;
1420e9e058fSAndreas Gohr    }
1430e9e058fSAndreas Gohr
1440e9e058fSAndreas Gohr    /**
145ed60c3b3SAndreas Gohr     * Add page to assignments
146ed60c3b3SAndreas Gohr     *
147ed60c3b3SAndreas Gohr     * @param string $page
148ed60c3b3SAndreas Gohr     * @param string $table
149ed60c3b3SAndreas Gohr     * @return bool
150ed60c3b3SAndreas Gohr     */
151ed713594SAndreas Gohr    public function assignPageSchema($page, $table) {
152ed60c3b3SAndreas Gohr        $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 1)';
153ed60c3b3SAndreas Gohr        return (bool) $this->sqlite->query($sql, array($page, $table));
154ed60c3b3SAndreas Gohr    }
155ed60c3b3SAndreas Gohr
156ed60c3b3SAndreas Gohr    /**
157ed60c3b3SAndreas Gohr     * Remove page from assignments
158ed60c3b3SAndreas Gohr     *
159ed60c3b3SAndreas Gohr     * @param string $page
160ed60c3b3SAndreas Gohr     * @param string $table
161ed60c3b3SAndreas Gohr     * @return bool
162ed60c3b3SAndreas Gohr     */
163ed713594SAndreas Gohr    public function deassignPageSchema($page, $table) {
164ed60c3b3SAndreas Gohr        $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 0)';
165ed60c3b3SAndreas Gohr        return (bool) $this->sqlite->query($sql, array($page, $table));
1661a8d1235SAndreas Gohr    }
1671a8d1235SAndreas Gohr
1681a8d1235SAndreas Gohr    /**
16949d38573SAndreas Gohr     * Get the whole pattern table
1701a8d1235SAndreas Gohr     *
1711a8d1235SAndreas Gohr     * @return array
1721a8d1235SAndreas Gohr     */
17333d7be6aSAndreas Gohr    public function getAllPatterns() {
17449d38573SAndreas Gohr        return $this->patterns;
1751a8d1235SAndreas Gohr    }
1761a8d1235SAndreas Gohr
1771a8d1235SAndreas Gohr    /**
178fb31ca9fSAndreas Gohr     * Returns a list of table names assigned to the given page
179fb31ca9fSAndreas Gohr     *
180fb31ca9fSAndreas Gohr     * @param string $page
1819ff81b7fSAndreas Gohr     * @param bool $checkpatterns Should the current patterns be re-evaluated?
1829ff81b7fSAndreas Gohr     * @return \string[] tables assigned
183fb31ca9fSAndreas Gohr     */
1849ff81b7fSAndreas Gohr    public function getPageAssignments($page, $checkpatterns=true) {
185fb31ca9fSAndreas Gohr        $tables = array();
186fb31ca9fSAndreas Gohr        $page = cleanID($page);
187fb31ca9fSAndreas Gohr
1889ff81b7fSAndreas Gohr        if($checkpatterns) {
1899ff81b7fSAndreas Gohr            // evaluate patterns
1909ff81b7fSAndreas Gohr            $pns = ':' . getNS($page) . ':';
19149d38573SAndreas Gohr            foreach($this->patterns as $row) {
192ed60c3b3SAndreas Gohr                if($this->matchPagePattern($row['pattern'], $page, $pns)) {
193ed60c3b3SAndreas Gohr                    $tables[] = $row['tbl'];
194fb31ca9fSAndreas Gohr                }
195fb31ca9fSAndreas Gohr            }
1969ff81b7fSAndreas Gohr        } else {
1979ff81b7fSAndreas Gohr            // just select
1989ff81b7fSAndreas Gohr            $sql = 'SELECT tbl FROM schema_assignments WHERE pid = ? AND assigned = 1';
1999ff81b7fSAndreas Gohr            $res = $this->sqlite->query($sql, array($page));
2009ff81b7fSAndreas Gohr            $list = $this->sqlite->res2arr($res);
2019ff81b7fSAndreas Gohr            $this->sqlite->res_close($res);
2029ff81b7fSAndreas Gohr            foreach($list as $row) {
2039ff81b7fSAndreas Gohr                $tables[] = $row['tbl'];
2049ff81b7fSAndreas Gohr            }
2059ff81b7fSAndreas Gohr        }
206fb31ca9fSAndreas Gohr
207fb31ca9fSAndreas Gohr        return array_unique($tables);
208fb31ca9fSAndreas Gohr    }
20956672c36SAndreas Gohr
21056672c36SAndreas Gohr    /**
211153400c7SAndreas Gohr     * Get the pages known to struct and their assignment state
212153400c7SAndreas Gohr     *
213153400c7SAndreas Gohr     * @param null|string $schema limit results to the given schema
214153400c7SAndreas Gohr     * @param bool $assignedonly limit results to currently assigned only
215153400c7SAndreas Gohr     * @return array
216153400c7SAndreas Gohr     */
217153400c7SAndreas Gohr    public function getPages($schema = null, $assignedonly = false) {
218153400c7SAndreas Gohr        $sql = 'SELECT pid, tbl, assigned FROM schema_assignments WHERE 1=1';
219153400c7SAndreas Gohr
220153400c7SAndreas Gohr        $opts = array();
221153400c7SAndreas Gohr        if($schema) {
222153400c7SAndreas Gohr            $sql .= ' AND tbl = ?';
223153400c7SAndreas Gohr            $opts[] = $schema;
224153400c7SAndreas Gohr        }
225153400c7SAndreas Gohr        if($assignedonly) {
226153400c7SAndreas Gohr            $sql .= ' AND assigned = 1';
227153400c7SAndreas Gohr        }
228153400c7SAndreas Gohr
229153400c7SAndreas Gohr        $sql .= ' ORDER BY pid, tbl';
230153400c7SAndreas Gohr
231153400c7SAndreas Gohr        $res = $this->sqlite->query($sql, $opts);
232153400c7SAndreas Gohr        $list = $this->sqlite->res2arr($res);
233153400c7SAndreas Gohr        $this->sqlite->res_close($res);
234153400c7SAndreas Gohr
235153400c7SAndreas Gohr        $result = array();
236153400c7SAndreas Gohr        foreach($list as $row) {
237153400c7SAndreas Gohr            $pid = $row['pid'];
238153400c7SAndreas Gohr            $tbl = $row['tbl'];
239153400c7SAndreas Gohr            if(!isset($result[$pid])) $result[$pid] = array();
240153400c7SAndreas Gohr            $result[$pid][$tbl] = (bool) $row['assigned'];
241153400c7SAndreas Gohr        }
242153400c7SAndreas Gohr
243153400c7SAndreas Gohr        return $result;
244153400c7SAndreas Gohr    }
245153400c7SAndreas Gohr
246153400c7SAndreas Gohr    /**
247ed60c3b3SAndreas Gohr     * Check if the given pattern matches the given page
248ed60c3b3SAndreas Gohr     *
249ed60c3b3SAndreas Gohr     * @param string $pattern the pattern to check against
250ed60c3b3SAndreas Gohr     * @param string $page the cleaned pageid to check
251ed60c3b3SAndreas Gohr     * @param string|null $pns optimization, the colon wrapped namespace of the page, set null for automatic
252ed60c3b3SAndreas Gohr     * @return bool
253ed60c3b3SAndreas Gohr     */
254ed60c3b3SAndreas Gohr    protected function matchPagePattern($pattern, $page, $pns = null) {
2550e9e058fSAndreas Gohr        if(trim($pattern,':') == '**') return true; // match all
2560e9e058fSAndreas Gohr
2579914e87eSAndreas Gohr        // regex patterns
2589914e87eSAndreas Gohr        if($pattern{0} == '/') {
2599914e87eSAndreas Gohr            return (bool) preg_match($pattern, ":$page");
2609914e87eSAndreas Gohr        }
2619914e87eSAndreas Gohr
262ed60c3b3SAndreas Gohr        if(is_null($pns)) {
263ed60c3b3SAndreas Gohr            $pns = ':' . getNS($page) . ':';
264ed60c3b3SAndreas Gohr        }
265ed60c3b3SAndreas Gohr
266ed60c3b3SAndreas Gohr        $ans = ':' . cleanID($pattern) . ':';
267ed60c3b3SAndreas Gohr        if(substr($pattern, -2) == '**') {
268ed60c3b3SAndreas Gohr            // upper namespaces match
269ed60c3b3SAndreas Gohr            if(strpos($pns, $ans) === 0) {
270ed60c3b3SAndreas Gohr                return true;
271ed60c3b3SAndreas Gohr            }
272ed60c3b3SAndreas Gohr        } else if(substr($pattern, -1) == '*') {
273ed60c3b3SAndreas Gohr            // namespaces match exact
274ed60c3b3SAndreas Gohr            if($ans == $pns) {
275ed60c3b3SAndreas Gohr                return true;
276ed60c3b3SAndreas Gohr            }
277ed60c3b3SAndreas Gohr        } else {
278ed60c3b3SAndreas Gohr            // exact match
279ed60c3b3SAndreas Gohr            if(cleanID($pattern) == $page) {
280ed60c3b3SAndreas Gohr                return true;
281ed60c3b3SAndreas Gohr            }
282ed60c3b3SAndreas Gohr        }
283ed60c3b3SAndreas Gohr
284ed60c3b3SAndreas Gohr        return false;
285ed60c3b3SAndreas Gohr    }
286ed60c3b3SAndreas Gohr
287ed60c3b3SAndreas Gohr    /**
28856672c36SAndreas Gohr     * Returns all tables of schemas that existed and stored data for the page back then
28956672c36SAndreas Gohr     *
2900e9e058fSAndreas Gohr     * @deprecated because we're always only interested in the current state of affairs, even when restoring.
29156672c36SAndreas Gohr     *
29256672c36SAndreas Gohr     * @param string $page
29356672c36SAndreas Gohr     * @param string $ts
29456672c36SAndreas Gohr     * @return array
29556672c36SAndreas Gohr     */
29656672c36SAndreas Gohr    public function getHistoricAssignments($page, $ts) {
29756672c36SAndreas Gohr        $sql = "SELECT DISTINCT tbl FROM schemas WHERE ts <= ? ORDER BY ts DESC";
29856672c36SAndreas Gohr        $res = $this->sqlite->query($sql, $ts);
29956672c36SAndreas Gohr        $tables = $this->sqlite->res2arr($res);
30056672c36SAndreas Gohr        $this->sqlite->res_close($res);
30156672c36SAndreas Gohr
30256672c36SAndreas Gohr        $assigned = array();
30356672c36SAndreas Gohr        foreach($tables as $row) {
30456672c36SAndreas Gohr            $table = $row['tbl'];
305ed60c3b3SAndreas Gohr            /** @noinspection SqlResolve */
30656672c36SAndreas Gohr            $sql = "SELECT pid FROM data_$table WHERE pid = ? AND rev <= ? LIMIT 1";
30756672c36SAndreas Gohr            $res = $this->sqlite->query($sql, $page, $ts);
30856672c36SAndreas Gohr            $found = $this->sqlite->res2arr($res);
30956672c36SAndreas Gohr            $this->sqlite->res_close($res);
31056672c36SAndreas Gohr
31156672c36SAndreas Gohr            if($found) $assigned[] = $table;
31256672c36SAndreas Gohr        }
31356672c36SAndreas Gohr
31456672c36SAndreas Gohr        return $assigned;
31556672c36SAndreas Gohr    }
316*b25bb9feSMichael Grosse
317*b25bb9feSMichael Grosse    /**
318*b25bb9feSMichael Grosse     * fetch all pages where the schema isn't assigned, yet and reevaluate the page assignments for those pages and assign when needed
319*b25bb9feSMichael Grosse     *
320*b25bb9feSMichael Grosse     * @param $table
321*b25bb9feSMichael Grosse     */
322*b25bb9feSMichael Grosse    public function propagatePageAssignments($table) {
323*b25bb9feSMichael Grosse        $sql = 'SELECT pid FROM schema_assignments WHERE tbl != ? OR assigned != 1';
324*b25bb9feSMichael Grosse        $res = $this->sqlite->query($sql, $table);
325*b25bb9feSMichael Grosse        $pagerows = $this->sqlite->res2arr($res);
326*b25bb9feSMichael Grosse        $this->sqlite->res_close($res);
327*b25bb9feSMichael Grosse
328*b25bb9feSMichael Grosse        foreach ($pagerows as $row) {
329*b25bb9feSMichael Grosse            $tables = $this->getPageAssignments($row['pid'], true);
330*b25bb9feSMichael Grosse            if (in_array($table, $tables)) {
331*b25bb9feSMichael Grosse                $this->assignPageSchema($row['pid'], $table);
332*b25bb9feSMichael Grosse            }
333*b25bb9feSMichael Grosse        }
334*b25bb9feSMichael Grosse    }
335fb31ca9fSAndreas Gohr}
336