#!/usr/bin/php DRUPAL_ROOT, 'SITE_NAME' => SITE_NAME, ); $fatal_err = FALSE; foreach ($vars as $name => $val) { if (empty($val)) { print "ERROR: \"$name\" constant not defined, aborting\n"; $fatal_err = TRUE; } } if ($fatal_err) { exit(1); } $script_name = $argv[0]; // Setup variables for Drupal bootstrap $_SERVER['HTTP_HOST'] = SITE_NAME; $_SERVER['REQUEST_URI'] = '/' . $script_name; $_SERVER['SCRIPT_NAME'] = '/' . $script_name; $_SERVER['PHP_SELF'] = '/' . $script_name; $_SERVER['SCRIPT_FILENAME'] = $_SERVER['PWD'] .'/'. $script_name; $_SERVER['PATH_TRANSLATED'] = $_SERVER['SCRIPT_FILENAME']; if (!chdir(DRUPAL_ROOT)) { print "ERROR: Can't chdir(DRUPAL_ROOT), aborting.\n"; exit(1); } // Make sure our umask is sane for generating directories and files. umask(022); require_once 'includes/bootstrap.inc'; drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); define('BASE_DIRECTORY', DRUPAL_ROOT .'/'. file_create_path(variable_get('project_release_history_directory', 'release-history'))); if (!is_dir(BASE_DIRECTORY)) { wd_err(t("ERROR: History directory (%directory) does not exist, aborting.\n", array('%directory' => BASE_DIRECTORY))); exit(1); } /// @todo Add command-line args to only generate a given project/version. project_release_history_generate_all(); project_list_generate(); // ------------------------------------------------------------ // Functions: main work // ------------------------------------------------------------ /** * Figure out what project and API terms to generate the history for. */ function project_release_history_generate_all() { $api_terms = project_release_compatibility_list(); wd_msg(t('Generating XML release history files for all projects.')); // Generate all.xml files for projects with releases. $query = db_query("SELECT DISTINCT(pid) FROM {project_release_nodes}"); $i = 0; while ($project = db_fetch_object($query)) { project_release_history_generate_project_xml($project->pid); $i++; } wd_msg(format_plural($i, 'Generated an XML release history summary for a project.', 'Generated XML release history summaries for @count projects.')); // Generate XML files based on API compatibility. $tids = array_keys($api_terms); $placeholders = implode(',', array_fill(0, count($tids), '%d')); $query = db_query("SELECT DISTINCT(prn.pid), tn.tid FROM {project_release_nodes} prn INNER JOIN {term_node} tn ON prn.nid = tn.nid WHERE tn.tid IN ($placeholders)", $tids); $i = 0; while ($project = db_fetch_object($query)) { project_release_history_generate_project_xml($project->pid, $project->tid); $i++; } wd_msg(t('Completed XML release history files for @num_projects.', array('@num_projects' => format_plural($i, '1 project/version pair', '@count project/version pairs')))); } /** * Generate the XML history file for a given project name and API * compatibility term. * * @todo If a history file already exists for this combination, this * function will generate a new history and atomically replace the old * one (currently, just logs to watchdog for debugging). * * @todo If there's no subdirectory in the directory tree for this * project yet, this function creates one. * * @param $project_nid * Project ID (node id of the project node) to generate history for. * @param $api_tid * Taxonomy id (tid) of the API compatibility term to use, or NULL if * all terms are considered. */ function project_release_history_generate_project_xml($project_nid, $api_tid = NULL) { $api_vid = _project_release_get_api_vid(); if (isset($api_tid)) { // Restrict output to a specific API compatibility term. $api_terms = project_release_compatibility_list(); if (!isset($api_terms[$api_tid])) { wd_err(t('API compatibility term %tid not found.', array('%tid' => $api_tid))); return FALSE; } $api_version = $api_terms[$api_tid]; // Get project-wide data: $sql = "SELECT DISTINCT n.title, n.nid, n.status, p.uri, u.name AS username FROM {node} n INNER JOIN {project_projects} p ON n.nid = p.nid INNER JOIN {project_release_supported_versions} prsv ON prsv.nid = n.nid INNER JOIN {users} u ON n.uid = u.uid WHERE prsv.tid = %d AND p.nid = %d"; $query = db_query($sql, $api_tid, $project_nid); } else { // Consider all API compatibility terms. $api_version = 'all'; $sql = "SELECT n.title, n.nid, n.status, p.uri, u.name AS username FROM {node} n INNER JOIN {project_projects} p ON n.nid = p.nid INNER JOIN {users} u ON n.uid = u.uid WHERE p.nid = %d"; $query = db_query($sql, $project_nid); } if (!db_num_rows($query)) { wd_err(t('Project ID %pid not found', array('%pid' => $project_nid))); return FALSE; } $project = db_fetch_object($query); $xml = '
' . check_plain($full_xml));
return FALSE;
}
// We have to close this handle before we can rename().
fclose($hist_fd);
// Now we can atomically rename the .new into place in the "live" spot.
if (!_rename($tmp_filename, $filename)) {
wd_err($errors['rename']);
return FALSE;
}
return TRUE;
}
/**
* Generate a list of all projects available on this server.
*/
function project_list_generate() {
$api_vid = _project_release_get_api_vid();
$query = db_query("SELECT n.title, n.nid, n.status, p.uri, u.name AS username FROM {node} n INNER JOIN {project_projects} p ON n.nid = p.nid INNER JOIN {users} u ON n.uid = u.uid");
if (!db_num_rows($query)) {
wd_err(t('No projects found on this server.'));
return FALSE;
}
$xml = '';
while ($project = db_fetch_object($query)) {
$xml .= " \n";
$xml .= ' '. check_plain($project->title) ." \n";
$xml .= ' '. check_plain($project->uri) ." \n";
$xml .= ' '. url("node/$project->nid", NULL, NULL, TRUE) ."\n";
$xml .= ' '. check_plain($project->username). " \n";
$term_query = db_query("SELECT v.name AS vocab_name, v.vid, td.name AS term_name, td.tid FROM {term_node} tn INNER JOIN {term_data} td ON tn.tid = td.tid INNER JOIN {vocabulary} v ON td.vid = v.vid WHERE tn.nid = %d", $project->nid);
$xml_terms = '';
while ($term = db_fetch_object($term_query)) {
$xml_terms .= ' '. check_plain($term->vocab_name) .' ';
$xml_terms .= ''. check_plain($term->term_name) ." \n";
}
if (!empty($xml_terms)) {
$xml .= " \n". $xml_terms ." \n";
}
if (!$project->status) {
// If it's not published, we can skip the rest for this project.
$xml .= " unpublished \n";
}
else {
$xml .= " published \n";
// Include a list of API terms if available.
$term_query = db_query("SELECT DISTINCT(td.tid), td.name AS term_name FROM {project_release_nodes} prn INNER JOIN {term_node} tn ON prn.nid = tn.nid INNER JOIN {term_data} td ON tn.tid = td.tid WHERE prn.pid = %d AND td.vid = %d ORDER BY td.weight ASC", $project->nid, $api_vid);
$xml_api_terms = '';
while ($api_term = db_fetch_object($term_query)) {
$xml_api_terms .= ' '. check_plain($api_term->term_name) ." \n";
}
if (!empty($xml_api_terms)) {
$xml .= " \n". $xml_api_terms ." \n";
}
}
$xml .= " \n";
}
project_release_history_write_xml($xml);
}
// ------------------------------------------------------------
// Functions: utility methods
// ------------------------------------------------------------
/**
* Wrapper function for watchdog() to log notice messages.
*/
function wd_msg($msg, $link = NULL) {
watchdog('release_history', $msg, WATCHDOG_NOTICE, $link);
}
/**
* Wrapper function for watchdog() to log error messages.
*/
function wd_err($msg, $link = NULL) {
watchdog('release_hist_err', $msg, WATCHDOG_ERROR, $link);
}
/**
* Rename on Windows isn't atomic like it is on *nix systems.
* See http://www.php.net/rename about this bug.
*/
function _rename($oldfile, $newfile) {
if (substr(PHP_OS, 0, 3) == 'WIN') {
if (copy($oldfile, $newfile)) {
unlink($oldfile);
return TRUE;
}
return FALSE;
}
else {
return rename($oldfile, $newfile);
}
}
/**
* Sorting function to ensure releases are in the right order in the XML file.
*
* Loop over the fields in the release node we care about, and the first field
* that differs between the two releases determines the order.
*
* We first check the 'weight' (of the API version term) for when we're
* building a single list of all versions, not a per-API version listing. In
* this case, lower numbers should float to the top.
*
* We also need to special-case the 'rebuild' field, which is how we know if
* it's a dev snapshot or official release. Rebuild == 1 should always come
* last within a given major version, since that's how update_status expects
* the ordering to ensure that we never recommend a -dev release if there's an
* official release available. So, like weight, the lower number for 'rebuild'
* should float to the top.
*
* For every other field, we want the bigger numbers come first.
*
* @see project_release_history_generate_project_xml()
* @see usort()
*/
function _release_sort($a, $b) {
// This array maps fields in the release node to the sort order, where -1
// means to sort like Drupal weights, and +1 means the bigger numbers are
// higher in the listing.
$fields = array(
'weight' => -1,
'version_major' => 1,
'rebuild' => -1,
'version_minor' => 1,
'version_patch' => 1,
'file_date' => 1,
);
foreach ($fields as $field => $sign) {
if (!isset($a->$field) && !isset($b->$field)) {
continue;
}
if ($a->$field == $b->$field) {
continue;
}
return ($a->$field < $b->$field) ? $sign : (-1 * $sign);
}
}