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 * 10ba766201SAndreas Gohr * @package dokuwiki\plugin\struct\meta 111a8d1235SAndreas Gohr */ 12fb31ca9fSAndreas Gohrclass Assignments { 13fb31ca9fSAndreas Gohr 14fb31ca9fSAndreas Gohr /** @var \helper_plugin_sqlite|null */ 15fb31ca9fSAndreas Gohr protected $sqlite; 16fb31ca9fSAndreas Gohr 1733d7be6aSAndreas Gohr /** @var array All the assignments patterns */ 1849d38573SAndreas Gohr protected $patterns; 19fb31ca9fSAndreas Gohr 20*fc26989eSAndreas Gohr /** @var string[] All lookup schemas for error checking */ 21*fc26989eSAndreas Gohr protected $lookups; 22*fc26989eSAndreas Gohr 23fb31ca9fSAndreas Gohr /** 24fb31ca9fSAndreas Gohr * Assignments constructor. 25fb31ca9fSAndreas Gohr */ 26fb31ca9fSAndreas Gohr public function __construct() { 27fb31ca9fSAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 28fb31ca9fSAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 29fb31ca9fSAndreas Gohr $this->sqlite = $helper->getDB(); 30*fc26989eSAndreas Gohr if(!$this->sqlite) return; 31fb31ca9fSAndreas Gohr 32*fc26989eSAndreas Gohr $this->loadPatterns(); 33*fc26989eSAndreas Gohr $this->lookups = Schema::getAll('lookup'); 34fb31ca9fSAndreas Gohr } 35fb31ca9fSAndreas Gohr 36fb31ca9fSAndreas Gohr /** 3749d38573SAndreas Gohr * Load existing assignment patterns 38fb31ca9fSAndreas Gohr */ 3933d7be6aSAndreas Gohr protected function loadPatterns() { 4049d38573SAndreas Gohr $sql = 'SELECT * FROM schema_assignments_patterns ORDER BY pattern'; 41fb31ca9fSAndreas Gohr $res = $this->sqlite->query($sql); 4249d38573SAndreas Gohr $this->patterns = $this->sqlite->res2arr($res); 43fb31ca9fSAndreas Gohr $this->sqlite->res_close($res); 44fb31ca9fSAndreas Gohr } 45fb31ca9fSAndreas Gohr 46fb31ca9fSAndreas Gohr /** 4749d38573SAndreas Gohr * Add a new assignment pattern to the pattern table 481a8d1235SAndreas Gohr * 4949d38573SAndreas Gohr * @param string $pattern 501a8d1235SAndreas Gohr * @param string $table 511a8d1235SAndreas Gohr * @return bool 521a8d1235SAndreas Gohr */ 5333d7be6aSAndreas Gohr public function addPattern($pattern, $table) { 54*fc26989eSAndreas Gohr if(in_array($table, $this->lookups)) { 55*fc26989eSAndreas Gohr throw new StructException('nolookupassign'); 56*fc26989eSAndreas Gohr } 57*fc26989eSAndreas Gohr 58ed60c3b3SAndreas Gohr // add the pattern 5949d38573SAndreas Gohr $sql = 'REPLACE INTO schema_assignments_patterns (pattern, tbl) VALUES (?,?)'; 60ed60c3b3SAndreas Gohr $ok = (bool) $this->sqlite->query($sql, array($pattern, $table)); 61ed60c3b3SAndreas Gohr 62ed60c3b3SAndreas Gohr // reload patterns 63ed60c3b3SAndreas Gohr $this->loadPatterns(); 64ed60c3b3SAndreas Gohr 65ed60c3b3SAndreas Gohr // fetch all pages where the schema isn't assigned, yet 66ed60c3b3SAndreas Gohr $sql = 'SELECT pid FROM schema_assignments WHERE tbl != ? OR assigned != 1'; 67ed60c3b3SAndreas Gohr $res = $this->sqlite->query($sql, $table); 680e9e058fSAndreas Gohr $pagerows = $this->sqlite->res2arr($res); 69ed60c3b3SAndreas Gohr $this->sqlite->res_close($res); 70ed60c3b3SAndreas Gohr 71ed60c3b3SAndreas Gohr // reevalute the pages and assign when needed 720e9e058fSAndreas Gohr foreach($pagerows as $row) { 730e9e058fSAndreas Gohr $tables = $this->getPageAssignments($row['pid'], true); 74ed60c3b3SAndreas Gohr if(in_array($table, $tables)) { 750e9e058fSAndreas Gohr $this->assignPageSchema($row['pid'], $table); 76ed60c3b3SAndreas Gohr } 77ed60c3b3SAndreas Gohr } 78ed60c3b3SAndreas Gohr 79ed60c3b3SAndreas Gohr return $ok; 801a8d1235SAndreas Gohr } 811a8d1235SAndreas Gohr 821a8d1235SAndreas Gohr /** 8349d38573SAndreas Gohr * Remove an existing assignment pattern from the pattern table 841a8d1235SAndreas Gohr * 8549d38573SAndreas Gohr * @param string $pattern 861a8d1235SAndreas Gohr * @param string $table 871a8d1235SAndreas Gohr * @return bool 881a8d1235SAndreas Gohr */ 8933d7be6aSAndreas Gohr public function removePattern($pattern, $table) { 90ed60c3b3SAndreas Gohr // remove the pattern 9149d38573SAndreas Gohr $sql = 'DELETE FROM schema_assignments_patterns WHERE pattern = ? AND tbl = ?'; 92ed60c3b3SAndreas Gohr $ok = (bool) $this->sqlite->query($sql, array($pattern, $table)); 93ed60c3b3SAndreas Gohr 94ed60c3b3SAndreas Gohr // reload patterns 95ed60c3b3SAndreas Gohr $this->loadPatterns(); 96ed60c3b3SAndreas Gohr 97ed60c3b3SAndreas Gohr // fetch possibly affected pages 98ed60c3b3SAndreas Gohr $sql = 'SELECT pid FROM schema_assignments WHERE tbl = ?'; 99ed60c3b3SAndreas Gohr $res = $this->sqlite->query($sql, $table); 1000e9e058fSAndreas Gohr $pagerows = $this->sqlite->res2arr($res); 101ed60c3b3SAndreas Gohr $this->sqlite->res_close($res); 102ed60c3b3SAndreas Gohr 103ed60c3b3SAndreas Gohr // reevalute the pages and unassign when needed 1040e9e058fSAndreas Gohr foreach($pagerows as $row) { 105be94e9d9SAndreas Gohr $tables = $this->getPageAssignments($row['pid'], true); 106ed60c3b3SAndreas Gohr if(!in_array($table, $tables)) { 107be94e9d9SAndreas Gohr $this->deassignPageSchema($row['pid'], $table); 108ed60c3b3SAndreas Gohr } 109ed60c3b3SAndreas Gohr } 110ed60c3b3SAndreas Gohr 111ed60c3b3SAndreas Gohr return $ok; 112ed60c3b3SAndreas Gohr } 113ed60c3b3SAndreas Gohr 114ed60c3b3SAndreas Gohr /** 1150173e75dSAndreas Gohr * Rechecks all assignments of a given page against the current patterns 1160173e75dSAndreas Gohr * 1170173e75dSAndreas Gohr * @param string $pid 1180173e75dSAndreas Gohr */ 1190173e75dSAndreas Gohr public function reevaluatePageAssignments($pid) { 1200173e75dSAndreas Gohr // reload patterns 1210173e75dSAndreas Gohr $this->loadPatterns(); 1220173e75dSAndreas Gohr $tables = $this->getPageAssignments($pid, true); 1230173e75dSAndreas Gohr 1240173e75dSAndreas Gohr // fetch possibly affected tables 1250173e75dSAndreas Gohr $sql = 'SELECT tbl FROM schema_assignments WHERE pid = ?'; 1260173e75dSAndreas Gohr $res = $this->sqlite->query($sql, $pid); 1270173e75dSAndreas Gohr $tablerows = $this->sqlite->res2arr($res); 1280173e75dSAndreas Gohr $this->sqlite->res_close($res); 1290173e75dSAndreas Gohr 1300173e75dSAndreas Gohr // reevalute the tables and apply assignments 1310173e75dSAndreas Gohr foreach($tablerows as $row) { 1320173e75dSAndreas Gohr if(in_array($row['tbl'], $tables)) { 1330173e75dSAndreas Gohr $this->assignPageSchema($pid, $row['tbl']); 1340173e75dSAndreas Gohr } else { 1350173e75dSAndreas Gohr $this->deassignPageSchema($pid, $row['tbl']); 1360173e75dSAndreas Gohr } 1370173e75dSAndreas Gohr } 1380173e75dSAndreas Gohr } 1390173e75dSAndreas Gohr 1400173e75dSAndreas Gohr /** 1410e9e058fSAndreas Gohr * Clear all patterns - deassigns all pages 1420e9e058fSAndreas Gohr * 1430e9e058fSAndreas Gohr * This is mostly useful for testing and not used in the interface currently 1440e9e058fSAndreas Gohr * 145153400c7SAndreas Gohr * @param bool $full fully delete all previous assignments 1460e9e058fSAndreas Gohr * @return bool 1470e9e058fSAndreas Gohr */ 148153400c7SAndreas Gohr public function clear($full=false) { 1490e9e058fSAndreas Gohr $sql = 'DELETE FROM schema_assignments_patterns'; 1500e9e058fSAndreas Gohr $ok = (bool) $this->sqlite->query($sql); 1510e9e058fSAndreas Gohr 152153400c7SAndreas Gohr if($full) { 153153400c7SAndreas Gohr $sql = 'DELETE FROM schema_assignments'; 154153400c7SAndreas Gohr } else { 1550e9e058fSAndreas Gohr $sql = 'UPDATE schema_assignments SET assigned = 0'; 156153400c7SAndreas Gohr } 1570e9e058fSAndreas Gohr $ok = $ok && (bool) $this->sqlite->query($sql); 1580e9e058fSAndreas Gohr 1590e9e058fSAndreas Gohr // reload patterns 1600e9e058fSAndreas Gohr $this->loadPatterns(); 1610e9e058fSAndreas Gohr 1620e9e058fSAndreas Gohr return $ok; 1630e9e058fSAndreas Gohr } 1640e9e058fSAndreas Gohr 1650e9e058fSAndreas Gohr /** 166ed60c3b3SAndreas Gohr * Add page to assignments 167ed60c3b3SAndreas Gohr * 168ed60c3b3SAndreas Gohr * @param string $page 169ed60c3b3SAndreas Gohr * @param string $table 170ed60c3b3SAndreas Gohr * @return bool 171ed60c3b3SAndreas Gohr */ 172ed713594SAndreas Gohr public function assignPageSchema($page, $table) { 173ed60c3b3SAndreas Gohr $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 1)'; 174ed60c3b3SAndreas Gohr return (bool) $this->sqlite->query($sql, array($page, $table)); 175ed60c3b3SAndreas Gohr } 176ed60c3b3SAndreas Gohr 177ed60c3b3SAndreas Gohr /** 178ed60c3b3SAndreas Gohr * Remove page from assignments 179ed60c3b3SAndreas Gohr * 180ed60c3b3SAndreas Gohr * @param string $page 181ed60c3b3SAndreas Gohr * @param string $table 182ed60c3b3SAndreas Gohr * @return bool 183ed60c3b3SAndreas Gohr */ 184ed713594SAndreas Gohr public function deassignPageSchema($page, $table) { 185ed60c3b3SAndreas Gohr $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 0)'; 186ed60c3b3SAndreas Gohr return (bool) $this->sqlite->query($sql, array($page, $table)); 1871a8d1235SAndreas Gohr } 1881a8d1235SAndreas Gohr 1891a8d1235SAndreas Gohr /** 19049d38573SAndreas Gohr * Get the whole pattern table 1911a8d1235SAndreas Gohr * 1921a8d1235SAndreas Gohr * @return array 1931a8d1235SAndreas Gohr */ 19433d7be6aSAndreas Gohr public function getAllPatterns() { 19549d38573SAndreas Gohr return $this->patterns; 1961a8d1235SAndreas Gohr } 1971a8d1235SAndreas Gohr 1981a8d1235SAndreas Gohr /** 199fb31ca9fSAndreas Gohr * Returns a list of table names assigned to the given page 200fb31ca9fSAndreas Gohr * 201fb31ca9fSAndreas Gohr * @param string $page 2029ff81b7fSAndreas Gohr * @param bool $checkpatterns Should the current patterns be re-evaluated? 2039ff81b7fSAndreas Gohr * @return \string[] tables assigned 204fb31ca9fSAndreas Gohr */ 2059ff81b7fSAndreas Gohr public function getPageAssignments($page, $checkpatterns=true) { 206fb31ca9fSAndreas Gohr $tables = array(); 207fb31ca9fSAndreas Gohr $page = cleanID($page); 208fb31ca9fSAndreas Gohr 2099ff81b7fSAndreas Gohr if($checkpatterns) { 2109ff81b7fSAndreas Gohr // evaluate patterns 2119ff81b7fSAndreas Gohr $pns = ':' . getNS($page) . ':'; 21249d38573SAndreas Gohr foreach($this->patterns as $row) { 213ed60c3b3SAndreas Gohr if($this->matchPagePattern($row['pattern'], $page, $pns)) { 214*fc26989eSAndreas Gohr if(in_array($row['tbl'], $this->lookups)) continue; // wrong assignment 215ed60c3b3SAndreas Gohr $tables[] = $row['tbl']; 216fb31ca9fSAndreas Gohr } 217fb31ca9fSAndreas Gohr } 2189ff81b7fSAndreas Gohr } else { 2199ff81b7fSAndreas Gohr // just select 2209ff81b7fSAndreas Gohr $sql = 'SELECT tbl FROM schema_assignments WHERE pid = ? AND assigned = 1'; 2219ff81b7fSAndreas Gohr $res = $this->sqlite->query($sql, array($page)); 2229ff81b7fSAndreas Gohr $list = $this->sqlite->res2arr($res); 2239ff81b7fSAndreas Gohr $this->sqlite->res_close($res); 2249ff81b7fSAndreas Gohr foreach($list as $row) { 225*fc26989eSAndreas Gohr if(in_array($row['tbl'], $this->lookups)) continue; // wrong assignment 2269ff81b7fSAndreas Gohr $tables[] = $row['tbl']; 2279ff81b7fSAndreas Gohr } 2289ff81b7fSAndreas Gohr } 229fb31ca9fSAndreas Gohr 230fb31ca9fSAndreas Gohr return array_unique($tables); 231fb31ca9fSAndreas Gohr } 23256672c36SAndreas Gohr 23356672c36SAndreas Gohr /** 234153400c7SAndreas Gohr * Get the pages known to struct and their assignment state 235153400c7SAndreas Gohr * 236153400c7SAndreas Gohr * @param null|string $schema limit results to the given schema 237153400c7SAndreas Gohr * @param bool $assignedonly limit results to currently assigned only 238153400c7SAndreas Gohr * @return array 239153400c7SAndreas Gohr */ 240153400c7SAndreas Gohr public function getPages($schema = null, $assignedonly = false) { 241153400c7SAndreas Gohr $sql = 'SELECT pid, tbl, assigned FROM schema_assignments WHERE 1=1'; 242153400c7SAndreas Gohr 243153400c7SAndreas Gohr $opts = array(); 244153400c7SAndreas Gohr if($schema) { 245153400c7SAndreas Gohr $sql .= ' AND tbl = ?'; 246153400c7SAndreas Gohr $opts[] = $schema; 247153400c7SAndreas Gohr } 248153400c7SAndreas Gohr if($assignedonly) { 249153400c7SAndreas Gohr $sql .= ' AND assigned = 1'; 250153400c7SAndreas Gohr } 251153400c7SAndreas Gohr 252153400c7SAndreas Gohr $sql .= ' ORDER BY pid, tbl'; 253153400c7SAndreas Gohr 254153400c7SAndreas Gohr $res = $this->sqlite->query($sql, $opts); 255153400c7SAndreas Gohr $list = $this->sqlite->res2arr($res); 256153400c7SAndreas Gohr $this->sqlite->res_close($res); 257153400c7SAndreas Gohr 258153400c7SAndreas Gohr $result = array(); 259153400c7SAndreas Gohr foreach($list as $row) { 260153400c7SAndreas Gohr $pid = $row['pid']; 261153400c7SAndreas Gohr $tbl = $row['tbl']; 262153400c7SAndreas Gohr if(!isset($result[$pid])) $result[$pid] = array(); 263153400c7SAndreas Gohr $result[$pid][$tbl] = (bool) $row['assigned']; 264153400c7SAndreas Gohr } 265153400c7SAndreas Gohr 266153400c7SAndreas Gohr return $result; 267153400c7SAndreas Gohr } 268153400c7SAndreas Gohr 269153400c7SAndreas Gohr /** 270ed60c3b3SAndreas Gohr * Check if the given pattern matches the given page 271ed60c3b3SAndreas Gohr * 272ed60c3b3SAndreas Gohr * @param string $pattern the pattern to check against 273ed60c3b3SAndreas Gohr * @param string $page the cleaned pageid to check 274ed60c3b3SAndreas Gohr * @param string|null $pns optimization, the colon wrapped namespace of the page, set null for automatic 275ed60c3b3SAndreas Gohr * @return bool 276ed60c3b3SAndreas Gohr */ 277ed60c3b3SAndreas Gohr protected function matchPagePattern($pattern, $page, $pns = null) { 2780e9e058fSAndreas Gohr if(trim($pattern,':') == '**') return true; // match all 2790e9e058fSAndreas Gohr 2809914e87eSAndreas Gohr // regex patterns 2819914e87eSAndreas Gohr if($pattern{0} == '/') { 2829914e87eSAndreas Gohr return (bool) preg_match($pattern, ":$page"); 2839914e87eSAndreas Gohr } 2849914e87eSAndreas Gohr 285ed60c3b3SAndreas Gohr if(is_null($pns)) { 286ed60c3b3SAndreas Gohr $pns = ':' . getNS($page) . ':'; 287ed60c3b3SAndreas Gohr } 288ed60c3b3SAndreas Gohr 289ed60c3b3SAndreas Gohr $ans = ':' . cleanID($pattern) . ':'; 290ed60c3b3SAndreas Gohr if(substr($pattern, -2) == '**') { 291ed60c3b3SAndreas Gohr // upper namespaces match 292ed60c3b3SAndreas Gohr if(strpos($pns, $ans) === 0) { 293ed60c3b3SAndreas Gohr return true; 294ed60c3b3SAndreas Gohr } 295ed60c3b3SAndreas Gohr } else if(substr($pattern, -1) == '*') { 296ed60c3b3SAndreas Gohr // namespaces match exact 297ed60c3b3SAndreas Gohr if($ans == $pns) { 298ed60c3b3SAndreas Gohr return true; 299ed60c3b3SAndreas Gohr } 300ed60c3b3SAndreas Gohr } else { 301ed60c3b3SAndreas Gohr // exact match 302ed60c3b3SAndreas Gohr if(cleanID($pattern) == $page) { 303ed60c3b3SAndreas Gohr return true; 304ed60c3b3SAndreas Gohr } 305ed60c3b3SAndreas Gohr } 306ed60c3b3SAndreas Gohr 307ed60c3b3SAndreas Gohr return false; 308ed60c3b3SAndreas Gohr } 309ed60c3b3SAndreas Gohr 310ed60c3b3SAndreas Gohr /** 31156672c36SAndreas Gohr * Returns all tables of schemas that existed and stored data for the page back then 31256672c36SAndreas Gohr * 3130e9e058fSAndreas Gohr * @deprecated because we're always only interested in the current state of affairs, even when restoring. 31456672c36SAndreas Gohr * 31556672c36SAndreas Gohr * @param string $page 31656672c36SAndreas Gohr * @param string $ts 31756672c36SAndreas Gohr * @return array 31856672c36SAndreas Gohr */ 31956672c36SAndreas Gohr public function getHistoricAssignments($page, $ts) { 32056672c36SAndreas Gohr $sql = "SELECT DISTINCT tbl FROM schemas WHERE ts <= ? ORDER BY ts DESC"; 32156672c36SAndreas Gohr $res = $this->sqlite->query($sql, $ts); 32256672c36SAndreas Gohr $tables = $this->sqlite->res2arr($res); 32356672c36SAndreas Gohr $this->sqlite->res_close($res); 32456672c36SAndreas Gohr 32556672c36SAndreas Gohr $assigned = array(); 32656672c36SAndreas Gohr foreach($tables as $row) { 32756672c36SAndreas Gohr $table = $row['tbl']; 328ed60c3b3SAndreas Gohr /** @noinspection SqlResolve */ 32956672c36SAndreas Gohr $sql = "SELECT pid FROM data_$table WHERE pid = ? AND rev <= ? LIMIT 1"; 33056672c36SAndreas Gohr $res = $this->sqlite->query($sql, $page, $ts); 33156672c36SAndreas Gohr $found = $this->sqlite->res2arr($res); 33256672c36SAndreas Gohr $this->sqlite->res_close($res); 33356672c36SAndreas Gohr 33456672c36SAndreas Gohr if($found) $assigned[] = $table; 33556672c36SAndreas Gohr } 33656672c36SAndreas Gohr 33756672c36SAndreas Gohr return $assigned; 33856672c36SAndreas Gohr } 339fb31ca9fSAndreas Gohr} 340