xref: /plugin/struct/meta/Assignments.php (revision ed60c3b35483367298781f7b408d38ff6458040d)
1<?php
2
3namespace plugin\struct\meta;
4
5/**
6 * Class Assignments
7 *
8 * Manages the assignment of schemas (table names) to pages and namespaces
9 *
10 * @package plugin\struct\meta
11 */
12class Assignments {
13
14    /** @var \helper_plugin_sqlite|null */
15    protected $sqlite;
16
17    /** @var  array All the assignments patterns */
18    protected $patterns;
19
20    /**
21     * Assignments constructor.
22     */
23    public function __construct() {
24        /** @var \helper_plugin_struct_db $helper */
25        $helper = plugin_load('helper', 'struct_db');
26        $this->sqlite = $helper->getDB();
27
28        if($this->sqlite) $this->loadPatterns();
29    }
30
31    /**
32     * Load existing assignment patterns
33     */
34    protected function loadPatterns() {
35        $sql = 'SELECT * FROM schema_assignments_patterns ORDER BY pattern';
36        $res = $this->sqlite->query($sql);
37        $this->patterns = $this->sqlite->res2arr($res);
38        $this->sqlite->res_close($res);
39    }
40
41    /**
42     * Add a new assignment pattern to the pattern table
43     *
44     * @param string $pattern
45     * @param string $table
46     * @return bool
47     */
48    public function addPattern($pattern, $table) {
49        // add the pattern
50        $sql = 'REPLACE INTO schema_assignments_patterns (pattern, tbl) VALUES (?,?)';
51        $ok = (bool) $this->sqlite->query($sql, array($pattern, $table));
52
53        // reload patterns
54        $this->loadPatterns();
55
56        // fetch all pages where the schema isn't assigned, yet
57        $sql = 'SELECT pid FROM schema_assignments WHERE tbl != ? OR assigned != 1';
58        $res = $this->sqlite->query($sql, $table);
59        $pages = $this->sqlite->res2arr($res);
60        $this->sqlite->res_close($res);
61
62        // reevalute the pages and assign when needed
63        foreach($pages as $page) {
64            $tables = $this->getPageAssignments($page);
65            if(in_array($table, $tables)) {
66                $this->assignPageSchema($page, $table);
67            }
68        }
69
70        return $ok;
71    }
72
73    /**
74     * Remove an existing assignment pattern from the pattern table
75     *
76     * @param string $pattern
77     * @param string $table
78     * @return bool
79     */
80    public function removePattern($pattern, $table) {
81        // remove the pattern
82        $sql = 'DELETE FROM schema_assignments_patterns WHERE pattern = ? AND tbl = ?';
83        $ok = (bool) $this->sqlite->query($sql, array($pattern, $table));
84
85        // reload patterns
86        $this->loadPatterns();
87
88        // fetch possibly affected pages
89        $sql = 'SELECT pid FROM schema_assignments WHERE tbl = ?';
90        $res = $this->sqlite->query($sql, $table);
91        $pages = $this->sqlite->res2arr($res);
92        $this->sqlite->res_close($res);
93
94        // reevalute the pages and unassign when needed
95        foreach($pages as $page) {
96            $tables = $this->getPageAssignments($page);
97            if(!in_array($table, $tables)) {
98                $this->deassignPageSchema($page, $table);
99            }
100        }
101
102        return $ok;
103    }
104
105    /**
106     * Add page to assignments
107     *
108     * @param string $page
109     * @param string $table
110     * @return bool
111     */
112    public function assignPageSchema($page, $table) {
113        $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 1)';
114        return (bool) $this->sqlite->query($sql, array($page, $table));
115    }
116
117    /**
118     * Remove page from assignments
119     *
120     * @param string $page
121     * @param string $table
122     * @return bool
123     */
124    public function deassignPageSchema($page, $table) {
125        $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 0)';
126        return (bool) $this->sqlite->query($sql, array($page, $table));
127    }
128
129    /**
130     * Get the whole pattern table
131     *
132     * @return array
133     */
134    public function getAllPatterns() {
135        return $this->patterns;
136    }
137
138    /**
139     * Returns a list of table names assigned to the given page
140     *
141     * @param string $page
142     * @return string[] tables assigned
143     */
144    public function getPageAssignments($page) {
145        $tables = array();
146
147        $page = cleanID($page);
148        $pns = ':' . getNS($page) . ':';
149
150        foreach($this->patterns as $row) {
151            if($this->matchPagePattern($row['pattern'], $page, $pns)) {
152                $tables[] = $row['tbl'];
153            }
154        }
155
156        return array_unique($tables);
157    }
158
159    /**
160     * Check if the given pattern matches the given page
161     *
162     * @param string $pattern the pattern to check against
163     * @param string $page the cleaned pageid to check
164     * @param string|null $pns optimization, the colon wrapped namespace of the page, set null for automatic
165     * @return bool
166     */
167    protected function matchPagePattern($pattern, $page, $pns = null) {
168        if(is_null($pns)) {
169            $pns = ':' . getNS($page) . ':';
170        }
171
172        $ans = ':' . cleanID($pattern) . ':';
173
174        if(substr($pattern, -2) == '**') {
175            // upper namespaces match
176            if(strpos($pns, $ans) === 0) {
177                return true;
178            }
179        } else if(substr($pattern, -1) == '*') {
180            // namespaces match exact
181            if($ans == $pns) {
182                return true;
183            }
184        } else {
185            // exact match
186            if(cleanID($pattern) == $page) {
187                return true;
188            }
189        }
190
191        return false;
192    }
193
194    /**
195     * Returns all tables of schemas that existed and stored data for the page back then
196     *
197     * @todo this is not used currently and can probably be removed again, because we're
198     *       always only interested in the current state of affairs, even when restoring.
199     *
200     * @param string $page
201     * @param string $ts
202     * @return array
203     */
204    public function getHistoricAssignments($page, $ts) {
205        $sql = "SELECT DISTINCT tbl FROM schemas WHERE ts <= ? ORDER BY ts DESC";
206        $res = $this->sqlite->query($sql, $ts);
207        $tables = $this->sqlite->res2arr($res);
208        $this->sqlite->res_close($res);
209
210        $assigned = array();
211        foreach($tables as $row) {
212            $table = $row['tbl'];
213            /** @noinspection SqlResolve */
214            $sql = "SELECT pid FROM data_$table WHERE pid = ? AND rev <= ? LIMIT 1";
215            $res = $this->sqlite->query($sql, $page, $ts);
216            $found = $this->sqlite->res2arr($res);
217            $this->sqlite->res_close($res);
218
219            if($found) $assigned[] = $table;
220        }
221
222        return $assigned;
223    }
224}
225