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 * Load existing assignment patterns 54 */ 55 protected function loadPatterns() 56 { 57 $sql = 'SELECT * FROM structpublish_assignments_patterns ORDER BY pattern'; 58 $res = $this->sqlite->query($sql); 59 $this->patterns = $this->sqlite->res2arr($res); 60 $this->sqlite->res_close($res); 61 } 62 63 /** 64 * Add a new assignment pattern to the pattern table 65 * 66 * @param string $pattern 67 * @param string $user 68 * @param string $status 69 * @return bool 70 */ 71 public function addPattern($pattern, $user, $status) 72 { 73 // add the pattern 74 $sql = 'REPLACE INTO structpublish_assignments_patterns (pattern, user, status) VALUES (?,?,?)'; 75 $ok = (bool)$this->sqlite->query($sql, [$pattern, $user, $status]); 76 77 // reload patterns 78 $this->loadPatterns(); 79 80 // FIXME how to update / propagate assignments? 81 82 return $ok; 83 } 84 85 /** 86 * Remove an existing assignment pattern from the pattern table 87 * 88 * @param string $pattern 89 * @param string $user 90 * @param string $status 91 * @return bool 92 */ 93 public function removePattern($pattern, $user, $status) 94 { 95 // remove the pattern 96 $sql = 'DELETE FROM structpublish_assignments_patterns WHERE pattern = ? AND user = ? AND status = ?'; 97 $ok = (bool)$this->sqlite->query($sql, [$pattern, $user, $status]); 98 99 // reload patterns 100 $this->loadPatterns(); 101 102 // fetch possibly affected pages 103 $sql = 'SELECT pid FROM structpublish_assignments WHERE user = ? AND status = ?'; 104 $res = $this->sqlite->query($sql, [$user, $status]); 105 $pagerows = $this->sqlite->res2arr($res); 106 $this->sqlite->res_close($res); 107 108 // reevalute the pages and unassign when needed 109 foreach ($pagerows as $row) { 110 $rules = $this->getPageAssignments($row['pid'], true); 111 // remove assignments matching the rule 112 foreach ($rules as $status => $users) { 113 foreach ($users as $user) { 114 $this->deassignPage($row['pid'], $user, $status); 115 } 116 } 117 } 118 119 return $ok; 120 } 121 122 /** 123 * Updates all assignments of a given page against the current patterns 124 * 125 * @param string $pid 126 */ 127 public function updatePageAssignments($pid) 128 { 129 // reload patterns 130 $this->loadPatterns(); 131 $rules = $this->getPageAssignments($pid, true); 132 133 foreach ($rules as $status => $users) { 134 foreach ($users as $user) { 135 $this->assignPage($pid, $user, $status); 136 } 137 } 138 139 // fetch known pages 140 /** @var \helper_plugin_structpublish_db $helper */ 141 $helper = plugin_load('helper', 'structpublish_db'); 142 $pages = $helper->getPages($pid); 143 144 // FIXME reevalute existing assignments 145 } 146 147 /** 148 * Clear all patterns - deassigns all pages 149 * 150 * This is mostly useful for testing and not used in the interface currently 151 * 152 * @param bool $full fully delete all previous assignments 153 * @return bool 154 */ 155 public function clear($full = false) 156 { 157 $sql = 'DELETE FROM structpublish_assignments_patterns'; 158 $ok = (bool)$this->sqlite->query($sql); 159 160 if ($full) { 161 $sql = 'DELETE FROM structpublish_assignments'; 162 } else { 163 $sql = 'UPDATE structpublish_assignments SET assigned = 0'; 164 } 165 $ok = $ok && (bool)$this->sqlite->query($sql); 166 167 // reload patterns 168 $this->loadPatterns(); 169 170 return $ok; 171 } 172 173 /** 174 * Add page to assignments 175 * 176 * @param string $page 177 * @param string $user 178 * @param string $status 179 * @return bool 180 */ 181 public function assignPage($page, $user = null, $status = null) 182 { 183 $sql = 'REPLACE INTO structpublish_assignments (pid, user, status, assigned) VALUES (?, ?, ?, 1)'; 184 return (bool)$this->sqlite->query($sql, [$page, $user, $status]); 185 } 186 187 /** 188 * Remove page from assignments 189 * 190 * @param string $page 191 * @param string $user 192 * @return bool 193 */ 194 public function deassignPage($page, $user, $status) 195 { 196 $sql = 'REPLACE INTO structpublish_assignments (pid, user, status, assigned) VALUES (?, ?, ?, 0)'; 197 return (bool)$this->sqlite->query($sql, [$page, $user, $status]); 198 } 199 200 /** 201 * Get the whole pattern table 202 * 203 * @return array 204 */ 205 public function getAllPatterns() 206 { 207 return $this->patterns; 208 } 209 210 /** 211 * Returns a list of users per status assigned to the given page 212 * 213 * @param string $page 214 * @param bool $checkpatterns Should the current patterns be re-evaluated? 215 * @return array users assigned 216 */ 217 public function getPageAssignments($page, $checkpatterns = true) 218 { 219 $rules = []; 220 $page = cleanID($page); 221 222 if ($checkpatterns) { 223 $helper = plugin_load('helper', 'structpublish_assignments'); 224 // evaluate patterns 225 $pns = ':' . getNS($page) . ':'; 226 foreach ($this->patterns as $row) { 227 if ($helper->matchPagePattern($row['pattern'], $page, $pns)) { 228 $rules[$row['status']][] = $row['user']; 229 } 230 } 231 } else { 232 // just select 233 $sql = 'SELECT user, status FROM structpublish_assignments WHERE pid = ? AND assigned = 1'; 234 $res = $this->sqlite->query($sql, [$page]); 235 $list = $this->sqlite->res2arr($res); 236 $this->sqlite->res_close($res); 237 foreach ($list as $row) { 238 $rules[$row['status']][] = $row['user']; 239 } 240 } 241 242 return $rules; 243 } 244 245 /** 246 * Get the pages known to struct and their assignment state 247 * 248 * @param bool $assignedonly limit results to currently assigned only 249 * @return array 250 */ 251 public function getPages($assignedOnly = false) 252 { 253 $sql = 'SELECT pid, user, status, assigned FROM structpublish_assignments WHERE 1=1'; 254 255 $opts = array(); 256 257 if ($assignedOnly) { 258 $sql .= ' AND assigned = 1'; 259 } 260 261 $sql .= ' ORDER BY pid, user, status'; 262 263 $res = $this->sqlite->query($sql, $opts); 264 $list = $this->sqlite->res2arr($res); 265 $this->sqlite->res_close($res); 266 267 $result = array(); 268 foreach ($list as $row) { 269 $pid = $row['pid']; 270 $user = $row['user']; 271 $status = $row['status']; 272 if (!isset($result[$pid])) $result[$pid] = array(); 273 $result[$pid][$user][$status] = (bool)$row['assigned']; 274 } 275 276 return $result; 277 } 278} 279