]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/PagePerm.php
Performance: Do not check RECENTCHANGESBOX cache on actions not using it
[SourceForge/phpwiki.git] / lib / PagePerm.php
1 <?php // -*-php-*-
2 rcs_id('$Id$');
3 /*
4  Copyright 2004,2007 $ThePhpWikiProgrammingTeam
5  Copyright 2009 Marc-Etienne Vargenau, Alcatel-Lucent
6
7  This file is part of PhpWiki.
8
9  PhpWiki is free software; you can redistribute it and/or modify
10  it under the terms of the GNU General Public License as published by
11  the Free Software Foundation; either version 2 of the License, or
12  (at your option) any later version.
13
14  PhpWiki is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  GNU General Public License for more details.
18
19  You should have received a copy of the GNU General Public License
20  along with PhpWiki; if not, write to the Free Software
21  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23
24 /**
25    Permissions per page and action based on current user, 
26    ownership and group membership implemented with ACL's (Access Control Lists),
27    opposed to the simplier unix-like ugo:rwx system.
28    The previous system was only based on action and current user. (lib/main.php)
29
30    Permissions may be inherited from its parent pages, a optional the 
31    optional master page ("."), and predefined default permissions, if "." 
32    is not defined.
33    Pagenames starting with "." have special default permissions.
34    For Authentication see WikiUserNew.php, WikiGroup.php and main.php
35    Page Permissions are in PhpWiki since v1.3.9 and enabled since v1.4.0
36
37    This file might replace the following functions from main.php:
38      Request::_notAuthorized($require_level)
39        display the denied message and optionally a login form 
40        to gain higher privileges
41      Request::getActionDescription($action)
42        helper to localize the _notAuthorized message per action, 
43        when login is tried.
44      Request::getDisallowedActionDescription($action)
45        helper to localize the _notAuthorized message per action, 
46        when it aborts
47      Request::requiredAuthority($action)
48        returns the needed user level
49        has a hook for plugins on POST
50      Request::requiredAuthorityForAction($action)
51        just returns the level per action, will be replaced with the 
52        action + page pair
53
54      The defined main.php actions map to simplier access types:
55        browse => view
56        edit   => edit
57        create => edit or create
58        remove => remove
59        rename => change
60        store prefs => change
61        list in PageList => list
62 */
63
64 /* Symbolic special ACL groups. Untranslated to be stored in page metadata*/
65 define('ACL_EVERY',        '_EVERY');
66 define('ACL_ANONYMOUS',    '_ANONYMOUS');
67 define('ACL_BOGOUSER',     '_BOGOUSER');
68 define('ACL_HASHOMEPAGE',  '_HASHOMEPAGE');
69 define('ACL_SIGNED',       '_SIGNED');
70 define('ACL_AUTHENTICATED','_AUTHENTICATED');
71 define('ACL_ADMIN',        '_ADMIN');
72 define('ACL_OWNER',        '_OWNER');
73 define('ACL_CREATOR',      '_CREATOR');
74
75 // Return an page permissions array for this page.
76 // To provide ui helpers to view and change page permissions:
77 //   <tr><th>Group</th><th>Access</th><th>Allow or Forbid</th></tr>
78 //   <tr><td>$group</td><td>_($access)</td><td> [ ] </td></tr>
79 function pagePermissions($pagename) {
80     global $request;
81     $page = $request->getPage($pagename);
82     // Page not found (new page); returned inherited permissions, to be displayed in gray
83     if (! $page->exists() ) {
84         if ($pagename == '.') // stop recursion
85             return array('default', new PagePermission());
86         else {
87             return array('inherited', pagePermissions(getParentPage($pagename)));
88         }
89     } elseif ($perm = getPagePermissions($page)) {
90         return array('page', $perm);
91     // or no permissions defined; returned inherited permissions, to be displayed in gray
92     } elseif ($pagename == '.') { // stop recursion in pathological case. 
93         // "." defined, without any acl
94         return array('default', new PagePermission());
95     } else {
96         return array('inherited', pagePermissions(getParentPage($pagename)));
97     }
98 }
99
100 function pagePermissionsSimpleFormat($perm_tree, $owner, $group=false) {
101     list($type,$perm) = pagePermissionsAcl($perm_tree[0], $perm_tree);
102     /*
103     $type = $perm_tree[0];
104     $perm = pagePermissionsAcl($perm_tree);
105     if (is_object($perm_tree[1]))
106         $perm = $perm_tree[1];
107     elseif (is_array($perm_tree[1])) {
108         $perm_tree = pagePermissionsSimpleFormat($perm_tree[1],$owner,$group);
109         if (isa($perm_tree[1],'pagepermission'))
110             $perm = $perm_tree[1];
111         elseif (isa($perm_tree,'htmlelement'))
112             return $perm_tree;
113     }
114     */
115     if ($type == 'page')
116         return HTML::tt(HTML::strong($perm->asRwxString($owner, $group)));
117     elseif ($type == 'default')
118         return HTML::tt($perm->asRwxString($owner, $group));
119     elseif ($type == 'inherited') {
120         return HTML::tt(array('class'=>'inherited', 'style'=>'color:#aaa;'),
121                         $perm->asRwxString($owner, $group));
122     }
123 }
124
125 function pagePermissionsAcl($type,$perm_tree) {
126     $perm = $perm_tree[1];
127     while (!is_object($perm)) {
128         $perm_tree = pagePermissionsAcl($type, $perm);
129         $perm = $perm_tree[1];
130     }
131     return array($type,$perm);
132 }
133
134 // view => who
135 // edit => who
136 function pagePermissionsAclFormat($perm_tree, $editable=false) {
137     list($type,$perm) = pagePermissionsAcl($perm_tree[0], $perm_tree);
138     if ($editable)
139         return $perm->asEditableTable($type);
140     else
141         return $perm->asTable($type);
142 }
143
144 /** 
145  * Check the permissions for the current action.
146  * Walk down the inheritance tree. Collect all permissions until 
147  * the minimum required level is gained, which is not 
148  * overruled by more specific forbid rules.
149  * Todo: cache result per access and page in session?
150  */
151 function requiredAuthorityForPage ($action) {
152     global $request;
153     $auth = _requiredAuthorityForPagename(action2access($action),
154                                           $request->getArg('pagename'));
155     assert($auth !== -1);
156     if ($auth)
157         return $request->_user->_level;
158     else
159         return WIKIAUTH_UNOBTAINABLE;
160 }
161
162 // Translate action or plugin to the simplier access types:
163 function action2access ($action) {
164     global $request;
165     switch ($action) {
166     case 'browse':
167     case 'viewsource':
168     case 'diff':
169     case 'select':
170     case 'search':
171     case 'pdf':
172     case 'captcha':
173     case 'zip':
174         return 'view';
175
176     case 'dumpserial':
177         if (INSECURE_ACTIONS_LOCALHOST_ONLY and is_localhost())
178             return 'dump';
179         else
180             return 'view';
181
182     // performance and security relevant
183     case 'xmlrpc':
184     case 'soap':
185     case 'ziphtml':
186     case 'dumphtml':
187         return 'dump';
188
189     // invent a new access-perm massedit? or switch back to change, or keep it at edit?
190     case _("PhpWikiAdministration")."/"._("Rename"):
191     case _("PhpWikiAdministration")."/"._("Replace"):
192     case 'replace':
193     case 'rename':
194     case 'revert':
195     case 'edit':
196         return 'edit';
197     case 'create':
198         $page = $request->getPage();
199         if (!$page->exists())
200             return 'create';
201         else
202             return 'view'; 
203         break;
204     case 'upload':
205     case 'loadfile': 
206         // probably create/edit but we cannot check all page permissions, can we?
207     case 'remove':
208     case 'purge':
209     case 'lock':
210     case 'unlock':
211     case 'upgrade':
212     case 'chown':
213     case 'setacl':
214         return 'change';
215     default:
216         //Todo: Plugins should be able to override its access type
217         if (isWikiWord($action))
218             return 'view';
219         else
220             return 'change';
221         break;
222     }
223 }
224
225 // Recursive helper to do the real work.
226 // Using a simple perm cache for page-access pairs.
227 // Maybe page-(current+edit+change?)action pairs will help
228 function _requiredAuthorityForPagename($access, $pagename) {
229     static $permcache = array();
230     
231     if (array_key_exists($pagename, $permcache)
232         and array_key_exists($access, $permcache[$pagename]))
233         return $permcache[$pagename][$access];
234         
235     global $request;
236     $page = $request->getPage($pagename);
237     if (defined('GFORGE') and GFORGE) {
238         if ($pagename != '.' && isset($request->_user->_is_external) && $request->_user->_is_external && ! $page->get('external')) {
239                 $permcache[$pagename][$access] = 0;
240                 return 0;
241         }
242     }
243     // Page not found; check against default permissions
244     if (! $page->exists() ) {
245         $perm = new PagePermission();
246         $result = ($perm->isAuthorized($access, $request->_user) === true);
247         $permcache[$pagename][$access] = $result;
248         return $result;
249     }
250     // no ACL defined; check for special dotfile or walk down
251     if (! ($perm = getPagePermissions($page))) { 
252         if ($pagename == '.') {
253             $perm = new PagePermission();
254             if ($perm->isAuthorized('change', $request->_user)) {
255                 // warn the user to set ACL of ".", if he has permissions to do so.
256                 trigger_error(". (dotpage == rootpage for inheriting pageperm ACLs) exists without any ACL!\n".
257                               "Please do ?action=setacl&pagename=.", E_USER_WARNING);
258             }
259             $result = ($perm->isAuthorized($access, $request->_user) === true);
260             $permcache[$pagename][$access] = $result;
261             return $result;
262         } elseif ($pagename[0] == '.') {
263             $perm = new PagePermission(PagePermission::dotPerms());
264             $result = ($perm->isAuthorized($access, $request->_user) === true);
265             $permcache[$pagename][$access] = $result;
266             return $result;
267         }
268         return _requiredAuthorityForPagename($access, getParentPage($pagename));
269     }
270     // ACL defined; check if isAuthorized returns true or false or undecided
271     $authorized = $perm->isAuthorized($access, $request->_user);
272     if ($authorized !== -1) { // interestingly true is also -1
273         $permcache[$pagename][$access] = $authorized;
274         return $authorized;
275     } elseif ($pagename == '.') {
276         return false;
277     } else {    
278         return _requiredAuthorityForPagename($access, getParentPage($pagename));
279     }
280 }
281
282 /**
283  * @param  string $pagename   page from which the parent page is searched.
284  * @return string parent      pagename or the (possibly pseudo) dot-pagename.
285  */
286 function getParentPage($pagename) {
287     if (isSubPage($pagename)) {
288         return subPageSlice($pagename, 0);
289     } else {
290         return '.';
291     }
292 }
293
294 // Read the ACL from the page
295 // Done: Not existing pages should NOT be queried. 
296 // Check the parent page instead and don't take the default ACL's
297 function getPagePermissions ($page) {
298     if ($hash = $page->get('perm'))  // hash => object
299         return new PagePermission(unserialize($hash));
300     else 
301         return false;
302 }
303
304 // Store the ACL in the page
305 function setPagePermissions ($page,$perm) {
306     $perm->store($page);
307 }
308
309 function getAccessDescription($access) {
310     static $accessDescriptions;
311     if (! $accessDescriptions) {
312         $accessDescriptions = array(
313                                     'list'     => _("List this page and all subpages"),
314                                     'view'     => _("View this page and all subpages"),
315                                     'edit'     => _("Edit this page and all subpages"),
316                                     'create'   => _("Create a new (sub)page"),
317                                     'dump'     => _("Download page contents"),
318                                     'change'   => _("Change page attributes"),
319                                     'remove'   => _("Remove this page"),
320                                     'purge'    => _("Purge this page"),
321                                     );
322     }
323     if (in_array($access, array_keys($accessDescriptions)))
324         return $accessDescriptions[$access];
325     else
326         return $access;
327 }
328
329 // from php.net docs
330 function array_diff_assoc_recursive($array1, $array2) {
331     foreach ($array1 as $key => $value) {
332          if (is_array($value)) {
333              if (!is_array($array2[$key])) {
334                  $difference[$key] = $value;
335              } else {
336                  $new_diff = array_diff_assoc_recursive($value, $array2[$key]);
337                  if ($new_diff != false) {
338                      $difference[$key] = $new_diff;
339                  } 
340              }
341          } elseif(!isset($array2[$key]) || $array2[$key] != $value) {
342              $difference[$key] = $value;
343          }
344     }
345     return !isset($difference) ? 0 : $difference;
346 }
347
348 /**
349  * The ACL object per page. It is stored in a page, but can also 
350  * be merged with ACL's from other pages or taken from the master (pseudo) dot-file.
351  *
352  * A hash of "access" => "requires" pairs.
353  *   "access"   is a shortcut for common actions, which map to main.php actions
354  *   "requires" required username or groupname or any special group => true or false
355  *
356  * Define any special rules here, like don't list dot-pages.
357  */ 
358 class PagePermission {
359     var $perm;
360
361     function PagePermission($hash = array()) {
362         $this->_group = &$GLOBALS['request']->getGroup();
363         if (is_array($hash) and !empty($hash)) {
364             $accessTypes = $this->accessTypes();
365             foreach ($hash as $access => $requires) {
366                 if (in_array($access, $accessTypes))
367                     $this->perm[$access] = $requires;
368                 else
369                     trigger_error(sprintf(_("Unsupported ACL access type %s ignored."), $access),
370                                   E_USER_WARNING);
371             }
372         } else {
373             // set default permissions, the so called dot-file acl's
374             $this->perm = $this->defaultPerms();
375         }
376         return $this;
377     }
378
379     /**
380      * The workhorse to check the user against the current ACL pairs.
381      * Must translate the various special groups to the actual users settings 
382      * (userid, group membership).
383      */
384     function isAuthorized($access, $user) {
385         $allow = -1;
386         if (!empty($this->perm{$access})) {
387             foreach ($this->perm[$access] as $group => $bool) {
388                 if ($this->isMember($user, $group)) {
389                     return $bool;
390                 } elseif ($allow == -1) { // not a member and undecided: check other groups
391                     $allow = !$bool;
392                 }
393             }
394         }
395         return $allow; // undecided
396     }
397
398     /**
399      * Translate the various special groups to the actual users settings 
400      * (userid, group membership).
401      */
402     function isMember($user, $group) {
403         global $request;
404         if ($group === ACL_EVERY) return true;
405         if (!isset($this->_group)) $member =& $request->getGroup();
406         else $member =& $this->_group;
407         //$user = & $request->_user;
408         if ($group === ACL_ADMIN)   // WIKI_ADMIN or member of _("Administrators")
409             return $user->isAdmin() or 
410                    ($user->isAuthenticated() and 
411                    $member->isMember(GROUP_ADMIN));
412         if ($group === ACL_ANONYMOUS) 
413             return ! $user->isSignedIn();
414         if ($group === ACL_BOGOUSER)
415             if (ENABLE_USER_NEW)
416                 return isa($user,'_BogoUser') or 
417                       (isWikiWord($user->_userid) and $user->_level >= WIKIAUTH_BOGO);
418             else return isWikiWord($user->UserName());
419         if ($group === ACL_HASHOMEPAGE)
420             return $user->hasHomePage();
421         if ($group === ACL_SIGNED)
422             return $user->isSignedIn();
423         if ($group === ACL_AUTHENTICATED)
424             return $user->isAuthenticated();
425         if ($group === ACL_OWNER) {
426             if (!$user->isAuthenticated()) return false;
427             $page = $request->getPage();
428             $owner = $page->getOwner();
429             return ($owner === $user->UserName() 
430                     or $member->isMember($owner));
431         }
432         if ($group === ACL_CREATOR) {
433             if (!$user->isAuthenticated()) return false;
434             $page = $request->getPage();
435             $creator = $page->getCreator();
436             return ($creator === $user->UserName() 
437                     or $member->isMember($creator));
438         }
439         /* Or named groups or usernames.
440            Note: We don't seperate groups and users here. 
441            Users overrides groups with the same name. 
442         */
443         return $user->UserName() === $group or
444                $member->isMember($group);
445     }
446
447     /**
448      * returns hash of default permissions.
449      * check if the page '.' exists and returns this instead.
450      */
451     function defaultPerms() {
452         //Todo: check for the existance of '.' and take this instead.
453         //Todo: honor more config.ini auth settings here
454         $perm = array('view'   => array(ACL_EVERY => true),
455                       'edit'   => array(ACL_EVERY => true),
456                       'create' => array(ACL_EVERY => true),
457                       'list'   => array(ACL_EVERY => true),
458                       'remove' => array(ACL_ADMIN => true,
459                                         ACL_OWNER => true),
460                       'purge'  => array(ACL_ADMIN => true,
461                                         ACL_OWNER => true),
462                       'dump'   => array(ACL_ADMIN => true,
463                                         ACL_OWNER => true),
464                       'change' => array(ACL_ADMIN => true,
465                                         ACL_OWNER => true));
466         if (ZIPDUMP_AUTH)
467             $perm['dump'] = array(ACL_ADMIN => true,
468                                   ACL_OWNER => true);
469         elseif (INSECURE_ACTIONS_LOCALHOST_ONLY) {
470             if (is_localhost())
471                 $perm['dump'] = array(ACL_EVERY => true);
472             else
473                 $perm['dump'] = array(ACL_ADMIN => true);
474         }
475         else
476             $perm['dump'] = array(ACL_EVERY => true);
477         if (defined('REQUIRE_SIGNIN_BEFORE_EDIT') && REQUIRE_SIGNIN_BEFORE_EDIT)
478             $perm['edit'] = array(ACL_SIGNED => true);
479         // view:
480         if (!ALLOW_ANON_USER) {
481             if (!ALLOW_USER_PASSWORDS) 
482                 $perm['view'] = array(ACL_SIGNED => true);
483             else                
484                 $perm['view'] = array(ACL_AUTHENTICATED => true);
485             $perm['view'][ACL_BOGOUSER] = ALLOW_BOGO_LOGIN ? true : false;
486         }
487         // edit:
488         if (!ALLOW_ANON_EDIT) {
489             if (!ALLOW_USER_PASSWORDS) 
490                 $perm['edit'] = array(ACL_SIGNED => true);
491             else                
492                 $perm['edit'] = array(ACL_AUTHENTICATED => true);
493             $perm['edit'][ACL_BOGOUSER] = ALLOW_BOGO_LOGIN ? true : false;
494             $perm['create'] = $perm['edit'];
495         }
496         return $perm;
497     }
498
499     /**
500      * FIXME: check valid groups and access
501      */
502     function sanify() {
503         foreach ($this->perm as $access => $groups) {
504             foreach ($groups as $group => $bool) {
505                 $this->perm[$access][$group] = (boolean) $bool;
506             }
507         }
508     }
509
510     /**
511      * do a recursive comparison
512      */
513     function equal($otherperm) {
514         // The equal function seems to be unable to detect removed perm.
515         // Use case is when a rule is removed.
516         return (print_r($this->perm, true) === print_r($otherperm, true));
517
518 //      $diff = array_diff_assoc_recursive($this->perm, $otherperm);
519 //        return empty($diff);
520     }
521     
522     /**
523      * returns list of all supported access types.
524      */
525     function accessTypes() {
526         return array_keys(PagePermission::defaultPerms());
527     }
528
529     /**
530      * special permissions for dot-files, beginning with '.'
531      * maybe also for '_' files?
532      */
533     function dotPerms() {
534         $def = array(ACL_ADMIN => true,
535                      ACL_OWNER => true);
536         $perm = array();
537         foreach (PagePermission::accessTypes() as $access) {
538             $perm[$access] = $def;
539         }
540         return $perm;
541     }
542
543     /**
544      *  dead code. not needed inside the object. see getPagePermissions($page)
545      */
546     function retrieve($page) {
547         $hash = $page->get('perm');
548         if ($hash)  // hash => object
549             $perm = new PagePermission(unserialize($hash));
550         else 
551             $perm = new PagePermission();
552         $perm->sanify();
553         return $perm;
554     }
555
556     function store($page) {
557         // object => hash
558         $this->sanify();
559         return $page->set('perm',serialize($this->perm));
560     }
561
562     function groupName ($group) {
563         if ($group[0] == '_') return constant("GROUP".$group);
564         else return $group;
565     }
566     
567     /* type: page, default, inherited */
568     function asTable($type) {
569         $table = HTML::table();
570         foreach ($this->perm as $access => $perms) {
571             $td = HTML::table(array('class' => 'cal'));
572             foreach ($perms as $group => $bool) {
573                 $td->pushContent(HTML::tr(HTML::td(array('align'=>'right'),$group),
574                                                    HTML::td($bool ? '[X]' : '[ ]')));
575             }
576             $table->pushContent(HTML::tr(array('valign' => 'top'),
577                                          HTML::td($access),HTML::td($td)));
578         }
579         if ($type == 'default')
580             $table->setAttr('style','border: dotted thin black; background-color:#eee;');
581         elseif ($type == 'inherited')
582             $table->setAttr('style','border: dotted thin black; background-color:#ddd;');
583         elseif ($type == 'page')
584             $table->setAttr('style','border: solid thin black; font-weight: bold;');
585         return $table;
586     }
587     
588     /* type: page, default, inherited */
589     function asEditableTable($type) {
590         global $WikiTheme;
591         if (!isset($this->_group)) { 
592             $this->_group =& $GLOBALS['request']->getGroup();
593         }
594         $table = HTML::table();
595         $table->pushContent(HTML::tr(
596                                      HTML::th(array('align' => 'left'),
597                                               _("Access")),
598                                      HTML::th(array('align'=>'right'),
599                                               _("Group/User")),
600                                      HTML::th(_("Grant")),
601                                      HTML::th(_("Del/+")),
602                                      HTML::th(_("Description"))));
603         
604         $allGroups = $this->_group->_specialGroups();
605         foreach ($this->_group->getAllGroupsIn() as $group) {
606             if (!in_array($group,$this->_group->specialGroups()))
607                 $allGroups[] = $group;
608         }
609         //array_unique(array_merge($this->_group->getAllGroupsIn(),
610         $deletesrc = $WikiTheme->_findData('images/delete.png');
611         $addsrc = $WikiTheme->_findData('images/add.png');
612         $nbsp = HTML::raw('&nbsp;');
613         foreach ($this->perm as $access => $groups) {
614             //$permlist = HTML::table(array('class' => 'cal'));
615             $first_only = true;
616             $newperm = HTML::input(array('type' => 'checkbox',
617                                          'name' => "acl[_new_perm][$access]",
618                                          'value' => 1));
619             $addbutton = HTML::input(array('type' => 'checkbox',
620                                            'name' => "acl[_add_group][$access]",
621                                            //'src'  => $addsrc,
622                                            //'alt'   => "Add",
623                                            'title' => _("Add this ACL"),
624                                            'value' => 1));
625             $newgroup = HTML::select(array('name' => "acl[_new_group][$access]",
626                                            'style'=> 'text-align: right;',
627                                            'size' => 1));
628             foreach ($allGroups as $groupname) {
629                 if (!isset($groups[$groupname]))
630                     $newgroup->pushContent(HTML::option(array('value' => $groupname),
631                                                         $this->groupName($groupname)));
632             }
633             if (empty($groups)) {
634                 $addbutton->setAttr('checked','checked');
635                 $newperm->setAttr('checked','checked');
636                 $table->pushContent(
637                     HTML::tr(array('valign' => 'top'),
638                              HTML::td(HTML::strong($access.":")),
639                              HTML::td($newgroup),
640                              HTML::td($nbsp,$newperm),
641                              HTML::td($nbsp,$addbutton),
642                              HTML::td(HTML::em(getAccessDescription($access)))));
643             }
644             foreach ($groups as $group => $bool) {
645                 $checkbox = HTML::input(array('type' => 'checkbox',
646                                               'name' => "acl[$access][$group]",
647                                               'title' => _("Allow / Deny"),
648                                               'value' => 1));
649                 if ($bool) $checkbox->setAttr('checked','checked');
650                 $checkbox = HTML(HTML::input(array('type' => 'hidden',
651                                                    'name' => "acl[$access][$group]",
652                                                    'value' => 0)),
653                                  $checkbox);
654                 $deletebutton = HTML::input(array('type' => 'checkbox',
655                                                   'name' => "acl[_del_group][$access][$group]",
656                                                   'style' => 'background: #aaa url('.$deletesrc.')',
657                                                   //'src'  => $deletesrc,
658                                                   //'alt'   => "Del",
659                                                   'title' => _("Delete this ACL"),
660                                                   'value' => 1));
661                 if ($first_only) {
662                     $table->pushContent(
663                         HTML::tr(
664                                  HTML::td(HTML::strong($access.":")),
665                                  HTML::td(array('class' => 'cal-today','align'=>'right'),
666                                           HTML::strong($this->groupName($group))),
667                                  HTML::td(array('align'=>'center'),$nbsp,$checkbox),
668                                  HTML::td(array('align'=>'right','style' => 'background: #aaa url('.$deletesrc.') no-repeat'),$deletebutton),
669                                  HTML::td(HTML::em(getAccessDescription($access)))));
670                     $first_only = false;
671                 } else {
672                     $table->pushContent(
673                         HTML::tr(
674                                  HTML::td(),
675                                  HTML::td(array('class' => 'cal-today','align'=>'right'),
676                                           HTML::strong($this->groupName($group))),
677                                  HTML::td(array('align'=>'center'),$nbsp,$checkbox),
678                                  HTML::td(array('align'=>'right','style' => 'background: #aaa url('.$deletesrc.') no-repeat'),$deletebutton),
679                                  HTML::td()));
680                 }
681             }
682             if (!empty($groups))
683                 $table->pushContent(
684                     HTML::tr(array('valign' => 'top'),
685                              HTML::td(array('align'=>'right'),_("add ")),
686                              HTML::td($newgroup),
687                              HTML::td(array('align'=>'center'),$nbsp,$newperm),
688                              HTML::td(array('align'=>'right','style' => 'background: #ccc url('.$addsrc.') no-repeat'),$addbutton),
689                              HTML::td(HTML::small(_("Check to add this ACL")))));
690         }
691         if ($type == 'default')
692             $table->setAttr('style','border: dotted thin black; background-color:#eee;');
693         elseif ($type == 'inherited')
694             $table->setAttr('style','border: dotted thin black; background-color:#ddd;');
695         elseif ($type == 'page')
696             $table->setAttr('style','border: solid thin black; font-weight: bold;');
697         return $table;
698     }
699
700     // Print ACL as lines of [+-]user[,group,...]:access[,access...]
701     // Seperate acl's by "; " or whitespace
702     // See http://opag.ca/wiki/HelpOnAccessControlLists
703     // As used by WikiAdminSetAclSimple
704     function asAclLines() {
705         $s = ''; $line = '';
706         $this->sanify();
707         foreach ($this->perm as $access => $groups) {
708             // unify groups for same access+bool
709             //    view:CREATOR,-OWNER,
710             $line = $access.':';
711             foreach ($groups as $group => $bool) {
712                 $line .= ($bool?'':'-').$group.",";
713             }
714             if (substr($line,-1) == ',')
715                 $s .= substr($line,0,-1)."; ";
716         }
717         if (substr($s,-2) == '; ')
718             $s = substr($s,0,-2);
719         return $s;
720     }
721
722     // Print ACL as group followed by actions allowed for the group
723     function asAclGroupLines() {
724
725         $s = '';
726         $perm =& $this->perm;
727         $actions = array("view", "edit", "create", "list", "remove", "purge", "dump", "change");
728         $groups = array(ACL_EVERY, ACL_ANONYMOUS, ACL_BOGOUSER, ACL_HASHOMEPAGE, ACL_SIGNED, ACL_AUTHENTICATED, ACL_ADMIN, ACL_OWNER, ACL_CREATOR);
729
730         foreach ($groups as $group) {
731             $none = true;
732             foreach ($actions as $action) {
733                 if (isset($perm[$action][$group])) {
734                     if ($none) {
735                         $none = false;
736                         $s .= "$group:";
737                     }
738                     $s .= (($perm[$action][$group] ? " " : " -") . $action);
739                 }
740             }
741             if (!($none)) {
742                 $s .= "; ";
743             }
744         }
745         return $s;
746     }
747
748     // This is just a bad hack for testing.
749     // Simplify the ACL to a unix-like "rwx------+" string
750     // See getfacl(8)
751     function asRwxString($owner,$group=false) {
752         global $request;
753         // simplify object => rwxrw---x+ string as in cygwin (+ denotes additional ACLs)
754         $perm =& $this->perm;
755         // get effective user and group
756         $s = '---------+';
757         if (isset($perm['view'][$owner]) or 
758             (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
759             $s[0] = 'r';
760         if (isset($perm['edit'][$owner]) or 
761             (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
762             $s[1] = 'w';
763         if (isset($perm['change'][$owner]) or 
764             (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
765             $s[2] = 'x';
766         if (!empty($group)) {
767             if (isset($perm['view'][$group]) or 
768                 (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
769                 $s[3] = 'r';
770             if (isset($perm['edit'][$group]) or 
771                 (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
772                 $s[4] = 'w';
773             if (isset($perm['change'][$group]) or 
774                 (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
775                 $s[5] = 'x';
776         }
777         if (isset($perm['view'][ACL_EVERY]) or 
778             (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
779             $s[6] = 'r';
780         if (isset($perm['edit'][ACL_EVERY]) or 
781             (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
782             $s[7] = 'w';
783         if (isset($perm['change'][ACL_EVERY]) or 
784             (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
785             $s[8] = 'x';
786         return $s;
787     }
788 }
789
790 // Local Variables:
791 // mode: php
792 // tab-width: 8
793 // c-basic-offset: 4
794 // c-hanging-comment-ender-p: nil
795 // indent-tabs-mode: nil
796 // End:
797 ?>