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