1<?php 2 3namespace dokuwiki\plugin\structpublish\meta; 4 5use dokuwiki\plugin\sqlite\SQLiteDB; 6 7/** 8 * Class Assignments 9 * 10 * Manages the assignment of users to pages and namespaces 11 * This is a singleton. Assignment data is only loaded once per request. 12 * 13 * @see \dokuwiki\plugin\struct\meta\Assignments 14 */ 15class Assignments 16{ 17 /** @var SQLiteDB */ 18 protected $sqlite; 19 20 /** @var array All the assignments patterns */ 21 protected $patterns; 22 23 /** @var Assignments */ 24 protected static $instance = null; 25 26 /** 27 * Get the singleton instance of the Assignments 28 * 29 * @param bool $forcereload create a new instance to reload the assignment data 30 * @return Assignments 31 */ 32 public static function getInstance($forcereload = false) 33 { 34 if (is_null(self::$instance) or $forcereload) { 35 $class = get_called_class(); 36 self::$instance = new $class(); 37 } 38 return self::$instance; 39 } 40 41 /** 42 * Assignments constructor. 43 * 44 * Not public. Use Assignments::getInstance() instead 45 */ 46 protected function __construct() 47 { 48 /** @var \helper_plugin_structpublish_db $helper */ 49 $helper = plugin_load('helper', 'struct_db'); 50 $this->sqlite = $helper->getDB(); 51 52 $this->loadPatterns(); 53 } 54 55 /** 56 * Load existing assignment patterns 57 */ 58 protected function loadPatterns() 59 { 60 $sql = 'SELECT * FROM structpublish_assignments_patterns ORDER BY pattern'; 61 $this->patterns = $this->sqlite->queryAll($sql); 62 } 63 64 /** 65 * Add a new assignment pattern to the pattern table 66 * 67 * @param string $pattern 68 * @param string $user 69 * @param string $status 70 * @return bool 71 */ 72 public function addPattern($pattern, $user, $status) 73 { 74 // add the pattern 75 $sql = 'REPLACE INTO structpublish_assignments_patterns (pattern, user, status) VALUES (?,?,?)'; 76 $ok = (bool) $this->sqlite->query($sql, [$pattern, $user, $status]); 77 78 // reload patterns 79 $this->loadPatterns(); 80 81 // update assignments 82 // fetch known pages 83 /** @var \helper_plugin_structpublish_db $dbHelper */ 84 $dbHelper = plugin_load('helper', 'structpublish_db'); 85 $pids = $dbHelper->getPages(); 86 87 // wrap in transaction 88 $this->sqlite->query('BEGIN TRANSACTION'); 89 90 foreach ($pids as $pid) { 91 $this->updatePageAssignments($pid); 92 } 93 94 $ok = $ok && $this->sqlite->query('COMMIT TRANSACTION'); 95 if (!$ok) { 96 $this->sqlite->query('ROLLBACK TRANSACTION'); 97 } 98 99 return $ok; 100 } 101 102 /** 103 * Remove an existing assignment pattern from the pattern table 104 * 105 * @param string $pattern 106 * @param string $user 107 * @param string $status 108 * @return bool 109 */ 110 public function removePattern($pattern, $user, $status) 111 { 112 // remove the pattern 113 $sql = 'DELETE FROM structpublish_assignments_patterns WHERE pattern = ? AND user = ? AND status = ?'; 114 $ok = (bool) $this->sqlite->query($sql, [$pattern, $user, $status]); 115 116 // reload patterns 117 $this->loadPatterns(); 118 119 // fetch possibly affected pages 120 $sql = 'SELECT pid FROM structpublish_assignments WHERE user = ? AND status = ?'; 121 $pagerows = $this->sqlite->queryAll($sql, [$user, $status]); 122 123 // remove page assignments matching the pattern being removed 124 $ok = true; 125 foreach ($pagerows as $row) { 126 $ok = $ok && $this->deassignPage($row['pid'], $user, $status); 127 } 128 129 return $ok; 130 } 131 132 /** 133 * Updates all assignments of a given page against the current patterns 134 * 135 * @param string $pid 136 */ 137 public function updatePageAssignments($pid, $reload = false) 138 { 139 if ($reload) { 140 $this->loadPatterns(); 141 } 142 $rules = $this->getPageAssignments($pid, true); 143 144 foreach ($rules as $status => $users) { 145 foreach ($users as $user) { 146 $this->assignPage($pid, $user, $status); 147 } 148 } 149 150 // FIXME reevalute existing assignments for exclusion 151 } 152 153 /** 154 * Clear all patterns - deassigns all pages 155 * 156 * This is mostly useful for testing and not used in the interface currently 157 * 158 * @param bool $full fully delete all previous assignments 159 * @return bool 160 */ 161 public function clear($full = false) 162 { 163 $sql = 'DELETE FROM structpublish_assignments_patterns'; 164 $ok = (bool) $this->sqlite->query($sql); 165 166 if ($full) { 167 $sql = 'DELETE FROM structpublish_assignments'; 168 } else { 169 $sql = 'UPDATE structpublish_assignments SET assigned = 0'; 170 } 171 $ok = $ok && (bool) $this->sqlite->query($sql); 172 173 // reload patterns 174 $this->loadPatterns(); 175 176 return $ok; 177 } 178 179 /** 180 * Add page to assignments 181 * 182 * @param string $page 183 * @param string $user 184 * @param string $status 185 * @return bool 186 */ 187 public function assignPage($page, $user = null, $status = null) 188 { 189 $sql = 'REPLACE INTO structpublish_assignments (pid, user, status, assigned) VALUES (?, ?, ?, 1)'; 190 return (bool) $this->sqlite->query($sql, [$page, $user, $status]); 191 } 192 193 /** 194 * Remove page from assignments 195 * 196 * @param string $page 197 * @param string $user 198 * @return bool 199 */ 200 public function deassignPage($page, $user, $status) 201 { 202 $sql = 'UPDATE structpublish_assignments SET assigned = 0 WHERE pid = ? AND user = ? AND status = ?'; 203 return (bool) $this->sqlite->query($sql, [$page, $user, $status]); 204 } 205 206 /** 207 * Get the whole pattern table 208 * 209 * @return array 210 */ 211 public function getAllPatterns() 212 { 213 return $this->patterns; 214 } 215 216 /** 217 * Returns a list of user/group string lists per status assigned to the given page 218 * 219 * @param string $page 220 * @param bool $checkpatterns Should the current patterns be re-evaluated? 221 * @return array users assigned [role => [user, ...], ...] 222 */ 223 public function getPageAssignments($page, $checkpatterns = true) 224 { 225 $rules = []; 226 $page = cleanID($page); 227 228 if ($checkpatterns) { 229 $helper = plugin_load('helper', 'structpublish_assignments'); 230 // evaluate patterns 231 $pns = ':' . getNS($page) . ':'; 232 foreach ($this->patterns as $row) { 233 if ($helper->matchPagePattern($row['pattern'], $page, $pns)) { 234 $rules[$row['status']][] = $row['user']; 235 } 236 } 237 } else { 238 // just select 239 $sql = 'SELECT user, status FROM structpublish_assignments WHERE pid = ? AND assigned = 1'; 240 $list = $this->sqlite->queryAll($sql, [$page]); 241 foreach ($list as $row) { 242 $rules[$row['status']][] = $row['user']; 243 } 244 } 245 246 return $rules; 247 } 248 249 /** 250 * Get the pages known to struct and their assignment state 251 * 252 * @param bool $assignedonly limit results to currently assigned only 253 * @return array 254 */ 255 public function getPages($assignedOnly = false) 256 { 257 $sql = 'SELECT pid, user, status, assigned FROM structpublish_assignments WHERE 1=1'; 258 259 $opts = array(); 260 261 if ($assignedOnly) { 262 $sql .= ' AND assigned = 1'; 263 } 264 265 $sql .= ' ORDER BY pid, user, status'; 266 267 $list = $this->sqlite->queryAll($sql, $opts); 268 269 $result = array(); 270 foreach ($list as $row) { 271 $pid = $row['pid']; 272 $user = $row['user']; 273 $status = $row['status']; 274 if (!isset($result[$pid])) { 275 $result[$pid] = array(); 276 } 277 $result[$pid][$user][$status] = (bool) $row['assigned']; 278 } 279 280 return $result; 281 } 282 283 /** 284 * @return SQLiteDB 285 */ 286 public function getSqlite() 287 { 288 return $this->sqlite; 289 } 290} 291