Index: project.api.php =================================================================== RCS file: project.api.php diff -N project.api.php --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ project.api.php 11 Aug 2010 17:27:27 -0000 @@ -0,0 +1,24 @@ + array(array('uri', 8)), ), ); + $schema['project_maintainer'] = array( + 'description' => t('Users who have various per-project maintainer permissions.'), + 'fields' => array( + 'nid' => array( + 'description' => t('Foreign key: {project_projects}.nid of the project.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'uid' => array( + 'description' => t('Foreign key: {users}.uid of a user with any project maintainer permissions.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'administer_project' => array( + 'description' => t('Can this user edit the given project.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'administer_project_maintainers' => array( + 'description' => t('Can this user manipulate the maintainers for the given project.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('nid', 'uid'), + ); + return $schema; } @@ -137,3 +172,56 @@ function project_update_6001() { return $ret; } +/** + * Add the {project_maintainer} table. + */ +function project_update_6002() { + $ret = array(); + $table = array( + 'description' => t('Users who have various per-project maintainer permissions.'), + 'fields' => array( + 'nid' => array( + 'description' => t('Foreign key: {project_projects}.nid of the project.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'uid' => array( + 'description' => t('Foreign key: {users}.uid of a user with any project maintainer permissions.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'administer_project' => array( + 'description' => t('Can this user edit the given project.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'administer_project_maintainers' => array( + 'description' => t('Can this user manipulate the maintainers for the given project.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('nid', 'uid'), + ); + db_create_table($ret, 'project_maintainer', $table); + + // Initially populate the table so that every project owner has full + // powers on their own projects. + $ret[] = update_sql("INSERT INTO {project_maintainer} (nid, uid, administer_project, administer_project_maintainers) SELECT nid, uid, 1, 1 FROM {node} WHERE type = 'project_project'"); + + // If CVS module is enabled, also populate the table from the + // {cvs_project_maintainers} table so that everyone with CVS access can + // administer the project but not manipulate maintainer permissions. + if (module_exists('cvs')) { + $ret[] = update_sql("INSERT INTO {project_maintainer} (nid, uid, administer_project, administer_project_maintainers) SELECT nid, uid, 1, 0 FROM {cvs_project_maintainers}"); + } + return $ret; +} Index: project.module =================================================================== RCS file: /Users/wright/drupal/local_repo/contributions/modules/project/project.module,v retrieving revision 1.361 diff -u -p -r1.361 project.module --- project.module 23 Jul 2010 04:12:12 -0000 1.361 +++ project.module 12 Aug 2010 02:01:06 -0000 @@ -518,6 +518,16 @@ function project_menu() { 'type' => MENU_NORMAL_ITEM, ); + $items['node/%project_node/maintainers'] = array( + 'title' => 'Maintainers', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('project_maintainers_form', 1), + 'file' => 'includes/project_maintainers.inc', + 'access callback' => 'project_check_admin_access', + 'access arguments' => array(1, 'administer project maintainers'), + 'type' => MENU_LOCAL_TASK, + ); + $items['node/%project_edit_project/edit/project'] = array( 'title' => 'Project', 'page callback' => 'node_page', @@ -567,7 +577,10 @@ function project_edit_project_load($nid) return project_node_load($nid); } -function project_check_admin_access($project, $cvs_access = NULL) { +/** + * See if the current user has permission to administer the given project. + */ +function project_check_admin_access($project, $permission = 'administer projects') { global $user; if (empty($user->uid)) { return FALSE; @@ -578,31 +591,21 @@ function project_check_admin_access($pro return FALSE; } - if (user_access('administer projects')) { + // If the current user has the site-wide permission, always grant access. + if (user_access($permission)) { return TRUE; } - // If $cvs_access is not defined, check to make sure the user has cvs access - // and that the user's cvs account is approved. - if (project_use_cvs($project_obj) && !isset($cvs_access)) { - if (db_result(db_query("SELECT COUNT(*) FROM {cvs_accounts} WHERE uid = %d AND status = %d", $user->uid, CVS_APPROVED))) { - $cvs_access = TRUE; - } - else { - $cvs_access = FALSE; - } - } - if (user_access('maintain projects')) { + // Project owners are treated as super users and can always access. if ($user->uid == $project_obj->uid) { return TRUE; } - if (project_use_cvs($project_obj) && $cvs_access) { - if (db_result(db_query("SELECT COUNT(*) FROM {cvs_project_maintainers} WHERE uid = %d AND nid = %d", $user->uid, $project_obj->nid))) { - return TRUE; - } - } + + // Otherwise, see if the user has the right permission for this project. + return !empty($project_obj->project['maintainers'][$user->uid]['permissions'][$permission]); } + // If we haven't granted access yet, deny it. return FALSE; } @@ -730,12 +733,96 @@ function project_project_access($op, $no } /** + * Load all per-project permission information and return it. + * + * This invokes hook_project_permission_info() and + * hook_project_permission_alter(), and caches the results in RAM. + * + * @see hook_project_permission_info() + * @see hook_project_permission_alter() + * @see drupal_alter() + */ +function project_permission_load() { + static $project_permissions = array(); + if (empty($project_permissions)) { + $project_permissions = module_invoke_all('project_permission_info'); + drupal_alter('project_permission', $project_permissions); + } + return $project_permissions; +} + +/** + * Implement hook_project_permission_info() + */ +function project_project_permission_info() { + return array( + 'administer_project' => 'administer projects', + 'administer_project_maintainers' => 'administer project maintainers', + ); +} + +/** + * Add a maintainer with the specified permissions to a given project. + * + * @param $nid + * The Project NID to add the maintainer to. + * @param $uid + * The user ID of the maintainer to add. + * @param array $permissions + * Associative array of which project-level permissions the maintainer + * should have. The keys are permission names, and the values are if the + * permission should be granted or not. + */ +function project_maintainer_add($nid, $uid, $permissions = array()) { + db_query("INSERT INTO {project_maintainer} (nid, uid, administer_project, administer_project_maintainers) VALUES (%d, %d, %d, %d)", $nid, $uid, $permissions['administer projects'], $permissions['administer project maintainers']); +} + +/** + * Update the permissions associated with a maintainer for a given project. + * + * @param $nid + * The Project NID to update the maintainer for. + * @param $uid + * The user ID of the maintainer to update. + * @param array $permissions + * Associative array of which project-level permissions the maintainer + * should have. The keys are permission names, and the values are if the + * permission should be granted or not. + */ +function project_maintainer_update($nid, $uid, $permissions = array()) { + db_query("UPDATE {project_maintainer} SET administer_project = %d, administer_project_maintainers = %d WHERE nid = %d AND uid = %d", $permissions['administer projects'], $permissions['administer project maintainers'], $nid, $uid); +} + +/** + * Remove a maintainer from a given project. + * + * @param $nid + * The Project NID to remove the maintainer from. + * @param $uid + * The user ID of the maintainer to remove. + */ +function project_maintainer_remove($nid, $uid) { + db_query("DELETE FROM {project_maintainer} WHERE nid = %d and uid = %d", $nid, $uid); +} + +/** * Implement hook_load(). */ function project_project_load($node) { $additions = db_fetch_array(db_query('SELECT * FROM {project_projects} WHERE nid = %d', $node->nid)); $project = new stdClass; $project->project = $additions; + // We don't want to load all the permissions here, just the ones that + // Project itself is responsible for, so we use our implementation of the + // hook, instead of the global load function. + $project_perms = project_project_permission_info(); + $maintainers = db_query('SELECT u.name, pm.* FROM {project_maintainer} pm INNER JOIN {users} u ON pm.uid = u.uid WHERE pm.nid = %d', $node->nid); + while ($maintainer = db_fetch_object($maintainers)) { + $project->project['maintainers'][$maintainer->uid]['name'] = $maintainer->name; + foreach ($project_perms as $db_field => $perm_name) { + $project->project['maintainers'][$maintainer->uid]['permissions'][$perm_name] = $maintainer->$db_field; + } + } return $project; } @@ -996,6 +1083,12 @@ function project_theme() { 'title' => NULL, ), ), + 'project_maintainers_form' => array( + 'file' => 'includes/project_maintainers.inc', + 'arguments' => array( + 'form' => NULL, + ), + ), 'project_project_node_form_taxonomy' => array( 'file' => 'project.inc', 'arguments' => array( Index: includes/project_maintainers.inc =================================================================== RCS file: includes/project_maintainers.inc diff -N includes/project_maintainers.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ includes/project_maintainers.inc 12 Aug 2010 01:36:53 -0000 @@ -0,0 +1,140 @@ + t('User')); + $form['#header']['administer_projects'] = array('data' => t('Administer project')); + $form['#header']['administer_project_maintainers'] = array('data' => t('Administer maintainers')); + + if (!empty($project->project['maintainers'])) { + foreach ($project->project['maintainers'] as $uid => $maintainer) { + $form['maintainers'][$uid] = array(); + $form['maintainers'][$uid]['name'] = array( + '#type' => 'value', + '#value' => $maintainer['name'], + ); + foreach ($project_project_perms as $perm) { + $form['maintainers'][$uid]['permissions'][$perm] = array( + '#type' => 'checkbox', + '#default_value' => !empty($maintainer['permissions'][$perm]), + ); + } + } + } + + $form['new_maintainer'] = array(); + $form['new_maintainer']['user'] = array( + '#type' => 'textfield', + '#size' => 30, + '#maxlength' => 60, + '#autocomplete_path' => 'user/autocomplete', + ); + // we'll fill this in with a real value during validate() + $form['new_maintainer']['uid'] = array( + '#type' => 'value', + '#value' => 0, + ); + foreach ($project_project_perms as $perm) { + $form['new_maintainer']['permissions'][$perm] = array( + '#type' => 'checkbox', + ); + } + + $form['submit'] = array('#type' => 'submit', '#value' => t('Update')); + + return $form; +} + +function theme_project_maintainers_form($form) { + $output = ''; + + $header = $form['#header']; + $rows = array(); + + // Render all the existing maintainers. + if (is_array($form['maintainers'])) { + foreach (element_children($form['maintainers']) as $uid) { + $row = array(); + $account = new stdClass; + $account->uid = $uid; + $account->name = $form['maintainers'][$uid]['name']['#value']; + $row[] = theme('username', $account); + foreach (element_children($form['maintainers'][$uid]['permissions']) as $perm) { + $row[] = drupal_render($form['maintainers'][$uid]['permissions'][$perm]); + } + $rows[] = $row; + } + } + + // Create the final row for adding a new maintainer. + $row = array(); + $row[] = drupal_render($form['new_maintainer']['user']); + foreach (element_children($form['new_maintainer']['permissions']) as $perm) { + $row[] = drupal_render($form['new_maintainer']['permissions'][$perm]); + } + $rows[] = $row; + + $output .= theme('table', $header, $rows); + $output .= drupal_render($form); + return $output; +} + +/** + * Validation callback for the project maintainers form. + */ +function project_maintainers_form_validate($form, &$form_state) { + $new_maintainer = $form_state['values']['new_maintainer']; + if (!empty($new_maintainer['user'])) { + $user_result = db_fetch_object(db_query("SELECT name, uid FROM {users} WHERE name = '%s'", $new_maintainer['user'])); + if (empty($user_result->uid)) { + form_set_error('new_maintainer][user', t('%user is not a valid user on this site.', array('%user' => $new_maintainer['user'])), 'error'); + return; + } + if (!empty($form['#project']->project['maintainers'][$user_result->uid])) { + form_set_error('new_maintainer][user', t('%user is already a maintainer of this project.', array('%user' => $new_maintainer['user'])), 'error'); + return; + } + // Save the uid in the form so we don't have to look it up again at submit. + form_set_value($form['new_maintainer']['uid'], $user_result->uid, $form_state); + } + else { + foreach ($new_maintainer['permissions'] as $name => $value) { + if (!empty($value)) { + form_set_error('new_maintainer][user', t('You must specify a valid user name to grant permissions.')); + } + } + } +} + +/** + * Submit callback for the project maintainers form. + */ +function project_maintainers_form_submit($form, &$form_state) { + $project_nid = $form['#project']->nid; + + // Loop over all the maintainers and update their permissions accordingly. + if (!empty($form_state['values']['maintainers'])) { + foreach ($form_state['values']['maintainers'] as $uid => $maintainer) { + project_maintainer_update($project_nid, $uid, $maintainer['permissions']); + } + } + + // See if we need to insert a record for a new maintainer. + if (!empty($form_state['values']['new_maintainer']['uid'])) { + project_maintainer_add($project_nid, $form_state['values']['new_maintainer']['uid'], $form_state['values']['new_maintainer']['permissions']); + } + +} Index: release/project_release.install =================================================================== RCS file: /Users/wright/drupal/local_repo/contributions/modules/project/release/project_release.install,v retrieving revision 1.31 diff -u -p -r1.31 project_release.install --- release/project_release.install 7 Jun 2010 22:45:39 -0000 1.31 +++ release/project_release.install 12 Aug 2010 01:47:18 -0000 @@ -351,6 +351,35 @@ function project_release_schema() { 'expire' => array('expire') ), ); + + $schema['project_release_project_maintainer'] = array( + 'description' => t('Users who have various per-project maintainer permissions.'), + 'fields' => array( + 'nid' => array( + 'description' => t('Foreign key: {project_projects}.nid of the project.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'uid' => array( + 'description' => t('Foreign key: {users}.uid of a user with any project maintainer permissions.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'administer_project_releases' => array( + 'description' => t('Can this user create and administer releases for the given project.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('nid', 'uid'), + ); + return $schema; } @@ -706,3 +735,51 @@ function project_release_update_6009() { } return $ret; } + +/** + * Add the {project_release_project_maintainer} table. + */ +function project_release_update_6010() { + $ret = array(); + + $table = array( + 'description' => t('Users who have various per-project maintainer permissions.'), + 'fields' => array( + 'nid' => array( + 'description' => t('Foreign key: {project_projects}.nid of the project.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'uid' => array( + 'description' => t('Foreign key: {users}.uid of a user with any project maintainer permissions.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'administer_project_releases' => array( + 'description' => t('Can this user create and administer releases for the given project.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('nid', 'uid'), + ); + db_create_table($ret, 'project_release_project_maintainer', $table); + + // Initially populate the table so that every project owner has full + // powers on their own projects. + $ret[] = update_sql("INSERT INTO {project_release_project_maintainer} (nid, uid, administer_project_releases) SELECT nid, uid, 1 FROM {node} WHERE type = 'project_project'"); + + // If CVS module is enabled, also populate the table from the + // {cvs_project_maintainers} table so that everyone with CVS access can + // administer releases. + if (module_exists('cvs')) { + $ret[] = update_sql("INSERT INTO {project_release_project_maintainer} (nid, uid, administer_project_releases) SELECT nid, uid, 1 FROM {cvs_project_maintainers}"); + } + return $ret; +} Index: release/project_release.module =================================================================== RCS file: /Users/wright/drupal/local_repo/contributions/modules/project/release/project_release.module,v retrieving revision 1.153 diff -u -p -r1.153 project_release.module --- release/project_release.module 8 Jul 2010 23:16:17 -0000 1.153 +++ release/project_release.module 12 Aug 2010 02:04:47 -0000 @@ -40,8 +40,8 @@ function project_release_menu() { 'title' => 'Releases', 'page callback' => 'project_release_project_edit_releases', 'page arguments' => array(1), - 'access callback' => 'node_access', - 'access arguments' => array('update', 1), + 'access callback' => 'project_check_admin_access', + 'access arguments' => array(1, 'administer project releases'), 'type' => MENU_LOCAL_TASK, 'file' => 'includes/project_edit_releases.inc', ); @@ -120,7 +120,7 @@ function project_release_access($op, $no // We can't just use project_project_access() here, since we // need to check access to the project itself, not the release // node, so we use the helper method and pass the project id. - return project_check_admin_access($node->project_release['pid']); + return project_check_admin_access($node->project_release['pid'], 'administer project releases'); case 'delete': // No one should ever delete a release node, only unpublish it. return FALSE; @@ -142,6 +142,59 @@ function project_release_node_info() { } /** + * Implement hook_project_permission_info() + */ +function project_release_project_permission_info() { + return array( + 'administer_project_releases' => 'administer project releases', + ); +} + +/** + * Add a maintainer with the specified permissions to a given project. + * + * @param $nid + * The Project NID to add the maintainer to. + * @param $uid + * The user ID of the maintainer to add. + * @param array $permissions + * Associative array of which project-level permissions the maintainer + * should have. The keys are permission names, and the values are if the + * permission should be granted or not. + */ +function project_release_project_maintainer_add($nid, $uid, $permissions = array()) { + db_query("INSERT INTO {project_release_project_maintainer} (nid, uid, administer_project_releases) VALUES (%d, %d, %d)", $nid, $uid, $permissions['administer project releases']); +} + +/** + * Update the permissions associated with a maintainer for a given project. + * + * @param $nid + * The Project NID to update the maintainer for. + * @param $uid + * The user ID of the maintainer to update. + * @param array $permissions + * Associative array of which project-level permissions the maintainer + * should have. The keys are permission names, and the values are if the + * permission should be granted or not. + */ +function project_release_project_maintainer_update($nid, $uid, $permissions = array()) { + db_query("UPDATE {project_release_project_maintainer} SET administer_project_releases = %d WHERE nid = %d AND uid = %d", $permissions['administer project releases'], $nid, $uid); +} + +/** + * Remove a maintainer from a given project. + * + * @param $nid + * The Project NID to remove the maintainer from. + * @param $uid + * The user ID of the maintainer to remove. + */ +function project_release_project_maintainer_remove($nid, $uid) { + db_query("DELETE FROM {project_release_project_maintainer} WHERE nid = %d and uid = %d", $nid, $uid); +} + +/** * Implement of hook_form() for project_release nodes. */ function project_release_form(&$release, &$form_state) { @@ -609,7 +662,7 @@ function project_release_view($node, $te } // Display packaging errors to admins. - if (project_check_admin_access($node->project_release['pid'])) { + if (project_check_admin_access($node->project_release['pid'], 'administer project releases')) { $rows = array(); $result = db_query('SELECT * FROM {project_release_package_errors} WHERE nid = %d', $node->nid); $error = db_fetch_object($result); @@ -690,7 +743,7 @@ function project_release_get_releases($p $where = ''; $join = ''; $args = array($project->nid); - if (!project_check_admin_access($project)) { + if (!project_check_admin_access($project, 'administer project releases')) { if (!empty($rids)) { $where = "AND (n.status = %d OR n.nid IN (". db_placeholders($rids) ."))"; $args[] = 1; @@ -837,6 +890,50 @@ function project_release_alter_release_f $form['buttons']['submit']['#submit'][] = 'project_release_node_submit'; } +function project_release_form_project_maintainers_form_alter(&$form) { + $project_release_project_perms = project_release_project_permission_info(); + $project = $form['#project']; + + // Add our checkboxes for the existing maintainers. + if (!empty($project->project['maintainers'])) { + foreach ($project->project['maintainers'] as $uid => $maintainer) { + foreach ($project_release_project_perms as $perm) { + $form['maintainers'][$uid]['permissions'][$perm] = array( + '#type' => 'checkbox', + '#default_value' => !empty($maintainer['permissions'][$perm]), + ); + } + } + } + + foreach ($project_release_project_perms as $key => $perm) { + // Update the header + $form['#header'][$key] = array('data' => drupal_ucfirst($perm)); + + // Add checkboxes for the new maintainer row + $form['new_maintainer']['permissions'][$perm] = array( + '#type' => 'checkbox', + ); + } + + $form['#submit'][] = 'project_release_project_maintainers_form_submit'; +} + +function project_release_project_maintainers_form_submit($form, &$form_state) { + $project_nid = $form['#project']->nid; + + // Loop over all the maintainers and update their permissions accordingly. + if (!empty($form_state['values']['maintainers'])) { + foreach ($form_state['values']['maintainers'] as $uid => $maintainer) { + project_release_project_maintainer_update($project_nid, $uid, $maintainer['permissions']); + } + } + + // See if we need to insert a record for a new maintainer. + if (!empty($form_state['values']['new_maintainer']['uid'])) { + project_release_project_maintainer_add($project_nid, $form_state['values']['new_maintainer']['uid'], $form_state['values']['new_maintainer']['permissions']); + } +} /** * @defgroup project_release_nodeapi Node API hooks @@ -895,6 +992,21 @@ function project_release_project_nodeapi $node->project_release['project_release_show_snapshots'] = TRUE; } } + + // Also load project_release maintainer info. We don't want to load all the + // permissions here, just the ones that project_release is responsible for, + // so we use our implementation of the hook, instead of the global load + // function. + $project_release_project_perms = project_release_project_permission_info(); + $maintainers = db_query('SELECT u.name, prpm.* FROM {project_release_project_maintainer} prpm INNER JOIN {users} u ON prpm.uid = u.uid WHERE prpm.nid = %d', $node->nid); + while ($maintainer = db_fetch_object($maintainers)) { + if (empty($node->project['maintainers'][$maintainer->uid])) { + $node->project['maintainers'][$maintainer->uid]['name'] = $maintainer->name; + } + foreach ($project_release_project_perms as $db_field => $perm_name) { + $node->project['maintainers'][$maintainer->uid]['permissions'][$perm_name] = $maintainer->$db_field; + } + } } /** @@ -1230,7 +1342,7 @@ function project_release_project_page_li ), ); - if (project_check_admin_access($node->nid)) { + if (project_check_admin_access($node->nid, 'administer project releases')) { $links['project_release']['links']['add_new_release'] = l(t('Add new release'), 'node/add/project_release/'. $node->nid); $links['project_release']['links']['administer_releases'] = l(t('Administer releases'), 'node/'. $node->nid .'/edit/releases'); } Index: release/includes/release_node_form.inc =================================================================== RCS file: /Users/wright/drupal/local_repo/contributions/modules/project/release/includes/release_node_form.inc,v retrieving revision 1.11 diff -u -p -r1.11 release_node_form.inc --- release/includes/release_node_form.inc 30 Jan 2010 02:33:40 -0000 1.11 +++ release/includes/release_node_form.inc 12 Aug 2010 02:04:20 -0000 @@ -24,7 +24,7 @@ function _project_release_form(&$release } // Make sure this user should have permissions to add releases for // the requested project - if (!project_check_admin_access($project)) { + if (!project_check_admin_access($project, 'administer project releases')) { drupal_access_denied(); module_invoke_all('exit'); exit;