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 $pagerows = $this->sqlite->res2arr($res); 60 $this->sqlite->res_close($res); 61 62 // reevalute the pages and assign when needed 63 foreach($pagerows as $row) { 64 $tables = $this->getPageAssignments($row['pid'], true); 65 if(in_array($table, $tables)) { 66 $this->assignPageSchema($row['pid'], $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 $pagerows = $this->sqlite->res2arr($res); 92 $this->sqlite->res_close($res); 93 94 // reevalute the pages and unassign when needed 95 foreach($pagerows as $row) { 96 $tables = $this->getPageAssignments($row['pid'], true); 97 if(!in_array($table, $tables)) { 98 $this->deassignPageSchema($row['pid'], $table); 99 } 100 } 101 102 return $ok; 103 } 104 105 /** 106 * Clear all patterns - deassigns all pages 107 * 108 * This is mostly useful for testing and not used in the interface currently 109 * 110 * @return bool 111 */ 112 public function clear() { 113 $sql = 'DELETE FROM schema_assignments_patterns'; 114 $ok = (bool) $this->sqlite->query($sql); 115 116 $sql = 'UPDATE schema_assignments SET assigned = 0'; 117 $ok = $ok && (bool) $this->sqlite->query($sql); 118 119 // reload patterns 120 $this->loadPatterns(); 121 122 return $ok; 123 } 124 125 /** 126 * Add page to assignments 127 * 128 * @param string $page 129 * @param string $table 130 * @return bool 131 */ 132 public function assignPageSchema($page, $table) { 133 $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 1)'; 134 return (bool) $this->sqlite->query($sql, array($page, $table)); 135 } 136 137 /** 138 * Remove page from assignments 139 * 140 * @param string $page 141 * @param string $table 142 * @return bool 143 */ 144 public function deassignPageSchema($page, $table) { 145 $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 0)'; 146 return (bool) $this->sqlite->query($sql, array($page, $table)); 147 } 148 149 /** 150 * Get the whole pattern table 151 * 152 * @return array 153 */ 154 public function getAllPatterns() { 155 return $this->patterns; 156 } 157 158 /** 159 * Returns a list of table names assigned to the given page 160 * 161 * @param string $page 162 * @param bool $checkpatterns Should the current patterns be re-evaluated? 163 * @return \string[] tables assigned 164 */ 165 public function getPageAssignments($page, $checkpatterns=true) { 166 $tables = array(); 167 $page = cleanID($page); 168 169 if($checkpatterns) { 170 // evaluate patterns 171 $pns = ':' . getNS($page) . ':'; 172 foreach($this->patterns as $row) { 173 if($this->matchPagePattern($row['pattern'], $page, $pns)) { 174 $tables[] = $row['tbl']; 175 } 176 } 177 } else { 178 // just select 179 $sql = 'SELECT tbl FROM schema_assignments WHERE pid = ? AND assigned = 1'; 180 $res = $this->sqlite->query($sql, array($page)); 181 $list = $this->sqlite->res2arr($res); 182 $this->sqlite->res_close($res); 183 foreach($list as $row) { 184 $tables[] = $row['tbl']; 185 } 186 } 187 188 return array_unique($tables); 189 } 190 191 /** 192 * Check if the given pattern matches the given page 193 * 194 * @param string $pattern the pattern to check against 195 * @param string $page the cleaned pageid to check 196 * @param string|null $pns optimization, the colon wrapped namespace of the page, set null for automatic 197 * @return bool 198 */ 199 protected function matchPagePattern($pattern, $page, $pns = null) { 200 if(trim($pattern,':') == '**') return true; // match all 201 202 if(is_null($pns)) { 203 $pns = ':' . getNS($page) . ':'; 204 } 205 206 $ans = ':' . cleanID($pattern) . ':'; 207 208 if(substr($pattern, -2) == '**') { 209 // upper namespaces match 210 if(strpos($pns, $ans) === 0) { 211 return true; 212 } 213 } else if(substr($pattern, -1) == '*') { 214 // namespaces match exact 215 if($ans == $pns) { 216 return true; 217 } 218 } else { 219 // exact match 220 if(cleanID($pattern) == $page) { 221 return true; 222 } 223 } 224 225 return false; 226 } 227 228 /** 229 * Returns all tables of schemas that existed and stored data for the page back then 230 * 231 * @deprecated because we're always only interested in the current state of affairs, even when restoring. 232 * 233 * @param string $page 234 * @param string $ts 235 * @return array 236 */ 237 public function getHistoricAssignments($page, $ts) { 238 $sql = "SELECT DISTINCT tbl FROM schemas WHERE ts <= ? ORDER BY ts DESC"; 239 $res = $this->sqlite->query($sql, $ts); 240 $tables = $this->sqlite->res2arr($res); 241 $this->sqlite->res_close($res); 242 243 $assigned = array(); 244 foreach($tables as $row) { 245 $table = $row['tbl']; 246 /** @noinspection SqlResolve */ 247 $sql = "SELECT pid FROM data_$table WHERE pid = ? AND rev <= ? LIMIT 1"; 248 $res = $this->sqlite->query($sql, $page, $ts); 249 $found = $this->sqlite->res2arr($res); 250 $this->sqlite->res_close($res); 251 252 if($found) $assigned[] = $table; 253 } 254 255 return $assigned; 256 } 257} 258