'Bug bingo',
'page callback' => 'drupalorg_project_bingo_jump',
'page arguments' => array('bug-core'),
'access arguments' => array('access content'),
'type' => MENU_SUGGESTED_ITEM,
);
$items['contrib-bug-bingo'] = array(
'title' => 'Contrib bug bingo',
'page callback' => 'drupalorg_project_bingo_jump',
'page arguments' => array('bug-contrib'),
'access arguments' => array('access content'),
'type' => MENU_SUGGESTED_ITEM,
);
$items['patch-bingo'] = array(
'title' => 'Patch bingo',
'page callback' => 'drupalorg_project_bingo_jump',
'page arguments' => array('patch-core'),
'access arguments' => array('access content'),
'type' => MENU_SUGGESTED_ITEM,
);
$items['contrib-patch-bingo'] = array(
'title' => 'Contrib patch bingo',
'page callback' => 'drupalorg_project_bingo_jump',
'page arguments' => array('patch-contrib'),
'access arguments' => array('access content'),
'type' => MENU_SUGGESTED_ITEM,
);
// Some redirect menu items.
$items['project/drupal project'] = array(
'access arguments' => array('access content'),
'page callback' => 'drupal_goto',
'page arguments' => array('project/drupal'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implementation of hook_menu_alter().
*/
function drupalorg_project_menu_alter(&$items) {
// Clobber the menu item from project_solr with a redirect.
$items['project'] = array(
'access arguments' => array('access content'),
'page callback' => 'drupal_goto',
'page arguments' => array('download'),
'type' => MENU_CALLBACK,
);
// Override the access callback on the project promote page to enforce our
// own special rules in a few cases.
$items['node/%project_edit_project/edit/promote']['access callback'] = 'drupalorg_project_promote_project_access';
$items['node/%project_edit_project/edit/promote/confirm']['access callback'] = 'drupalorg_project_promote_project_access';
}
/**
* Menu access callback for the form to promote sandboxes to full projects.
*
* @param $node
* The project object to check access against.
*/
function drupalorg_project_promote_project_access($node) {
$forbidden_tids = drupalorg_project_get_no_promote_project_tids();
foreach ($forbidden_tids as $tid) {
if (!empty($node->taxonomy[$tid])) {
return FALSE;
}
}
return project_promote_project_access($node);
}
/**
* Return an array of project type tids that cannot be promoted.
*
* This helper function only exists since you can't define() an array in PHP.
*/
function drupalorg_project_get_no_promote_project_tids() {
return array(13);
}
/**
* Implementation of hook_help().
*/
function drupalorg_project_help($path, $arg) {
switch ($path) {
case 'project/usage':
case 'project/usage/%':
return '
'. t('These statistics are incomplete; only Drupal websites using the Update Status module are included in the data. As this module is now included with the download of Drupal since version 6.x, the data is heavily biased toward newer sites. Read more information about how these statistics are calculated.', array('!update_status_url' => url('project/update_status'), '!usage_description_url' => url('node/329620'))) .'
';
}
if ($arg[0] == 'project' && $arg[1] == 'issues-term' && $arg[2] == '346') {
return ''. t('A more detailed overview of the issues can be seen in the Community Initiatives handbook section titled Upgrade Drupal.org from Drupal 5 to 6.') .'
';
}
}
/**
* Implementation of hook_cron().
*/
function drupalorg_project_cron() {
drupalorg_project_issue_counts();
}
// == Issue counter ============================================================
/**
* Count number of issues in various issue queues on drupal.org.
*
* This function only counts issues for core releases that we're currently
* tracking.
*
* @see drupalorg_project_bingo_block_output()
* @see DRUPALORG_CORE_VERSION_TID
* @see DRUPALORG_CORE_VERSION_NAME
*/
function drupalorg_project_issue_counts() {
$issue_rids = array();
/// @todo Further optimize this if/when {node}.status is denormalized into
/// {project_release_nodes}.
$query = db_query("SELECT prn.nid FROM {project_release_nodes} prn INNER JOIN {node} n ON prn.nid = n.nid WHERE prn.pid = %d AND prn.version_api_tid = %d AND n.status = 1", DRUPALORG_CORE_NID, DRUPALORG_CORE_VERSION_TID);
while ($release = db_fetch_object($query)) {
$issue_rids[] = $release->nid;
}
$issue_rid_placeholders = db_placeholders($issue_rids);
$args = array_merge(array(DRUPALORG_CORE_NID), $issue_rids);
$issue_counts['Pending bugs'] = db_result(db_query("SELECT COUNT(*) FROM {project_issues} pi INNER JOIN {node} n ON pi.nid = n.nid WHERE n.status = 1 AND pid = %d AND category = 'bug' AND sid = 1 AND priority IN (1,2,4) AND rid IN ($issue_rid_placeholders)", $args));
$issue_counts['Critical issues'] = db_result(db_query("SELECT COUNT(*) FROM {project_issues} pi INNER JOIN {node} n ON pi.nid = n.nid WHERE n.status = 1 AND pid = %d AND category IN ('bug', 'task') AND sid IN (1,8,13,14) AND priority = 1 AND rid IN ($issue_rid_placeholders)", $args));
$issue_counts['Major issues'] = db_result(db_query("SELECT COUNT(*) FROM {project_issues} pi INNER JOIN {node} n ON pi.nid = n.nid WHERE n.status = 1 AND pid = %d AND category IN ('bug', 'task') AND sid IN (1,8,13,14) AND priority = 4 AND rid IN ($issue_rid_placeholders)", $args));
$issue_counts['Patch queue'] = db_result(db_query("SELECT COUNT(*) FROM {project_issues} pi INNER JOIN {node} n ON pi.nid = n.nid WHERE n.status = 1 AND pid = %d AND sid IN (8,13,14) AND rid IN ($issue_rid_placeholders)", $args));
$issue_counts['Patches to review'] = db_result(db_query("SELECT COUNT(*) FROM {project_issues} pi INNER JOIN {node} n ON pi.nid = n.nid WHERE n.status = 1 AND pid = %d AND sid IN (8) AND rid IN ($issue_rid_placeholders)", $args));
variable_set('drupalorg_project_issue_counts', $issue_counts);
}
// == Altering of various forms ================================================
/**
* Implementation of hook_form_alter().
*/
function drupalorg_project_form_alter(&$form, $form_state, $form_id) {
// Add a description for the Priority and Status values.
if ($form_id == 'project_issue_node_form' || ($form_id == 'comment_form' && !empty($form['original_issue']['issue_info']))) {
$priority_status_description = array(
// The "standard" class adds a clear so the description is positioned
// below the floated pull-downs. "fieldset-description" is from
// bluebeach to make the font size smaller.
'#prefix' => '',
'#value' => t('Descriptions of the
Priority and
Status values can be found in the
Issue queue handbook.', array('!priority_url' => '/node/45111', '!status_url' => '/node/156119', '!issue_queue_url' => '/node/317')),
'#suffix' => '
',
);
// We want this fieldset at the very bottom, just above the buttons, but
// we need different weights for that depending on if it's a new issue or
// a follow-up comment.
if ($form_id == 'project_issue_node_form') {
$form['taxonomy']['#weight'] = 35;
$form['issue_info']['description'] = $priority_status_description;
}
else {
$form['taxonomy']['#weight'] = 4;
$form['original_issue']['issue_info']['description'] = $priority_status_description;
}
// Prefill values from $_GET.
if (isset($_GET['version'])) {
$form['project_info']['rid']['#default_value'] = $_GET['version'];
}
if (isset($_GET['component'])) {
$form['project_info']['component']['#default_value'] = $_GET['component'];
}
if (isset($_GET['categories'])) {
$form['issue_info']['category']['#default_value'] = $_GET['categories'];
}
if (isset($_GET['priorities'])) {
$form['issue_info']['priority']['#default_value'] = $_GET['priorities'];
}
if (isset($_GET['assigned'])) {
$form['issue_info']['assigned']['#default_value'] = $_GET['assigned'];
}
if (isset($_GET['status'])) {
$form['issue_info']['sid']['#default_value'] = $_GET['status'];
}
if (isset($_GET['title'])) {
$form['issue_details']['title']['#default_value'] = $_GET['title'];
}
if (isset($_GET['body'])) {
$form['issue_details']['body']['#default_value'] = $_GET['body'];
}
if (isset($_GET['tags'])) {
$form['taxonomy']['#collapsed'] = FALSE;
$form['taxonomy']['tags'][reset(array_keys($form['taxonomy']['tags']))]['#default_value'] = $_GET['tags'];
}
}
// Add security update related checks to release forms.
if ($form_id == 'project_release_node_form') {
drupalorg_project_release_node_form_alter($form, $form_state);
}
if ($form_id == 'project_project_node_form') {
// Deprecate the "license" field, as it must always be GPLv2+ anyway.
if (! $form['project']['license']['#default_value']) {
$form['project']['license'] = array(
'#type' => 'item',
'#title' => $form['project']['license']['#title'],
'#value' => t('@link_name', array(
// Constants defined in drupalorg_project.module.
'@link_url' => DRUPALORG_DEFAULT_LICENSE_LINK,
'@link_name' => DRUPALORG_DEFAULT_LICENSE,
)),
);
}
// Translation projects on drupal.org are being phased out in favor of
// localize.drupal.org, so prevent new translation projects from being
// created.
if ($form['project_taxonomy']['project_type']['#default_value'] != DRUPALORG_TRANSLATION_TID) {
unset($form['project_taxonomy']['project_type']['#options'][DRUPALORG_TRANSLATION_TID]);
}
$form['project_taxonomy']['project_type']['#description'] = t('Note: Translation projects have been moved and are now actively maintained at localize.drupal.org.', array('@localize-link' => 'http://localize.drupal.org/'));
}
}
// == Security release restrictions ============================================
/**
* Alter release node forms properly for security updates.
*
* Ensure that only privileged users can modify a security release tag and when
* people add this tag, they get to know about the process.
*/
function drupalorg_project_release_node_form_alter(&$form, $form_state) {
// See if this is a release for a translation project, and if so, deny.
$project = $form['project']['#value'];
if (arg(2) != 'edit' && !empty($project->taxonomy[DRUPALORG_TRANSLATION_TID])) {
foreach (element_children($form) as $child) {
unset($form[$child]);
}
$form['translations_denied'] = array(
'#type' => 'markup',
'#value' => t('Translation projects have been moved and are now actively maintained at localize.drupal.org. You are no longer allowed to create new releases of translation projects on drupal.org.', array('@localize-link' => 'http://localize.drupal.org/')),
);
return;
}
if (!empty($form['taxonomy'])) {
$vid = drupalorg_project_get_release_type_vid();
$security_tid = drupalorg_project_get_security_update_tid();
if (!empty($form['taxonomy'][$vid])) {
$form['taxonomy'][$vid]['#weight'] = 10;
if (empty($form['nid']['#value'])) {
// Adding a new release.
if (!empty($form['project_release']['rebuild']['#value'])) {
// This is a -dev, don't let anyone mark it a security update.
_drupalorg_project_remove_security_update($form);
}
else {
// Regular release
$form['taxonomy'][$vid]['#description'] = t('What is a release type?', array('@handbook_url' => url('node/'. DRUPALORG_RELEASE_TYPE_HANDBOOK_NID)));
// Add wrapper div for drupalorg_project.js
$form['taxonomy'][$vid]['#prefix'] = '';
$form['taxonomy'][$vid]['#suffix'] = '
';
$form['#validate'][] = 'drupalorg_project_security_release_form_validate';
$confirm_class = 'security-update-confirm';
// Hide the confirmation checkbox on page load unless 'Security
// update' is already selected.
if (empty($form_state['values']['taxonomy'][$vid]) || (array_search($security_tid, $form_state['values']['taxonomy'][$vid]) === FALSE)) {
$confirm_class .= ' js-hide';
}
$form['security_update_confirm'] = array(
'#type' => 'checkbox',
'#title' => t('Are you sure you want to mark this release as a Security update?', array('@security_update_url' => url('node/'. DRUPALORG_SECURITY_UPDATE_HANDBOOK_NID))),
'#prefix' => '',
'#suffix' => '
',
'#weight' => -2,
'#description' => t('If you select %security_update, your release will not be published without the manual intervention of the Drupal Security Team. You should have already contacted the Security Team to coordinate a security advisory (SA) for your release before you committed any security-related patches.', array('%security_update' => t('Security update'), '@security_url' => DRUPALORG_SECURITY_TEAM_URL, '@contact_url' => DRUPALORG_SECURITY_CONTACT_URL)),
'#default_value' => !empty($form_state['values']['security_update_confirm']),
);
$drupalorg_project_path = drupal_get_path('module', 'drupalorg_project');
drupal_add_js($drupalorg_project_path .'/drupalorg_project.js');
drupal_add_css($drupalorg_project_path .'/drupalorg_project.css');
}
}
else {
// Editing an existing release.
if (array_search($security_tid, $form['taxonomy'][$vid]['#default_value']) !== FALSE) {
// If this release is already marked as a Security update, don't
// let regular users change it any futher.
if (!user_access('administer projects')) {
$form['taxonomy'][$vid]['#disabled'] = TRUE;
$form['taxonomy'][$vid]['#value'] = $form['taxonomy'][$vid]['#default_value'];
}
$form['taxonomy'][$vid]['#description'] = t('What is a release type? Since this release is already marked as a %security_update, you can no longer change the release type. If you believe you need to do so for some reason, you should contact the Drupal Security Team.', array('@handbook_url' => url('node/'. DRUPALORG_RELEASE_TYPE_HANDBOOK_NID), '%security_update' => t('Security update'), '@security_url' => DRUPALORG_SECURITY_TEAM_URL, '@contact_url' => DRUPALORG_SECURITY_CONTACT_URL));
}
else {
// Not a Security update, remove that option entirely if this is
// either a -dev snapshot or a non-admin user.
if (!empty($form['#node']->rebuild) || !user_access('administer projects')) {
_drupalorg_project_remove_security_update($form);
}
$form['taxonomy'][$vid]['#description'] = t('What is a release type?', array('@handbook_url' => url('node/'. DRUPALORG_RELEASE_TYPE_HANDBOOK_NID)));
}
}
}
}
}
/**
* Utility function to remove the security update term on node forms.
*/
function _drupalorg_project_remove_security_update(&$form) {
$vid = drupalorg_project_get_release_type_vid();
$security_tid = drupalorg_project_get_security_update_tid();
foreach ($form['taxonomy'][$vid]['#options'] as $i => $option) {
if (!empty($option->option)) {
$tid = key($option->option);
if ($tid == $security_tid) {
unset($form['taxonomy'][$vid]['#options'][$i]);
return;
}
}
}
}
/**
* Set the project_release_type_vid Drupal variable and return its value.
*/
function drupalorg_project_get_release_type_vid() {
static $vid = 0;
if (empty($vid)) {
$vid = variable_get('project_release_type_vid', 0);
if (empty($vid)) {
$vid = db_result(db_query("SELECT v.vid FROM {vocabulary} v INNER JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE n.type = 'project_release' AND v.name = 'Release type'"));
variable_set('project_release_type_vid', $vid);
}
}
return $vid;
}
/**
* Set the project_release_type_security_update_tid Drupal variable and return its value.
*/
function drupalorg_project_get_security_update_tid() {
static $tid = 0;
if (empty($tid)) {
$tid = variable_get('project_release_type_security_update_tid', 0);
if (empty($tid)) {
$tid = db_result(db_query("SELECT tid FROM {term_data} WHERE vid = %d AND name = '%s'", drupalorg_project_get_release_type_vid(), 'Security update'));
variable_set('project_release_type_security_update_tid', $tid);
}
}
return $tid;
}
/**
* Form validation function for security release tag check.
*/
function drupalorg_project_security_release_form_validate($form, &$form_state) {
$vid = drupalorg_project_get_release_type_vid();
$security_tid = drupalorg_project_get_security_update_tid();
if (!empty($form_state['values']['taxonomy'][$vid][$security_tid]) && empty($form_state['values']['security_update_confirm'])) {
// Ensure the user confirms that this release should be marked security.
form_set_error('security_update_confirm', t('You must confirm you want this release to be a Security update', array('@security_update_url' => url('node/'. DRUPALORG_SECURITY_UPDATE_HANDBOOK_NID))));
}
}
// == Project links ============================================================
/**
* Implemenation of hook_project_page_link_alter().
*/
function drupalorg_project_project_page_link_alter(&$links, $node) {
global $user;
// Make the development and resources sections actual core blocks.
$links['development']['type'] = 'block';
$links['resources']['type'] = 'block';
// Provide contextual links for sandboxes.
$project_tids = array_keys($node->taxonomy);
$no_promote_tids = drupalorg_project_get_no_promote_project_tids();
$forbidden_tids = array_intersect($no_promote_tids, $project_tids);
if ($node->project['sandbox'] == 1 && empty($forbidden_tids) && $user->uid == $node->uid) {
$links['sandbox']['weight'] = 1;
if (!user_access('create full projects')) {
$links['sandbox']['links']['apply_full'] = l(t('Apply for full project access'), 'node/1011698');
}
else {
$links['sandbox']['links']['no_releases'] = l(t('Promote to full project'), "node/$node->nid/edit/promote");
}
}
// Link to security handbook page.
$links['development']['links']['report_security_issue'] = l(t('Report a security issue'), 'security-team');
// Link to translation downloads.
$translation_download = '';
if (is_array($node->taxonomy) && isset($node->taxonomy[DRUPALORG_TRANSLATION_TID])) {
// Language projects get links to their language teams.
$translation_download = 'http://localize.drupal.org/translate/languages/';
}
elseif (!empty($node->project_release['releases'])) {
// Otherwise only include links if we have releases and at least one supported.
$has_supported = db_result(db_query_range(db_rewrite_sql('SELECT 1 FROM {project_release_supported_versions} WHERE nid = %d AND supported = 1'), $node->nid, 0, 1));
if ($has_supported > 0) {
$translation_download = 'http://localize.drupal.org/translate/projects/';
}
}
if (!empty($translation_download)) {
$links['resources']['links']['view_translations'] = l(t('View project translations'), $translation_download . $node->project['uri']);
}
unset($links['resources']['links']['project_usage']);
}
// == Utility functions ========================================================
/**
* Project issue URL generator for Drupal issues.
*
* @param $query
* A keyed array of options to pass to the query parameter of url().
* - version (An array of release node IDs)
* - status (An array of status IDs)
* - priorities (An array of priority IDs)
* - categories (An array of category names)
* @param $absolute
* Set to TRUE to get an absolute URL with http://drupal.org/...
*/
function drupalorg_project_issue_url($query = array(), $absolute = FALSE) {
return url('project/issues/search/drupal', array('query' => $query, 'absolute' => $absolute));
}
/**
* Project issue link generator for Drupal issues.
*
* @param $text
* String to use as text for the link.
* @param $query
* A keyed array of options to pass to the query parameter of l().
* - version (An array of release node IDs)
* - status (An array of status IDs)
* - priorities (An array of priority IDs)
* - categories (An array of category names)
* @param $absolute
* Set to TRUE to get an absolute URL with http://drupal.org/...
*/
function drupalorg_project_issue_link($text, $query = array(), $absolute = FALSE) {
return l($text, 'project/issues/search/drupal', array('query' => $query, 'absolute' => $absolute));
}
// == Node overrides ===========================================================
/**
* Implementation of hook_nodeapi().
*/
function drupalorg_project_nodeapi(&$node, $op = 'view', $teaser = FALSE, $page = FALSE) {
if ($op == 'view' && $page) {
$extra = '';
switch ($node->nid) {
case 199251: // List projects with commit RSS feeds.
$extra = drupalorg_project_list_commit_rss();
break;
case 97084: // List branches ever created in contrib.
$extra = drupalorg_project_list_branches_contrib();
break;
case 93997: // List branches ever created in core.
$extra = drupalorg_project_list_branches_core();
break;
case 9730:
$url_queue = drupalorg_project_issue_url(
array(
'status' => array(8, 13, 14),
),
TRUE /* Absolute */
);
header('Location: '. $url_queue);
exit;
case 9731:
$url_bugs = drupalorg_project_issue_url(
array(
'status' => array(1),
'categories' => array('bug'),
),
TRUE /* Absolute */
);
header('Location: '. $url_bugs);
exit;
case 9732:
$url_tasks = drupalorg_project_issue_url(
array(
'status' => array(1),
'categories' => array('task'),
),
TRUE /* Absolute */
);
header('Location: '. $url_tasks);
exit;
case 133282:
drupal_goto('patch/create');
exit;
}
$node->content['body']['#value'] .= $extra;
if ($node->type == 'project_project') {
if ($node->project['sandbox']) {
$user_link = l(t("!name's sandbox", array('!name' => $node->name)), 'project/user/'. $node->uid);
drupal_set_title($user_link .': '. check_plain($node->title));
$node->content['project_sandbox_warning']['#value'] = '';
$node->content['project_sandbox_warning']['#weight'] = -1;
}
else {
$node->content['project_info']['#value'] = drupalorg_project_meta_data($node);
}
}
}
}
/**
* List projects with commit RSS links.
*/
function drupalorg_project_list_commit_rss() {
$count_query = "SELECT COUNT(*) FROM {node} WHERE type = 'project_project' AND status = 1";
$total = db_result(db_query($count_query));
$header = array(array('data' => t('Number'), 'field' => 'nid', 'sort' => 'desc'), t('Title'), t('Commits RSS'));
$result = pager_query("SELECT nid, title, status FROM {node} WHERE type = 'project_project' AND status = 1 ". tablesort_sql($header), 100, 0, $count_query);
$rows = array();
$page = (int)$_GET['page'];
$count = $total - $page * 100;
while($row = db_fetch_array($result)) {
// @TODO Need RSS feeds and commitlog/project views, see http://drupal.org/node/1024958
$url = url('commitlog');
$rows[]= array($count--, l($row['title'], 'node/'. $row['nid']), theme('feed_icon', $url, $row['title']));
}
drupal_set_message("There are $total projects.");
return theme('table', $header, $rows). theme('pager');
}
/**
* Show branches ever created in contrib based on versioncontrol module data.
*/
function drupalorg_project_list_branches_contrib() {
$output = '';
$query = db_query("SELECT DISTINCT name, COUNT(*) AS total
FROM {versioncontrol_labels} vcl
INNER JOIN {versioncontrol_project_projects} vcp ON vcp.repo_id = vcl.repo_id
WHERE vcp.nid != %d AND vcl.type = %d
GROUP BY name ORDER BY name DESC", DRUPALORG_CORE_NID, VERSIONCONTROL_OPERATION_BRANCH);
while($tag = db_fetch_object($query)) {
$output .= ' - ' . check_plain($tag->tag) . ' ('. format_plural($tag->total, '1 project', '@count projects') .')
';
}
$output .= '
';
return $output;
}
/**
* Show branches ever created in core based on versioncontrol module data.
*/
function drupalorg_project_list_branches_core() {
$output = '';
$query = db_query("SELECT name
FROM {versioncontrol_labels} vcl
INNER JOIN {versioncontrol_project_projects} vcp ON vcp.repo_id = vcl.repo_id
WHERE vcp.nid = %d AND type = %d
ORDER BY name DESC", DRUPALORG_CORE_NID, VERSIONCONTROL_OPERATION_BRANCH);
while($tag = db_fetch_object($query)) {
$output .= ' - ' . check_plain($tag->tag) . '
';
}
$output .= '
';
$output .= '';
$output .= 'The tags currently available in Drupal core are:
';
$output .= '';
$query = db_query("SELECT name
FROM {versioncontrol_labels} vcl
INNER JOIN {versioncontrol_project_projects} vcp ON vcp.repo_id = vcl.repo_id
WHERE vcp.nid = %d AND type = %d
ORDER BY name DESC", DRUPALORG_CORE_NID, VERSIONCONTROL_OPERATION_TAG);
while($tag = db_fetch_object($query)) {
$output .= ' - ' . check_plain($tag->tag) . '
';
}
$output .= '
';
return $output;
}
// == Bug bingo ================================================================
/**
* SQL randomizer for issues.
*/
function drupalorg_project_bingo_jump($type = NULL) {
$sql = array(
// Bug, core.
'bug-core' => "SELECT nid FROM {project_issues} WHERE sid IN (1) AND category = 'bug' AND pid = %d ORDER BY RAND() LIMIT 1",
// Bug, contrib.
'bug-contrib' => "SELECT nid FROM {project_issues} WHERE sid IN (1) AND category = 'bug' AND pid != %d ORDER BY RAND() LIMIT 1",
// Patch, core.
'patch-core' => "SELECT nid FROM {project_issues} WHERE sid IN (8,13,14) AND pid = %d ORDER BY RAND() LIMIT 1",
// Patch, contrib.
'patch-contrib' => "SELECT nid FROM {project_issues} WHERE sid IN (8,13,14) AND pid != %d ORDER BY RAND() LIMIT 1",
);
if (!isset($type) || !isset($sql[$type])) {
$type = 'bug-core';
}
$nid = db_result(db_query($sql[$type], DRUPALORG_CORE_NID));
if ($_GET['stop'] != 1) {
drupal_goto('node/'. $nid);
}
}
/**
* Implementation of hook_block().
*/
function drupalorg_project_block($op = 'list', $delta = 0, $edit = array()) {
switch ($op) {
case 'list':
$blocks[0]['info'] = t('Contributor Links');
return $blocks;
case 'view':
$block['subject'] = t('Contributor Links');
$block['content'] = drupalorg_project_bingo_block_output();
return $block;
}
}
/**
* Output links with issue counts for different types of issues.
*/
function drupalorg_project_bingo_block_output() {
$counts = variable_get('drupalorg_project_issue_counts', array());
$counts_pending = $counts['Pending bugs'];
$counts_critical = $counts['Critical issues'];
$counts_major = $counts['Major issues'];
$counts_queue = $counts['Patch queue'];
$counts_review = $counts['Patches to review'];
$links = array(
l('Community initiatives', 'community-initiatives'),
array(
'data' => 'Queues',
'children' => array(
l('Your issues', 'project/issues/user'),
drupalorg_project_issue_link(
$counts_pending .' Pending bugs (D7)',
array(
'version' => array(DRUPALORG_CORE_VERSION_NAME),
'status' => array(1),
'priorities' => array(1, 2, 4),
'categories' => array('bug'),
)
),
drupalorg_project_issue_link(
$counts_critical .' Critical issues (D7)',
array(
'version' => array(DRUPALORG_CORE_VERSION_NAME),
'status' => array(1, 8, 13, 14),
'priorities' => array(1),
'categories' => array('bug', 'task'),
)
),
drupalorg_project_issue_link(
$counts_major .' Major issues (D7)',
array(
'version' => array(DRUPALORG_CORE_VERSION_NAME),
'status' => array(1, 8, 13, 14),
'priorities' => array(4),
'categories' => array('bug', 'task'),
)
),
drupalorg_project_issue_link(
$counts_queue .' Patch queue (D7)',
array(
'version' => array(DRUPALORG_CORE_VERSION_NAME),
'status' => array(8, 13, 14),
)
),
drupalorg_project_issue_link(
$counts_review .' Patches to review (D7)',
array(
'version' => array(DRUPALORG_CORE_VERSION_NAME),
'status' => array(8, 14),
)
),
drupalorg_project_issue_link(
'Performance issues (D7)',
array(
'version' => array(DRUPALORG_CORE_VERSION_NAME),
'issue_tags' => 'Performance',
)
),
drupalorg_project_issue_link(
'Usability issues (D7)',
array(
'version' => array(DRUPALORG_CORE_VERSION_NAME),
'issue_tags' => 'Usability, d7ux',
)
),
drupalorg_project_issue_link(
'Accessibility issues (D7)',
array(
'version' => array(DRUPALORG_CORE_VERSION_NAME),
'issue_tags' => 'accessibility',
)
),
),
),
array(
'data' => 'Play patch bingo!',
'children' => array(
l('Drupal Core', 'patch-bingo', array('attributes' => array('title' => 'Select a random patch for review'))),
l('Contributions', 'contrib-patch-bingo', array('attributes' => array('title' => 'Select a random patch for review from the contributions'))),
),
),
array(
'data' => 'Play bug bingo!',
'children' => array(
l('Drupal Core', 'bug-bingo', array('attributes' => array('title' => 'Select a random bug to fix'))),
l('Contributions', 'contrib-bug-bingo', array('attributes' => array('title' => 'Select a random bug to fix from the contributions'))),
),
),
l('Mailing list archives', 'mailing-lists'),
l('Drupal.org webmasters', 'project/issues/webmasters'),
l('Drupal.org server administrators', 'project/issues/infrastructure'),
l('Drupal.org Project applications', 'project/issues/projectapplications'),
array(
'data' => 'Web links',
'children' => array(
l('Planet Drupal', 'planet'),
l('Drupal talk', 'talk'),
l('Drupal dojo', 'http://groups.drupal.org/drupal-dojo'),
),
),
);
return theme('item_list', $links);
}
/**
* Implementation of template_preprocess_drupalorg_home().
*/
function drupalorg_project_preprocess_drupalorg_home(&$vars) {
$result = db_query_range("SELECT vco.message as message, vco.date as created, vco.revision as revision, u.name, u.uid FROM {versioncontrol_operations} vco INNER JOIN {users} u ON vco.author_uid = u.uid ORDER BY vco.date DESC", 0, 5);
$recent_updates = '';
while ($message = db_fetch_object($result)) {
$recent_updates .= ''. l(truncate_utf8($message->message, 80) .'...', 'commitlog/commit/'. $message->revision) .'
'. theme('node_submitted', $message) .'
';
}
// We have no place to link this to in a nice way.
$recent_updates .= ''. l(t('More commit messages...'), 'commitlog') .'
';
$vars['tab_content_git'] = $recent_updates;
}
/**
* Gather information for the bottom of project pages.
*
* @param $node
* The node object.
* @return
* The HTML code for the book meta information.
*/
function drupalorg_project_meta_data($node) {
$info = array();
if ($node->nid == DRUPALORG_CORE_NID) {
return;
}
if (count($node->taxonomy)) {
$project_vid = _project_get_vid();
// It's pointless to query, since we need to hard-code singular
// versions while the term names are plural.
$project_types = drupalorg_project_get_project_types(TRUE);
$vocabularies = taxonomy_get_vocabularies();
// Group the terms by vid.
$taxonomy = array();
foreach ($node->taxonomy as $term_id => $term) {
// Ignore the project type terms (the top level terms from the
// project vocabulary) since those are already visible on the page.
if (!empty($project_types[$term->tid])) {
$project_type = $term;
$project_type->label = $project_types[$term->tid];
}
else {
$taxonomy[$term->vid][] = $term;
}
$term->rendered = TRUE;
}
foreach ($vocabularies as $vocabulary) {
if (isset($taxonomy[$vocabulary->vid])) {
$class = '';
$links = array();
foreach ($taxonomy[$vocabulary->vid] as $term) {
$options = array();
if ($term->vid == '44') { // Maintenance status
$maintenance_status = $term->tid;
switch ($term->tid) {
case '13032': // Abandoned
$options['attributes'] = array('class' => 'alert');
break;
case '9990': // Seeking co-maintainer(s)
case '9992': // Seeking new maintainer
$options['attributes'] = array('class' => 'warning');
break;
}
}
elseif ($term->vid == '46') { // Development status
$development_status = $term->tid;
switch ($term->tid) {
case '9994': // Obsolete
$options['attributes'] = array('class' => 'alert');
break;
case '13030': // Maintenance fixes only
case '16374': // No futher development
$options['attributes'] = array('class' => 'warning');
break;
}
}
// Special-case module category terms to link to download+extend.
if ($term->vid == $project_vid && $project_type->tid == '14') {
// Only use the 'terms' style for module categories.
$class = 'terms';
$term_path = 'project/modules';
$options['query'] = array('filters' => 'tid:' . $term->tid);
}
else {
$term_path = taxonomy_term_path($term);
}
$links[] = l($term->name, $term_path, $options);
}
if ($vocabulary->vid == $project_vid) {
// These are hard-coded, don't incur cost of check_plain().
$label = t('!project_type categories', array('!project_type' => $project_type->label));
}
else {
$label = check_plain($vocabulary->name);
}
$info[$label] = '' . implode(', ', $links) . '';
}
}
if (empty($maintenance_status)) {
$label = t('Maintenance status');
$info[$label] = '' . t('Unknown') . '';
}
if (empty($development_status) && !empty($node->versioncontrol_project['repo_id'])) {
$label = t('Development status');
$info[$label] = '' . t('Unknown') . '';
}
}
// Usage stats.
$total_usage = project_usage_get_project_total_usage($node->nid);
if ($total_usage) {
$label = t('Reported installs');
$info[$label] = format_plural($total_usage,
'1 site currently reports using this @project-type. View usage statistics.',
'@count sites currently report using this @project-type. View usage statistics.',
array(
'@project-type' => drupal_strtolower($project_type->label),
'!project-shortname' => $node->project['uri'],
)
);
}
// Automated tests.
$has_auto_tests = db_result(db_query("SELECT pid FROM {pift_project} WHERE pid = %d", $node->nid));
if (!empty($has_auto_tests)) {
$label = t('Automated tests');
/// @todo Indicate if the tests are passing or not.
$info[$label] = t('Enabled');
}
$modified = format_date($node->changed, 'custom', 'F j, Y');
return theme('drupalorg_project_meta_data', $info, $modified);
}
/**
* Implementation of hook_theme().
*/
function drupalorg_project_theme($existing, $type, $theme, $path) {
return array(
'drupalorg_project_meta_data' => array(
'arguments' => array(
'info' => NULL,
'modified' => NULL,
),
'template' => 'project-meta-data',
),
);
}
/**
* Return an array mapping term ID to name for the top-level project types.
*
* @param boolean $singular
* Should the name be singular or plural. Defaults to plural.
*/
function drupalorg_project_get_project_types($singular = FALSE) {
if ($singular) {
return array(
'13' => t('Drupal project'),
'14' => t('Module'),
'15' => t('Theme'),
'32' => t('Theme engine'),
'29' => t('Translation'),
'96' => t('Installation profile'),
);
}
else {
return array(
'13' => t('Drupal project'),
'14' => t('Modules'),
'15' => t('Themes'),
'32' => t('Theme engines'),
'29' => t('Translations'),
'96' => t('Installation profiles'),
);
}
}
/**
* Implement hook_ctools_plugin_directory().
*/
function drupalorg_project_ctools_plugin_directory($module, $plugin) {
if ($module == 'project_release' && $plugin == 'release_packager') {
return "plugins/$plugin";
}
}