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