1<?php 2 3namespace dokuwiki\plugin\structpublish\meta; 4 5/** 6 * Class Assignments 7 * 8 * Manages the assignment of users to pages and namespaces 9 * This is a singleton. Assignment data is only loaded once per request. 10 * 11 * @see \dokuwiki\plugin\struct\meta\Assignments 12 */ 13class Assignments 14{ 15 /** @var \helper_plugin_sqlite|null */ 16 protected $sqlite; 17 18 /** @var array All the assignments patterns */ 19 protected $patterns; 20 21 /** @var Assignments */ 22 protected static $instance = null; 23 24 /** 25 * Get the singleton instance of the Assignments 26 * 27 * @param bool $forcereload create a new instace to reload the assignment data 28 * @return Assignments 29 */ 30 public static function getInstance($forcereload = false) 31 { 32 if (is_null(self::$instance) or $forcereload) { 33 $class = get_called_class(); 34 self::$instance = new $class(); 35 } 36 return self::$instance; 37 } 38 39 /** 40 * Assignments constructor. 41 * 42 * Not public. Use Assignments::getInstance() instead 43 */ 44 protected function __construct() 45 { 46 /** @var \helper_plugin_structpublish_db $helper */ 47 $helper = plugin_load('helper', 'struct_db'); 48 $this->sqlite = $helper->getDB(); 49 50 $this->loadPatterns(); 51 } 52 53 /** 54 * Load existing assignment patterns 55 */ 56 protected function loadPatterns() 57 { 58 $sql = 'SELECT * FROM structpublish_assignments_patterns ORDER BY pattern'; 59 $res = $this->sqlite->query($sql); 60 $this->patterns = $this->sqlite->res2arr($res); 61 $this->sqlite->res_close($res); 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 $res = $this->sqlite->query($sql, [$user, $status]); 122 $pagerows = $this->sqlite->res2arr($res); 123 $this->sqlite->res_close($res); 124 125 // reevalute the pages and unassign when needed 126 foreach ($pagerows as $row) { 127 $rules = $this->getPageAssignments($row['pid'], false); 128 // remove assignments matching the rule 129 foreach ($rules as $status => $users) { 130 foreach ($users as $user) { 131 $this->deassignPage($row['pid'], $user, $status); 132 } 133 } 134 } 135 136 return $ok; 137 } 138 139 /** 140 * Updates all assignments of a given page against the current patterns 141 * 142 * @param string $pid 143 */ 144 public function updatePageAssignments($pid, $reload = false) 145 { 146 if ($reload) { 147 $this->loadPatterns(); 148 } 149 $rules = $this->getPageAssignments($pid, true); 150 151 foreach ($rules as $status => $users) { 152 foreach ($users as $user) { 153 $this->assignPage($pid, $user, $status); 154 } 155 } 156 157 // FIXME reevalute existing assignments for exclusion 158 } 159 160 /** 161 * Clear all patterns - deassigns all pages 162 * 163 * This is mostly useful for testing and not used in the interface currently 164 * 165 * @param bool $full fully delete all previous assignments 166 * @return bool 167 */ 168 public function clear($full = false) 169 { 170 $sql = 'DELETE FROM structpublish_assignments_patterns'; 171 $ok = (bool) $this->sqlite->query($sql); 172 173 if ($full) { 174 $sql = 'DELETE FROM structpublish_assignments'; 175 } else { 176 $sql = 'UPDATE structpublish_assignments SET assigned = 0'; 177 } 178 $ok = $ok && (bool) $this->sqlite->query($sql); 179 180 // reload patterns 181 $this->loadPatterns(); 182 183 return $ok; 184 } 185 186 /** 187 * Add page to assignments 188 * 189 * @param string $page 190 * @param string $user 191 * @param string $status 192 * @return bool 193 */ 194 public function assignPage($page, $user = null, $status = null) 195 { 196 $sql = 'REPLACE INTO structpublish_assignments (pid, user, status, assigned) VALUES (?, ?, ?, 1)'; 197 return (bool) $this->sqlite->query($sql, [$page, $user, $status]); 198 } 199 200 /** 201 * Remove page from assignments 202 * 203 * @param string $page 204 * @param string $user 205 * @return bool 206 */ 207 public function deassignPage($page, $user, $status) 208 { 209 $sql = 'REPLACE INTO structpublish_assignments (pid, user, status, assigned) VALUES (?, ?, ?, 0)'; 210 return (bool) $this->sqlite->query($sql, [$page, $user, $status]); 211 } 212 213 /** 214 * Get the whole pattern table 215 * 216 * @return array 217 */ 218 public function getAllPatterns() 219 { 220 return $this->patterns; 221 } 222 223 /** 224 * Returns a list of user/group string lists per status assigned to the given page 225 * 226 * @param string $page 227 * @param bool $checkpatterns Should the current patterns be re-evaluated? 228 * @return array users assigned [role => [user, ...], ...] 229 */ 230 public function getPageAssignments($page, $checkpatterns = true) 231 { 232 $rules = []; 233 $page = cleanID($page); 234 235 if ($checkpatterns) { 236 $helper = plugin_load('helper', 'structpublish_assignments'); 237 // evaluate patterns 238 $pns = ':' . getNS($page) . ':'; 239 foreach ($this->patterns as $row) { 240 if ($helper->matchPagePattern($row['pattern'], $page, $pns)) { 241 $rules[$row['status']][] = $row['user']; 242 } 243 } 244 } else { 245 // just select 246 $sql = 'SELECT user, status FROM structpublish_assignments WHERE pid = ? AND assigned = 1'; 247 $res = $this->sqlite->query($sql, [$page]); 248 $list = $this->sqlite->res2arr($res); 249 $this->sqlite->res_close($res); 250 foreach ($list as $row) { 251 $rules[$row['status']][] = $row['user']; 252 } 253 } 254 255 return $rules; 256 } 257 258 /** 259 * Get the pages known to struct and their assignment state 260 * 261 * @param bool $assignedonly limit results to currently assigned only 262 * @return array 263 */ 264 public function getPages($assignedOnly = false) 265 { 266 $sql = 'SELECT pid, user, status, assigned FROM structpublish_assignments WHERE 1=1'; 267 268 $opts = array(); 269 270 if ($assignedOnly) { 271 $sql .= ' AND assigned = 1'; 272 } 273 274 $sql .= ' ORDER BY pid, user, status'; 275 276 $res = $this->sqlite->query($sql, $opts); 277 $list = $this->sqlite->res2arr($res); 278 $this->sqlite->res_close($res); 279 280 $result = array(); 281 foreach ($list as $row) { 282 $pid = $row['pid']; 283 $user = $row['user']; 284 $status = $row['status']; 285 if (!isset($result[$pid])) { 286 $result[$pid] = array(); 287 } 288 $result[$pid][$user][$status] = (bool) $row['assigned']; 289 } 290 291 return $result; 292 } 293 294 /** 295 * @return \helper_plugin_sqlite|null 296 */ 297 public function getSqlite() 298 { 299 return $this->sqlite; 300 } 301} 302