1fb31ca9fSAndreas Gohr<?php 2fb31ca9fSAndreas Gohr 3ba766201SAndreas Gohrnamespace dokuwiki\plugin\struct\meta; 4fb31ca9fSAndreas Gohr 51a8d1235SAndreas Gohr/** 61a8d1235SAndreas Gohr * Class Assignments 71a8d1235SAndreas Gohr * 81a8d1235SAndreas Gohr * Manages the assignment of schemas (table names) to pages and namespaces 91a8d1235SAndreas Gohr * 10025cb9daSAndreas Gohr * This is a singleton. Assignment data is only loaded once per request. 11025cb9daSAndreas Gohr * 12ba766201SAndreas Gohr * @package dokuwiki\plugin\struct\meta 131a8d1235SAndreas Gohr */ 14*d6d97f60SAnna Dabrowskaclass Assignments 15*d6d97f60SAnna Dabrowska{ 16fb31ca9fSAndreas Gohr 17fb31ca9fSAndreas Gohr /** @var \helper_plugin_sqlite|null */ 18fb31ca9fSAndreas Gohr protected $sqlite; 19fb31ca9fSAndreas Gohr 2033d7be6aSAndreas Gohr /** @var array All the assignments patterns */ 2149d38573SAndreas Gohr protected $patterns; 22fb31ca9fSAndreas Gohr 23fc26989eSAndreas Gohr /** @var string[] All lookup schemas for error checking */ 24fc26989eSAndreas Gohr protected $lookups; 25fc26989eSAndreas Gohr 26025cb9daSAndreas Gohr /** @var Assignments */ 27025cb9daSAndreas Gohr protected static $instance = null; 28025cb9daSAndreas Gohr 29025cb9daSAndreas Gohr /** 30025cb9daSAndreas Gohr * Get the singleton instance of the Assignments 31025cb9daSAndreas Gohr * 32025cb9daSAndreas Gohr * @param bool $forcereload create a new instace to reload the assignment data 33025cb9daSAndreas Gohr * @return Assignments 34025cb9daSAndreas Gohr */ 35*d6d97f60SAnna Dabrowska public static function getInstance($forcereload = false) 36*d6d97f60SAnna Dabrowska { 37025cb9daSAndreas Gohr if (is_null(self::$instance) or $forcereload) { 38025cb9daSAndreas Gohr $class = get_called_class(); 39025cb9daSAndreas Gohr self::$instance = new $class(); 40025cb9daSAndreas Gohr } 41025cb9daSAndreas Gohr return self::$instance; 42025cb9daSAndreas Gohr } 43025cb9daSAndreas Gohr 44fb31ca9fSAndreas Gohr /** 45fb31ca9fSAndreas Gohr * Assignments constructor. 46025cb9daSAndreas Gohr * 47025cb9daSAndreas Gohr * Not public. Use Assignments::getInstance() instead 48fb31ca9fSAndreas Gohr */ 49*d6d97f60SAnna Dabrowska protected function __construct() 50*d6d97f60SAnna Dabrowska { 51fb31ca9fSAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 52fb31ca9fSAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 53fb31ca9fSAndreas Gohr $this->sqlite = $helper->getDB(); 54fb31ca9fSAndreas Gohr 55fc26989eSAndreas Gohr $this->loadPatterns(); 56fc26989eSAndreas Gohr $this->lookups = Schema::getAll('lookup'); 57fb31ca9fSAndreas Gohr } 58fb31ca9fSAndreas Gohr 59025cb9daSAndreas Gohr 60025cb9daSAndreas Gohr 61fb31ca9fSAndreas Gohr /** 6249d38573SAndreas Gohr * Load existing assignment patterns 63fb31ca9fSAndreas Gohr */ 64*d6d97f60SAnna Dabrowska protected function loadPatterns() 65*d6d97f60SAnna Dabrowska { 6649d38573SAndreas Gohr $sql = 'SELECT * FROM schema_assignments_patterns ORDER BY pattern'; 67fb31ca9fSAndreas Gohr $res = $this->sqlite->query($sql); 6849d38573SAndreas Gohr $this->patterns = $this->sqlite->res2arr($res); 69fb31ca9fSAndreas Gohr $this->sqlite->res_close($res); 70fb31ca9fSAndreas Gohr } 71fb31ca9fSAndreas Gohr 72fb31ca9fSAndreas Gohr /** 7349d38573SAndreas Gohr * Add a new assignment pattern to the pattern table 741a8d1235SAndreas Gohr * 7549d38573SAndreas Gohr * @param string $pattern 761a8d1235SAndreas Gohr * @param string $table 771a8d1235SAndreas Gohr * @return bool 781a8d1235SAndreas Gohr */ 79*d6d97f60SAnna Dabrowska public function addPattern($pattern, $table) 80*d6d97f60SAnna Dabrowska { 81fc26989eSAndreas Gohr if (in_array($table, $this->lookups)) { 82fc26989eSAndreas Gohr throw new StructException('nolookupassign'); 83fc26989eSAndreas Gohr } 84fc26989eSAndreas Gohr 85ed60c3b3SAndreas Gohr // add the pattern 8649d38573SAndreas Gohr $sql = 'REPLACE INTO schema_assignments_patterns (pattern, tbl) VALUES (?,?)'; 87ed60c3b3SAndreas Gohr $ok = (bool) $this->sqlite->query($sql, array($pattern, $table)); 88ed60c3b3SAndreas Gohr 89ed60c3b3SAndreas Gohr // reload patterns 90ed60c3b3SAndreas Gohr $this->loadPatterns(); 91b25bb9feSMichael Grosse $this->propagatePageAssignments($table); 92ed60c3b3SAndreas Gohr 93ed60c3b3SAndreas Gohr 94ed60c3b3SAndreas Gohr return $ok; 951a8d1235SAndreas Gohr } 961a8d1235SAndreas Gohr 971a8d1235SAndreas Gohr /** 9849d38573SAndreas Gohr * Remove an existing assignment pattern from the pattern table 991a8d1235SAndreas Gohr * 10049d38573SAndreas Gohr * @param string $pattern 1011a8d1235SAndreas Gohr * @param string $table 1021a8d1235SAndreas Gohr * @return bool 1031a8d1235SAndreas Gohr */ 104*d6d97f60SAnna Dabrowska public function removePattern($pattern, $table) 105*d6d97f60SAnna Dabrowska { 106ed60c3b3SAndreas Gohr // remove the pattern 10749d38573SAndreas Gohr $sql = 'DELETE FROM schema_assignments_patterns WHERE pattern = ? AND tbl = ?'; 108ed60c3b3SAndreas Gohr $ok = (bool) $this->sqlite->query($sql, array($pattern, $table)); 109ed60c3b3SAndreas Gohr 110ed60c3b3SAndreas Gohr // reload patterns 111ed60c3b3SAndreas Gohr $this->loadPatterns(); 112ed60c3b3SAndreas Gohr 113ed60c3b3SAndreas Gohr // fetch possibly affected pages 114ed60c3b3SAndreas Gohr $sql = 'SELECT pid FROM schema_assignments WHERE tbl = ?'; 115ed60c3b3SAndreas Gohr $res = $this->sqlite->query($sql, $table); 1160e9e058fSAndreas Gohr $pagerows = $this->sqlite->res2arr($res); 117ed60c3b3SAndreas Gohr $this->sqlite->res_close($res); 118ed60c3b3SAndreas Gohr 119ed60c3b3SAndreas Gohr // reevalute the pages and unassign when needed 1200e9e058fSAndreas Gohr foreach ($pagerows as $row) { 121be94e9d9SAndreas Gohr $tables = $this->getPageAssignments($row['pid'], true); 122ed60c3b3SAndreas Gohr if (!in_array($table, $tables)) { 123be94e9d9SAndreas Gohr $this->deassignPageSchema($row['pid'], $table); 124ed60c3b3SAndreas Gohr } 125ed60c3b3SAndreas Gohr } 126ed60c3b3SAndreas Gohr 127ed60c3b3SAndreas Gohr return $ok; 128ed60c3b3SAndreas Gohr } 129ed60c3b3SAndreas Gohr 130ed60c3b3SAndreas Gohr /** 1310173e75dSAndreas Gohr * Rechecks all assignments of a given page against the current patterns 1320173e75dSAndreas Gohr * 1330173e75dSAndreas Gohr * @param string $pid 1340173e75dSAndreas Gohr */ 135*d6d97f60SAnna Dabrowska public function reevaluatePageAssignments($pid) 136*d6d97f60SAnna Dabrowska { 1370173e75dSAndreas Gohr // reload patterns 1380173e75dSAndreas Gohr $this->loadPatterns(); 1390173e75dSAndreas Gohr $tables = $this->getPageAssignments($pid, true); 1400173e75dSAndreas Gohr 1410173e75dSAndreas Gohr // fetch possibly affected tables 1420173e75dSAndreas Gohr $sql = 'SELECT tbl FROM schema_assignments WHERE pid = ?'; 1430173e75dSAndreas Gohr $res = $this->sqlite->query($sql, $pid); 1440173e75dSAndreas Gohr $tablerows = $this->sqlite->res2arr($res); 1450173e75dSAndreas Gohr $this->sqlite->res_close($res); 1460173e75dSAndreas Gohr 1470173e75dSAndreas Gohr // reevalute the tables and apply assignments 1480173e75dSAndreas Gohr foreach ($tablerows as $row) { 1490173e75dSAndreas Gohr if (in_array($row['tbl'], $tables)) { 1500173e75dSAndreas Gohr $this->assignPageSchema($pid, $row['tbl']); 1510173e75dSAndreas Gohr } else { 1520173e75dSAndreas Gohr $this->deassignPageSchema($pid, $row['tbl']); 1530173e75dSAndreas Gohr } 1540173e75dSAndreas Gohr } 1550173e75dSAndreas Gohr } 1560173e75dSAndreas Gohr 1570173e75dSAndreas Gohr /** 1580e9e058fSAndreas Gohr * Clear all patterns - deassigns all pages 1590e9e058fSAndreas Gohr * 1600e9e058fSAndreas Gohr * This is mostly useful for testing and not used in the interface currently 1610e9e058fSAndreas Gohr * 162153400c7SAndreas Gohr * @param bool $full fully delete all previous assignments 1630e9e058fSAndreas Gohr * @return bool 1640e9e058fSAndreas Gohr */ 165*d6d97f60SAnna Dabrowska public function clear($full = false) 166*d6d97f60SAnna Dabrowska { 1670e9e058fSAndreas Gohr $sql = 'DELETE FROM schema_assignments_patterns'; 1680e9e058fSAndreas Gohr $ok = (bool) $this->sqlite->query($sql); 1690e9e058fSAndreas Gohr 170153400c7SAndreas Gohr if ($full) { 171153400c7SAndreas Gohr $sql = 'DELETE FROM schema_assignments'; 172153400c7SAndreas Gohr } else { 1730e9e058fSAndreas Gohr $sql = 'UPDATE schema_assignments SET assigned = 0'; 174153400c7SAndreas Gohr } 1750e9e058fSAndreas Gohr $ok = $ok && (bool) $this->sqlite->query($sql); 1760e9e058fSAndreas Gohr 1770e9e058fSAndreas Gohr // reload patterns 1780e9e058fSAndreas Gohr $this->loadPatterns(); 1790e9e058fSAndreas Gohr 1800e9e058fSAndreas Gohr return $ok; 1810e9e058fSAndreas Gohr } 1820e9e058fSAndreas Gohr 1830e9e058fSAndreas Gohr /** 184ed60c3b3SAndreas Gohr * Add page to assignments 185ed60c3b3SAndreas Gohr * 186ed60c3b3SAndreas Gohr * @param string $page 187ed60c3b3SAndreas Gohr * @param string $table 188ed60c3b3SAndreas Gohr * @return bool 189ed60c3b3SAndreas Gohr */ 190*d6d97f60SAnna Dabrowska public function assignPageSchema($page, $table) 191*d6d97f60SAnna Dabrowska { 192ed60c3b3SAndreas Gohr $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 1)'; 193ed60c3b3SAndreas Gohr return (bool) $this->sqlite->query($sql, array($page, $table)); 194ed60c3b3SAndreas Gohr } 195ed60c3b3SAndreas Gohr 196ed60c3b3SAndreas Gohr /** 197ed60c3b3SAndreas Gohr * Remove page from assignments 198ed60c3b3SAndreas Gohr * 199ed60c3b3SAndreas Gohr * @param string $page 200ed60c3b3SAndreas Gohr * @param string $table 201ed60c3b3SAndreas Gohr * @return bool 202ed60c3b3SAndreas Gohr */ 203*d6d97f60SAnna Dabrowska public function deassignPageSchema($page, $table) 204*d6d97f60SAnna Dabrowska { 205ed60c3b3SAndreas Gohr $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 0)'; 206ed60c3b3SAndreas Gohr return (bool) $this->sqlite->query($sql, array($page, $table)); 2071a8d1235SAndreas Gohr } 2081a8d1235SAndreas Gohr 2091a8d1235SAndreas Gohr /** 21049d38573SAndreas Gohr * Get the whole pattern table 2111a8d1235SAndreas Gohr * 2121a8d1235SAndreas Gohr * @return array 2131a8d1235SAndreas Gohr */ 214*d6d97f60SAnna Dabrowska public function getAllPatterns() 215*d6d97f60SAnna Dabrowska { 21649d38573SAndreas Gohr return $this->patterns; 2171a8d1235SAndreas Gohr } 2181a8d1235SAndreas Gohr 2191a8d1235SAndreas Gohr /** 220fb31ca9fSAndreas Gohr * Returns a list of table names assigned to the given page 221fb31ca9fSAndreas Gohr * 222fb31ca9fSAndreas Gohr * @param string $page 2239ff81b7fSAndreas Gohr * @param bool $checkpatterns Should the current patterns be re-evaluated? 2249ff81b7fSAndreas Gohr * @return \string[] tables assigned 225fb31ca9fSAndreas Gohr */ 226*d6d97f60SAnna Dabrowska public function getPageAssignments($page, $checkpatterns = true) 227*d6d97f60SAnna Dabrowska { 228fb31ca9fSAndreas Gohr $tables = array(); 229fb31ca9fSAndreas Gohr $page = cleanID($page); 230fb31ca9fSAndreas Gohr 2319ff81b7fSAndreas Gohr if ($checkpatterns) { 2329ff81b7fSAndreas Gohr // evaluate patterns 2339ff81b7fSAndreas Gohr $pns = ':' . getNS($page) . ':'; 23449d38573SAndreas Gohr foreach ($this->patterns as $row) { 235ed60c3b3SAndreas Gohr if ($this->matchPagePattern($row['pattern'], $page, $pns)) { 236fc26989eSAndreas Gohr if (in_array($row['tbl'], $this->lookups)) continue; // wrong assignment 237ed60c3b3SAndreas Gohr $tables[] = $row['tbl']; 238fb31ca9fSAndreas Gohr } 239fb31ca9fSAndreas Gohr } 2409ff81b7fSAndreas Gohr } else { 2419ff81b7fSAndreas Gohr // just select 2429ff81b7fSAndreas Gohr $sql = 'SELECT tbl FROM schema_assignments WHERE pid = ? AND assigned = 1'; 2439ff81b7fSAndreas Gohr $res = $this->sqlite->query($sql, array($page)); 2449ff81b7fSAndreas Gohr $list = $this->sqlite->res2arr($res); 2459ff81b7fSAndreas Gohr $this->sqlite->res_close($res); 2469ff81b7fSAndreas Gohr foreach ($list as $row) { 247fc26989eSAndreas Gohr if (in_array($row['tbl'], $this->lookups)) continue; // wrong assignment 2489ff81b7fSAndreas Gohr $tables[] = $row['tbl']; 2499ff81b7fSAndreas Gohr } 2509ff81b7fSAndreas Gohr } 251fb31ca9fSAndreas Gohr 252fb31ca9fSAndreas Gohr return array_unique($tables); 253fb31ca9fSAndreas Gohr } 25456672c36SAndreas Gohr 25556672c36SAndreas Gohr /** 256153400c7SAndreas Gohr * Get the pages known to struct and their assignment state 257153400c7SAndreas Gohr * 258153400c7SAndreas Gohr * @param null|string $schema limit results to the given schema 259153400c7SAndreas Gohr * @param bool $assignedonly limit results to currently assigned only 260153400c7SAndreas Gohr * @return array 261153400c7SAndreas Gohr */ 262*d6d97f60SAnna Dabrowska public function getPages($schema = null, $assignedonly = false) 263*d6d97f60SAnna Dabrowska { 264153400c7SAndreas Gohr $sql = 'SELECT pid, tbl, assigned FROM schema_assignments WHERE 1=1'; 265153400c7SAndreas Gohr 266153400c7SAndreas Gohr $opts = array(); 267153400c7SAndreas Gohr if ($schema) { 268153400c7SAndreas Gohr $sql .= ' AND tbl = ?'; 269153400c7SAndreas Gohr $opts[] = $schema; 270153400c7SAndreas Gohr } 271153400c7SAndreas Gohr if ($assignedonly) { 272153400c7SAndreas Gohr $sql .= ' AND assigned = 1'; 273153400c7SAndreas Gohr } 274153400c7SAndreas Gohr 275153400c7SAndreas Gohr $sql .= ' ORDER BY pid, tbl'; 276153400c7SAndreas Gohr 277153400c7SAndreas Gohr $res = $this->sqlite->query($sql, $opts); 278153400c7SAndreas Gohr $list = $this->sqlite->res2arr($res); 279153400c7SAndreas Gohr $this->sqlite->res_close($res); 280153400c7SAndreas Gohr 281153400c7SAndreas Gohr $result = array(); 282153400c7SAndreas Gohr foreach ($list as $row) { 283153400c7SAndreas Gohr $pid = $row['pid']; 284153400c7SAndreas Gohr $tbl = $row['tbl']; 285153400c7SAndreas Gohr if (!isset($result[$pid])) $result[$pid] = array(); 286153400c7SAndreas Gohr $result[$pid][$tbl] = (bool) $row['assigned']; 287153400c7SAndreas Gohr } 288153400c7SAndreas Gohr 289153400c7SAndreas Gohr return $result; 290153400c7SAndreas Gohr } 291153400c7SAndreas Gohr 292153400c7SAndreas Gohr /** 293ed60c3b3SAndreas Gohr * Check if the given pattern matches the given page 294ed60c3b3SAndreas Gohr * 295ed60c3b3SAndreas Gohr * @param string $pattern the pattern to check against 296ed60c3b3SAndreas Gohr * @param string $page the cleaned pageid to check 297ed60c3b3SAndreas Gohr * @param string|null $pns optimization, the colon wrapped namespace of the page, set null for automatic 298ed60c3b3SAndreas Gohr * @return bool 299ed60c3b3SAndreas Gohr */ 300*d6d97f60SAnna Dabrowska protected function matchPagePattern($pattern, $page, $pns = null) 301*d6d97f60SAnna Dabrowska { 3020e9e058fSAndreas Gohr if (trim($pattern, ':') == '**') return true; // match all 3030e9e058fSAndreas Gohr 3049914e87eSAndreas Gohr // regex patterns 3058da1363aSAndreas Gohr if ($pattern[0] == '/') { 3069914e87eSAndreas Gohr return (bool) preg_match($pattern, ":$page"); 3079914e87eSAndreas Gohr } 3089914e87eSAndreas Gohr 309ed60c3b3SAndreas Gohr if (is_null($pns)) { 310ed60c3b3SAndreas Gohr $pns = ':' . getNS($page) . ':'; 311ed60c3b3SAndreas Gohr } 312ed60c3b3SAndreas Gohr 313ed60c3b3SAndreas Gohr $ans = ':' . cleanID($pattern) . ':'; 314ed60c3b3SAndreas Gohr if (substr($pattern, -2) == '**') { 315ed60c3b3SAndreas Gohr // upper namespaces match 316ed60c3b3SAndreas Gohr if (strpos($pns, $ans) === 0) { 317ed60c3b3SAndreas Gohr return true; 318ed60c3b3SAndreas Gohr } 319ed60c3b3SAndreas Gohr } elseif (substr($pattern, -1) == '*') { 320ed60c3b3SAndreas Gohr // namespaces match exact 321ed60c3b3SAndreas Gohr if ($ans == $pns) { 322ed60c3b3SAndreas Gohr return true; 323ed60c3b3SAndreas Gohr } 324ed60c3b3SAndreas Gohr } else { 325ed60c3b3SAndreas Gohr // exact match 326ed60c3b3SAndreas Gohr if (cleanID($pattern) == $page) { 327ed60c3b3SAndreas Gohr return true; 328ed60c3b3SAndreas Gohr } 329ed60c3b3SAndreas Gohr } 330ed60c3b3SAndreas Gohr 331ed60c3b3SAndreas Gohr return false; 332ed60c3b3SAndreas Gohr } 333ed60c3b3SAndreas Gohr 334ed60c3b3SAndreas Gohr /** 33556672c36SAndreas Gohr * Returns all tables of schemas that existed and stored data for the page back then 33656672c36SAndreas Gohr * 3370e9e058fSAndreas Gohr * @deprecated because we're always only interested in the current state of affairs, even when restoring. 33856672c36SAndreas Gohr * 33956672c36SAndreas Gohr * @param string $page 34056672c36SAndreas Gohr * @param string $ts 34156672c36SAndreas Gohr * @return array 34256672c36SAndreas Gohr */ 343*d6d97f60SAnna Dabrowska public function getHistoricAssignments($page, $ts) 344*d6d97f60SAnna Dabrowska { 34556672c36SAndreas Gohr $sql = "SELECT DISTINCT tbl FROM schemas WHERE ts <= ? ORDER BY ts DESC"; 34656672c36SAndreas Gohr $res = $this->sqlite->query($sql, $ts); 34756672c36SAndreas Gohr $tables = $this->sqlite->res2arr($res); 34856672c36SAndreas Gohr $this->sqlite->res_close($res); 34956672c36SAndreas Gohr 35056672c36SAndreas Gohr $assigned = array(); 35156672c36SAndreas Gohr foreach ($tables as $row) { 35256672c36SAndreas Gohr $table = $row['tbl']; 353ed60c3b3SAndreas Gohr /** @noinspection SqlResolve */ 35456672c36SAndreas Gohr $sql = "SELECT pid FROM data_$table WHERE pid = ? AND rev <= ? LIMIT 1"; 35556672c36SAndreas Gohr $res = $this->sqlite->query($sql, $page, $ts); 35656672c36SAndreas Gohr $found = $this->sqlite->res2arr($res); 35756672c36SAndreas Gohr $this->sqlite->res_close($res); 35856672c36SAndreas Gohr 35956672c36SAndreas Gohr if ($found) $assigned[] = $table; 36056672c36SAndreas Gohr } 36156672c36SAndreas Gohr 36256672c36SAndreas Gohr return $assigned; 36356672c36SAndreas Gohr } 364b25bb9feSMichael Grosse 365b25bb9feSMichael Grosse /** 366b25bb9feSMichael Grosse * fetch all pages where the schema isn't assigned, yet and reevaluate the page assignments for those pages and assign when needed 367b25bb9feSMichael Grosse * 368b25bb9feSMichael Grosse * @param $table 369b25bb9feSMichael Grosse */ 370*d6d97f60SAnna Dabrowska public function propagatePageAssignments($table) 371*d6d97f60SAnna Dabrowska { 372b25bb9feSMichael Grosse $sql = 'SELECT pid FROM schema_assignments WHERE tbl != ? OR assigned != 1'; 373b25bb9feSMichael Grosse $res = $this->sqlite->query($sql, $table); 374b25bb9feSMichael Grosse $pagerows = $this->sqlite->res2arr($res); 375b25bb9feSMichael Grosse $this->sqlite->res_close($res); 376b25bb9feSMichael Grosse 377b25bb9feSMichael Grosse foreach ($pagerows as $row) { 378b25bb9feSMichael Grosse $tables = $this->getPageAssignments($row['pid'], true); 379b25bb9feSMichael Grosse if (in_array($table, $tables)) { 380b25bb9feSMichael Grosse $this->assignPageSchema($row['pid'], $table); 381b25bb9feSMichael Grosse } 382b25bb9feSMichael Grosse } 383b25bb9feSMichael Grosse } 384fb31ca9fSAndreas Gohr} 385