1fb31ca9fSAndreas Gohr<?php 2fb31ca9fSAndreas Gohr 3ba766201SAndreas Gohrnamespace dokuwiki\plugin\struct\meta; 4fb31ca9fSAndreas Gohr 51a8d1235SAndreas Gohr/** 61a8d1235SAndreas Gohr * Class Assignments 71a8d1235SAndreas Gohr * 86636674bSAnna Dabrowska * Manages the assignment of schemas (table names) to pages and namespaces. 96636674bSAnna Dabrowska * An assignment is created when actual struct data is attached to the page. 106636674bSAnna Dabrowska * Assignment are never deleted, only their "assigned" status is changed. 111a8d1235SAndreas Gohr * 12025cb9daSAndreas Gohr * This is a singleton. Assignment data is only loaded once per request. 13025cb9daSAndreas Gohr * 14ba766201SAndreas Gohr * @package dokuwiki\plugin\struct\meta 151a8d1235SAndreas Gohr */ 16d6d97f60SAnna Dabrowskaclass Assignments 17d6d97f60SAnna Dabrowska{ 18fb31ca9fSAndreas Gohr /** @var \helper_plugin_sqlite|null */ 19fb31ca9fSAndreas Gohr protected $sqlite; 20fb31ca9fSAndreas Gohr 2133d7be6aSAndreas Gohr /** @var array All the assignments patterns */ 2249d38573SAndreas Gohr protected $patterns; 23fb31ca9fSAndreas Gohr 24025cb9daSAndreas Gohr /** @var Assignments */ 257234bfb1Ssplitbrain protected static $instance; 26025cb9daSAndreas Gohr 27025cb9daSAndreas Gohr /** 28025cb9daSAndreas Gohr * Get the singleton instance of the Assignments 29025cb9daSAndreas Gohr * 30025cb9daSAndreas Gohr * @param bool $forcereload create a new instace to reload the assignment data 31025cb9daSAndreas Gohr * @return Assignments 32025cb9daSAndreas Gohr */ 33d6d97f60SAnna Dabrowska public static function getInstance($forcereload = false) 34d6d97f60SAnna Dabrowska { 357234bfb1Ssplitbrain if (is_null(self::$instance) || $forcereload) { 367234bfb1Ssplitbrain $class = static::class; 37025cb9daSAndreas Gohr self::$instance = new $class(); 38025cb9daSAndreas Gohr } 39025cb9daSAndreas Gohr return self::$instance; 40025cb9daSAndreas Gohr } 41025cb9daSAndreas Gohr 42fb31ca9fSAndreas Gohr /** 43fb31ca9fSAndreas Gohr * Assignments constructor. 44025cb9daSAndreas Gohr * 45025cb9daSAndreas Gohr * Not public. Use Assignments::getInstance() instead 46fb31ca9fSAndreas Gohr */ 47d6d97f60SAnna Dabrowska protected function __construct() 48d6d97f60SAnna Dabrowska { 49fb31ca9fSAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 50fb31ca9fSAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 51fb31ca9fSAndreas Gohr $this->sqlite = $helper->getDB(); 52fb31ca9fSAndreas Gohr 53fc26989eSAndreas Gohr $this->loadPatterns(); 54fb31ca9fSAndreas Gohr } 55fb31ca9fSAndreas Gohr 56025cb9daSAndreas Gohr 57fb31ca9fSAndreas Gohr /** 5849d38573SAndreas Gohr * Load existing assignment patterns 59fb31ca9fSAndreas Gohr */ 60d6d97f60SAnna Dabrowska protected function loadPatterns() 61d6d97f60SAnna Dabrowska { 6249d38573SAndreas Gohr $sql = 'SELECT * FROM schema_assignments_patterns ORDER BY pattern'; 6379b29326SAnna Dabrowska $this->patterns = $this->sqlite->queryAll($sql); 64fb31ca9fSAndreas Gohr } 65fb31ca9fSAndreas Gohr 66fb31ca9fSAndreas Gohr /** 6749d38573SAndreas Gohr * Add a new assignment pattern to the pattern table 681a8d1235SAndreas Gohr * 6949d38573SAndreas Gohr * @param string $pattern 701a8d1235SAndreas Gohr * @param string $table 711a8d1235SAndreas Gohr * @return bool 721a8d1235SAndreas Gohr */ 73d6d97f60SAnna Dabrowska public function addPattern($pattern, $table) 74d6d97f60SAnna Dabrowska { 75ed60c3b3SAndreas Gohr // add the pattern 7649d38573SAndreas Gohr $sql = 'REPLACE INTO schema_assignments_patterns (pattern, tbl) VALUES (?,?)'; 7779b29326SAnna Dabrowska $ok = (bool)$this->sqlite->query($sql, [$pattern, $table]); 78ed60c3b3SAndreas Gohr 79ed60c3b3SAndreas Gohr // reload patterns 80ed60c3b3SAndreas Gohr $this->loadPatterns(); 81b25bb9feSMichael Grosse $this->propagatePageAssignments($table); 82ed60c3b3SAndreas Gohr 83ed60c3b3SAndreas Gohr 84ed60c3b3SAndreas Gohr return $ok; 851a8d1235SAndreas Gohr } 861a8d1235SAndreas Gohr 871a8d1235SAndreas Gohr /** 8849d38573SAndreas Gohr * Remove an existing assignment pattern from the pattern table 891a8d1235SAndreas Gohr * 9049d38573SAndreas Gohr * @param string $pattern 911a8d1235SAndreas Gohr * @param string $table 921a8d1235SAndreas Gohr * @return bool 931a8d1235SAndreas Gohr */ 94d6d97f60SAnna Dabrowska public function removePattern($pattern, $table) 95d6d97f60SAnna Dabrowska { 96ed60c3b3SAndreas Gohr // remove the pattern 9749d38573SAndreas Gohr $sql = 'DELETE FROM schema_assignments_patterns WHERE pattern = ? AND tbl = ?'; 9879b29326SAnna Dabrowska $ok = (bool)$this->sqlite->query($sql, [$pattern, $table]); 99ed60c3b3SAndreas Gohr 100ed60c3b3SAndreas Gohr // reload patterns 101ed60c3b3SAndreas Gohr $this->loadPatterns(); 102ed60c3b3SAndreas Gohr 103ed60c3b3SAndreas Gohr // fetch possibly affected pages 104ed60c3b3SAndreas Gohr $sql = 'SELECT pid FROM schema_assignments WHERE tbl = ?'; 10579b29326SAnna Dabrowska $pagerows = $this->sqlite->queryAll($sql, [$table]); 106ed60c3b3SAndreas Gohr 107ed60c3b3SAndreas Gohr // reevalute the pages and unassign when needed 1080e9e058fSAndreas Gohr foreach ($pagerows as $row) { 109be94e9d9SAndreas Gohr $tables = $this->getPageAssignments($row['pid'], true); 110ed60c3b3SAndreas Gohr if (!in_array($table, $tables)) { 111be94e9d9SAndreas Gohr $this->deassignPageSchema($row['pid'], $table); 112ed60c3b3SAndreas Gohr } 113ed60c3b3SAndreas Gohr } 114ed60c3b3SAndreas Gohr 115ed60c3b3SAndreas Gohr return $ok; 116ed60c3b3SAndreas Gohr } 117ed60c3b3SAndreas Gohr 118ed60c3b3SAndreas Gohr /** 1190173e75dSAndreas Gohr * Rechecks all assignments of a given page against the current patterns 1200173e75dSAndreas Gohr * 1210173e75dSAndreas Gohr * @param string $pid 1220173e75dSAndreas Gohr */ 123d6d97f60SAnna Dabrowska public function reevaluatePageAssignments($pid) 124d6d97f60SAnna Dabrowska { 1250173e75dSAndreas Gohr // reload patterns 1260173e75dSAndreas Gohr $this->loadPatterns(); 1270173e75dSAndreas Gohr $tables = $this->getPageAssignments($pid, true); 1280173e75dSAndreas Gohr 1290173e75dSAndreas Gohr // fetch possibly affected tables 1300173e75dSAndreas Gohr $sql = 'SELECT tbl FROM schema_assignments WHERE pid = ?'; 13179b29326SAnna Dabrowska $tablerows = $this->sqlite->queryAll($sql, [$pid]); 1320173e75dSAndreas Gohr 1330173e75dSAndreas Gohr // reevalute the tables and apply assignments 1340173e75dSAndreas Gohr foreach ($tablerows as $row) { 1350173e75dSAndreas Gohr if (in_array($row['tbl'], $tables)) { 1360173e75dSAndreas Gohr $this->assignPageSchema($pid, $row['tbl']); 1370173e75dSAndreas Gohr } else { 1380173e75dSAndreas Gohr $this->deassignPageSchema($pid, $row['tbl']); 1390173e75dSAndreas Gohr } 1400173e75dSAndreas Gohr } 1410173e75dSAndreas Gohr } 1420173e75dSAndreas Gohr 1430173e75dSAndreas Gohr /** 1440e9e058fSAndreas Gohr * Clear all patterns - deassigns all pages 1450e9e058fSAndreas Gohr * 1460e9e058fSAndreas Gohr * This is mostly useful for testing and not used in the interface currently 1470e9e058fSAndreas Gohr * 148153400c7SAndreas Gohr * @param bool $full fully delete all previous assignments 1490e9e058fSAndreas Gohr * @return bool 1500e9e058fSAndreas Gohr */ 151d6d97f60SAnna Dabrowska public function clear($full = false) 152d6d97f60SAnna Dabrowska { 1530e9e058fSAndreas Gohr $sql = 'DELETE FROM schema_assignments_patterns'; 1540e9e058fSAndreas Gohr $ok = (bool)$this->sqlite->query($sql); 1550e9e058fSAndreas Gohr 156153400c7SAndreas Gohr if ($full) { 157153400c7SAndreas Gohr $sql = 'DELETE FROM schema_assignments'; 158153400c7SAndreas Gohr } else { 1590e9e058fSAndreas Gohr $sql = 'UPDATE schema_assignments SET assigned = 0'; 160153400c7SAndreas Gohr } 1610e9e058fSAndreas Gohr $ok = $ok && (bool)$this->sqlite->query($sql); 1620e9e058fSAndreas Gohr 1630e9e058fSAndreas Gohr // reload patterns 1640e9e058fSAndreas Gohr $this->loadPatterns(); 1650e9e058fSAndreas Gohr 1660e9e058fSAndreas Gohr return $ok; 1670e9e058fSAndreas Gohr } 1680e9e058fSAndreas Gohr 1690e9e058fSAndreas Gohr /** 170ed60c3b3SAndreas Gohr * Add page to assignments 171ed60c3b3SAndreas Gohr * 172ed60c3b3SAndreas Gohr * @param string $page 173ed60c3b3SAndreas Gohr * @param string $table 174ed60c3b3SAndreas Gohr * @return bool 175ed60c3b3SAndreas Gohr */ 176d6d97f60SAnna Dabrowska public function assignPageSchema($page, $table) 177d6d97f60SAnna Dabrowska { 178ed60c3b3SAndreas Gohr $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 1)'; 1797234bfb1Ssplitbrain return (bool)$this->sqlite->query($sql, [$page, $table]); 180ed60c3b3SAndreas Gohr } 181ed60c3b3SAndreas Gohr 182ed60c3b3SAndreas Gohr /** 183ed60c3b3SAndreas Gohr * Remove page from assignments 184ed60c3b3SAndreas Gohr * 185ed60c3b3SAndreas Gohr * @param string $page 186ed60c3b3SAndreas Gohr * @param string $table 187ed60c3b3SAndreas Gohr * @return bool 188ed60c3b3SAndreas Gohr */ 189d6d97f60SAnna Dabrowska public function deassignPageSchema($page, $table) 190d6d97f60SAnna Dabrowska { 191ed60c3b3SAndreas Gohr $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 0)'; 1927234bfb1Ssplitbrain return (bool)$this->sqlite->query($sql, [$page, $table]); 1931a8d1235SAndreas Gohr } 1941a8d1235SAndreas Gohr 1951a8d1235SAndreas Gohr /** 19649d38573SAndreas Gohr * Get the whole pattern table 1971a8d1235SAndreas Gohr * 1981a8d1235SAndreas Gohr * @return array 1991a8d1235SAndreas Gohr */ 200d6d97f60SAnna Dabrowska public function getAllPatterns() 201d6d97f60SAnna Dabrowska { 20249d38573SAndreas Gohr return $this->patterns; 2031a8d1235SAndreas Gohr } 2041a8d1235SAndreas Gohr 2051a8d1235SAndreas Gohr /** 206fb31ca9fSAndreas Gohr * Returns a list of table names assigned to the given page 207fb31ca9fSAndreas Gohr * 208fb31ca9fSAndreas Gohr * @param string $page 2099ff81b7fSAndreas Gohr * @param bool $checkpatterns Should the current patterns be re-evaluated? 2109ff81b7fSAndreas Gohr * @return \string[] tables assigned 211fb31ca9fSAndreas Gohr */ 212d6d97f60SAnna Dabrowska public function getPageAssignments($page, $checkpatterns = true) 213d6d97f60SAnna Dabrowska { 2147234bfb1Ssplitbrain $tables = []; 215fb31ca9fSAndreas Gohr $page = cleanID($page); 216fb31ca9fSAndreas Gohr 2179ff81b7fSAndreas Gohr if ($checkpatterns) { 2189ff81b7fSAndreas Gohr // evaluate patterns 2199ff81b7fSAndreas Gohr $pns = ':' . getNS($page) . ':'; 22049d38573SAndreas Gohr foreach ($this->patterns as $row) { 221ed60c3b3SAndreas Gohr if ($this->matchPagePattern($row['pattern'], $page, $pns)) { 222ed60c3b3SAndreas Gohr $tables[] = $row['tbl']; 223fb31ca9fSAndreas Gohr } 224fb31ca9fSAndreas Gohr } 2259ff81b7fSAndreas Gohr } else { 2269ff81b7fSAndreas Gohr // just select 2279ff81b7fSAndreas Gohr $sql = 'SELECT tbl FROM schema_assignments WHERE pid = ? AND assigned = 1'; 22879b29326SAnna Dabrowska $list = $this->sqlite->queryAll($sql, [$page]); 2299ff81b7fSAndreas Gohr foreach ($list as $row) { 2309ff81b7fSAndreas Gohr $tables[] = $row['tbl']; 2319ff81b7fSAndreas Gohr } 2329ff81b7fSAndreas Gohr } 233fb31ca9fSAndreas Gohr 234fb31ca9fSAndreas Gohr return array_unique($tables); 235fb31ca9fSAndreas Gohr } 23656672c36SAndreas Gohr 23756672c36SAndreas Gohr /** 238153400c7SAndreas Gohr * Get the pages known to struct and their assignment state 239153400c7SAndreas Gohr * 240153400c7SAndreas Gohr * @param null|string $schema limit results to the given schema 241153400c7SAndreas Gohr * @param bool $assignedonly limit results to currently assigned only 242153400c7SAndreas Gohr * @return array 243153400c7SAndreas Gohr */ 244d6d97f60SAnna Dabrowska public function getPages($schema = null, $assignedonly = false) 245d6d97f60SAnna Dabrowska { 246153400c7SAndreas Gohr $sql = 'SELECT pid, tbl, assigned FROM schema_assignments WHERE 1=1'; 247153400c7SAndreas Gohr 2487234bfb1Ssplitbrain $opts = []; 249153400c7SAndreas Gohr if ($schema) { 250153400c7SAndreas Gohr $sql .= ' AND tbl = ?'; 251153400c7SAndreas Gohr $opts[] = $schema; 252153400c7SAndreas Gohr } 253153400c7SAndreas Gohr if ($assignedonly) { 254153400c7SAndreas Gohr $sql .= ' AND assigned = 1'; 255153400c7SAndreas Gohr } 256153400c7SAndreas Gohr 257153400c7SAndreas Gohr $sql .= ' ORDER BY pid, tbl'; 258153400c7SAndreas Gohr 25979b29326SAnna Dabrowska $list = $this->sqlite->queryAll($sql, $opts); 260153400c7SAndreas Gohr 2617234bfb1Ssplitbrain $result = []; 262153400c7SAndreas Gohr foreach ($list as $row) { 263153400c7SAndreas Gohr $pid = $row['pid']; 264153400c7SAndreas Gohr $tbl = $row['tbl']; 2657234bfb1Ssplitbrain if (!isset($result[$pid])) $result[$pid] = []; 266153400c7SAndreas Gohr $result[$pid][$tbl] = (bool)$row['assigned']; 267153400c7SAndreas Gohr } 268153400c7SAndreas Gohr 269153400c7SAndreas Gohr return $result; 270153400c7SAndreas Gohr } 271153400c7SAndreas Gohr 272153400c7SAndreas Gohr /** 273ed60c3b3SAndreas Gohr * Check if the given pattern matches the given page 274ed60c3b3SAndreas Gohr * 275ed60c3b3SAndreas Gohr * @param string $pattern the pattern to check against 276ed60c3b3SAndreas Gohr * @param string $page the cleaned pageid to check 277ed60c3b3SAndreas Gohr * @param string|null $pns optimization, the colon wrapped namespace of the page, set null for automatic 278ed60c3b3SAndreas Gohr * @return bool 279ed60c3b3SAndreas Gohr */ 280d6d97f60SAnna Dabrowska protected function matchPagePattern($pattern, $page, $pns = null) 281d6d97f60SAnna Dabrowska { 2820e9e058fSAndreas Gohr if (trim($pattern, ':') == '**') return true; // match all 2830e9e058fSAndreas Gohr 2849914e87eSAndreas Gohr // regex patterns 2858da1363aSAndreas Gohr if ($pattern[0] == '/') { 2869914e87eSAndreas Gohr return (bool)preg_match($pattern, ":$page"); 2879914e87eSAndreas Gohr } 2889914e87eSAndreas Gohr 289ed60c3b3SAndreas Gohr if (is_null($pns)) { 290ed60c3b3SAndreas Gohr $pns = ':' . getNS($page) . ':'; 291ed60c3b3SAndreas Gohr } 292ed60c3b3SAndreas Gohr 293ed60c3b3SAndreas Gohr $ans = ':' . cleanID($pattern) . ':'; 294*ba662a60SAndreas Gohr if (str_ends_with($pattern, '**')) { 295ed60c3b3SAndreas Gohr // upper namespaces match 296*ba662a60SAndreas Gohr if (str_starts_with($pns, $ans)) { 297ed60c3b3SAndreas Gohr return true; 298ed60c3b3SAndreas Gohr } 299*ba662a60SAndreas Gohr } elseif (str_ends_with($pattern, '*')) { 300ed60c3b3SAndreas Gohr // namespaces match exact 301ed60c3b3SAndreas Gohr if ($ans == $pns) { 302ed60c3b3SAndreas Gohr return true; 303ed60c3b3SAndreas Gohr } 3047234bfb1Ssplitbrain } elseif (cleanID($pattern) == $page) { 305ed60c3b3SAndreas Gohr // exact match 306ed60c3b3SAndreas Gohr return true; 307ed60c3b3SAndreas Gohr } 308ed60c3b3SAndreas Gohr 309ed60c3b3SAndreas Gohr return false; 310ed60c3b3SAndreas Gohr } 311ed60c3b3SAndreas Gohr 312ed60c3b3SAndreas Gohr /** 31356672c36SAndreas Gohr * Returns all tables of schemas that existed and stored data for the page back then 31456672c36SAndreas Gohr * 3150e9e058fSAndreas Gohr * @deprecated because we're always only interested in the current state of affairs, even when restoring. 31656672c36SAndreas Gohr * 31756672c36SAndreas Gohr * @param string $page 31856672c36SAndreas Gohr * @param string $ts 31956672c36SAndreas Gohr * @return array 32056672c36SAndreas Gohr */ 321d6d97f60SAnna Dabrowska public function getHistoricAssignments($page, $ts) 322d6d97f60SAnna Dabrowska { 32356672c36SAndreas Gohr $sql = "SELECT DISTINCT tbl FROM schemas WHERE ts <= ? ORDER BY ts DESC"; 32479b29326SAnna Dabrowska $tables = $this->sqlite->queryAll($sql, [$ts]); 32556672c36SAndreas Gohr 3267234bfb1Ssplitbrain $assigned = []; 32756672c36SAndreas Gohr foreach ($tables as $row) { 32856672c36SAndreas Gohr $table = $row['tbl']; 329ed60c3b3SAndreas Gohr /** @noinspection SqlResolve */ 33056672c36SAndreas Gohr $sql = "SELECT pid FROM data_$table WHERE pid = ? AND rev <= ? LIMIT 1"; 33179b29326SAnna Dabrowska $found = $this->sqlite->queryAll($sql, [$page, $ts]); 33256672c36SAndreas Gohr 33356672c36SAndreas Gohr if ($found) $assigned[] = $table; 33456672c36SAndreas Gohr } 33556672c36SAndreas Gohr 33656672c36SAndreas Gohr return $assigned; 33756672c36SAndreas Gohr } 338b25bb9feSMichael Grosse 339b25bb9feSMichael Grosse /** 34017a3a578SAndreas Gohr * fetch all pages where the schema isn't assigned, yet 34117a3a578SAndreas Gohr * and reevaluate the page assignments for those pages and assign when needed 342b25bb9feSMichael Grosse * 343b25bb9feSMichael Grosse * @param $table 344b25bb9feSMichael Grosse */ 345d6d97f60SAnna Dabrowska public function propagatePageAssignments($table) 346d6d97f60SAnna Dabrowska { 347b25bb9feSMichael Grosse $sql = 'SELECT pid FROM schema_assignments WHERE tbl != ? OR assigned != 1'; 34879b29326SAnna Dabrowska $pagerows = $this->sqlite->queryAll($sql, [$table]); 349b25bb9feSMichael Grosse 350b25bb9feSMichael Grosse foreach ($pagerows as $row) { 351b25bb9feSMichael Grosse $tables = $this->getPageAssignments($row['pid'], true); 352b25bb9feSMichael Grosse if (in_array($table, $tables)) { 353b25bb9feSMichael Grosse $this->assignPageSchema($row['pid'], $table); 354b25bb9feSMichael Grosse } 355b25bb9feSMichael Grosse } 356b25bb9feSMichael Grosse } 357fb31ca9fSAndreas Gohr} 358