'Git access',
'page callback' => 'drupalorg_git_gateway_user_page',
'page arguments' => array(1),
'access callback' => 'user_edit_access',
'access arguments' => array(1),
'load arguments' => array('%map', '%index'),
'weight' => 1,
'type' => MENU_LOCAL_TASK,
);
$items['admin/settings/drupalorg-git'] = array(
'title' => 'Drupal.org Git gateway',
'page callback' => 'drupal_get_form',
'page arguments' => array('drupalorg_git_gateway_defaults'),
'access arguments' => array('administer version control systems'),
'description' => 'Update Git e-mail text or globally disable push access.',
'type' => MENU_NORMAL_ITEM,
);
$items['admin/settings/drupalorg-git/defaults'] = array(
'title' => 'Defaults',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => - 10,
);
$items['admin/settings/drupalorg-git/gatectl'] = array(
'title' => 'Global Push Control',
'page arguments' => array('drupalorg_git_gateway_gatectl'),
'access arguments' => array('administer version control systems'),
'type' => MENU_LOCAL_TASK,
);
return $items;
}
/**
* Implementation of hook_user().
*/
function drupalorg_git_gateway_user($op, &$edit, &$account, $category = NULL) {
// An ugly shim to ensure that things show up nicely in the sub-tabs generated
// by the User module.
if ($op == 'categories') {
return array(
'git' => array(
'name' => 'git',
'title' => t('Git access'),
'weight' => 1,
// use defaults for access callback and arguments
),
);
}
else if ($op == 'after_update') {
// Determine appropriate role grants for the account. By doing this in
// after_update, we don't have to swim upstream against the rest of the user
// system.
//
// Start by assuming we should not grant the perm.
$grant = 0;
// 0x01 == grant basic; 0x02 == grant vetted
if (empty($account->git_disabled)) {
// Grant the perm IFF consent has been given and a username has been set.
$grant |= (!empty($account->git_username) && !empty($account->git_consent)) ? 0x01 : 0;
}
// Vetted state persists through disabling.
$grant |= !empty($account->git_vetted) ? 0x02 : 0;
$del_rids = array();
if ($grant & 0x01) {
$vals = array(
'uid' => $account->uid,
'rid' => DRUPALORG_GIT_GATEWAY_RID,
);
db_merge('users_roles')->key($vals)->fields($vals)->execute();
$account->roles[DRUPALORG_GIT_GATEWAY_RID] = 'Git user';
}
else {
unset($account->roles[DRUPALORG_GIT_GATEWAY_RID]);
$del_rids[] = DRUPALORG_GIT_GATEWAY_RID;
}
if ($grant & 0x02) {
$vals = array(
'uid' => $account->uid,
'rid' => DRUPALORG_GIT_GATEWAY_VETTED_RID,
);
db_merge('users_roles')->key($vals)->fields($vals)->execute();
$account->roles[DRUPALORG_GIT_GATEWAY_VETTED_RID] = 'Git vetted user';
}
else {
unset($account->roles[DRUPALORG_GIT_GATEWAY_VETTED_RID]);
$del_rids[] = DRUPALORG_GIT_GATEWAY_VETTED_RID;
}
if (0x03 & ~$grant) {
// If both perms shouldn't be available, run a delete to be safe.
$del = db_delete('users_roles')
->condition('uid', $account->uid)
->condition('rid', $del_rids)
->execute();
}
}
}
/**
* Implements hook_form_alter().
*
* Modify the base user account editing form to disable the Git user and Git
* vetted user roles from being modified directly.
*/
function drupalorg_git_gateway_form_alter(&$form, &$form_state, $form_id) {
if ($form_id != 'user_profile_form' || $form['_category']['#value'] != 'account') {
return;
}
// Disable the 'Git user' and 'Git vetted user' roles in the same way the
// authenticated user role is disabled.
if (user_access('administer permissions')) {
$options =& $form['account']['roles']['#options'];
foreach (array(DRUPALORG_GIT_GATEWAY_RID, DRUPALORG_GIT_GATEWAY_VETTED_RID) as $rid) {
$checkbox = array(
'#type' => 'checkbox',
'#title' => $options[$rid],
'#default_value' => in_array($rid, $form['account']['roles']['#default_value']),
'#disabled' => TRUE,
);
$form['account']['roles'][$rid] = $checkbox;
// Remove the role from the original set of checkboxes.
unset($options[$rid]);
}
$desc = t('The "Git user" and "Git vetted user" roles cannot be changed directly here. Manage them on the !link tab.', array('!link' => l('Git access', 'user/' . $form['_account']['#value']->uid . '/edit/git')));
$form['account']['roles'][DRUPALORG_GIT_GATEWAY_VETTED_RID]['#description'] = $desc;
}
}
/**
* Implementation of hook_help().
*/
function drupalorg_git_gateway_help($path, $arg) {
global $user;
switch ($path) {
case 'node/add':
if (!empty($user->uid) && empty($user->git_consent) && !empty($user->git_vetted)) {
return drupalorg_git_gateway_no_consent_error($user);
}
break;
case 'project/user':
if (!empty($user->uid) && empty($user->git_consent)) {
// Vetted users know what's up, they just need to be reminded to check
// the 'Git access' checkbox.
if (!empty($user->git_vetted)) {
return drupalorg_git_gateway_no_consent_error($user);
}
// Non-vetted users should be pointed to the documentation on projects.
else {
return t('To learn about projects, see Contributing code on Drupal.org.');
}
}
break;
}
}
/**
* Print out a message to users that have not consented to using Git.
*
* Includes a link to the 'Git access' tab on their account profile.
*
* @param $account
* The fully-loaded user object of the account to print the message for.
*
* @return string
* The text to display to users who have not consented to using Git.
*/
function drupalorg_git_gateway_no_consent_error($account) {
$git_access_url = url('user/' . $account->uid . '/edit/git', array('query' => drupal_get_destination()));
return t('In order to create projects, agree to the terms of service on Git access.', array('@git_access' => $git_access_url));
}
function drupalorg_git_gateway_user_page($account) {
require_once drupal_get_path('module', 'drupalorg_git_gateway') . "/drupalorg_git_gateway.user-form.inc";
return _drupalorg_git_gateway_user_page($account);
}
function drupalorg_git_gateway_defaults(&$form_state) {
$form = array();
$form['project_git_gateway_disabled_msg'] = array(
'#type' => 'textarea',
'#title' => t('Default Git access disabled e-mail'),
'#default_value' => variable_get('project_git_gateway_disabled_msg', DRUPALORG_GIT_DISABLE_MSG),
'#description' => t('The default message to send to a user when disabling their Git push access.'),
'#rows' => 10,
);
return system_settings_form($form);
}
function drupalorg_git_gateway_gatectl(&$form_state) {
$form = array();
$push_options = array(
DRUPALORG_GIT_GATECTL_CORE => 'Disable pushes to core',
DRUPALORG_GIT_GATECTL_PROJECTS => 'Disable pushes to all full projects',
DRUPALORG_GIT_GATECTL_SANDBOXES => 'Disable pushes to all sandbox projects',
);
$pushctl = (int) variable_get('drupalorg_git_gateway_pushctl', 0);
$push_defaults = array();
if ($pushctl & DRUPALORG_GIT_GATECTL_CORE) {
$push_defaults[] = DRUPALORG_GIT_GATECTL_CORE;
}
if ($pushctl & DRUPALORG_GIT_GATECTL_PROJECTS) {
$push_defaults[] = DRUPALORG_GIT_GATECTL_PROJECTS;
}
if ($pushctl & DRUPALORG_GIT_GATECTL_SANDBOXES) {
$push_defaults[] = DRUPALORG_GIT_GATECTL_SANDBOXES;
}
$form['pushctl'] = array(
'#type' => 'checkboxes',
'#title' => t('Git push global control'),
'#options' => $push_options,
'#default_value' => $push_defaults,
'#description' => t('Toggling these checkboxes will immediately disable push access to the respective set of repositories. This is a hugely powerful setting, do not muck about with it!'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
return $form;
}
function drupalorg_git_gateway_gatectl_submit($form, &$form_state) {
$pushctl = 0;
foreach(array_filter($form_state['values']['pushctl']) as $val) {
$pushctl |= $val;
}
$old_pushctl = (int) variable_get('drupalorg_git_gateway_pushctl', 0);
variable_set('drupalorg_git_gateway_pushctl', $pushctl);
// Invoke notification hook if old pushctl != new pushctl
if ($pushctl != $old_pushctl) {
module_invoke_all('drupalorg_pushctl_changed', $pushctl);
}
}
/**
* Return total number of approved Git accounts.
*/
function drupalorg_git_gateway_get_total_accounts() {
return db_result(db_query('SELECT count(distinct(uid)) FROM {users_roles} WHERE rid IN (%d, %d)', DRUPALORG_GIT_GATEWAY_RID, DRUPALORG_GIT_GATEWAY_VETTED_RID));
}
/**
* Created a suggested username based on the provided name value, and ensuring
* no conflicts with existing git usernames.
*
* @param string $name
*/
function drupalorg_git_gateway_suggest_git_username($name) {
$base_suggestion = preg_replace('/[^A-Za-z0-9\._-]/', '', $name);
$len = strlen($base_suggestion);
$result = db_query("SELECT git_username FROM {users} WHERE git_username LIKE ('%s%')", $name);
$matches = array();
while ($row = db_fetch_object($result)) {
$matches[] = $row->git_username;
}
// Ensure no duplicates, and add an incrementing counter if one is found.
$i = 0;
while (array_search($base_suggestion, $matches) !== FALSE) {
$base_suggestion = substr($base_suggestion, 0, $len) . ++$i;
}
return $base_suggestion;
}
/**
* Implements hook_versioncontrol_project_auth_data_alter().
*
* Add more auth data to the array being returned via drush for the auth
* service, in particular the 'global' state of the account. Also ensure that
* we're using the git username, not the vanilla username.
*
* @param $data
* @param $repository
*/
function drupalorg_git_gateway_versioncontrol_project_auth_data_alter(&$data, $repository, $project) {
// Stick in the repo set val so the daemon knows how to deal with pushctl settings.
if ($project->nid == DRUPALORG_CORE_NID) {
$data['repo_group'] = DRUPALORG_GIT_GATECTL_CORE;
}
else {
$data['repo_group'] = empty($project->project['sandbox']) ? DRUPALORG_GIT_GATECTL_PROJECTS : DRUPALORG_GIT_GATECTL_SANDBOXES;
}
// Attach project published/unpublished information.
$data['status'] = (int) $project->status;
$select = db_select('project_release_nodes', 'prn')->fields('prn', array('tag', 'rebuild'));
$vpp_alias = $select->addJoin('INNER', 'versioncontrol_project_projects', 'vpp', 'prn.pid = vpp.nid');
$result = $select->condition($vpp_alias . '.repo_id', $repository->repo_id)
->execute()->fetchAllKeyed();
$tags = $branches = array();
foreach ($result as $label_name => $branch) {
if (empty($branch)) {
$tags[] = $label_name;
}
else {
$branches[] = $label_name;
}
}
$data['protected_labels'] = array(
'tags' => $tags,
'branches' => $branches,
);
$result = db_select('users', 'u')
->fields('u', array('name', 'git_username'))
->condition('u.name', array_keys($data['users']), 'IN')
->execute()->fetchAllKeyed();
foreach ($result as $name => $git_username) {
$account = user_load($data['users'][$name]['uid']);
if ($name != $git_username) {
$data['users'][$git_username] = $data['users'][$name];
unset($data['users'][$name]);
}
$ap =& $data['users'][$git_username];
$ap['global'] = 0;
// User has global access disabled for some reason.
if (!isset($account->roles[DRUPALORG_GIT_GATEWAY_RID]) || empty($account->status)) {
$ap['global'] |= DRUPALORG_GIT_AUTH_NO_ROLE;
if (!empty($account->git_disabled)) {
$ap['global'] |= DRUPALORG_GIT_AUTH_ACCOUNT_SUSPENDED;
}
if (empty($account->git_consent)) {
$ap['global'] |= DRUPALORG_GIT_AUTH_NOT_CONSENTED;
}
if (empty($account->status)) {
$ap['global'] |= DRUPALORG_GIT_AUTH_ACCOUNT_BLOCKED;
}
}
}
}
/**
* Log an incoming push event to our special logging tables.
*
* This is all a stopgap until we have a real activity stream system.
*
* @param array $data
* An array of push data, originating from the post-receive hook.
*/
function drupalorg_git_gateway_versioncontrol_git_raw_push_data($data) {
$push_id = db_insert('drupalorg_git_push_log')->fields(array(
'uid' => $data['uid'],
'timestamp' => $data['timestamp'],
'repo_id' => $data['repo_id'],
))->execute();
// Create a new insert object for multi-insert fun.
$insert = db_insert('drupalorg_git_push_log_refs')
->fields(array('push_id', 'refname', 'reftype', 'old_obj', 'new_obj'));
$all_refdata = explode("\n", $data['data']);
foreach ($all_refdata as $refdata) {
if (empty($refdata)) {
continue; // last element is usually empty, skip it
}
list($start, $end, $refpath) = explode(' ', $refdata);
$_refpath = explode('/', $refpath);
$ref = array_pop($_refpath);
$insert->values(array(
'push_id' => $push_id,
'refname' => $ref,
'reftype' => $_refpath[1] == 'tags' ? 2 : 1,
'old_obj' => $start,
'new_obj' => $end,
));
}
$insert->execute();
}
/**
* Implements hook_mail()
*
* @param $key
* @param $message
*/
function drupalorg_git_gateway_mail($key, &$message, $params = array()) {
$language = $message['language'];
if ($key == 'disabled') {
$message['subject'] = t('Git push access disabled');
$message['body'] = $params['body'];
return;
}
}