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 20fb31ca9fSAndreas Gohr /** 21fb31ca9fSAndreas Gohr * Assignments constructor. 22fb31ca9fSAndreas Gohr */ 23fb31ca9fSAndreas Gohr public function __construct() { 24fb31ca9fSAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 25fb31ca9fSAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 26fb31ca9fSAndreas Gohr $this->sqlite = $helper->getDB(); 27fb31ca9fSAndreas Gohr 2833d7be6aSAndreas Gohr if($this->sqlite) $this->loadPatterns(); 29fb31ca9fSAndreas Gohr } 30fb31ca9fSAndreas Gohr 31fb31ca9fSAndreas Gohr /** 3249d38573SAndreas Gohr * Load existing assignment patterns 33fb31ca9fSAndreas Gohr */ 3433d7be6aSAndreas Gohr protected function loadPatterns() { 3549d38573SAndreas Gohr $sql = 'SELECT * FROM schema_assignments_patterns ORDER BY pattern'; 36fb31ca9fSAndreas Gohr $res = $this->sqlite->query($sql); 3749d38573SAndreas Gohr $this->patterns = $this->sqlite->res2arr($res); 38fb31ca9fSAndreas Gohr $this->sqlite->res_close($res); 39fb31ca9fSAndreas Gohr } 40fb31ca9fSAndreas Gohr 41fb31ca9fSAndreas Gohr /** 4249d38573SAndreas Gohr * Add a new assignment pattern to the pattern table 431a8d1235SAndreas Gohr * 4449d38573SAndreas Gohr * @param string $pattern 451a8d1235SAndreas Gohr * @param string $table 461a8d1235SAndreas Gohr * @return bool 471a8d1235SAndreas Gohr */ 4833d7be6aSAndreas Gohr public function addPattern($pattern, $table) { 49ed60c3b3SAndreas Gohr // add the pattern 5049d38573SAndreas Gohr $sql = 'REPLACE INTO schema_assignments_patterns (pattern, tbl) VALUES (?,?)'; 51ed60c3b3SAndreas Gohr $ok = (bool) $this->sqlite->query($sql, array($pattern, $table)); 52ed60c3b3SAndreas Gohr 53ed60c3b3SAndreas Gohr // reload patterns 54ed60c3b3SAndreas Gohr $this->loadPatterns(); 55*b25bb9feSMichael Grosse $this->propagatePageAssignments($table); 56ed60c3b3SAndreas Gohr 57ed60c3b3SAndreas Gohr 58ed60c3b3SAndreas Gohr return $ok; 591a8d1235SAndreas Gohr } 601a8d1235SAndreas Gohr 611a8d1235SAndreas Gohr /** 6249d38573SAndreas Gohr * Remove an existing assignment pattern from the pattern table 631a8d1235SAndreas Gohr * 6449d38573SAndreas Gohr * @param string $pattern 651a8d1235SAndreas Gohr * @param string $table 661a8d1235SAndreas Gohr * @return bool 671a8d1235SAndreas Gohr */ 6833d7be6aSAndreas Gohr public function removePattern($pattern, $table) { 69ed60c3b3SAndreas Gohr // remove the pattern 7049d38573SAndreas Gohr $sql = 'DELETE FROM schema_assignments_patterns WHERE pattern = ? AND tbl = ?'; 71ed60c3b3SAndreas Gohr $ok = (bool) $this->sqlite->query($sql, array($pattern, $table)); 72ed60c3b3SAndreas Gohr 73ed60c3b3SAndreas Gohr // reload patterns 74ed60c3b3SAndreas Gohr $this->loadPatterns(); 75ed60c3b3SAndreas Gohr 76ed60c3b3SAndreas Gohr // fetch possibly affected pages 77ed60c3b3SAndreas Gohr $sql = 'SELECT pid FROM schema_assignments WHERE tbl = ?'; 78ed60c3b3SAndreas Gohr $res = $this->sqlite->query($sql, $table); 790e9e058fSAndreas Gohr $pagerows = $this->sqlite->res2arr($res); 80ed60c3b3SAndreas Gohr $this->sqlite->res_close($res); 81ed60c3b3SAndreas Gohr 82ed60c3b3SAndreas Gohr // reevalute the pages and unassign when needed 830e9e058fSAndreas Gohr foreach($pagerows as $row) { 84be94e9d9SAndreas Gohr $tables = $this->getPageAssignments($row['pid'], true); 85ed60c3b3SAndreas Gohr if(!in_array($table, $tables)) { 86be94e9d9SAndreas Gohr $this->deassignPageSchema($row['pid'], $table); 87ed60c3b3SAndreas Gohr } 88ed60c3b3SAndreas Gohr } 89ed60c3b3SAndreas Gohr 90ed60c3b3SAndreas Gohr return $ok; 91ed60c3b3SAndreas Gohr } 92ed60c3b3SAndreas Gohr 93ed60c3b3SAndreas Gohr /** 940173e75dSAndreas Gohr * Rechecks all assignments of a given page against the current patterns 950173e75dSAndreas Gohr * 960173e75dSAndreas Gohr * @param string $pid 970173e75dSAndreas Gohr */ 980173e75dSAndreas Gohr public function reevaluatePageAssignments($pid) { 990173e75dSAndreas Gohr // reload patterns 1000173e75dSAndreas Gohr $this->loadPatterns(); 1010173e75dSAndreas Gohr $tables = $this->getPageAssignments($pid, true); 1020173e75dSAndreas Gohr 1030173e75dSAndreas Gohr // fetch possibly affected tables 1040173e75dSAndreas Gohr $sql = 'SELECT tbl FROM schema_assignments WHERE pid = ?'; 1050173e75dSAndreas Gohr $res = $this->sqlite->query($sql, $pid); 1060173e75dSAndreas Gohr $tablerows = $this->sqlite->res2arr($res); 1070173e75dSAndreas Gohr $this->sqlite->res_close($res); 1080173e75dSAndreas Gohr 1090173e75dSAndreas Gohr // reevalute the tables and apply assignments 1100173e75dSAndreas Gohr foreach($tablerows as $row) { 1110173e75dSAndreas Gohr if(in_array($row['tbl'], $tables)) { 1120173e75dSAndreas Gohr $this->assignPageSchema($pid, $row['tbl']); 1130173e75dSAndreas Gohr } else { 1140173e75dSAndreas Gohr $this->deassignPageSchema($pid, $row['tbl']); 1150173e75dSAndreas Gohr } 1160173e75dSAndreas Gohr } 1170173e75dSAndreas Gohr } 1180173e75dSAndreas Gohr 1190173e75dSAndreas Gohr /** 1200e9e058fSAndreas Gohr * Clear all patterns - deassigns all pages 1210e9e058fSAndreas Gohr * 1220e9e058fSAndreas Gohr * This is mostly useful for testing and not used in the interface currently 1230e9e058fSAndreas Gohr * 124153400c7SAndreas Gohr * @param bool $full fully delete all previous assignments 1250e9e058fSAndreas Gohr * @return bool 1260e9e058fSAndreas Gohr */ 127153400c7SAndreas Gohr public function clear($full=false) { 1280e9e058fSAndreas Gohr $sql = 'DELETE FROM schema_assignments_patterns'; 1290e9e058fSAndreas Gohr $ok = (bool) $this->sqlite->query($sql); 1300e9e058fSAndreas Gohr 131153400c7SAndreas Gohr if($full) { 132153400c7SAndreas Gohr $sql = 'DELETE FROM schema_assignments'; 133153400c7SAndreas Gohr } else { 1340e9e058fSAndreas Gohr $sql = 'UPDATE schema_assignments SET assigned = 0'; 135153400c7SAndreas Gohr } 1360e9e058fSAndreas Gohr $ok = $ok && (bool) $this->sqlite->query($sql); 1370e9e058fSAndreas Gohr 1380e9e058fSAndreas Gohr // reload patterns 1390e9e058fSAndreas Gohr $this->loadPatterns(); 1400e9e058fSAndreas Gohr 1410e9e058fSAndreas Gohr return $ok; 1420e9e058fSAndreas Gohr } 1430e9e058fSAndreas Gohr 1440e9e058fSAndreas Gohr /** 145ed60c3b3SAndreas Gohr * Add page to assignments 146ed60c3b3SAndreas Gohr * 147ed60c3b3SAndreas Gohr * @param string $page 148ed60c3b3SAndreas Gohr * @param string $table 149ed60c3b3SAndreas Gohr * @return bool 150ed60c3b3SAndreas Gohr */ 151ed713594SAndreas Gohr public function assignPageSchema($page, $table) { 152ed60c3b3SAndreas Gohr $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 1)'; 153ed60c3b3SAndreas Gohr return (bool) $this->sqlite->query($sql, array($page, $table)); 154ed60c3b3SAndreas Gohr } 155ed60c3b3SAndreas Gohr 156ed60c3b3SAndreas Gohr /** 157ed60c3b3SAndreas Gohr * Remove page from assignments 158ed60c3b3SAndreas Gohr * 159ed60c3b3SAndreas Gohr * @param string $page 160ed60c3b3SAndreas Gohr * @param string $table 161ed60c3b3SAndreas Gohr * @return bool 162ed60c3b3SAndreas Gohr */ 163ed713594SAndreas Gohr public function deassignPageSchema($page, $table) { 164ed60c3b3SAndreas Gohr $sql = 'REPLACE INTO schema_assignments (pid, tbl, assigned) VALUES (?, ?, 0)'; 165ed60c3b3SAndreas Gohr return (bool) $this->sqlite->query($sql, array($page, $table)); 1661a8d1235SAndreas Gohr } 1671a8d1235SAndreas Gohr 1681a8d1235SAndreas Gohr /** 16949d38573SAndreas Gohr * Get the whole pattern table 1701a8d1235SAndreas Gohr * 1711a8d1235SAndreas Gohr * @return array 1721a8d1235SAndreas Gohr */ 17333d7be6aSAndreas Gohr public function getAllPatterns() { 17449d38573SAndreas Gohr return $this->patterns; 1751a8d1235SAndreas Gohr } 1761a8d1235SAndreas Gohr 1771a8d1235SAndreas Gohr /** 178fb31ca9fSAndreas Gohr * Returns a list of table names assigned to the given page 179fb31ca9fSAndreas Gohr * 180fb31ca9fSAndreas Gohr * @param string $page 1819ff81b7fSAndreas Gohr * @param bool $checkpatterns Should the current patterns be re-evaluated? 1829ff81b7fSAndreas Gohr * @return \string[] tables assigned 183fb31ca9fSAndreas Gohr */ 1849ff81b7fSAndreas Gohr public function getPageAssignments($page, $checkpatterns=true) { 185fb31ca9fSAndreas Gohr $tables = array(); 186fb31ca9fSAndreas Gohr $page = cleanID($page); 187fb31ca9fSAndreas Gohr 1889ff81b7fSAndreas Gohr if($checkpatterns) { 1899ff81b7fSAndreas Gohr // evaluate patterns 1909ff81b7fSAndreas Gohr $pns = ':' . getNS($page) . ':'; 19149d38573SAndreas Gohr foreach($this->patterns as $row) { 192ed60c3b3SAndreas Gohr if($this->matchPagePattern($row['pattern'], $page, $pns)) { 193ed60c3b3SAndreas Gohr $tables[] = $row['tbl']; 194fb31ca9fSAndreas Gohr } 195fb31ca9fSAndreas Gohr } 1969ff81b7fSAndreas Gohr } else { 1979ff81b7fSAndreas Gohr // just select 1989ff81b7fSAndreas Gohr $sql = 'SELECT tbl FROM schema_assignments WHERE pid = ? AND assigned = 1'; 1999ff81b7fSAndreas Gohr $res = $this->sqlite->query($sql, array($page)); 2009ff81b7fSAndreas Gohr $list = $this->sqlite->res2arr($res); 2019ff81b7fSAndreas Gohr $this->sqlite->res_close($res); 2029ff81b7fSAndreas Gohr foreach($list as $row) { 2039ff81b7fSAndreas Gohr $tables[] = $row['tbl']; 2049ff81b7fSAndreas Gohr } 2059ff81b7fSAndreas Gohr } 206fb31ca9fSAndreas Gohr 207fb31ca9fSAndreas Gohr return array_unique($tables); 208fb31ca9fSAndreas Gohr } 20956672c36SAndreas Gohr 21056672c36SAndreas Gohr /** 211153400c7SAndreas Gohr * Get the pages known to struct and their assignment state 212153400c7SAndreas Gohr * 213153400c7SAndreas Gohr * @param null|string $schema limit results to the given schema 214153400c7SAndreas Gohr * @param bool $assignedonly limit results to currently assigned only 215153400c7SAndreas Gohr * @return array 216153400c7SAndreas Gohr */ 217153400c7SAndreas Gohr public function getPages($schema = null, $assignedonly = false) { 218153400c7SAndreas Gohr $sql = 'SELECT pid, tbl, assigned FROM schema_assignments WHERE 1=1'; 219153400c7SAndreas Gohr 220153400c7SAndreas Gohr $opts = array(); 221153400c7SAndreas Gohr if($schema) { 222153400c7SAndreas Gohr $sql .= ' AND tbl = ?'; 223153400c7SAndreas Gohr $opts[] = $schema; 224153400c7SAndreas Gohr } 225153400c7SAndreas Gohr if($assignedonly) { 226153400c7SAndreas Gohr $sql .= ' AND assigned = 1'; 227153400c7SAndreas Gohr } 228153400c7SAndreas Gohr 229153400c7SAndreas Gohr $sql .= ' ORDER BY pid, tbl'; 230153400c7SAndreas Gohr 231153400c7SAndreas Gohr $res = $this->sqlite->query($sql, $opts); 232153400c7SAndreas Gohr $list = $this->sqlite->res2arr($res); 233153400c7SAndreas Gohr $this->sqlite->res_close($res); 234153400c7SAndreas Gohr 235153400c7SAndreas Gohr $result = array(); 236153400c7SAndreas Gohr foreach($list as $row) { 237153400c7SAndreas Gohr $pid = $row['pid']; 238153400c7SAndreas Gohr $tbl = $row['tbl']; 239153400c7SAndreas Gohr if(!isset($result[$pid])) $result[$pid] = array(); 240153400c7SAndreas Gohr $result[$pid][$tbl] = (bool) $row['assigned']; 241153400c7SAndreas Gohr } 242153400c7SAndreas Gohr 243153400c7SAndreas Gohr return $result; 244153400c7SAndreas Gohr } 245153400c7SAndreas Gohr 246153400c7SAndreas Gohr /** 247ed60c3b3SAndreas Gohr * Check if the given pattern matches the given page 248ed60c3b3SAndreas Gohr * 249ed60c3b3SAndreas Gohr * @param string $pattern the pattern to check against 250ed60c3b3SAndreas Gohr * @param string $page the cleaned pageid to check 251ed60c3b3SAndreas Gohr * @param string|null $pns optimization, the colon wrapped namespace of the page, set null for automatic 252ed60c3b3SAndreas Gohr * @return bool 253ed60c3b3SAndreas Gohr */ 254ed60c3b3SAndreas Gohr protected function matchPagePattern($pattern, $page, $pns = null) { 2550e9e058fSAndreas Gohr if(trim($pattern,':') == '**') return true; // match all 2560e9e058fSAndreas Gohr 2579914e87eSAndreas Gohr // regex patterns 2589914e87eSAndreas Gohr if($pattern{0} == '/') { 2599914e87eSAndreas Gohr return (bool) preg_match($pattern, ":$page"); 2609914e87eSAndreas Gohr } 2619914e87eSAndreas Gohr 262ed60c3b3SAndreas Gohr if(is_null($pns)) { 263ed60c3b3SAndreas Gohr $pns = ':' . getNS($page) . ':'; 264ed60c3b3SAndreas Gohr } 265ed60c3b3SAndreas Gohr 266ed60c3b3SAndreas Gohr $ans = ':' . cleanID($pattern) . ':'; 267ed60c3b3SAndreas Gohr if(substr($pattern, -2) == '**') { 268ed60c3b3SAndreas Gohr // upper namespaces match 269ed60c3b3SAndreas Gohr if(strpos($pns, $ans) === 0) { 270ed60c3b3SAndreas Gohr return true; 271ed60c3b3SAndreas Gohr } 272ed60c3b3SAndreas Gohr } else if(substr($pattern, -1) == '*') { 273ed60c3b3SAndreas Gohr // namespaces match exact 274ed60c3b3SAndreas Gohr if($ans == $pns) { 275ed60c3b3SAndreas Gohr return true; 276ed60c3b3SAndreas Gohr } 277ed60c3b3SAndreas Gohr } else { 278ed60c3b3SAndreas Gohr // exact match 279ed60c3b3SAndreas Gohr if(cleanID($pattern) == $page) { 280ed60c3b3SAndreas Gohr return true; 281ed60c3b3SAndreas Gohr } 282ed60c3b3SAndreas Gohr } 283ed60c3b3SAndreas Gohr 284ed60c3b3SAndreas Gohr return false; 285ed60c3b3SAndreas Gohr } 286ed60c3b3SAndreas Gohr 287ed60c3b3SAndreas Gohr /** 28856672c36SAndreas Gohr * Returns all tables of schemas that existed and stored data for the page back then 28956672c36SAndreas Gohr * 2900e9e058fSAndreas Gohr * @deprecated because we're always only interested in the current state of affairs, even when restoring. 29156672c36SAndreas Gohr * 29256672c36SAndreas Gohr * @param string $page 29356672c36SAndreas Gohr * @param string $ts 29456672c36SAndreas Gohr * @return array 29556672c36SAndreas Gohr */ 29656672c36SAndreas Gohr public function getHistoricAssignments($page, $ts) { 29756672c36SAndreas Gohr $sql = "SELECT DISTINCT tbl FROM schemas WHERE ts <= ? ORDER BY ts DESC"; 29856672c36SAndreas Gohr $res = $this->sqlite->query($sql, $ts); 29956672c36SAndreas Gohr $tables = $this->sqlite->res2arr($res); 30056672c36SAndreas Gohr $this->sqlite->res_close($res); 30156672c36SAndreas Gohr 30256672c36SAndreas Gohr $assigned = array(); 30356672c36SAndreas Gohr foreach($tables as $row) { 30456672c36SAndreas Gohr $table = $row['tbl']; 305ed60c3b3SAndreas Gohr /** @noinspection SqlResolve */ 30656672c36SAndreas Gohr $sql = "SELECT pid FROM data_$table WHERE pid = ? AND rev <= ? LIMIT 1"; 30756672c36SAndreas Gohr $res = $this->sqlite->query($sql, $page, $ts); 30856672c36SAndreas Gohr $found = $this->sqlite->res2arr($res); 30956672c36SAndreas Gohr $this->sqlite->res_close($res); 31056672c36SAndreas Gohr 31156672c36SAndreas Gohr if($found) $assigned[] = $table; 31256672c36SAndreas Gohr } 31356672c36SAndreas Gohr 31456672c36SAndreas Gohr return $assigned; 31556672c36SAndreas Gohr } 316*b25bb9feSMichael Grosse 317*b25bb9feSMichael Grosse /** 318*b25bb9feSMichael Grosse * fetch all pages where the schema isn't assigned, yet and reevaluate the page assignments for those pages and assign when needed 319*b25bb9feSMichael Grosse * 320*b25bb9feSMichael Grosse * @param $table 321*b25bb9feSMichael Grosse */ 322*b25bb9feSMichael Grosse public function propagatePageAssignments($table) { 323*b25bb9feSMichael Grosse $sql = 'SELECT pid FROM schema_assignments WHERE tbl != ? OR assigned != 1'; 324*b25bb9feSMichael Grosse $res = $this->sqlite->query($sql, $table); 325*b25bb9feSMichael Grosse $pagerows = $this->sqlite->res2arr($res); 326*b25bb9feSMichael Grosse $this->sqlite->res_close($res); 327*b25bb9feSMichael Grosse 328*b25bb9feSMichael Grosse foreach ($pagerows as $row) { 329*b25bb9feSMichael Grosse $tables = $this->getPageAssignments($row['pid'], true); 330*b25bb9feSMichael Grosse if (in_array($table, $tables)) { 331*b25bb9feSMichael Grosse $this->assignPageSchema($row['pid'], $table); 332*b25bb9feSMichael Grosse } 333*b25bb9feSMichael Grosse } 334*b25bb9feSMichael Grosse } 335fb31ca9fSAndreas Gohr} 336