'Committers', 'page callback' => 'versioncontrol_project_committers_page', 'page arguments' => array(1), 'type' => MENU_CALLBACK, 'access callback' => 'node_access', 'access arguments' => array('view', 1), ); $items['node/%project_node/commits'] = array( 'title' => 'Committers', 'page callback' => 'versioncontrol_project_commits_page', 'page arguments' => array(1), 'type' => MENU_CALLBACK, 'access callback' => 'versioncontrol_project_commits_page_access', 'access arguments' => array(1), ); return $items; } function versioncontrol_project_ctools_plugin_directory($module, $plugin) { if ($module == 'versioncontrol') { return "plugins/$plugin"; } } /** * Helper function to control access to per-project commit view. */ function versioncontrol_project_commits_page_access($node) { return node_access('view', $node) && user_access('access commit messages'); } /** * Implementation of hook_block(). */ function versioncontrol_project_block($op = 'list', $delta = 0, $edit = array()) { switch ($op) { case 'list': return array( 'project_maintainers' => array( 'info' => t('Version Control Project: Project maintainers'), 'cache' => BLOCK_NO_CACHE, ), ); case 'configure': return versioncontrol_project_block_configure($delta); case 'save': return versioncontrol_project_block_save($delta, $edit); case 'view': return versioncontrol_project_block_view($delta); } } /** * Implementation of hook_theme(). */ function versioncontrol_project_theme() { return array( 'versioncontrol_project_maintainer_list' => array( 'arguments' => array('node', 'maintainers'), ), ); } /** * Implementation of hook_nodeapi(). */ function versioncontrol_project_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { if ($node->type == 'project_project') { switch ($op) { case 'load': versioncontrol_project_project_nodeapi_load($node); break; case 'insert': versioncontrol_project_project_nodeapi_insert($node); break; case 'delete': versioncontrol_project_project_nodeapi_delete($node); break; } } } /** * Load versioncontrol_project fields into project node object. */ function versioncontrol_project_project_nodeapi_load(&$node) { $result = db_fetch_object(db_query('SELECT * FROM {versioncontrol_project_projects} WHERE nid = %d', $node->nid)); if ($result) { $fields = array('repo_id'); foreach ($fields as $field) { $node->versioncontrol_project[$field] = $result->$field; } $node->versioncontrol_project['repo'] = versioncontrol_project_repository_load($node->nid); } } /** * Save versioncontrol_project information when a new project is created. */ function versioncontrol_project_project_nodeapi_insert(&$node) { db_query("INSERT INTO {versioncontrol_project_projects} (nid, repo_id) VALUES (%d, %d)", $node->nid, 0); } /** * Delete versioncontrol_project information when a new project is deleted. */ function versioncontrol_project_project_nodeapi_delete(&$node) { db_query("DELETE FROM {versioncontrol_project_projects} WHERE nid = %d", $node->nid); } /** * Update or add repo associated with a given project. * * @param $project_id * Node ID of the project. * @param $repo_id * The repo ID of the versioncontrol repo to associate. */ function versioncontrol_project_set_project($project_id, $repo_id) { db_query("UPDATE {versioncontrol_project_projects} SET repo_id = %d WHERE nid = %d", $repo_id, $project_id); if (!db_affected_rows()) { // Didn't update anything, add this as a new project. db_query("INSERT INTO {versioncontrol_project_projects} (nid, repo_id) VALUES (%d, %d)", $project_id, $repo_id); } } function versioncontrol_project_query_versioncontrol_repo_load_multiple_alter(QueryAlterableInterface $query) { $alias = $query->leftJoin('versioncontrol_project_projects', 'vcpp', 'base.repo_id = vcpp.repo_id'); $query->addField($alias, 'nid', 'project_nid'); } /** * Load the repository object associated with a project. */ function versioncontrol_project_repository_load($project_id) { $repo_id = db_result(db_query("SELECT repo_id FROM {versioncontrol_project_projects} WHERE nid = %d", $project_id)); if ($repo_id) { $repository = versioncontrol_repository_load_multiple(array($repo_id)); return empty($repository) ? FALSE : reset($repository); } return FALSE; } /** * Load the project object associated with a repo. */ function versioncontrol_project_project_load($repo_id) { $nid = db_result(db_query("SELECT nid FROM {versioncontrol_project_projects} WHERE repo_id = %d", $repo_id)); if ($nid) { return node_load($nid); } return FALSE; } /** * Implementation of hook_versioncontrol_entity_repository_delete(). */ function versioncontrol_project_versioncontrol_entity_repository_delete($repository) { // Delete mapping when repo is deleted. $project = versioncontrol_project_project_load($repository->repo_id); if ($project) { versioncontrol_project_set_project($project->nid, 0); } } /** * Implement hook_project_permission_info() */ function versioncontrol_project_project_permission_info($project = NULL) { // Make sure that we have a valid repo associated with this project, and // that it is using the auth plugin that we care about. if (!is_null($project) && $repo = versioncontrol_project_repository_load($project->nid)) { if (versioncontrol_project_verify_repo_auth_handler($repo)) { return array( 'write to vcs' => array( 'title' => t('Write to VCS'), 'description' => t('Allows a user to commit or push to the repository associated with this project.'), ), ); } } } /** * Implement hook_project_maintainer_save() */ function versioncontrol_project_project_maintainer_save($nid, $uid, $permissions = array()) { $repo = versioncontrol_project_repository_load($nid); if ($repo && versioncontrol_project_verify_repo_auth_handler($repo)) { $auth_handler = $repo->getAuthHandler(); $auth_data = $auth_handler->getUserData(); if (!isset($auth_data[$uid])) { $auth_data[$uid] = array( 'branch_create' => VersioncontrolAuthHandlerMappedAccounts::DENY, 'branch_update' => VersioncontrolAuthHandlerMappedAccounts::DENY, 'branch_delete' => VersioncontrolAuthHandlerMappedAccounts::DENY, 'tag_create' => VersioncontrolAuthHandlerMappedAccounts::DENY, 'tag_update' => VersioncontrolAuthHandlerMappedAccounts::DENY, 'tag_delete' => VersioncontrolAuthHandlerMappedAccounts::DENY, 'per-label' => array() ); } $auth_data[$uid]['access'] = ($permissions['write to vcs']) ? VersioncontrolAuthHandlerMappedAccounts::ALL : VersioncontrolAuthHandlerMappedAccounts::DENY; $auth_handler->setUserData($uid, $auth_data[$uid]); $auth_handler->save(); // Clear cache for the maintainers block $cid = 'project_maintainers:'. $nid; _versioncontrol_project_block_cache_set($cid, NULL); } } /** * Implement hook_project_maintainer_remove() */ function versioncontrol_project_project_maintainer_remove($nid, $uid) { $repo = versioncontrol_project_repository_load($nid); if ($repo && versioncontrol_project_verify_repo_auth_handler($repo)) { $auth_handler = $repo->getAuthHandler(); $auth_handler->deleteUserData($uid); $auth_handler->save(); // Clear cache for the maintainers block $cid = 'project_maintainers:'. $nid; _versioncontrol_project_block_cache_set($cid, NULL); } } /** * Implement hook_project_maintainer_project_load() */ function versioncontrol_project_project_maintainer_project_load($nid, &$maintainers) { $repo = versioncontrol_project_repository_load($nid); if ($repo && versioncontrol_project_verify_repo_auth_handler($repo)) { $auth_data = $repo->getAuthHandler()->getUserData(); if (!empty($auth_data)) { foreach ($auth_data as $account) { if (empty($maintainers[$account['uid']])) { $maintainers[$account['uid']]['name'] = db_result(db_query("SELECT name FROM {users} WHERE uid = %d", $account['uid'])); } $maintainers[$account['uid']]['permissions']['write to vcs'] = ($account['access'] == VersioncontrolAuthHandlerMappedAccounts::ALL) ? 1 : 0; } } } } /** * Verify that a repo that is using a specific auth plugin. * * @param $repo * A fully loaded repo * * @param $plugin * (optional) The name of the plugin to check against. Defaults to 'account' * * @return bool * TRUE is the auth_handler plugin matches the requested plugin, else FALSE. */ function versioncontrol_project_verify_repo_auth_handler($repo, $plugin = NULL) { $plugin = is_null($plugin) ? 'account' : $plugin; return $repo && isset($repo->plugins['auth_handler']) && $repo->plugins['auth_handler'] == $plugin; } /** * Implementation of hook_views_api(). */ function versioncontrol_project_views_api() { return array( 'api' => 2, 'path' => drupal_get_path('module', 'versioncontrol_project') .'/views', ); } /** * Return a list of users who committed to the specified project. * * @param $nid * Project node ID to get committers for. * @param $sort * Optional ORDER BY clause to use for the list of committers. Defaults to * sorting by the most recent commit. * @param $only_maintainers * Optional boolean to indicate if the list should only include users that * currently have VCS write access to the project. Defaults to TRUE. * * @return * Array containing objects of committer information, keyed by uid. */ function versioncontrol_project_get_project_committers($nid, $order = '', $only_maintainers = TRUE) { if (empty($order)) { $order = ' ORDER BY last_commit DESC'; } $repo = versioncontrol_project_repository_load($nid); $query = "SELECT o.author_uid as uid, MIN(o.date) AS first_commit, MAX(o.date) AS last_commit, COUNT(o.vc_op_id) AS commits, u.name FROM {versioncontrol_operations} o INNER JOIN {users} u ON o.author_uid = u.uid WHERE o.repo_id = %d AND o.author_uid != 0 GROUP BY o.repo_id, o.author_uid $order"; $result = db_query($query, $repo->repo_id); $committers = array(); while ($committer = db_fetch_object($result)) { $committers[$committer->uid] = $committer; } if ($only_maintainers) { return array_intersect_key($committers, versioncontrol_project_get_project_maintainers($repo->repo_id)); } return $committers; } /** * Page callback to display all committers for a given project. */ function versioncontrol_project_committers_page($project) { drupal_set_title(t('Committers for %name', array('%name' => $project->title))); $time = time(); project_project_set_breadcrumb($project, TRUE); $header = array( array('data' => t('User'), 'field' => 'u.name'), array('data' => t('Last commit'), 'field' => 'last_commit', 'sort' => 'desc'), array('data' => t('First commit'), 'field' => 'first_commit'), array('data' => t('Commits'), 'field' => 'commits'), ); // Use FALSE for the 3rd argument to get all committers, not just the users // that currently have commit access. $committers = versioncontrol_project_get_project_committers($project->nid, tablesort_sql($header), FALSE); $rows = array(); foreach ($committers as $committer) { $rows[] = array( theme('username', $committer), t('!time ago', array('!time' => format_interval($time - $committer->last_commit, 1))), t('!time ago', array('!time' => format_interval($time - $committer->first_commit, 1))), format_plural($committer->commits, '1 commit', '@count commits') ); } return theme('table', $header, $rows); } /** * Implementation of hook_block() for the 'configure' operation. */ function versioncontrol_project_block_configure($delta) { $form = array(); switch ($delta) { case 'project_maintainers': for ($i=1; $i<=10; $i++) { $options[$i] = $i; } $options['all'] = t('All committers'); $form['versioncontrol_project_maintainers_block_length'] = array( '#type' => 'select', '#options' => $options, '#title' => t('Number of maintainers to display'), '#default_value' => variable_get('versioncontrol_project_maintainers_block_length', 5), ); break; } return $form; } /** * Implementation of hook_block() for the 'save' operation. */ function versioncontrol_project_block_save($delta, $edit) { switch ($delta) { case 'project_maintainers': variable_set('versioncontrol_project_maintainers_block_length', $edit['versioncontrol_project_maintainers_block_length']); break; } } /** * Implementation of hook_block() for the 'view' operation. */ function versioncontrol_project_block_view($delta) { $block = array(); switch ($delta) { case 'project_maintainers': if (($node = project_get_project_from_menu()) && !empty($node->versioncontrol_project['repo_id']) && node_access('view', $node)) { $cid = 'project_maintainers:'. $node->nid; $maintainers = _versioncontrol_project_block_cache_get($cid); if (empty($maintainers)) { $maintainers = versioncontrol_project_get_project_committers($node->nid); _versioncontrol_project_block_cache_set($cid, $maintainers); } $block = array( 'subject' => t('Maintainers for @project', array('@project' => $node->title)), 'content' => theme('versioncontrol_project_maintainer_list', $node, $maintainers, variable_get('versioncontrol_project_maintainers_block_length', 5)), ); } break; } return $block; } function _versioncontrol_project_block_cache_get($cid) { $data = db_result(db_query("SELECT data FROM {versioncontrol_project_maintainers_block_cache} WHERE cid = '%s'", $cid)); if (!empty($data)) { return unserialize($data); } } function _versioncontrol_project_block_cache_set($cid, $data) { if (empty($data)) { db_query("DELETE FROM {versioncontrol_project_maintainers_block_cache} WHERE cid = '%s'", $cid); } else { $serialized = serialize($data); db_query("UPDATE {versioncontrol_project_maintainers_block_cache} SET data = '%s' WHERE cid = '%s'", $serialized, $cid); if (!db_affected_rows()) { db_query("INSERT INTO {versioncontrol_project_maintainers_block_cache} (cid, data) VALUES ('%s', '%s')", $cid, $serialized); } } } /** * Return themed HTML for the Versioncontrol project maintainers block. */ function theme_versioncontrol_project_maintainer_list($node, $maintainers, $max_maintainers = NULL) { $i = 0; $time = time(); $items = array(); foreach ($maintainers as $maintainer) { if (isset($max_maintainers) && is_numeric($max_maintainers) && $i >= $max_maintainers) { break; } $item = '
  • '; $item .= theme('username', $maintainer) .' - '; $item .= ''. format_plural($maintainer->commits, '1 commit', '@count commits') .'
    '; $item .= '
    '; $item .= t('last: !last_time ago, first: !first_time ago', array('!last_time' => format_interval($time - $maintainer->last_commit, 1), '!first_time' => format_interval($time - $maintainer->first_commit, 1))); $item .= '
    '; $item .= '
  • '; $i++; $items[] = $item; } $output = theme('item_list', $items); $output .= l('View all committers', 'node/'. $node->nid .'/committers'); return $output; } /** * Return a list of all maintainers with VCS write access to the repo. * * @return array * An array of users keyed by uid. */ function versioncontrol_project_get_project_maintainers($repo_id) { $maintainers = array(); $repo = versioncontrol_repository_load($repo_id); if (versioncontrol_project_verify_repo_auth_handler($repo)) { $auth_data = $repo->getAuthHandler()->getUserData(); foreach ($auth_data as $maintainer) { if ($maintainer['access'] == VersioncontrolAuthHandlerMappedAccounts::ALL) { $maintainers[$maintainer['uid']] = $maintainer; } } } return $maintainers; } /** * Implementation of hook_versioncontrol_entity_commit_insert(). */ function versioncontrol_project_versioncontrol_entity_commit_insert($operation) { if (isset($operation->repo_id)) { // invalidate block cache for maintainers block $project = versioncontrol_project_project_load($operation->repo_id); $cid = 'project_maintainers:'. $project->nid; _versioncontrol_project_block_cache_set($cid, NULL); } } /** * Implementation of hook_user(). */ function versioncontrol_project_user($op, &$edit, &$account, $category = NULL) { switch ($op) { case 'view': // Print a list of projects the specified user contributed to. if (!empty($account->uid)) { $result = db_query("SELECT DISTINCT(vcp.nid), n.title, COUNT(o.vc_op_id) AS commits FROM {versioncontrol_operations} o INNER JOIN {versioncontrol_project_projects} vcp ON vcp.repo_id = o.repo_id INNER JOIN {node} n ON n.status = 1 AND n.nid = vcp.nid WHERE o.author_uid = %d GROUP BY vcp.nid, n.title ORDER BY commits DESC", $account->uid); $total = 0; $projects = array(); while ($project = db_fetch_object($result)) { $project_link = l($project->title, 'node/'. $project->nid); $projects[] = format_plural($project->commits, '!project (1 commit)', '!project (@count commits)', array('!project' => $project_link)); $total += $project->commits; } if ($total > 0) { $projects[] = format_plural($total, 'Total: 1 commit', 'Total: @count commits'); } if (!empty($projects)) { $account->content['versioncontrol_project'] = array( '#type' => 'user_profile_category', '#title' => t('Projects'), '#weight' => 10, '#attributes' => array('class' => 'versioncontrol-project-user-commits'), ); $account->content['versioncontrol_project']['items'] = array( '#type' => 'user_profile_item', '#value' => theme('item_list', $projects), ); } } break; } } /** * Gets the auth data related to a project's repository. * * @param string $project_uri * The project.module's project uri. * * @return array * A multidimensional array of auth data. */ function versioncontrol_project_get_auth_data($project_uri) { $data = array(); if ($project = node_load(project_get_nid_from_uri($project_uri))) { $repository = $project->versioncontrol_project['repo']; $account_perms = array(); $data['project'] = $project->title; $data['project_nid'] = $project->nid; $data['repository_name'] = $repository->name; $data['repo_id'] = $repository->repo_id; $auth_data = $repository->getAuthHandler()->getUserData(); foreach ($auth_data as $uid => $value) { $account = new stdClass; $account = user_load($uid); $account_perms[$account->name] = $value; $account_perms[$account->name]['name'] = $account->name; $account_perms[$account->name]['pass'] = $account->pass; } $data['users'] = $account_perms; drupal_alter('versioncontrol_project_auth_data', $data, $repository, $project); } return $data; } /** * Implementation of hook_versioncontrol_project_auth_data_alter(). * * Adds user's sshkeys to the repository data if the sshkey module is present. */ function versioncontrol_project_versioncontrol_project_auth_data_alter(&$data, $repository) { if (module_exists('sshkey')) { foreach ($data['users'] as $name => &$value) { module_load_include('inc', 'sshkey'); $keys = sshkey_load_all_by_entity('user', $value['uid']); $value['ssh_keys'][' '] = ' '; foreach ($keys as &$key) { $value['ssh_keys'][$key->title] = $key->fingerprint; } } } } /** * Implemenation of hook_project_page_link_alter(). */ function versioncontrol_project_project_page_link_alter(&$links, $node) { if (!empty($node->versioncontrol_project['repo'])) { // Force the link for now, see http://drupal.org/node/1026594 $repo = $node->versioncontrol_project['repo']; $link = $repo->getUrlHandler()->getRepositoryViewUrl(); $links['development']['links']['browse_repository'] = l(t('Repository viewer'), $link); $links['development']['links']['view_commits'] = l(t('View commits'), "node/$node->nid/commits"); } } /** * Menu callback for per-project commits page */ function versioncontrol_project_commits_page($node) { drupal_set_title(t('Commits for @project', array('@project' => $node->title))); $set = versioncontrol_get_views_set('project_commit_view'); $view = $set->getViewName(); return views_embed_view($view, 'default', $node->nid); }