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, true); 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 $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 * 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 * @param bool $checkpatterns Should the current patterns be re-evaluated? 143 * @return \string[] tables assigned 144 */ 145 public function getPageAssignments($page, $checkpatterns=true) { 146 $tables = array(); 147 $page = cleanID($page); 148 149 if($checkpatterns) { 150 // evaluate patterns 151 $pns = ':' . getNS($page) . ':'; 152 foreach($this->patterns as $row) { 153 if($this->matchPagePattern($row['pattern'], $page, $pns)) { 154 $tables[] = $row['tbl']; 155 } 156 } 157 } else { 158 // just select 159 $sql = 'SELECT tbl FROM schema_assignments WHERE pid = ? AND assigned = 1'; 160 $res = $this->sqlite->query($sql, array($page)); 161 $list = $this->sqlite->res2arr($res); 162 $this->sqlite->res_close($res); 163 foreach($list as $row) { 164 $tables[] = $row['tbl']; 165 } 166 } 167 168 return array_unique($tables); 169 } 170 171 /** 172 * Check if the given pattern matches the given page 173 * 174 * @param string $pattern the pattern to check against 175 * @param string $page the cleaned pageid to check 176 * @param string|null $pns optimization, the colon wrapped namespace of the page, set null for automatic 177 * @return bool 178 */ 179 protected function matchPagePattern($pattern, $page, $pns = null) { 180 if(is_null($pns)) { 181 $pns = ':' . getNS($page) . ':'; 182 } 183 184 $ans = ':' . cleanID($pattern) . ':'; 185 186 if(substr($pattern, -2) == '**') { 187 // upper namespaces match 188 if(strpos($pns, $ans) === 0) { 189 return true; 190 } 191 } else if(substr($pattern, -1) == '*') { 192 // namespaces match exact 193 if($ans == $pns) { 194 return true; 195 } 196 } else { 197 // exact match 198 if(cleanID($pattern) == $page) { 199 return true; 200 } 201 } 202 203 return false; 204 } 205 206 /** 207 * Returns all tables of schemas that existed and stored data for the page back then 208 * 209 * @todo this is not used currently and can probably be removed again, because we're 210 * always only interested in the current state of affairs, even when restoring. 211 * 212 * @param string $page 213 * @param string $ts 214 * @return array 215 */ 216 public function getHistoricAssignments($page, $ts) { 217 $sql = "SELECT DISTINCT tbl FROM schemas WHERE ts <= ? ORDER BY ts DESC"; 218 $res = $this->sqlite->query($sql, $ts); 219 $tables = $this->sqlite->res2arr($res); 220 $this->sqlite->res_close($res); 221 222 $assigned = array(); 223 foreach($tables as $row) { 224 $table = $row['tbl']; 225 /** @noinspection SqlResolve */ 226 $sql = "SELECT pid FROM data_$table WHERE pid = ? AND rev <= ? LIMIT 1"; 227 $res = $this->sqlite->query($sql, $page, $ts); 228 $found = $this->sqlite->res2arr($res); 229 $this->sqlite->res_close($res); 230 231 if($found) $assigned[] = $table; 232 } 233 234 return $assigned; 235 } 236} 237