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