1 <?php
2 
3 use dokuwiki\Extension\AdminPlugin;
4 use dokuwiki\Utf8\Sort;
5 
6 /**
7  * ACL administration functions
8  *
9  * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
10  * @author     Andreas Gohr <andi@splitbrain.org>
11  * @author     Anika Henke <anika@selfthinker.org> (concepts)
12  * @author     Frank Schubert <frank@schokilade.de> (old version)
13  */
14 
15 /**
16  * All DokuWiki plugins to extend the admin function
17  * need to inherit from this class
18  */
19 class admin_plugin_acl extends AdminPlugin
20 {
21     public $acl;
22     protected $ns;
23     /**
24      * The currently selected item, associative array with id and type.
25      * Populated from (in this order):
26      * $_REQUEST['current_ns']
27      * $_REQUEST['current_id']
28      * $ns
29      * $ID
30      */
31     protected $current_item;
32     protected $who = '';
33     protected $usersgroups = [];
34     protected $specials = [];
35 
36     /**
37      * return prompt for admin menu
38      */
39     public function getMenuText($language)
40     {
41         return $this->getLang('admin_acl');
42     }
43 
44     /**
45      * return sort order for position in admin menu
46      */
47     public function getMenuSort()
48     {
49         return 1;
50     }
51 
52     /**
53      * handle user request
54      *
55      * Initializes internal vars and handles modifications
56      *
57      * @author Andreas Gohr <andi@splitbrain.org>
58      */
59     public function handle()
60     {
61         global $AUTH_ACL;
62         global $ID;
63         global $auth;
64         global $config_cascade;
65         global $INPUT;
66 
67         // fresh 1:1 copy without replacements
68         $AUTH_ACL = file($config_cascade['acl']['default']);
69 
70         // namespace given?
71         if ($INPUT->str('ns') == '*') {
72             $this->ns = '*';
73         } else {
74             $this->ns = cleanID($INPUT->str('ns'));
75         }
76 
77         if ($INPUT->str('current_ns')) {
78             $this->current_item = ['id' => cleanID($INPUT->str('current_ns')), 'type' => 'd'];
79         } elseif ($INPUT->str('current_id')) {
80             $this->current_item = ['id' => cleanID($INPUT->str('current_id')), 'type' => 'f'];
81         } elseif ($this->ns) {
82             $this->current_item = ['id' => $this->ns, 'type' => 'd'];
83         } else {
84             $this->current_item = ['id' => $ID, 'type' => 'f'];
85         }
86 
87         // user or group choosen?
88         $who = trim($INPUT->str('acl_w'));
89         if ($INPUT->str('acl_t') == '__g__' && $who) {
90             $this->who = '@' . ltrim($auth->cleanGroup($who), '@');
91         } elseif ($INPUT->str('acl_t') == '__u__' && $who) {
92             $this->who = ltrim($who, '@');
93             if ($this->who != '%USER%' && $this->who != '%GROUP%') { #keep wildcard as is
94                 $this->who = $auth->cleanUser($this->who);
95             }
96         } elseif (
97             $INPUT->str('acl_t') &&
98             $INPUT->str('acl_t') != '__u__' &&
99             $INPUT->str('acl_t') != '__g__'
100         ) {
101             $this->who = $INPUT->str('acl_t');
102         } elseif ($who) {
103             $this->who = $who;
104         }
105 
106         // handle modifications
107         if ($INPUT->has('cmd') && checkSecurityToken()) {
108             $cmd = $INPUT->extract('cmd')->str('cmd');
109 
110             // scope for modifications
111             if ($this->ns) {
112                 if ($this->ns == '*') {
113                     $scope = '*';
114                 } else {
115                     $scope = $this->ns . ':*';
116                 }
117             } else {
118                 $scope = $ID;
119             }
120 
121             if ($cmd == 'save' && $scope && $this->who && $INPUT->has('acl')) {
122                 // handle additions or single modifications
123                 $this->deleteACL($scope, $this->who);
124                 $this->addOrUpdateACL($scope, $this->who, $INPUT->int('acl'));
125             } elseif ($cmd == 'del' && $scope && $this->who) {
126                 // handle single deletions
127                 $this->deleteACL($scope, $this->who);
128             } elseif ($cmd == 'update') {
129                 $acl = $INPUT->arr('acl');
130 
131                 // handle update of the whole file
132                 foreach ($INPUT->arr('del') as $where => $names) {
133                     // remove all rules marked for deletion
134                     foreach ($names as $who)
135                         unset($acl[$where][$who]);
136                 }
137                 // prepare lines
138                 $lines = [];
139                 // keep header
140                 foreach ($AUTH_ACL as $line) {
141                     if ($line[0] == '#') {
142                         $lines[] = $line;
143                     } else {
144                         break;
145                     }
146                 }
147                 // re-add all rules
148                 foreach ($acl as $where => $opt) {
149                     foreach ($opt as $who => $perm) {
150                         if ($who[0] == '@') {
151                             if ($who != '@ALL') {
152                                 $who = '@' . ltrim($auth->cleanGroup($who), '@');
153                             }
154                         } elseif ($who != '%USER%' && $who != '%GROUP%') { #keep wildcard as is
155                             $who = $auth->cleanUser($who);
156                         }
157                         $who = auth_nameencode($who, true);
158                         $lines[] = "$where\t$who\t$perm\n";
159                     }
160                 }
161                 // save it
162                 io_saveFile($config_cascade['acl']['default'], implode('', $lines));
163             }
164 
165             // reload ACL config
166             $AUTH_ACL = file($config_cascade['acl']['default']);
167         }
168 
169         // initialize ACL array
170         $this->initAclConfig();
171     }
172 
173     /**
174      * ACL Output function
175      *
176      * print a table with all significant permissions for the
177      * current id
178      *
179      * @author  Frank Schubert <frank@schokilade.de>
180      * @author  Andreas Gohr <andi@splitbrain.org>
181      */
182     public function html()
183     {
184         echo '<div id="acl_manager">';
185         echo '<h1>' . $this->getLang('admin_acl') . '</h1>';
186         echo '<div class="level1">';
187 
188         echo '<div id="acl__tree">';
189         $this->makeExplorer();
190         echo '</div>';
191 
192         echo '<div id="acl__detail">';
193         $this->printDetail();
194         echo '</div>';
195         echo '</div>';
196 
197         echo '<div class="clearer"></div>';
198         echo '<h2>' . $this->getLang('current') . '</h2>';
199         echo '<div class="level2">';
200         $this->printAclTable();
201         echo '</div>';
202 
203         echo '<div class="footnotes"><div class="fn">';
204         echo '<sup><a id="fn__1" class="fn_bot" href="#fnt__1">1)</a></sup>';
205         echo '<div class="content">' . $this->getLang('p_include') . '</div>';
206         echo '</div></div>';
207 
208         echo '</div>';
209     }
210 
211     /**
212      * returns array with set options for building links
213      *
214      * @author Andreas Gohr <andi@splitbrain.org>
215      */
216     protected function getLinkOptions($addopts = null)
217     {
218         $opts = ['do' => 'admin', 'page' => 'acl'];
219         if ($this->ns) $opts['ns'] = $this->ns;
220         if ($this->who) $opts['acl_w'] = $this->who;
221 
222         if (is_null($addopts)) return $opts;
223         return array_merge($opts, $addopts);
224     }
225 
226     /**
227      * Display a tree menu to select a page or namespace
228      *
229      * @author Andreas Gohr <andi@splitbrain.org>
230      */
231     protected function makeExplorer()
232     {
233         global $conf;
234         global $ID;
235         global $lang;
236 
237         $ns = $this->ns;
238         if (empty($ns)) {
239             $ns = dirname(str_replace(':', '/', $ID));
240             if ($ns == '.') $ns = '';
241         } elseif ($ns == '*') {
242             $ns = '';
243         }
244         $ns = utf8_encodeFN(str_replace(':', '/', $ns));
245 
246         $data = $this->makeTree($ns);
247 
248         // wrap a list with the root level around the other namespaces
249         array_unshift($data, [
250             'level' => 0,
251             'id' => '*',
252             'type' => 'd',
253             'open' => 'true',
254             'label' => '[' . $lang['mediaroot'] . ']'
255         ]);
256 
257         echo html_buildlist(
258             $data,
259             'acl',
260             [$this, 'makeTreeItem'],
261             [$this, 'makeListItem']
262         );
263     }
264 
265     /**
266      * get a combined list of media and page files
267      *
268      * also called via AJAX
269      *
270      * @param string $folder an already converted filesystem folder of the current namespace
271      * @param string $limit limit the search to this folder
272      * @return array
273      */
274     public function makeTree($folder, $limit = '')
275     {
276         global $conf;
277 
278         // read tree structure from pages and media
279         $data = [];
280         search($data, $conf['datadir'], 'search_index', ['ns' => $folder], $limit);
281         $media = [];
282         search($media, $conf['mediadir'], 'search_index', ['ns' => $folder, 'nofiles' => true], $limit);
283         $data = array_merge($data, $media);
284         unset($media);
285 
286         // combine by sorting and removing duplicates
287         usort($data, [$this, 'treeSort']);
288         $count = count($data);
289         if ($count > 0) for ($i = 1; $i < $count; $i++) {
290             if ($data[$i - 1]['id'] == $data[$i]['id'] && $data[$i - 1]['type'] == $data[$i]['type']) {
291                 unset($data[$i]);
292                 $i++;  // duplicate found, next $i can't be a duplicate, so skip forward one
293             }
294         }
295         return $data;
296     }
297 
298     /**
299      * usort callback
300      *
301      * Sorts the combined trees of media and page files
302      */
303     public function treeSort($a, $b)
304     {
305         // handle the trivial cases first
306         if ($a['id'] == '') return -1;
307         if ($b['id'] == '') return 1;
308         // split up the id into parts
309         $a_ids = explode(':', $a['id']);
310         $b_ids = explode(':', $b['id']);
311         // now loop through the parts
312         while (count($a_ids) && count($b_ids)) {
313             // compare each level from upper to lower
314             // until a non-equal component is found
315             $cur_result = Sort::strcmp(array_shift($a_ids), array_shift($b_ids));
316             if ($cur_result) {
317                 // if one of the components is the last component and is a file
318                 // and the other one is either of a deeper level or a directory,
319                 // the file has to come after the deeper level or directory
320                 if ($a_ids === [] && $a['type'] == 'f' && (count($b_ids) || $b['type'] == 'd')) return 1;
321                 if ($b_ids === [] && $b['type'] == 'f' && (count($a_ids) || $a['type'] == 'd')) return -1;
322                 return $cur_result;
323             }
324         }
325         // The two ids seem to be equal. One of them might however refer
326         // to a page, one to a namespace, the namespace needs to be first.
327         if ($a_ids === [] && $b_ids === []) {
328             if ($a['type'] == $b['type']) return 0;
329             if ($a['type'] == 'f') return 1;
330             return -1;
331         }
332         // Now the empty part is either a page in the parent namespace
333         // that obviously needs to be after the namespace
334         // Or it is the namespace that contains the other part and should be
335         // before that other part.
336         if ($a_ids === []) return ($a['type'] == 'd') ? -1 : 1;
337         if ($b_ids === []) return ($b['type'] == 'd') ? 1 : -1;
338         return 0; //shouldn't happen
339     }
340 
341     /**
342      * Display the current ACL for selected where/who combination with
343      * selectors and modification form
344      *
345      * @author Andreas Gohr <andi@splitbrain.org>
346      */
347     protected function printDetail()
348     {
349         global $ID;
350 
351         echo '<form action="' . wl() . '" method="post" accept-charset="utf-8"><div class="no">';
352 
353         echo '<div id="acl__user">';
354         echo $this->getLang('acl_perms') . ' ';
355         $inl = $this->makeSelect();
356         echo sprintf(
357             '<input type="text" name="acl_w" class="edit" value="%s" />',
358             ($inl) ? '' : hsc(ltrim($this->who, '@'))
359         );
360         echo '<button type="submit">' . $this->getLang('btn_select') . '</button>';
361         echo '</div>';
362 
363         echo '<div id="acl__info">';
364         $this->printInfo();
365         echo '</div>';
366 
367         echo '<input type="hidden" name="ns" value="' . hsc($this->ns) . '" />';
368         echo '<input type="hidden" name="id" value="' . hsc($ID) . '" />';
369         echo '<input type="hidden" name="do" value="admin" />';
370         echo '<input type="hidden" name="page" value="acl" />';
371         echo '<input type="hidden" name="sectok" value="' . getSecurityToken() . '" />';
372         echo '</div></form>';
373     }
374 
375     /**
376      * Print info and editor
377      *
378      * also loaded via Ajax
379      */
380     public function printInfo()
381     {
382         global $ID;
383 
384         if ($this->who) {
385             $current = $this->getExactPermisson();
386 
387             // explain current permissions
388             $this->printExplanation($current);
389             // load editor
390             $this->printAclEditor($current);
391         } else {
392             echo '<p>';
393             if ($this->ns) {
394                 printf($this->getLang('p_choose_ns'), hsc($this->ns));
395             } else {
396                 printf($this->getLang('p_choose_id'), hsc($ID));
397             }
398             echo '</p>';
399 
400             echo $this->locale_xhtml('help');
401         }
402     }
403 
404     /**
405      * Display the ACL editor
406      *
407      * @author Andreas Gohr <andi@splitbrain.org>
408      */
409     protected function printAclEditor($current)
410     {
411         global $lang;
412 
413         echo '<fieldset>';
414         if (is_null($current)) {
415             echo '<legend>' . $this->getLang('acl_new') . '</legend>';
416         } else {
417             echo '<legend>' . $this->getLang('acl_mod') . '</legend>';
418         }
419 
420         echo $this->makeCheckboxes($current, empty($this->ns), 'acl');
421 
422         if (is_null($current)) {
423             echo '<button type="submit" name="cmd[save]">' . $lang['btn_save'] . '</button>';
424         } else {
425             echo '<button type="submit" name="cmd[save]">' . $lang['btn_update'] . '</button>';
426             echo '<button type="submit" name="cmd[del]">' . $lang['btn_delete'] . '</button>';
427         }
428 
429         echo '</fieldset>';
430     }
431 
432     /**
433      * Explain the currently set permissions in plain english/$lang
434      *
435      * @author Andreas Gohr <andi@splitbrain.org>
436      */
437     protected function printExplanation($current)
438     {
439         global $ID;
440         global $auth;
441 
442         $who = $this->who;
443         $ns = $this->ns;
444 
445         // prepare where to check
446         if ($ns) {
447             if ($ns == '*') {
448                 $check = '*';
449             } else {
450                 $check = $ns . ':*';
451             }
452         } else {
453             $check = $ID;
454         }
455 
456         // prepare who to check
457         if ($who[0] == '@') {
458             $user = '';
459             $groups = [ltrim($who, '@')];
460         } else {
461             $user = $who;
462             $info = $auth->getUserData($user);
463             if ($info === false) {
464                 $groups = [];
465             } else {
466                 $groups = $info['grps'];
467             }
468         }
469 
470         // check the permissions
471         $perm = auth_aclcheck($check, $user, $groups);
472 
473         // build array of named permissions
474         $names = [];
475         if ($perm) {
476             if ($ns) {
477                 if ($perm >= AUTH_DELETE) $names[] = $this->getLang('acl_perm16');
478                 if ($perm >= AUTH_UPLOAD) $names[] = $this->getLang('acl_perm8');
479                 if ($perm >= AUTH_CREATE) $names[] = $this->getLang('acl_perm4');
480             }
481             if ($perm >= AUTH_EDIT) $names[] = $this->getLang('acl_perm2');
482             if ($perm >= AUTH_READ) $names[] = $this->getLang('acl_perm1');
483             $names = array_reverse($names);
484         } else {
485             $names[] = $this->getLang('acl_perm0');
486         }
487 
488         // print permission explanation
489         echo '<p>';
490         if ($user) {
491             if ($ns) {
492                 printf($this->getLang('p_user_ns'), hsc($who), hsc($ns), implode(', ', $names));
493             } else {
494                 printf($this->getLang('p_user_id'), hsc($who), hsc($ID), implode(', ', $names));
495             }
496         } elseif ($ns) {
497             printf($this->getLang('p_group_ns'), hsc(ltrim($who, '@')), hsc($ns), implode(', ', $names));
498         } else {
499             printf($this->getLang('p_group_id'), hsc(ltrim($who, '@')), hsc($ID), implode(', ', $names));
500         }
501         echo '</p>';
502 
503         // add note if admin
504         if ($perm == AUTH_ADMIN) {
505             echo '<p>' . $this->getLang('p_isadmin') . '</p>';
506         } elseif (is_null($current)) {
507             echo '<p>' . $this->getLang('p_inherited') . '</p>';
508         }
509     }
510 
511 
512     /**
513      * Item formatter for the tree view
514      *
515      * User function for html_buildlist()
516      *
517      * @author Andreas Gohr <andi@splitbrain.org>
518      */
519     public function makeTreeItem($item)
520     {
521         $ret = '';
522         // what to display
523         if (!empty($item['label'])) {
524             $base = $item['label'];
525         } else {
526             $base = ':' . $item['id'];
527             $base = substr($base, strrpos($base, ':') + 1);
528         }
529 
530         // highlight?
531         if (($item['type'] == $this->current_item['type'] && $item['id'] == $this->current_item['id'])) {
532             $cl = ' cur';
533         } else {
534             $cl = '';
535         }
536 
537         // namespace or page?
538         if ($item['type'] == 'd') {
539             if ($item['open']) {
540                 $img = DOKU_BASE . 'lib/images/minus.gif';
541                 $alt = '−';
542             } else {
543                 $img = DOKU_BASE . 'lib/images/plus.gif';
544                 $alt = '+';
545             }
546             $ret .= '<img src="' . $img . '" alt="' . $alt . '" />';
547             $ret .= '<a href="' .
548                 wl('', $this->getLinkOptions(['ns' => $item['id'], 'sectok' => getSecurityToken()])) .
549                 '" class="idx_dir' . $cl . '">';
550             $ret .= $base;
551             $ret .= '</a>';
552         } else {
553             $ret .= '<a href="' .
554                 wl('', $this->getLinkOptions(['id' => $item['id'], 'ns' => '', 'sectok' => getSecurityToken()])) .
555                 '" class="wikilink1' . $cl . '">';
556             $ret .= noNS($item['id']);
557             $ret .= '</a>';
558         }
559         return $ret;
560     }
561 
562     /**
563      * List Item formatter
564      *
565      * @param array $item
566      * @return string
567      */
568     public function makeListItem($item)
569     {
570         return '<li class="level' . $item['level'] . ' ' .
571             ($item['open'] ? 'open' : 'closed') . '">';
572     }
573 
574 
575     /**
576      * Get current ACL settings as multidim array
577      *
578      * @author Andreas Gohr <andi@splitbrain.org>
579      */
580     public function initAclConfig()
581     {
582         global $AUTH_ACL;
583         global $conf;
584         $acl_config = [];
585         $usersgroups = [];
586 
587         // get special users and groups
588         $this->specials[] = '@ALL';
589         $this->specials[] = '@' . $conf['defaultgroup'];
590         if ($conf['manager'] != '!!not set!!') {
591             $this->specials = array_merge(
592                 $this->specials,
593                 array_map(
594                     'trim',
595                     explode(',', $conf['manager'])
596                 )
597             );
598         }
599         $this->specials = array_filter($this->specials);
600         $this->specials = array_unique($this->specials);
601         Sort::sort($this->specials);
602 
603         foreach ($AUTH_ACL as $line) {
604             $line = trim(preg_replace('/#.*$/', '', $line)); //ignore comments
605             if (!$line) continue;
606 
607             $acl = preg_split('/[ \t]+/', $line);
608             //0 is pagename, 1 is user, 2 is acl
609 
610             $acl[1] = rawurldecode($acl[1]);
611             $acl_config[$acl[0]][$acl[1]] = $acl[2];
612 
613             // store non-special users and groups for later selection dialog
614             $ug = $acl[1];
615             if (in_array($ug, $this->specials)) continue;
616             $usersgroups[] = $ug;
617         }
618 
619         $usersgroups = array_unique($usersgroups);
620         Sort::sort($usersgroups);
621         Sort::ksort($acl_config);
622         foreach (array_keys($acl_config) as $pagename) {
623             Sort::ksort($acl_config[$pagename]);
624         }
625 
626         $this->acl = $acl_config;
627         $this->usersgroups = $usersgroups;
628     }
629 
630     /**
631      * Display all currently set permissions in a table
632      *
633      * @author Andreas Gohr <andi@splitbrain.org>
634      */
635     protected function printAclTable()
636     {
637         global $lang;
638         global $ID;
639 
640         echo '<form action="' . wl() . '" method="post" accept-charset="utf-8"><div class="no">';
641         if ($this->ns) {
642             echo '<input type="hidden" name="ns" value="' . hsc($this->ns) . '" />';
643         } else {
644             echo '<input type="hidden" name="id" value="' . hsc($ID) . '" />';
645         }
646         echo '<input type="hidden" name="acl_w" value="' . hsc($this->who) . '" />';
647         echo '<input type="hidden" name="do" value="admin" />';
648         echo '<input type="hidden" name="page" value="acl" />';
649         echo '<input type="hidden" name="sectok" value="' . getSecurityToken() . '" />';
650         echo '<div class="table">';
651         echo '<table class="inline">';
652         echo '<tr>';
653         echo '<th>' . $this->getLang('where') . '</th>';
654         echo '<th>' . $this->getLang('who') . '</th>';
655         echo '<th>' . $this->getLang('perm') . '<sup><a id="fnt__1" class="fn_top" href="#fn__1">1)</a></sup></th>';
656         echo '<th>' . $lang['btn_delete'] . '</th>';
657         echo '</tr>';
658         foreach ($this->acl as $where => $set) {
659             foreach ($set as $who => $perm) {
660                 echo '<tr>';
661                 echo '<td>';
662                 if (str_ends_with($where, '*')) {
663                     echo '<span class="aclns">' . hsc($where) . '</span>';
664                     $ispage = false;
665                 } else {
666                     echo '<span class="aclpage">' . hsc($where) . '</span>';
667                     $ispage = true;
668                 }
669                 echo '</td>';
670 
671                 echo '<td>';
672                 if ($who[0] == '@') {
673                     echo '<span class="aclgroup">' . hsc($who) . '</span>';
674                 } else {
675                     echo '<span class="acluser">' . hsc($who) . '</span>';
676                 }
677                 echo '</td>';
678 
679                 echo '<td>';
680                 echo $this->makeCheckboxes($perm, $ispage, 'acl[' . $where . '][' . $who . ']');
681                 echo '</td>';
682 
683                 echo '<td class="check">';
684                 echo '<input type="checkbox" name="del[' . hsc($where) . '][]" value="' . hsc($who) . '" />';
685                 echo '</td>';
686                 echo '</tr>';
687             }
688         }
689 
690         echo '<tr>';
691         echo '<th class="action" colspan="4">';
692         echo '<button type="submit" name="cmd[update]">' . $lang['btn_update'] . '</button>';
693         echo '</th>';
694         echo '</tr>';
695         echo '</table>';
696         echo '</div>';
697         echo '</div></form>';
698     }
699 
700     /**
701      * Returns the permission which were set for exactly the given user/group
702      * and page/namespace. Returns null if no exact match is available
703      *
704      * @author Andreas Gohr <andi@splitbrain.org>
705      */
706     protected function getExactPermisson()
707     {
708         global $ID;
709         if ($this->ns) {
710             if ($this->ns == '*') {
711                 $check = '*';
712             } else {
713                 $check = $this->ns . ':*';
714             }
715         } else {
716             $check = $ID;
717         }
718 
719         if (isset($this->acl[$check][$this->who])) {
720             return $this->acl[$check][$this->who];
721         } else {
722             return null;
723         }
724     }
725 
726     /**
727      * adds new acl-entry to conf/acl.auth.php
728      *
729      * @author  Frank Schubert <frank@schokilade.de>
730      */
731     public function addOrUpdateACL($acl_scope, $acl_user, $acl_level)
732     {
733         global $config_cascade;
734 
735         // first make sure we won't end up with 2 lines matching this user and scope. See issue #1115
736         $this->deleteACL($acl_scope, $acl_user);
737         $acl_user = auth_nameencode($acl_user, true);
738 
739         // max level for pagenames is edit
740         if (strpos($acl_scope, '*') === false) {
741             if ($acl_level > AUTH_EDIT) $acl_level = AUTH_EDIT;
742         }
743 
744         $new_acl = "$acl_scope\t$acl_user\t$acl_level\n";
745 
746         return io_saveFile($config_cascade['acl']['default'], $new_acl, true);
747     }
748 
749     /**
750      * remove acl-entry from conf/acl.auth.php
751      *
752      * @author  Frank Schubert <frank@schokilade.de>
753      */
754     public function deleteACL($acl_scope, $acl_user)
755     {
756         global $config_cascade;
757         $acl_user = auth_nameencode($acl_user, true);
758 
759         $acl_pattern = '^' . preg_quote($acl_scope, '/') . '[ \t]+' . $acl_user . '[ \t]+[0-8].*$';
760 
761         return io_deleteFromFile($config_cascade['acl']['default'], "/$acl_pattern/", true);
762     }
763 
764     /**
765      * print the permission radio boxes
766      *
767      * @author  Frank Schubert <frank@schokilade.de>
768      * @author  Andreas Gohr <andi@splitbrain.org>
769      */
770     protected function makeCheckboxes($setperm, $ispage, $name)
771     {
772         global $lang;
773 
774         static $label = 0; //number labels
775         $ret = '';
776 
777         if ($ispage && $setperm > AUTH_EDIT) $setperm = AUTH_EDIT;
778 
779         foreach ([AUTH_NONE, AUTH_READ, AUTH_EDIT, AUTH_CREATE, AUTH_UPLOAD, AUTH_DELETE] as $perm) {
780             ++$label;
781 
782             //general checkbox attributes
783             $atts = [
784                 'type' => 'radio',
785                 'id' => 'pbox' . $label,
786                 'name' => $name,
787                 'value' => $perm
788             ];
789             //dynamic attributes
790             if (!is_null($setperm) && $setperm == $perm) $atts['checked'] = 'checked';
791             if ($ispage && $perm > AUTH_EDIT) {
792                 $atts['disabled'] = 'disabled';
793                 $class = ' class="disabled"';
794             } else {
795                 $class = '';
796             }
797 
798             //build code
799             $ret .= '<label for="pbox' . $label . '"' . $class . '>';
800             $ret .= '<input ' . buildAttributes($atts) . ' />&#160;';
801             $ret .= $this->getLang('acl_perm' . $perm);
802             $ret .= '</label>';
803         }
804         return $ret;
805     }
806 
807     /**
808      * Print a user/group selector (reusing already used users and groups)
809      *
810      * @author  Andreas Gohr <andi@splitbrain.org>
811      */
812     protected function makeSelect()
813     {
814         $inlist = false;
815         $usel = '';
816         $gsel = '';
817 
818         if (
819             $this->who &&
820             !in_array($this->who, $this->usersgroups) &&
821             !in_array($this->who, $this->specials)
822         ) {
823             if ($this->who[0] == '@') {
824                 $gsel = ' selected="selected"';
825             } else {
826                 $usel = ' selected="selected"';
827             }
828         } else {
829             $inlist = true;
830         }
831 
832         echo '<select name="acl_t" class="edit">';
833         echo '  <option value="__g__" class="aclgroup"' . $gsel . '>' . $this->getLang('acl_group') . '</option>';
834         echo '  <option value="__u__"  class="acluser"' . $usel . '>' . $this->getLang('acl_user') . '</option>';
835         if (!empty($this->specials)) {
836             echo '  <optgroup label="&#160;">';
837             foreach ($this->specials as $ug) {
838                 if ($ug == $this->who) {
839                     $sel = ' selected="selected"';
840                     $inlist = true;
841                 } else {
842                     $sel = '';
843                 }
844 
845                 if ($ug[0] == '@') {
846                     echo '  <option value="' . hsc($ug) . '" class="aclgroup"' . $sel . '>' . hsc($ug) . '</option>';
847                 } else {
848                     echo '  <option value="' . hsc($ug) . '" class="acluser"' . $sel . '>' . hsc($ug) . '</option>';
849                 }
850             }
851             echo '  </optgroup>';
852         }
853         if (!empty($this->usersgroups)) {
854             echo '  <optgroup label="&#160;">';
855             foreach ($this->usersgroups as $ug) {
856                 if ($ug == $this->who) {
857                     $sel = ' selected="selected"';
858                     $inlist = true;
859                 } else {
860                     $sel = '';
861                 }
862 
863                 if ($ug[0] == '@') {
864                     echo '  <option value="' . hsc($ug) . '" class="aclgroup"' . $sel . '>' . hsc($ug) . '</option>';
865                 } else {
866                     echo '  <option value="' . hsc($ug) . '" class="acluser"' . $sel . '>' . hsc($ug) . '</option>';
867                 }
868             }
869             echo '  </optgroup>';
870         }
871         echo '</select>';
872         return $inlist;
873     }
874 }
875