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