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