Complex classes like admin_plugin_acl often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use admin_plugin_acl, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class admin_plugin_acl extends DokuWiki_Admin_Plugin { |
||
18 | var $acl = null; |
||
19 | var $ns = null; |
||
20 | /** |
||
21 | * The currently selected item, associative array with id and type. |
||
22 | * Populated from (in this order): |
||
23 | * $_REQUEST['current_ns'] |
||
24 | * $_REQUEST['current_id'] |
||
25 | * $ns |
||
26 | * $ID |
||
27 | */ |
||
28 | var $current_item = null; |
||
29 | var $who = ''; |
||
30 | var $usersgroups = array(); |
||
31 | var $specials = array(); |
||
32 | |||
33 | /** |
||
34 | * return prompt for admin menu |
||
35 | */ |
||
36 | function getMenuText($language) { |
||
39 | |||
40 | /** |
||
41 | * return sort order for position in admin menu |
||
42 | */ |
||
43 | function getMenuSort() { |
||
46 | |||
47 | /** |
||
48 | * handle user request |
||
49 | * |
||
50 | * Initializes internal vars and handles modifications |
||
51 | * |
||
52 | * @author Andreas Gohr <[email protected]> |
||
53 | */ |
||
54 | function handle() { |
||
55 | global $AUTH_ACL; |
||
56 | global $ID; |
||
57 | global $auth; |
||
58 | global $config_cascade; |
||
59 | global $INPUT; |
||
60 | |||
61 | // fresh 1:1 copy without replacements |
||
62 | $AUTH_ACL = file($config_cascade['acl']['default']); |
||
63 | |||
64 | // namespace given? |
||
65 | if($INPUT->str('ns') == '*'){ |
||
66 | $this->ns = '*'; |
||
67 | }else{ |
||
68 | $this->ns = cleanID($INPUT->str('ns')); |
||
69 | } |
||
70 | |||
71 | if ($INPUT->str('current_ns')) { |
||
72 | $this->current_item = array('id' => cleanID($INPUT->str('current_ns')), 'type' => 'd'); |
||
73 | } elseif ($INPUT->str('current_id')) { |
||
74 | $this->current_item = array('id' => cleanID($INPUT->str('current_id')), 'type' => 'f'); |
||
75 | } elseif ($this->ns) { |
||
76 | $this->current_item = array('id' => $this->ns, 'type' => 'd'); |
||
77 | } else { |
||
78 | $this->current_item = array('id' => $ID, 'type' => 'f'); |
||
79 | } |
||
80 | |||
81 | // user or group choosen? |
||
82 | $who = trim($INPUT->str('acl_w')); |
||
83 | if($INPUT->str('acl_t') == '__g__' && $who){ |
||
84 | $this->who = '@'.ltrim($auth->cleanGroup($who),'@'); |
||
85 | }elseif($INPUT->str('acl_t') == '__u__' && $who){ |
||
86 | $this->who = ltrim($who,'@'); |
||
87 | if($this->who != '%USER%' && $this->who != '%GROUP%'){ #keep wildcard as is |
||
88 | $this->who = $auth->cleanUser($this->who); |
||
89 | } |
||
90 | }elseif($INPUT->str('acl_t') && |
||
91 | $INPUT->str('acl_t') != '__u__' && |
||
92 | $INPUT->str('acl_t') != '__g__'){ |
||
93 | $this->who = $INPUT->str('acl_t'); |
||
94 | }elseif($who){ |
||
95 | $this->who = $who; |
||
96 | } |
||
97 | |||
98 | // handle modifications |
||
99 | if($INPUT->has('cmd') && checkSecurityToken()){ |
||
100 | $cmd = $INPUT->extract('cmd')->str('cmd'); |
||
101 | |||
102 | // scope for modifications |
||
103 | if($this->ns){ |
||
104 | if($this->ns == '*'){ |
||
105 | $scope = '*'; |
||
106 | }else{ |
||
107 | $scope = $this->ns.':*'; |
||
108 | } |
||
109 | }else{ |
||
110 | $scope = $ID; |
||
111 | } |
||
112 | |||
113 | if($cmd == 'save' && $scope && $this->who && $INPUT->has('acl')){ |
||
114 | // handle additions or single modifications |
||
115 | $this->_acl_del($scope, $this->who); |
||
116 | $this->_acl_add($scope, $this->who, $INPUT->int('acl')); |
||
117 | }elseif($cmd == 'del' && $scope && $this->who){ |
||
118 | // handle single deletions |
||
119 | $this->_acl_del($scope, $this->who); |
||
120 | }elseif($cmd == 'update'){ |
||
121 | $acl = $INPUT->arr('acl'); |
||
122 | |||
123 | // handle update of the whole file |
||
124 | foreach($INPUT->arr('del') as $where => $names){ |
||
125 | // remove all rules marked for deletion |
||
126 | foreach($names as $who) |
||
127 | unset($acl[$where][$who]); |
||
128 | } |
||
129 | // prepare lines |
||
130 | $lines = array(); |
||
131 | // keep header |
||
132 | foreach($AUTH_ACL as $line){ |
||
133 | if($line{0} == '#'){ |
||
134 | $lines[] = $line; |
||
135 | }else{ |
||
136 | break; |
||
137 | } |
||
138 | } |
||
139 | // re-add all rules |
||
140 | foreach($acl as $where => $opt){ |
||
141 | foreach($opt as $who => $perm){ |
||
142 | if ($who[0]=='@') { |
||
143 | if ($who!='@ALL') { |
||
144 | $who = '@'.ltrim($auth->cleanGroup($who),'@'); |
||
145 | } |
||
146 | } elseif ($who != '%USER%' && $who != '%GROUP%'){ #keep wildcard as is |
||
147 | $who = $auth->cleanUser($who); |
||
148 | } |
||
149 | $who = auth_nameencode($who,true); |
||
150 | $lines[] = "$where\t$who\t$perm\n"; |
||
151 | } |
||
152 | } |
||
153 | // save it |
||
154 | io_saveFile($config_cascade['acl']['default'], join('',$lines)); |
||
155 | } |
||
156 | |||
157 | // reload ACL config |
||
158 | $AUTH_ACL = file($config_cascade['acl']['default']); |
||
159 | } |
||
160 | |||
161 | // initialize ACL array |
||
162 | $this->_init_acl_config(); |
||
163 | } |
||
164 | |||
165 | /** |
||
166 | * ACL Output function |
||
167 | * |
||
168 | * print a table with all significant permissions for the |
||
169 | * current id |
||
170 | * |
||
171 | * @author Frank Schubert <[email protected]> |
||
172 | * @author Andreas Gohr <[email protected]> |
||
173 | */ |
||
174 | function html() { |
||
175 | echo '<div id="acl_manager">'.NL; |
||
176 | echo '<h1>'.$this->getLang('admin_acl').'</h1>'.NL; |
||
177 | echo '<div class="level1">'.NL; |
||
178 | |||
179 | echo '<div id="acl__tree">'.NL; |
||
180 | $this->_html_explorer(); |
||
181 | echo '</div>'.NL; |
||
182 | |||
183 | echo '<div id="acl__detail">'.NL; |
||
184 | $this->_html_detail(); |
||
185 | echo '</div>'.NL; |
||
186 | echo '</div>'.NL; |
||
187 | |||
188 | echo '<div class="clearer"></div>'; |
||
189 | echo '<h2>'.$this->getLang('current').'</h2>'.NL; |
||
190 | echo '<div class="level2">'.NL; |
||
191 | $this->_html_table(); |
||
192 | echo '</div>'.NL; |
||
193 | |||
194 | echo '<div class="footnotes"><div class="fn">'.NL; |
||
195 | echo '<sup><a id="fn__1" class="fn_bot" href="#fnt__1">1)</a></sup>'.NL; |
||
196 | echo $this->getLang('p_include'); |
||
197 | echo '</div></div>'; |
||
198 | |||
199 | echo '</div>'.NL; |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * returns array with set options for building links |
||
204 | * |
||
205 | * @author Andreas Gohr <[email protected]> |
||
206 | */ |
||
207 | function _get_opts($addopts=null){ |
||
218 | |||
219 | /** |
||
220 | * Display a tree menu to select a page or namespace |
||
221 | * |
||
222 | * @author Andreas Gohr <[email protected]> |
||
223 | */ |
||
224 | function _html_explorer(){ |
||
225 | global $conf; |
||
226 | global $ID; |
||
227 | global $lang; |
||
228 | |||
229 | $ns = $this->ns; |
||
230 | if(empty($ns)){ |
||
231 | $ns = dirname(str_replace(':','/',$ID)); |
||
232 | if($ns == '.') $ns =''; |
||
233 | }elseif($ns == '*'){ |
||
234 | $ns =''; |
||
235 | } |
||
236 | $ns = utf8_encodeFN(str_replace(':','/',$ns)); |
||
237 | |||
238 | $data = $this->_get_tree($ns); |
||
239 | |||
240 | // wrap a list with the root level around the other namespaces |
||
241 | array_unshift($data, array( 'level' => 0, 'id' => '*', 'type' => 'd', |
||
242 | 'open' =>'true', 'label' => '['.$lang['mediaroot'].']')); |
||
243 | |||
244 | echo html_buildlist($data,'acl', |
||
245 | array($this,'_html_list_acl'), |
||
246 | array($this,'_html_li_acl')); |
||
247 | |||
248 | } |
||
249 | |||
250 | /** |
||
251 | * get a combined list of media and page files |
||
252 | * |
||
253 | * @param string $folder an already converted filesystem folder of the current namespace |
||
254 | * @param string $limit limit the search to this folder |
||
255 | */ |
||
256 | function _get_tree($folder,$limit=''){ |
||
257 | global $conf; |
||
258 | |||
259 | // read tree structure from pages and media |
||
260 | $data = array(); |
||
261 | search($data,$conf['datadir'],'search_index',array('ns' => $folder),$limit); |
||
262 | $media = array(); |
||
263 | search($media,$conf['mediadir'],'search_index',array('ns' => $folder, 'nofiles' => true),$limit); |
||
264 | $data = array_merge($data,$media); |
||
265 | unset($media); |
||
266 | |||
267 | // combine by sorting and removing duplicates |
||
268 | usort($data,array($this,'_tree_sort')); |
||
269 | $count = count($data); |
||
270 | if($count>0) for($i=1; $i<$count; $i++){ |
||
271 | if($data[$i-1]['id'] == $data[$i]['id'] && $data[$i-1]['type'] == $data[$i]['type']) { |
||
272 | unset($data[$i]); |
||
273 | $i++; // duplicate found, next $i can't be a duplicate, so skip forward one |
||
274 | } |
||
275 | } |
||
276 | return $data; |
||
277 | } |
||
278 | |||
279 | /** |
||
280 | * usort callback |
||
281 | * |
||
282 | * Sorts the combined trees of media and page files |
||
283 | */ |
||
284 | function _tree_sort($a,$b){ |
||
285 | // handle the trivial cases first |
||
286 | if ($a['id'] == '') return -1; |
||
287 | if ($b['id'] == '') return 1; |
||
288 | // split up the id into parts |
||
289 | $a_ids = explode(':', $a['id']); |
||
290 | $b_ids = explode(':', $b['id']); |
||
291 | // now loop through the parts |
||
292 | while (count($a_ids) && count($b_ids)) { |
||
293 | // compare each level from upper to lower |
||
294 | // until a non-equal component is found |
||
295 | $cur_result = strcmp(array_shift($a_ids), array_shift($b_ids)); |
||
296 | if ($cur_result) { |
||
297 | // if one of the components is the last component and is a file |
||
298 | // and the other one is either of a deeper level or a directory, |
||
299 | // the file has to come after the deeper level or directory |
||
300 | if (empty($a_ids) && $a['type'] == 'f' && (count($b_ids) || $b['type'] == 'd')) return 1; |
||
301 | if (empty($b_ids) && $b['type'] == 'f' && (count($a_ids) || $a['type'] == 'd')) return -1; |
||
302 | return $cur_result; |
||
303 | } |
||
304 | } |
||
305 | // The two ids seem to be equal. One of them might however refer |
||
306 | // to a page, one to a namespace, the namespace needs to be first. |
||
307 | if (empty($a_ids) && empty($b_ids)) { |
||
308 | if ($a['type'] == $b['type']) return 0; |
||
309 | if ($a['type'] == 'f') return 1; |
||
310 | return -1; |
||
311 | } |
||
312 | // Now the empty part is either a page in the parent namespace |
||
313 | // that obviously needs to be after the namespace |
||
314 | // Or it is the namespace that contains the other part and should be |
||
315 | // before that other part. |
||
316 | if (empty($a_ids)) return ($a['type'] == 'd') ? -1 : 1; |
||
317 | if (empty($b_ids)) return ($b['type'] == 'd') ? 1 : -1; |
||
318 | } |
||
319 | |||
320 | /** |
||
321 | * Display the current ACL for selected where/who combination with |
||
322 | * selectors and modification form |
||
323 | * |
||
324 | * @author Andreas Gohr <[email protected]> |
||
325 | */ |
||
326 | function _html_detail(){ |
||
327 | global $ID; |
||
328 | |||
329 | echo '<form action="'.wl().'" method="post" accept-charset="utf-8"><div class="no">'.NL; |
||
330 | |||
331 | echo '<div id="acl__user">'; |
||
332 | echo $this->getLang('acl_perms').' '; |
||
333 | $inl = $this->_html_select(); |
||
334 | echo '<input type="text" name="acl_w" class="edit" value="'.(($inl)?'':hsc(ltrim($this->who,'@'))).'" />'.NL; |
||
335 | echo '<button type="submit">'.$this->getLang('btn_select').'</button>'.NL; |
||
336 | echo '</div>'.NL; |
||
337 | |||
338 | echo '<div id="acl__info">'; |
||
339 | $this->_html_info(); |
||
340 | echo '</div>'; |
||
341 | |||
342 | echo '<input type="hidden" name="ns" value="'.hsc($this->ns).'" />'.NL; |
||
343 | echo '<input type="hidden" name="id" value="'.hsc($ID).'" />'.NL; |
||
344 | echo '<input type="hidden" name="do" value="admin" />'.NL; |
||
345 | echo '<input type="hidden" name="page" value="acl" />'.NL; |
||
346 | echo '<input type="hidden" name="sectok" value="'.getSecurityToken().'" />'.NL; |
||
347 | echo '</div></form>'.NL; |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * Print info and editor |
||
352 | */ |
||
353 | function _html_info(){ |
||
375 | |||
376 | /** |
||
377 | * Display the ACL editor |
||
378 | * |
||
379 | * @author Andreas Gohr <[email protected]> |
||
380 | */ |
||
381 | function _html_acleditor($current){ |
||
382 | global $lang; |
||
383 | |||
384 | echo '<fieldset>'; |
||
385 | if(is_null($current)){ |
||
386 | echo '<legend>'.$this->getLang('acl_new').'</legend>'; |
||
387 | }else{ |
||
388 | echo '<legend>'.$this->getLang('acl_mod').'</legend>'; |
||
389 | } |
||
390 | |||
391 | echo $this->_html_checkboxes($current,empty($this->ns),'acl'); |
||
392 | |||
393 | if(is_null($current)){ |
||
394 | echo '<button type="submit" name="cmd[save]">'.$lang['btn_save'].'</button>'.NL; |
||
395 | }else{ |
||
396 | echo '<button type="submit" name="cmd[save]">'.$lang['btn_update'].'</button>'.NL; |
||
397 | echo '<button type="submit" name="cmd[del]">'.$lang['btn_delete'].'</button>'.NL; |
||
398 | } |
||
399 | |||
400 | echo '</fieldset>'; |
||
401 | } |
||
402 | |||
403 | /** |
||
404 | * Explain the currently set permissions in plain english/$lang |
||
405 | * |
||
406 | * @author Andreas Gohr <[email protected]> |
||
407 | */ |
||
408 | function _html_explain($current){ |
||
409 | global $ID; |
||
410 | global $auth; |
||
411 | |||
412 | $who = $this->who; |
||
413 | $ns = $this->ns; |
||
414 | |||
415 | // prepare where to check |
||
416 | if($ns){ |
||
417 | if($ns == '*'){ |
||
418 | $check='*'; |
||
419 | }else{ |
||
420 | $check=$ns.':*'; |
||
421 | } |
||
422 | }else{ |
||
423 | $check = $ID; |
||
424 | } |
||
425 | |||
426 | // prepare who to check |
||
427 | if($who{0} == '@'){ |
||
428 | $user = ''; |
||
429 | $groups = array(ltrim($who,'@')); |
||
430 | }else{ |
||
431 | $user = $who; |
||
432 | $info = $auth->getUserData($user); |
||
433 | if($info === false){ |
||
434 | $groups = array(); |
||
435 | }else{ |
||
436 | $groups = $info['grps']; |
||
437 | } |
||
438 | } |
||
439 | |||
440 | // check the permissions |
||
441 | $perm = auth_aclcheck($check,$user,$groups); |
||
442 | |||
443 | // build array of named permissions |
||
444 | $names = array(); |
||
445 | if($perm){ |
||
446 | if($ns){ |
||
447 | if($perm >= AUTH_DELETE) $names[] = $this->getLang('acl_perm16'); |
||
448 | if($perm >= AUTH_UPLOAD) $names[] = $this->getLang('acl_perm8'); |
||
449 | if($perm >= AUTH_CREATE) $names[] = $this->getLang('acl_perm4'); |
||
450 | } |
||
451 | if($perm >= AUTH_EDIT) $names[] = $this->getLang('acl_perm2'); |
||
452 | if($perm >= AUTH_READ) $names[] = $this->getLang('acl_perm1'); |
||
453 | $names = array_reverse($names); |
||
454 | }else{ |
||
455 | $names[] = $this->getLang('acl_perm0'); |
||
456 | } |
||
457 | |||
458 | // print permission explanation |
||
459 | echo '<p>'; |
||
460 | if($user){ |
||
461 | if($ns){ |
||
462 | printf($this->getLang('p_user_ns'),hsc($who),hsc($ns),join(', ',$names)); |
||
463 | }else{ |
||
464 | printf($this->getLang('p_user_id'),hsc($who),hsc($ID),join(', ',$names)); |
||
465 | } |
||
466 | }else{ |
||
467 | if($ns){ |
||
468 | printf($this->getLang('p_group_ns'),hsc(ltrim($who,'@')),hsc($ns),join(', ',$names)); |
||
469 | }else{ |
||
470 | printf($this->getLang('p_group_id'),hsc(ltrim($who,'@')),hsc($ID),join(', ',$names)); |
||
471 | } |
||
472 | } |
||
473 | echo '</p>'; |
||
474 | |||
475 | // add note if admin |
||
476 | if($perm == AUTH_ADMIN){ |
||
477 | echo '<p>'.$this->getLang('p_isadmin').'</p>'; |
||
478 | }elseif(is_null($current)){ |
||
479 | echo '<p>'.$this->getLang('p_inherited').'</p>'; |
||
480 | } |
||
481 | } |
||
482 | |||
483 | |||
484 | /** |
||
485 | * Item formatter for the tree view |
||
486 | * |
||
487 | * User function for html_buildlist() |
||
488 | * |
||
489 | * @author Andreas Gohr <[email protected]> |
||
490 | */ |
||
491 | function _html_list_acl($item){ |
||
492 | $ret = ''; |
||
493 | // what to display |
||
494 | if(!empty($item['label'])){ |
||
495 | $base = $item['label']; |
||
496 | }else{ |
||
497 | $base = ':'.$item['id']; |
||
498 | $base = substr($base,strrpos($base,':')+1); |
||
499 | } |
||
500 | |||
501 | // highlight? |
||
502 | if( ($item['type']== $this->current_item['type'] && $item['id'] == $this->current_item['id'])) { |
||
503 | $cl = ' cur'; |
||
504 | } else { |
||
505 | $cl = ''; |
||
506 | } |
||
507 | |||
508 | // namespace or page? |
||
509 | if($item['type']=='d'){ |
||
510 | if($item['open']){ |
||
511 | $img = DOKU_BASE.'lib/images/minus.gif'; |
||
512 | $alt = '−'; |
||
513 | }else{ |
||
514 | $img = DOKU_BASE.'lib/images/plus.gif'; |
||
515 | $alt = '+'; |
||
516 | } |
||
517 | $ret .= '<img src="'.$img.'" alt="'.$alt.'" />'; |
||
518 | $ret .= '<a href="'.wl('',$this->_get_opts(array('ns'=>$item['id'],'sectok'=>getSecurityToken()))).'" class="idx_dir'.$cl.'">'; |
||
519 | $ret .= $base; |
||
520 | $ret .= '</a>'; |
||
521 | }else{ |
||
522 | $ret .= '<a href="'.wl('',$this->_get_opts(array('id'=>$item['id'],'ns'=>'','sectok'=>getSecurityToken()))).'" class="wikilink1'.$cl.'">'; |
||
523 | $ret .= noNS($item['id']); |
||
524 | $ret .= '</a>'; |
||
525 | } |
||
526 | return $ret; |
||
527 | } |
||
528 | |||
529 | |||
530 | function _html_li_acl($item){ |
||
534 | |||
535 | |||
536 | /** |
||
537 | * Get current ACL settings as multidim array |
||
538 | * |
||
539 | * @author Andreas Gohr <[email protected]> |
||
540 | */ |
||
541 | function _init_acl_config(){ |
||
542 | global $AUTH_ACL; |
||
543 | global $conf; |
||
582 | |||
583 | /** |
||
584 | * Display all currently set permissions in a table |
||
585 | * |
||
586 | * @author Andreas Gohr <[email protected]> |
||
587 | */ |
||
588 | function _html_table(){ |
||
651 | |||
652 | /** |
||
653 | * Returns the permission which were set for exactly the given user/group |
||
654 | * and page/namespace. Returns null if no exact match is available |
||
655 | * |
||
656 | * @author Andreas Gohr <[email protected]> |
||
657 | */ |
||
658 | function _get_exact_perm(){ |
||
676 | |||
677 | /** |
||
678 | * adds new acl-entry to conf/acl.auth.php |
||
679 | * |
||
680 | * @author Frank Schubert <[email protected]> |
||
681 | */ |
||
682 | function _acl_add($acl_scope, $acl_user, $acl_level){ |
||
695 | |||
696 | /** |
||
697 | * remove acl-entry from conf/acl.auth.php |
||
698 | * |
||
699 | * @author Frank Schubert <[email protected]> |
||
700 | */ |
||
701 | function _acl_del($acl_scope, $acl_user){ |
||
709 | |||
710 | /** |
||
711 | * print the permission radio boxes |
||
712 | * |
||
713 | * @author Frank Schubert <[email protected]> |
||
714 | * @author Andreas Gohr <[email protected]> |
||
715 | */ |
||
716 | function _html_checkboxes($setperm,$ispage,$name){ |
||
749 | |||
750 | /** |
||
751 | * Print a user/group selector (reusing already used users and groups) |
||
752 | * |
||
753 | * @author Andreas Gohr <[email protected]> |
||
754 | */ |
||
755 | function _html_select(){ |
||
815 | } |
||
816 |
Adding explicit visibility (
private
,protected
, orpublic
) is generally recommend to communicate to other developers how, and from where this method is intended to be used.