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 * @param bool $full fully delete all previous assignments 111 * @return bool 112 */ 113 public function clear($full=false) { 114 $sql = 'DELETE FROM schema_assignments_patterns'; 115 $ok = (bool) $this->sqlite->query($sql); 116 117 if($full) { 118 $sql = 'DELETE FROM schema_assignments'; 119 } else { 120 $sql = 'UPDATE schema_assignments SET assigned = 0'; 121 } 122 $ok = $ok && (bool) $this->sqlite->query($sql); 123 124 // reload patterns 125 $this->loadPatterns(); 126 127 return $ok; 128 } 129 130 /** 131 * Add page to assignments 132 * 133 * @param string $page 134 * @param string $table 135 * @return bool 136 */ 137 public function assignPageSchema($page, $table) { 138 $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 1)'; 139 return (bool) $this->sqlite->query($sql, array($page, $table)); 140 } 141 142 /** 143 * Remove page from assignments 144 * 145 * @param string $page 146 * @param string $table 147 * @return bool 148 */ 149 public function deassignPageSchema($page, $table) { 150 $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 0)'; 151 return (bool) $this->sqlite->query($sql, array($page, $table)); 152 } 153 154 /** 155 * Get the whole pattern table 156 * 157 * @return array 158 */ 159 public function getAllPatterns() { 160 return $this->patterns; 161 } 162 163 /** 164 * Returns a list of table names assigned to the given page 165 * 166 * @param string $page 167 * @param bool $checkpatterns Should the current patterns be re-evaluated? 168 * @return \string[] tables assigned 169 */ 170 public function getPageAssignments($page, $checkpatterns=true) { 171 $tables = array(); 172 $page = cleanID($page); 173 174 if($checkpatterns) { 175 // evaluate patterns 176 $pns = ':' . getNS($page) . ':'; 177 foreach($this->patterns as $row) { 178 if($this->matchPagePattern($row['pattern'], $page, $pns)) { 179 $tables[] = $row['tbl']; 180 } 181 } 182 } else { 183 // just select 184 $sql = 'SELECT tbl FROM schema_assignments WHERE pid = ? AND assigned = 1'; 185 $res = $this->sqlite->query($sql, array($page)); 186 $list = $this->sqlite->res2arr($res); 187 $this->sqlite->res_close($res); 188 foreach($list as $row) { 189 $tables[] = $row['tbl']; 190 } 191 } 192 193 return array_unique($tables); 194 } 195 196 /** 197 * Get the pages known to struct and their assignment state 198 * 199 * @param null|string $schema limit results to the given schema 200 * @param bool $assignedonly limit results to currently assigned only 201 * @return array 202 */ 203 public function getPages($schema = null, $assignedonly = false) { 204 $sql = 'SELECT pid, tbl, assigned FROM schema_assignments WHERE 1=1'; 205 206 $opts = array(); 207 if($schema) { 208 $sql .= ' AND tbl = ?'; 209 $opts[] = $schema; 210 } 211 if($assignedonly) { 212 $sql .= ' AND assigned = 1'; 213 } 214 215 $sql .= ' ORDER BY pid, tbl'; 216 217 $res = $this->sqlite->query($sql, $opts); 218 $list = $this->sqlite->res2arr($res); 219 $this->sqlite->res_close($res); 220 221 $result = array(); 222 foreach($list as $row) { 223 $pid = $row['pid']; 224 $tbl = $row['tbl']; 225 if(!isset($result[$pid])) $result[$pid] = array(); 226 $result[$pid][$tbl] = (bool) $row['assigned']; 227 } 228 229 return $result; 230 } 231 232 /** 233 * Check if the given pattern matches the given page 234 * 235 * @param string $pattern the pattern to check against 236 * @param string $page the cleaned pageid to check 237 * @param string|null $pns optimization, the colon wrapped namespace of the page, set null for automatic 238 * @return bool 239 */ 240 protected function matchPagePattern($pattern, $page, $pns = null) { 241 if(trim($pattern,':') == '**') return true; // match all 242 243 // regex patterns 244 if($pattern{0} == '/') { 245 return (bool) preg_match($pattern, ":$page"); 246 } 247 248 if(is_null($pns)) { 249 $pns = ':' . getNS($page) . ':'; 250 } 251 252 $ans = ':' . cleanID($pattern) . ':'; 253 if(substr($pattern, -2) == '**') { 254 // upper namespaces match 255 if(strpos($pns, $ans) === 0) { 256 return true; 257 } 258 } else if(substr($pattern, -1) == '*') { 259 // namespaces match exact 260 if($ans == $pns) { 261 return true; 262 } 263 } else { 264 // exact match 265 if(cleanID($pattern) == $page) { 266 return true; 267 } 268 } 269 270 return false; 271 } 272 273 /** 274 * Returns all tables of schemas that existed and stored data for the page back then 275 * 276 * @deprecated because we're always only interested in the current state of affairs, even when restoring. 277 * 278 * @param string $page 279 * @param string $ts 280 * @return array 281 */ 282 public function getHistoricAssignments($page, $ts) { 283 $sql = "SELECT DISTINCT tbl FROM schemas WHERE ts <= ? ORDER BY ts DESC"; 284 $res = $this->sqlite->query($sql, $ts); 285 $tables = $this->sqlite->res2arr($res); 286 $this->sqlite->res_close($res); 287 288 $assigned = array(); 289 foreach($tables as $row) { 290 $table = $row['tbl']; 291 /** @noinspection SqlResolve */ 292 $sql = "SELECT pid FROM data_$table WHERE pid = ? AND rev <= ? LIMIT 1"; 293 $res = $this->sqlite->query($sql, $page, $ts); 294 $found = $this->sqlite->res2arr($res); 295 $this->sqlite->res_close($res); 296 297 if($found) $assigned[] = $table; 298 } 299 300 return $assigned; 301 } 302} 303