3 * FusionForge role-based access control
5 * Copyright 2004, GForge, LLC
6 * Copyright 2009-2010, Roland Mas
7 * Copyright 2012-2014, Franck Villaume - TrivialDev
8 * Copyright 2012, Thorsten “mirabilos” Glaser <t.glaser@tarent.de>
9 * Copyright 2013, French Ministry of National Education
10 * Copyright 2014, Inria (Sylvain Beucler)
11 * http://fusionforge.org
13 * This file is part of FusionForge. FusionForge is free software;
14 * you can redistribute it and/or modify it under the terms of the
15 * GNU General Public License as published by the Free Software
16 * Foundation; either version 2 of the Licence, or (at your option)
19 * FusionForge is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License along
25 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
29 require $gfcommon.'include/PFO-RBAC.interface.php';
30 require_once $gfcommon.'frs/FRSPackageFactory.class.php';
31 require_once $gfcommon.'include/SysTasksQ.class.php';
33 // Code shared between classes
36 * TODO: RBAC::BaseRole Enter description here ...
39 abstract class BaseRole extends FFError {
41 * TODO: Enter description here ...
46 * TODO: Enter description here ...
51 * TODO: Enter description here ...
57 // var $setting_array;
59 public function __construct() {
60 // TODO: document these tables
61 // $gfcommon.'include/rbac_texts.php' may provide some hints...
62 $this->role_values = array(
63 'forge_admin' => array(0, 1),
64 'approve_projects' => array(0, 1),
65 'approve_news' => array(0, 1),
66 'approve_diary' => array(0, 1),
67 'forge_stats' => array(0, 1, 2),
69 'project_read' => array(0, 1),
70 'project_admin' => array(0, 1),
72 'tracker_admin' => array(0, 1),
73 'pm_admin' => array(0, 1),
74 'forum_admin' => array(0, 1),
75 'frs_admin' => array(0, 1, 2),
77 'tracker' => array(0, 1, 9, 11, 13, 15, 25, 27, 29, 31),
78 'pm' => array(0, 1, 3, 5, 7),
79 'forum' => array(0, 1, 2, 3, 4),
80 'frs' => array(0, 1, 2, 3, 4),
82 'new_tracker' => array(0, 1, 9, 11, 13, 15, 25, 27, 29, 31),
83 'new_pm' => array(0, 1, 3, 5, 7),
84 'new_forum' => array(0, 1, 2, 3, 4),
85 'new_frs' => array(0, 1, 2, 3, 4),
87 'scm' => array (0, 1, 2),
88 'docman' => array (0, 1, 2, 3, 4),
92 $this->global_settings = array(
93 'forge_admin', // “God mode”: all actions allowed
94 'approve_projects', // Ability to approve pending projects
95 'approve_news', // Ability to approve news bits to the forge front page
96 'approve_diary', // Ability to approve diary notes to the forge front page
100 // TODO: document these (Project-related permissions ?)
101 $this->defaults = array(
102 'Admin' => array( 'project_admin'=> 1,
110 'tracker_admin' => 1,
115 'Senior Developer' => array( 'project_read' => 1,
122 'tracker_admin' => 1,
127 'Junior Developer' => array( 'project_read' => 1,
136 'Doc Writer' => array( 'project_read' => 1,
144 'Support Tech' => array( 'project_read' => 1,
149 'tracker_admin' => 1,
157 public function getUsers() {
160 public function hasUser($user) {
161 throw new Exception ("Not implemented");
163 function hasGlobalPermission($section, $action = NULL) {
164 return $this->hasPermission ($section, -1, $action);
166 public function getSettings() {
167 throw new Exception ("Not implemented");
169 public function setSettings($data) {
170 throw new Exception ("Not implemented");
172 public function delete() {
173 throw new Exception ("Not implemented");
177 * getLinkedProjects - List of projects referencing that role
179 * Includes the home project (for roles that have one)
181 * @return array Array of Group objects
183 public function getLinkedProjects() {
186 $hp = $this->getHomeProject();
188 $ids[] = $hp->getID();
191 $res = db_query_params('SELECT group_id FROM role_project_refs WHERE role_id=$1',
192 array($this->getID()));
194 while ($arr = db_fetch_array ($res)) {
195 $ids[] = $arr['group_id'];
199 return group_get_objects(array_unique($ids));
202 function linkProject($project) { // From the PFO spec
204 $hp = $this->getHomeProject();
205 if ($hp != NULL && $hp->getID() == $project->getID()) {
206 $this->setError(_("Cannot link to home project"));
210 $res = db_query_params('SELECT group_id FROM role_project_refs WHERE role_id=$1 AND group_id=$2',
211 array($this->getID(),
214 if (db_numrows($res)) {
217 $res = db_query_params('INSERT INTO role_project_refs (role_id, group_id) VALUES ($1, $2)',
218 array($this->getID(),
220 if (!$res || db_affected_rows($res) < 1) {
221 $this->setError('linkProject('.$project->getID().') '.db_error());
225 $this->normalizeData();
227 foreach ($this->getUsers() as $u) {
228 if (!$SYS->sysCheckCreateUser($u->getID())) {
229 $this->setError($SYS->getErrorMessage());
237 function unlinkProject($project) { // From the PFO spec
239 $hp = $this->getHomeProject();
240 if ($hp != NULL && $hp->getID() == $project->getID()) {
241 $this->setError (_("Cannot unlink from home project"));
245 $res = db_query_params('DELETE FROM role_project_refs WHERE role_id=$1 AND group_id=$2',
246 array($this->getID(),
249 $this->setError('unlinkProject('.$project->getID().') '.db_error());
253 $this->removeObsoleteSettings();
255 foreach ($this->getUsers() as $u) {
256 if (!$SYS->sysCheckCreateUser($u->getID())) {
257 $this->setError($SYS->getErrorMessage());
262 $hook_params = array();
263 $hook_params['role'] =& $this;
264 $hook_params['project'] =& $project;
265 plugin_hook ("role_unlink_project", $hook_params);
267 # Change repo permissions when we change anonymous access
268 # Not done in SetSetting() because we used batch-mode removeObsoleteSettings()
269 $anon = RoleAnonymous::getInstance();
270 if ($this->getID() == $anon->getID()) {
271 $systasksq = new SysTasksQ();
272 $systasksq->add(SYSTASK_CORE, 'SCM_REPO', $project->getID());
279 * fetchData - May need to refresh database fields.
281 * If an update occurred and you need to access the updated info.
283 * @param int $role_id
284 * @return bool success
286 function fetchData($role_id) {
287 unset($this->data_array);
288 unset($this->setting_array);
289 unset($this->perms_array);
291 $res = db_query_params('SELECT * FROM pfo_role WHERE role_id=$1',
293 if (!$res || db_numrows($res) < 1) {
294 $this->setError('BaseRole::fetchData()::'.db_error());
297 $this->data_array = db_fetch_array($res);
298 if ($this->data_array['is_public'] == 't') {
299 $this->data_array['is_public'] = true;
301 $this->data_array['is_public'] = false;
303 $res = db_query_params('SELECT section_name, ref_id, perm_val FROM pfo_role_setting WHERE role_id=$1',
306 $this->setError('BaseRole::fetchData()::'.db_error());
309 // TODO: document perms_array
310 $this->perms_array=array();
311 while ($arr = db_fetch_array($res)) {
312 $this->perms_array[$arr['section_name']][$arr['ref_id']] = intval($arr['perm_val']);
318 function setSetting ($section, $reference, $value) {
319 $cur = $this->getSettingRaw($section, $reference);
320 if (($value == $cur) && ($cur != NULL)) {
324 $role_id = $this->getID();
326 db_query_params ('DELETE FROM pfo_role_setting WHERE role_id=$1 AND section_name=$2 AND ref_id=$3',
331 db_query_params ('INSERT INTO pfo_role_setting (role_id, section_name, ref_id, perm_val) VALUES ($1, $2, $3, $4)',
336 $this->perms_array[$section][$reference] = $value;
338 # Change repo permissions when we change anonymous access
339 $anon = RoleAnonymous::getInstance();
340 if ($section == 'scm' && $this->getID() == $anon->getID()) {
341 $systasksq = new SysTasksQ();
342 $systasksq->add(SYSTASK_CORE, 'SCM_REPO', $reference);
346 function getSettingsForProject ($project) {
348 $group_id = $project->getID();
350 $sections = array ('project_read', 'project_admin', 'scm', 'docman', 'tracker_admin', 'new_tracker');
351 foreach ($sections as $section) {
352 $result[$section][$group_id] = $this->getVal ($section, $group_id);
355 if ($project->usesTracker()) {
356 $atf = new ArtifactTypeFactory ($project);
357 if (!$atf->isError()) {
358 $tids = $atf->getAllArtifactTypeIds();
359 foreach ($tids as $tid) {
360 $result['tracker'][$tid] = $this->getVal ('tracker', $tid);
363 array_push ($sections,'tracker');
365 $sections_frs = array('frs_admin', 'new_frs');
366 foreach ($sections_frs as $section_frs) {
367 $result[$section_frs][$group_id] = $this->getVal($section_frs, $group_id);
369 $sections = array_merge($sections, $sections_frs);
371 if ($project->usesFRS()) {
372 $frspf = new FRSPackageFactory($project);
373 if (!$frspf->isError()) {
374 $pkgids = $frspf->getAllPackagesIds();
375 foreach ($pkgids as $pkgid) {
376 $result['frs'][$pkgid] = $this->getVal ('frs',$pkgid);
379 array_push($sections,'frs');
381 $sections = array_merge($sections, $sections_frs);
383 /*XXX merge from Branch_5_1: maybe this also only if usesForum? */
384 $sections_forum = array('forum_admin', 'new_forum');
385 foreach ($sections_forum as $section_forum) {
386 $result[$section_forum][$group_id] = $this->getVal ($section_forum, $group_id);
388 $sections = array_merge($sections, $sections_forum);
390 if ($project->usesForum()) {
391 $ff = new ForumFactory ($project);
392 if (!$ff->isError()) {
393 $fids = $ff->getAllForumIdsWithNews();
394 foreach ($fids as $fid) {
395 $result['forum'][$fid] = $this->getVal ('forum', $fid);
398 array_push ($sections,'forum');
401 /*XXX see above, maybe only if usesPM? */
402 $sections_pm = array('pm_admin', 'new_pm');
403 foreach ($sections_pm as $section_pm) {
404 $result[$section_pm][$group_id] = $this->getVal ($section_pm, $group_id);
406 $sections = array_merge($sections, $sections_pm);
408 if ($project->usesPM()) {
409 $pgf = new ProjectGroupFactory ($project);
410 if (!$pgf->isError()) {
411 $pgids = $pgf->getAllProjectGroupIds();
412 foreach ($pgids as $pgid) {
413 $result['pm'][$pgid] = $this->getVal ('pm', $pgid);
416 array_push ($sections,'pm');
419 // Add settings not yet listed so far (probably plugins)
420 // Currently handled:
421 // - global settings (ignored here)
422 // - project-wide settings (core and plugins)
423 // - settings for multiple-instance tools coming from the core (trackers/pm/forums)
425 // - settings for multiple-instance tools from plugins
426 foreach (array_keys ($this->perms_array) as $section) {
427 if (!in_array ($section, $sections)) {
428 if (!in_array ($section, $this->global_settings)) {
429 $result[$section][$group_id] = $this->getVal ($section, $group_id);
438 * getGlobalSettings - get the permissions for global settings
440 * The following sections are global : forge_admin, forge_stats, approve_projects, approve_news, approve_diary
442 * @return array array of permission for global settings
444 function getGlobalSettings() {
447 $sections = array ('forge_admin', 'forge_stats', 'approve_projects', 'approve_news', 'approve_diary');
448 foreach ($sections as $section) {
449 $result[$section][-1] = $this->getVal($section, -1);
451 // Add settings not yet listed so far (probably plugins)
452 foreach (array_keys ($this->perms_array) as $section) {
453 if (!in_array ($section, $sections)) {
454 if (in_array ($section, $this->global_settings)) {
455 $result[$section][-1] = $this->getVal ($section, -1);
464 * getSetting - TODO: Enter description here ...
466 * @param string $section
467 * @param unknown_type $reference
468 * @return number|bool
470 function getSetting($section, $reference) {
471 $value = $this->getSettingRaw($section, $reference);
472 if ($value == NULL) {
485 case 'approve_projects':
487 case 'approve_diary':
488 case 'project_admin':
489 if ($this->hasGlobalPermission('forge_admin')) {
496 if ($this->hasGlobalPermission('forge_admin')) {
503 if ($this->hasPermission('project_admin', $reference)) {
508 case 'tracker_admin':
511 if ($this->hasPermission('project_admin', $reference)) {
513 } elseif (!$this->hasPermission('project_read', $reference)) {
520 if ($this->hasPermission('project_admin', $reference)) {
522 } elseif (!$this->hasPermission('project_read', $reference)) {
529 if ($this->hasPermission('project_admin', $reference)) {
531 } elseif (!$this->hasPermission('project_read', $reference)) {
538 if ($this->hasPermission('frs_admin', frspackage_get_groupid($reference), 'admin')) {
540 } elseif (!$this->hasPermission('project_read', frspackage_get_groupid($reference))) {
546 if ($this->hasPermission('frs_admin', $reference, 'admin')) {
548 } elseif (!$this->hasPermission('project_read', $reference)) {
555 if ($this->hasPermission('forum_admin', forum_get_groupid($reference))) {
557 } elseif (!$this->hasPermission('project_read', forum_get_groupid($reference))) {
563 if ($this->hasPermission('forum_admin', $reference)) {
565 } elseif (!$this->hasPermission('project_read', $reference)) {
572 if ($this->hasPermission('tracker_admin', artifacttype_get_groupid($reference))) {
574 } elseif (!$this->hasPermission('project_read', artifacttype_get_groupid($reference))) {
580 if ($this->hasPermission('tracker_admin', $reference)) {
582 } elseif (!$this->hasPermission('project_read', $reference)) {
589 if ($this->hasPermission('pm_admin', projectgroup_get_groupid($reference))) {
591 } elseif (!$this->hasPermission('project_read', projectgroup_get_groupid($reference))) {
597 if ($this->hasPermission('pm_admin', $reference)) {
599 } elseif (!$this->hasPermission('project_read', $reference)) {
605 $hook_params = array();
606 $hook_params['role'] = $this;
607 $hook_params['section'] = $section;
608 $hook_params['reference'] = $reference;
609 $hook_params['value'] = $value;
610 $hook_params['result'] = NULL;
611 plugin_hook_by_reference ("role_get_setting", $hook_params);
612 return $hook_params['result'];
617 function getSettingRaw($section, $reference) {
618 if (isset ($this->perms_array[$section][$reference])) {
619 return $this->perms_array[$section][$reference];
624 * getVal - get a value out of the array of settings for this role.
626 * @param string $section The name of the role.
627 * @param int $ref_id The ref_id (ex: group_artifact_id, group_forum_id) for this item.
628 * @return int The value of this item.
630 function getVal($section, $ref_id) {
634 return $this->getSetting($section, $ref_id);
638 * &getRoleVals - get all the values and language text strings for this section.
640 * @param string $section
641 * @return array Assoc array of values for this section.
643 function &getRoleVals($section) {
644 global $role_vals, $rbac_permission_names;
645 setup_rbac_strings();
648 // Optimization - save array so it is only built once per page view
650 if (!isset($role_vals[$section])) {
652 for ($i=0; $i<count($this->role_values[$section]); $i++) {
654 // Build an associative array of these key values + localized description
656 $role_vals[$section][$this->role_values[$section][$i]] =
657 util_ifsetor($rbac_permission_names["$section".$this->role_values[$section][$i]],
658 _('UNKNOWN (internal error, report bug to FusionForge)'));
661 return $role_vals[$section];
664 function hasPermission($section, $reference, $action = NULL) {
666 $value = $this->getSetting ($section, $reference);
673 case 'approve_projects':
675 case 'approve_diary':
676 case 'project_admin':
678 case 'tracker_admin':
681 return ($value >= 1);
688 return ($value >= 1);
691 return ($value >= 2);
699 return ($value >= 1);
702 return ($value >= 2);
710 return ($value >= 1);
713 return ($value >= 2);
716 return ($value >= 3);
719 return ($value >= 4);
728 return ($value >= 1);
731 return ($value >= 2);
734 return ($value >= 3);
737 return ($value >= 4);
746 return ($value >= 1);
749 return ($value >= 2);
751 case 'unmoderated_post':
752 return ($value >= 3);
755 return ($value >= 4);
764 return (($value & 1) != 0);
767 return (($value & 2) != 0);
770 return (($value & 4) != 0);
773 return (($value & 8) != 0);
776 return (($value & 16) != 0);
785 return (($value & 1) != 0);
788 return (($value & 2) != 0);
791 return (($value & 4) != 0);
797 $hook_params = array();
798 $hook_params['section'] = $section;
799 $hook_params['reference'] = $reference;
800 $hook_params['action'] = $action;
801 $hook_params['value'] = $value;
802 $hook_params['result'] = false;
803 plugin_hook_by_reference ("role_has_permission", $hook_params);
804 return $hook_params['result'];
810 * update - update a role in the database.
812 * @param string $role_name The name of the role.
813 * @param array $data A multi-dimensional array of data in this format: $data['section_name']['ref_id']=$val
814 * @param bool $check_perms Perform permission checking
815 * @param bool $update_sys Update system users & groups membership
816 * @return bool True on success or false on failure.
818 function update($role_name,$data,$check_perms=true,$update_sys=true) {
821 if ($this->getHomeProject() == NULL) {
822 if (!forge_check_global_perm ('forge_admin')) {
823 $this->setPermissionDeniedError();
826 } elseif (!forge_check_perm ('project_admin', $this->getHomeProject()->getID())) {
827 $this->setPermissionDeniedError();
834 $role_id = $this->getID();
836 if ($role_name != $this->getName()) {
837 $this->setName($role_name);
840 db_prepare ('INSERT INTO pfo_role_setting (role_id, section_name, ref_id, perm_val) VALUES ($1, $2, $3, $4)',
841 'insert_into_pfo_role_setting');
842 db_prepare ('DELETE FROM pfo_role_setting WHERE role_id=$1 AND section_name=$2 AND ref_id=$3',
843 'delete_from_pfo_role_setting');
844 db_prepare ('UPDATE pfo_role_setting SET perm_val=$4 WHERE role_id=$1 AND section_name=$2 AND ref_id=$3',
845 'update_pfo_role_setting');
847 // Don't remove unknown permissions (e.g. forums permissions while forums are currently disabled)
848 //foreach ($this->perms_array as $sect => &$refs)
849 // foreach ($refs as $refid => $value)
850 // if (!isset($data[$sect][$refid]) or $data[$sect][$refid] != $value)
851 // db_execute('delete_from_pfo_role_setting', array($role_id, $sect, $refid));
853 // Insert new/changed permissions
854 $anon = RoleAnonymous::getInstance();
855 foreach ($data as $sect => &$refs) {
856 foreach ($refs as $refid => $value) {
857 if (!isset($this->perms_array[$sect][$refid])) {
859 db_execute('insert_into_pfo_role_setting',
860 array($role_id, $sect, $refid, $value));
861 } elseif ($this->perms_array[$sect][$refid] != $value) {
862 // changed permission
863 db_execute('update_pfo_role_setting',
864 array($role_id, $sect, $refid, $value));
868 # Change repo permissions when we edit anonymous access for a single project
869 if ($sect == 'scm' && $this->getID() == $anon->getID() && count($refs) == 1) {
870 $systasksq = new SysTasksQ();
871 $systasksq->add(SYSTASK_CORE, 'SCM_REPO', $refid);
875 db_unprepare ('insert_into_pfo_role_setting');
876 db_unprepare ('delete_from_pfo_role_setting');
877 db_unprepare ('update_pfo_role_setting');
879 $hook_params = array();
880 $hook_params['role'] =& $this;
881 $hook_params['role_id'] = $this->getID();
882 $hook_params['data'] = $data;
883 plugin_hook ("role_update", $hook_params);
886 $this->fetchData($this->getID());
889 foreach ($this->getUsers() as $u) {
890 if (!$SYS->sysCheckCreateUser($u->getID())) {
891 $this->setError($SYS->getErrorMessage());
900 function getDisplayableName($group = NULL) {
901 if ($this->getHomeProject() == NULL) {
902 return sprintf (_('%s (global role)'),
904 } elseif ($group == NULL
905 || $this->getHomeProject()->getID() != $group->getID()) {
906 return sprintf (_('%s (in project %s)'),
908 $this->getHomeProject()->getPublicName());
910 return $this->getName();
914 function removeObsoleteSettings() {
917 // Remove obsolete project-wide settings
918 $sections = array ('project_read', 'project_admin', 'frs_admin', 'new_frs', 'scm', 'docman', 'tracker_admin', 'new_tracker', 'forum_admin', 'new_forum', 'pm_admin', 'new_pm');
919 db_query_params ('DELETE FROM pfo_role_setting where role_id=$1 AND section_name=ANY($2) and ref_id NOT IN (SELECT home_group_id FROM pfo_role WHERE role_id=$1 AND home_group_id IS NOT NULL UNION SELECT group_id from role_project_refs WHERE role_id=$1)',
920 array ($this->getID(),
921 db_string_array_to_any_clause($sections)));
923 // Remove obsolete settings for multiple-instance tools
924 db_query_params ('DELETE FROM pfo_role_setting where role_id=$1 AND section_name=$2 and ref_id NOT IN (SELECT group_artifact_id FROM artifact_group_list WHERE group_id IN (SELECT home_group_id FROM pfo_role WHERE role_id=$1 AND home_group_id IS NOT NULL UNION SELECT group_id from role_project_refs WHERE role_id=$1))',
925 array ($this->getID(),
927 db_query_params ('DELETE FROM pfo_role_setting where role_id=$1 AND section_name=$2 and ref_id NOT IN (SELECT group_project_id FROM project_group_list WHERE group_id IN (SELECT home_group_id FROM pfo_role WHERE role_id=$1 AND home_group_id IS NOT NULL UNION SELECT group_id from role_project_refs WHERE role_id=$1))',
928 array ($this->getID(),
930 db_query_params ('DELETE FROM pfo_role_setting where role_id=$1 AND section_name=$2 and ref_id NOT IN (SELECT group_forum_id FROM forum_group_list WHERE group_id IN (SELECT home_group_id FROM pfo_role WHERE role_id=$1 AND home_group_id IS NOT NULL UNION SELECT group_id from role_project_refs WHERE role_id=$1))',
931 array ($this->getID(),
933 db_query_params ('DELETE FROM pfo_role_setting where role_id=$1 AND section_name=$2 and ref_id NOT IN (SELECT package_id FROM frs_package WHERE group_id IN (SELECT home_group_id FROM pfo_role WHERE role_id=$1 AND home_group_id IS NOT NULL UNION SELECT group_id from role_project_refs WHERE role_id=$1))',
934 array ($this->getID(), 'frs'));
937 $this->fetchData($this->getID());
941 function normalizePermsForSection (&$new_pa, $section, $refid) {
942 if (array_key_exists ($section, $this->perms_array)
943 && array_key_exists ($refid, $this->perms_array[$section])) {
944 $new_pa[$section][$refid] = $this->perms_array[$section][$refid];
945 } elseif (array_key_exists ($this->data_array['role_name'], $this->defaults)
946 && array_key_exists ($section, $this->defaults[$this->data_array['role_name']])) {
947 $new_pa[$section][$refid] = $this->defaults[$this->data_array['role_name']][$section];
949 $new_pa[$section][$refid] = 0;
954 function normalizeData() { // From the PFO spec
955 $this->removeObsoleteSettings();
957 $this->fetchData ($this->getID());
959 $projects = $this->getLinkedProjects();
962 // Add missing settings
963 // ...project-wide settings
964 $arr = array ('project_read', 'project_admin', 'scm', 'docman', 'frs_admin', 'new_frs', 'tracker_admin', 'new_tracker', 'forum_admin', 'new_forum', 'pm_admin', 'new_pm');
965 foreach ($projects as $p) {
966 foreach ($arr as $section) {
967 $this->normalizePermsForSection ($new_pa, $section, $p->getID());
970 $this->normalizePermsForSection($new_pa, 'forge_admin', -1);
971 $this->normalizePermsForSection($new_pa, 'approve_projects', -1);
972 $this->normalizePermsForSection($new_pa, 'approve_news', -1);
973 $this->normalizePermsForSection($new_pa, 'approve_diary', -1);
974 $this->normalizePermsForSection($new_pa, 'forge_stats', -1);
976 $hook_params = array();
977 $hook_params['role'] =& $this;
978 $hook_params['new_pa'] =& $new_pa;
979 plugin_hook ("role_normalize", $hook_params);
981 // ...tracker-related settings
982 $new_pa['tracker'] = array();
983 // Direct query to avoid querying each project - especially for global roles
984 $project_ids = array();
985 foreach ($projects as $p) {
986 $project_ids[] = $p->getID();
988 $res = db_query_params('SELECT group_artifact_id FROM artifact_group_list JOIN groups USING (group_id) WHERE use_tracker = 1 AND group_id = ANY($1)',
989 array(db_int_array_to_any_clause($project_ids)));
990 while ($row = db_fetch_array($res)) {
991 $tid = $row['group_artifact_id'];
992 if (array_key_exists ('tracker', $this->perms_array)
993 && array_key_exists ($tid, $this->perms_array['tracker']) ) {
994 $new_pa['tracker'][$tid] = $this->perms_array['tracker'][$tid];
995 } elseif (array_key_exists ('new_tracker', $this->perms_array)
996 && array_key_exists ($p->getID(), $this->perms_array['new_tracker']) ) {
997 $new_pa['tracker'][$tid] = $new_pa['new_tracker'][$p->getID()];
1001 // ...forum-related settings
1002 $new_pa['forum'] = array();
1003 foreach ($projects as $p) {
1004 if (!$p->usesForum()) {
1007 $ff = new ForumFactory ($p);
1008 if (!$ff->isError()) {
1009 $fids = $ff->getAllForumIdsWithNews();
1010 foreach ($fids as $fid) {
1011 if (array_key_exists ('forum', $this->perms_array)
1012 && array_key_exists ($fid, $this->perms_array['forum']) ) {
1013 $new_pa['forum'][$fid] = $this->perms_array['forum'][$fid];
1014 } elseif (array_key_exists ('new_forum', $this->perms_array)
1015 && array_key_exists ($p->getID(), $this->perms_array['new_forum']) ) {
1016 $new_pa['forum'][$fid] = $new_pa['new_forum'][$p->getID()];
1022 // ...pm-related settings
1023 $new_pa['pm'] = array();
1024 foreach ($projects as $p) {
1025 if (!$p->usesPM()) {
1028 $pgf = new ProjectGroupFactory ($p);
1029 if (!$pgf->isError()) {
1030 $pgids = $pgf->getAllProjectGroupIds();
1031 foreach ($pgids as $gid) {
1032 if (array_key_exists ('pm', $this->perms_array)
1033 && array_key_exists ($gid, $this->perms_array['pm']) ) {
1034 $new_pa['pm'][$gid] = $this->perms_array['pm'][$gid];
1035 } elseif (array_key_exists ('new_pm', $this->perms_array)
1036 && array_key_exists ($p->getID(), $this->perms_array['new_pm']) ) {
1037 $new_pa['pm'][$gid] = $new_pa['new_pm'][$p->getID()];
1043 // ...frs-related settings
1044 $new_pa['frs'] = array();
1045 foreach ($projects as $p) {
1046 if (!$p->usesFRS()) {
1049 $frspf = new FRSPackageFactory($p);
1050 if (!$frspf->isError()) {
1051 $frspids = $frspf->getAllPackagesIds();
1052 foreach ($frspids as $frspid) {
1053 if (array_key_exists('frs', $this->perms_array) && array_key_exists($frspid, $this->perms_array['frs'])) {
1054 $new_pa['frs'][$frspid] = $this->perms_array['frs'][$frspid];
1055 } elseif (array_key_exists('new_frs', $this->perms_array) && array_key_exists($p->getID(), $this->perms_array['new_frs']) ) {
1056 $new_pa['frs'][$frspid] = $new_pa['new_frs'][$p->getID()];
1062 $this->update($this->getName(), $new_pa, false, false);
1070 * TODO: RBAC::RoleExplicit Enter description here ...
1073 abstract class RoleExplicit extends BaseRole implements PFO_RoleExplicit {
1074 public function addUsers($users) {
1078 foreach ($users as $user) {
1079 $ids[] = $user->getID();
1082 $already_there = array();
1083 $res = db_query_params('SELECT user_id FROM pfo_user_role WHERE user_id=ANY($1) AND role_id=$2',
1084 array(db_int_array_to_any_clause($ids), $this->getID()));
1088 while ($arr = db_fetch_array($res)) {
1089 $already_there[] = $arr['user_id'];
1092 foreach ($ids as $id) {
1093 if (!in_array($id, $already_there)) {
1094 $res = db_query_params('INSERT INTO pfo_user_role (user_id, role_id) VALUES ($1, $2)',
1095 array ($id, $this->getID()));
1102 foreach ($this->getLinkedProjects() as $p) {
1103 foreach ($ids as $uid) {
1104 if (!$SYS->sysGroupCheckUser($p->getID(),$uid)) {
1113 public function addUser($user) {
1114 if (!$this->addUsers(array($user))) {
1117 $hook_params['user'] = $user;
1118 $hook_params['role'] = $this;
1119 plugin_hook ("role_adduser", $hook_params);
1124 public function removeUsers($users) {
1128 foreach ($users as $user) {
1129 $ids[] = $user->getID();
1132 $res = db_query_params('DELETE FROM pfo_user_role WHERE user_id = ANY($1) AND role_id = $2',
1133 array(db_int_array_to_any_clause($ids), $this->getID()));
1135 foreach ($this->getLinkedProjects() as $p) {
1136 foreach ($ids as $uid) {
1137 $SYS->sysGroupCheckUser($p->getID(), $uid);
1144 public function removeUser($user) {
1145 if (!$this->removeUsers(array($user))) {
1148 $hook_params['user'] = $user;
1149 $hook_params['role'] = $this;
1150 plugin_hook("role_removeuser", $hook_params);
1155 public function getUsers() {
1157 $res = db_query_params('SELECT user_id FROM pfo_user_role WHERE role_id=$1',
1158 array($this->getID()));
1159 while ($arr = db_fetch_array($res)) {
1160 $result[] = user_get_object($arr['user_id']);
1166 public function hasUser($user) {
1167 $res = db_query_params('SELECT user_id FROM pfo_user_role WHERE user_id=$1 AND role_id=$2',
1168 array($user->getID(), $this->getID()));
1169 if ($res && db_numrows($res)) {
1176 function getID() { // From the PFO spec
1177 return $this->data_array['role_id'];
1180 function getName() { // From the PFO spec
1181 return $this->data_array['role_name'];
1185 class RoleAnonymous extends BaseRole implements PFO_RoleAnonymous {
1186 // This role is implemented as a singleton
1187 private static $_instance;
1189 public static function getInstance() {
1190 if (isset(self::$_instance)) {
1191 return self::$_instance;
1195 self::$_instance = new $c;
1197 /* drop vote rights from RoleAnonymous */
1199 foreach (array('tracker', 'new_tracker') as $x) {
1201 foreach (self::$_instance->role_values[$x] as $z) {
1202 if (($z & 16) != 0) {
1207 self::$_instance->role_values[$x] = $y;
1210 $res = db_query_params('SELECT r.role_id FROM pfo_role r, pfo_role_class c WHERE r.role_class = c.class_id AND c.class_name = $1',
1211 array('PFO_RoleAnonymous'));
1212 if (!$res || !db_numrows($res)) {
1213 throw new Exception(_('No PFO_RoleAnonymous role in the database'));
1215 self::$_instance->_role_id = db_result ($res, 0, 'role_id');
1217 $hook_params = array();
1218 $hook_params['role'] =& self::$_instance;
1219 plugin_hook ('role_get', $hook_params);
1221 self::$_instance->fetchData(self::$_instance->_role_id);
1223 return self::$_instance;
1226 public function getID() {
1227 return $this->_role_id;
1229 public function isPublic() {
1232 public function setPublic($flag) {
1233 throw new Exception(_('Cannot setPublic() on RoleAnonymous'));
1235 public function getHomeProject() {
1238 public function getName() {
1239 return _('Anonymous/not logged in');
1241 public function setName($name) {
1242 throw new Exception(_('Cannot setName() on RoleAnonymous'));
1246 class RoleLoggedIn extends BaseRole implements PFO_RoleLoggedin {
1247 // This role is implemented as a singleton
1248 private static $_instance;
1250 public static function getInstance() {
1251 if (isset(self::$_instance)) {
1252 return self::$_instance;
1256 self::$_instance = new $c;
1258 $res = db_query_params('SELECT r.role_id FROM pfo_role r, pfo_role_class c WHERE r.role_class = c.class_id AND c.class_name = $1',
1259 array ('PFO_RoleLoggedIn'));
1260 if (!$res || !db_numrows($res)) {
1261 throw new Exception(_('No PFO_RoleLoggedIn role in the database'));
1263 self::$_instance->_role_id = db_result ($res, 0, 'role_id');
1265 $hook_params = array();
1266 $hook_params['role'] =& self::$_instance;
1267 plugin_hook ('role_get', $hook_params);
1269 self::$_instance->fetchData (self::$_instance->_role_id);
1271 return self::$_instance;
1274 public function getID() {
1275 return $this->_role_id;
1277 public function isPublic() {
1280 public function setPublic ($flag) {
1281 throw new Exception(_('Cannot setPublic() on RoleLoggedIn'));
1283 public function getHomeProject() {
1286 public function getName() {
1287 return _('Any user logged in');
1289 public function setName($name) {
1290 throw new Exception(_('Cannot setName() on RoleLoggedIn'));
1294 abstract class RoleUnion extends BaseRole implements PFO_RoleUnion {
1295 public function addRole($role) {
1296 throw new Exception(_('Not implemented'));
1298 public function removeRole($role) {
1299 throw new Exception(_('Not implemented'));
1304 * TODO: Enter description here ...
1307 class RoleComparator {
1308 var $criterion = 'composite';
1309 var $reference_project = NULL;
1311 function Compare ($a, $b) {
1312 switch ($this->criterion) {
1314 return strcoll ($a->getName(), $b->getName());
1322 return ($a < $b) ? -1 : 1;
1326 if ($this->reference_project == NULL) {
1327 return $this->CompareNoRef ($a, $b);
1329 $rpid = $this->reference_project->getID();
1330 $ap = $a->getHomeProject();
1331 $bp = $b->getHomeProject();
1332 $a_is_local = ($ap != NULL && $ap->getID() == $rpid); // Local
1333 $b_is_local = ($bp != NULL && $bp->getID() == $rpid);
1335 if ($a_is_local && !$b_is_local) {
1337 } elseif (!$a_is_local && $b_is_local) {
1340 return $this->CompareNoRef ($a, $b);
1345 * CompareNoRef - TODO: Enter description here ...
1350 function CompareNoRef ($a, $b) {
1351 $ap = $a->getHomeProject();
1352 $bp = $b->getHomeProject();
1353 if ($ap == NULL && $bp != NULL) {
1355 } elseif ($ap != NULL && $bp == NULL) {
1357 } elseif ($ap == NULL && $bp == NULL) {
1358 $tmp = strcoll ($a->getName(), $b->getName());
1361 $projcmp = new ProjectComparator();
1362 $projcmp->criterion = 'name';
1363 $tmp = $projcmp->Compare ($ap, $bp);
1364 if ($tmp) { /* Different projects, sort accordingly */
1367 return strcoll ($a->getName(), $b->getName());
1372 function sortRoleList (&$list, $relative_to = NULL, $criterion='composite') {
1373 $cmp = new RoleComparator();
1374 $cmp->criterion = $criterion;
1375 $cmp->reference_project = $relative_to;
1377 return usort ($list, array ($cmp, 'Compare'));
1382 // c-file-style: "bsd"