1<?php 2/** 3 * DokuWiki Plugin groupmgr (Syntax Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Alex Forencich <alex@alexforencich.com> 7 * 8 * Syntax: 9 * ~~GROUPMGR|[groups to manage]|[allowed users and groups]~~ 10 * 11 * Examples: 12 * ~~GROUPMGR|posters|@moderators~~ 13 * Members of group 'posters' can be managed by group 'moderators' 14 * 15 * ~~GROUPMGR|groupa, groupb|joe, @admin~~ 16 * Members of groups 'groupa' and 'groupb' can be managed by user 'joe' 17 * members of the 'admin' group 18 * 19 * Note: superuser groups can only be managed by super users, 20 * forbidden groups can be configured, 21 * and users cannot remove themselves from the group that lets them access 22 * the group manager (including admins) 23 * 24 * Note: if require_conf_namespace config option is set, then plugin looks in 25 * conf_namespace:$ID for configuration. Plugin will also check config 26 * namespace if a placeholder tag is used (~~GROUPMGR~~). This is the 27 * default configuration for security reasons. 28 * 29 */ 30 31// must be run within Dokuwiki 32if (!defined('DOKU_INC')) die(); 33 34if (!defined('DOKU_LF')) define('DOKU_LF', "\n"); 35if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t"); 36if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 37 38require_once DOKU_PLUGIN.'syntax.php'; 39 40function remove_item_by_value($val, $arr, $preserve = true) { 41 if (empty($arr) || !is_array($arr)) { return false; } 42 foreach(array_keys($arr,$val) as $key){ unset($arr[$key]); } 43 return ($preserve) ? $arr : array_values($arr); 44} 45 46class syntax_plugin_groupmgr extends DokuWiki_Syntax_Plugin { 47 /** 48 * Plugin information 49 */ 50 function getInfo(){ 51 return array( 52 'author' => 'Alex Forencich', 53 'email' => 'alex@alexforencich.com', 54 'date' => '2010-11-28', 55 'name' => 'Group Manager Syntax plugin', 56 'desc' => 'Embeddable group manager', 57 'url' => 'http://www.alexforencich.com/' 58 ); 59 } 60 61 /** 62 * Plugin type 63 */ 64 function getType() { 65 return 'substition'; 66 } 67 68 /** 69 * PType 70 */ 71 function getPType() { 72 return 'normal'; 73 } 74 75 /** 76 * Sort order 77 */ 78 function getSort() { 79 return 160; 80 } 81 82 /** 83 * Register syntax handler 84 */ 85 function connectTo($mode) { 86 $this->Lexer->addSpecialPattern('~~GROUPMGR\|[^~]*?~~',$mode,'plugin_groupmgr'); 87 $this->Lexer->addSpecialPattern('~~GROUPMGR~~',$mode,'plugin_groupmgr'); 88 } 89 90 /** 91 * Handle match 92 */ 93 function handle($match, $state, $pos, &$handler){ 94 $data = array(null, $state, $pos); 95 96 if (strlen($match) == 12) 97 return $data; 98 99 // Strip away tag 100 $match = substr($match, 11, -2); 101 102 // split arguments 103 $ar = explode("|", $match); 104 105 $match = array(); 106 107 // reorganize into array 108 foreach ($ar as $it) { 109 $ar2 = explode(",", $it); 110 foreach ($ar2 as &$it2) 111 $it2 = trim($it2); 112 $match[] = $ar2; 113 } 114 115 // pass to render method 116 $data[0] = $match; 117 118 return $data; 119 } 120 121 /** 122 * Render it 123 */ 124 function render($mode, &$renderer, $data) { 125 global $auth; 126 global $lang; 127 global $INFO; 128 global $conf; 129 global $ID; 130 131 // we are parsing a submitted comment... 132 if (isset($_REQUEST['comment'])) 133 return false; 134 135 // disable caching 136 $renderer->info['cache'] = false; 137 138 $this->setupLocale(); 139 140 if (!method_exists($auth,"retrieveUsers")) return false; 141 142 if ($mode == 'xhtml') { 143 // need config namespace? 144 if ($this->getConf('require_conf_namespace')) { 145 // set it to null, it will be reloaded anyway 146 $data[0] = null; 147 } 148 149 $conf_namespace = $this->getConf('conf_namespace'); 150 151 // empty tag? 152 if (is_null($data[0]) || count($data[0]) == 0) { 153 // load from conf namespace 154 // build page name 155 $conf_page = ""; 156 if (substr($ID, 0, strlen($conf_namespace)) != $conf_namespace) { 157 $conf_page .= $conf_namespace; 158 if (substr($conf_page, -1) != ':') $conf_page .= ":"; 159 } 160 $conf_page .= $ID; 161 162 // get file name 163 $fn = wikiFN($conf_page); 164 165 if (!file_exists($fn)) 166 return false; 167 168 // read file 169 $page = file_get_contents($fn); 170 171 // find config tag 172 $i = preg_match('/~~GROUPMGR\|[^~]*?~~/', $page, &$match); 173 174 if ($i == 0) 175 return false; 176 177 // parse config 178 $match = substr($match[0], 11, -2); 179 180 $ar = explode("|", $match); 181 $match = array(); 182 183 // reorganize into array 184 foreach ($ar as $it) { 185 $ar2 = explode(",", $it); 186 foreach ($ar2 as &$it2) 187 $it2 = trim($it2); 188 $match[] = $ar2; 189 } 190 191 // pass to render method 192 $data[0] = $match; 193 } 194 195 // don't render if an argument hasn't been specified 196 if (!isset($data[0][0]) || !isset($data[0][1])) 197 return false; 198 199 $grplst = $data[0][0]; 200 $authlst = $data[0][1]; 201 202 // parse forbidden groups 203 $forbiddengrplst = array(); 204 $str = $this->getConf('forbidden_groups'); 205 if (isset($str)) { 206 $arr = explode(",", $str); 207 foreach ($arr as $val) { 208 $val = trim($val); 209 $forbiddengrplst[] = $val; 210 } 211 } 212 213 // parse admin groups 214 $admingrplst = array(); 215 if (isset($conf['superuser'])) { 216 $arr = explode(",", $conf['superuser']); 217 foreach ($arr as $val) { 218 $val = trim($val); 219 if ($val[0] == "@") { 220 $val = substr($val, 1); 221 $admingrplst[] = $val; 222 } 223 } 224 } 225 226 // forbid admin groups if user is not a superuser 227 if (!$INFO['isadmin']) { 228 foreach ($admingrplst as $val) { 229 $forbiddengrplst[] = $val; 230 } 231 } 232 233 // remove forbidden groups from group list 234 foreach ($forbiddengrplst as $val) { 235 $grplst = remove_item_by_value($val, $grplst, false); 236 } 237 238 // build array of user's credentials 239 $check = array($_SERVER['REMOTE_USER']); 240 if (is_array($INFO['userinfo'])) { 241 foreach ($INFO['userinfo']['grps'] as $val) { 242 $check[] = "@" . $val; 243 } 244 } 245 246 // does user have permission? 247 // Also, save authenticated group for later 248 $authbygrp = ""; 249 $ok = 0; 250 foreach ($authlst as $val) { 251 if (in_array($val, $check)) { 252 $ok = 1; 253 if ($val[0] == "@") { 254 $authbygrp = substr($val, 1); 255 } 256 } 257 } 258 259 // continue if user has explicit permission or is an admin 260 if ($INFO['isadmin'] || $ok) { 261 // authorized 262 $status = 0; 263 264 // nab user info 265 $users = $auth->retrieveUsers(0, 0, array()); 266 267 // open form 268 $renderer->doc .= "<form method=\"post\" action=\"" . htmlspecialchars($_SERVER['REQUEST_URI']) 269 . "\" name=\"groupmgr\" enctype=\"application/x-www-form-urlencoded\">"; 270 271 // open table and print header 272 $renderer->doc .= "<table class=\"inline\">\n"; 273 $renderer->doc .= " <tbody>\n"; 274 $renderer->doc .= " <tr>\n"; 275 $renderer->doc .= " <th>" . $lang['user'] . "</th>\n"; 276 $renderer->doc .= " <th>" . $lang['fullname'] . "</th>\n"; 277 $renderer->doc .= " <th>" . $lang['email'] . "</th>\n"; 278 // loop through available groups 279 foreach ($grplst as $g) { 280 $renderer->doc .= " <th>" . htmlspecialchars($g) . "</th>\n"; 281 } 282 $renderer->doc .= " </tr>\n"; 283 284 // loop through users 285 foreach ($users as $name => $u) { 286 // print user info 287 $renderer->doc .= " <tr>\n"; 288 $renderer->doc .= " <td>" . htmlspecialchars($name); 289 // need tag so user isn't pulled out of a group if it was added 290 // between initial page load and update 291 // use MD5 hash to take care of formatting issues 292 $hn = md5($name); 293 $renderer->doc .= "<input type=\"hidden\" name=\"id_" . $hn . "\" value=\"1\" />"; 294 $renderer->doc .= "</td>\n"; 295 $renderer->doc .= " <td>" . htmlspecialchars($u['name']) . "</td>\n"; 296 $renderer->doc .= " <td>"; 297 $renderer->emaillink($u['mail']); 298 $renderer->doc .= "</td>\n"; 299 // loop through groups 300 foreach ($grplst as $g) { 301 $renderer->doc .= " <td>"; 302 303 $chk = "chk_" . $hn . "_" . md5($g); 304 305 // does this box need to be disabled? 306 // prevents user from taking himself out of an important group 307 $disabled = 0; 308 // if this box applies to a current group membership of the current user, continue check 309 if (in_array($g, $u['grps']) && $_SERVER['REMOTE_USER'] == $name) { 310 // if user is an admin and group is an admin group, disable 311 if ($INFO['isadmin'] && in_array($g, $admingrplst)) { 312 $disabled = 1; 313 // if user was authenticated by this group, disable 314 } else if (strlen($authbygrp) > 0 && $g == $authbygrp) { 315 $disabled = 1; 316 } 317 } 318 319 // update user group membership 320 // only update if something changed 321 // keep track of status 322 $update = array(); 323 if (!$disabled && $_POST["id_" . $hn]) { 324 if ($_POST[$chk]) { 325 if (!in_array($g, $u['grps'])) { 326 $u['grps'][] = $g; 327 $update['grps'] = $u['grps']; 328 } 329 } else { 330 if (in_array($g, $u['grps'])) { 331 $u['grps'] = remove_item_by_value($g, $u['grps'], false); 332 $update['grps'] = $u['grps']; 333 } 334 } 335 if (count($update) > 0) { 336 if ($auth->modifyUser($name, $update)) { 337 if ($status == 0) $status = 1; 338 } else { 339 $status = 2; 340 } 341 } 342 } 343 344 // display check box 345 $renderer->doc .= "<input type=\"checkbox\" name=\"" . $chk . "\""; 346 if (in_array($g, $u['grps'])) { 347 $renderer->doc .= " checked=\"true\""; 348 } 349 if ($disabled) { 350 $renderer->doc .= " disabled=\"true\""; 351 } 352 353 $renderer->doc .= " />"; 354 355 $renderer->doc .= "</td>\n"; 356 } 357 $renderer->doc .= " </tr>\n"; 358 } 359 360 $renderer->doc .= " </tbody>\n"; 361 $renderer->doc .= "</table>\n"; 362 363 // update button 364 $renderer->doc .= "<div><input class=\"button\" type=\"submit\" value=\"" . $lang['btn_update'] . "\" /></div>"; 365 366 $renderer->doc .= "</form>"; 367 368 // display relevant status message 369 if ($status == 1) { 370 msg($this->lang['updatesuccess'], 1); 371 } else if ($status == 2) { 372 msg($this->lang['updatefailed'], -1); 373 } 374 375 } else { 376 // not authorized 377 $renderer->doc .= "<p>" . $this->lang['notauthorized'] . "</p>\n"; 378 } 379 380 return true; 381 } 382 return false; 383 } 384} 385 386// vim:ts=4:sw=4:et:enc=utf-8: 387