| [ Index ] |
PHP Cross Reference of Drupal 6 (gatewave) |
[Summary view] [Print] [Text view]
1 <?php 2 // $Id: project_release.module,v 1.152 2010/01/30 02:43:24 dww Exp $ 3 4 define('PROJECT_RELEASE_DEFAULT_VERSION_FORMAT', '!major%minor%patch#extra'); 5 define('PROJECT_RELEASE_FILE_EXTENSIONS', 'zip gz tar bz2 rar tgz tar.gz dmg rpm deb'); 6 7 /** 8 * Constants for the possible values of {project_release_nodes}.update_status. 9 */ 10 define('PROJECT_RELEASE_UPDATE_STATUS_CURRENT', 0); 11 define('PROJECT_RELEASE_UPDATE_STATUS_NOT_CURRENT', 1); 12 define('PROJECT_RELEASE_UPDATE_STATUS_NOT_SECURE', 2); 13 14 /** 15 * @defgroup project_release_core Core Drupal hooks 16 */ 17 18 /** 19 * Implementation of hook_init(). 20 */ 21 function project_release_init() { 22 drupal_add_css(drupal_get_path('module', 'project_release') .'/project_release.css'); 23 project_release_get_api_taxonomy(); 24 25 // These constants are defined here since they use t() and the 26 // global $locale variable needs to be initialized before calling 27 // t() or you suffer a big performance hit. 28 define('PROJECT_RELEASE_VERSION_FORMAT_VALID_MSG', t('The version format string can only contain letters, numbers, and the characters . _ and - (in addition to the special characters used for identifying variables: % ! and #).')); 29 define('PROJECT_RELEASE_VERSION_FORMAT_HELP', t("Available variables are: %api, %major, %minor, %patch, %extra. The percent sign ('%') at the front of the variable name indicates that a period ('.') should be inserted as a delimiter before the value of the variable. The '%' can be replaced with a hash mark ('#') to use a hyphen ('-') delimiter, or with an exclaimation point ('!') to have the value printed without a delimiter. Any variable in the format string that has no value will be removed entirely from the final string.") .' '. PROJECT_RELEASE_VERSION_FORMAT_VALID_MSG); 30 } 31 32 /** 33 * Implementation of hook_menu() 34 * @ingroup project_release_core 35 */ 36 function project_release_menu() { 37 $items = array(); 38 39 $items['node/%project_node/edit/releases'] = array( 40 'title' => 'Releases', 41 'page callback' => 'project_release_project_edit_releases', 42 'page arguments' => array(1), 43 'access callback' => 'node_access', 44 'access arguments' => array('update', 1), 45 'type' => MENU_LOCAL_TASK, 46 'file' => 'includes/project_edit_releases.inc', 47 ); 48 49 $items['node/add/project-release/%'] = array( 50 'page callback' => 'node_add', 51 'page arguments' => array('project-release'), 52 'access callback' => 'node_access', 53 'access arguments' => array('create', 'project_release'), 54 'file' => 'node.pages.inc', 55 'file path' => drupal_get_path('module', 'node'), 56 'type' => MENU_CALLBACK, 57 ); 58 59 $items['admin/project/project-release-settings'] = array( 60 'description' => 'Configure the default version string for releases and other settings for the Project release module.', 61 'title' => 'Project release settings', 62 'page callback' => 'drupal_get_form', 63 'page arguments' => array('project_release_settings_form'), 64 'access arguments' => array('administer projects'), 65 'weight' => 1, 66 'type' => MENU_NORMAL_ITEM, 67 'file' => 'includes/admin.settings.inc', 68 ); 69 70 // Redirect node/add/project_release/* to node/add/project-release. 71 $items['node/add/project_release'] = array( 72 'page callback' => 'project_release_add_redirect_page', 73 'access callback' => 'node_access', 74 'access arguments' => array('create', 'project_release'), 75 'type' => MENU_CALLBACK, 76 'file' => 'includes/release_node_form.inc', 77 ); 78 79 return $items; 80 } 81 82 /** 83 * Implementation of hook_menu_alter(). 84 */ 85 function project_release_menu_alter(&$callbacks) { 86 $callbacks['node/add/project-release']['page callback'] = 'drupal_get_form'; 87 $callbacks['node/add/project-release']['page arguments'] = array('project_release_pick_project_form'); 88 $callbacks['node/add/project-release']['file'] = 'release_node_form.inc'; 89 $callbacks['node/add/project-release']['file path'] = drupal_get_path('module', 'project_release') . '/includes'; 90 } 91 92 /** 93 * @defgroup project_release_node Drupal node-type related hooks 94 */ 95 96 /** 97 * Implementation of hook_access(). 98 * @ingroup project_release_node 99 * 100 * TODO: Maybe we should add new permissions for accessing release 101 * nodes, but for now, we're just using the existing project perms. 102 */ 103 function project_release_access($op, $node, $account) { 104 switch ($op) { 105 case 'view': 106 // We want to use the identical logic for viewing projects, 107 // so we call that method directly. 108 return project_project_access($op, $node, $account); 109 case 'create': 110 // Due to how node_menu() works, we have to allow anyone with 111 // permission to maintain a project to be able to create a 112 // release node, or else you can have a faulty entry added to 113 // the {cache_menu} table that thinks you're not allowed to 114 // create *any* releases. So, we are more relaxed here, and 115 // enforce more closely in project_release_form(). As with the 116 // 'view' case above, we want the identical logic as project 117 // nodes, so we call that hook, instead of duplicating code. 118 return project_project_access($op, $node, $account); 119 case 'update': 120 // We can't just use project_project_access() here, since we 121 // need to check access to the project itself, not the release 122 // node, so we use the helper method and pass the project id. 123 return project_check_admin_access($node->project_release['pid']); 124 case 'delete': 125 // No one should ever delete a release node, only unpublish it. 126 return FALSE; 127 } 128 } 129 130 /** 131 * Implementation of hook_node_info(). 132 * @ingroup project_release_node 133 */ 134 function project_release_node_info() { 135 return array( 136 'project_release' => array( 137 'name' => t('Project release'), 138 'module' => 'project_release', 139 'description' => t('A release of a project with a specific version number.'), 140 ), 141 ); 142 } 143 144 /** 145 * Implement of hook_form() for project_release nodes. 146 */ 147 function project_release_form(&$release, &$form_state) { 148 module_load_include('inc', 'project_release', 'includes/release_node_form'); 149 return _project_release_form($release, $form_state); 150 } 151 152 /** 153 * Validation callback for project release node forms. 154 * 155 * @see _project_release_node_form_validate() 156 */ 157 function project_release_node_form_validate(&$form, &$form_state) { 158 module_load_include('inc', 'project_release', 'includes/release_node_form'); 159 return _project_release_node_form_validate($form, $form_state); 160 } 161 162 163 /** 164 * Implementation of hook_load(). 165 * @ingroup project_release_node 166 */ 167 function project_release_load($node) { 168 $additions = db_fetch_array(db_query("SELECT * FROM {project_release_nodes} WHERE nid = %d", $node->nid)); 169 // Add in file info. 170 $file_info = db_query("SELECT f.*, prf.filehash FROM {project_release_file} prf INNER JOIN {files} f ON prf.fid = f.fid WHERE prf.nid = %d", $node->nid); 171 $files = array(); 172 while ($file = db_fetch_object($file_info)) { 173 $files[$file->fid] = $file; 174 } 175 $additions['files'] = $files; 176 177 $release = new stdClass; 178 $release->project_release = $additions; 179 return $release; 180 } 181 182 /** 183 * Implementation of hook_insert(). 184 * 185 * @param $node 186 * Object containing form values from the project_release node form. Even 187 * though this is NOT a fully loaded $node object, the release-related 188 * values are in the $node->project_release array due to manual #tree and 189 * #parents hacking in project_release_form(). 190 * 191 * @ingroup project_release_node 192 */ 193 function project_release_insert($node) { 194 module_load_include('inc', 'project_release', 'includes/release_node_form'); 195 project_release_db_save($node, true); 196 } 197 198 /** 199 * Implementation of hook_update(). 200 * 201 * @param $node 202 * Object containing form values from the project_release node form. Even 203 * though this is NOT a fully loaded $node object, the release-related 204 * values are in the $node->project_release array due to manual #tree and 205 * #parents hacking in project_release_form(). 206 * 207 * @ingroup project_release_node 208 */ 209 function project_release_update($node) { 210 module_load_include('inc', 'project_release', 'includes/release_node_form'); 211 project_release_db_save($node, false); 212 } 213 214 /** 215 * Verifies the data for supported release versions, and updates if necessary. 216 * 217 * @param $pid 218 * The project ID. 219 * @param $tid 220 * The API compatibility term ID. 221 * @param $major 222 * The major version of the new/modified/deleted release. 223 * @param $delete 224 * Boolean to indicate if we're deleting a release of this major or not. 225 * 226 * @return 227 * TRUE if we updated a record in {project_release_supported_versions}, 228 * otherwise FALSE (e.g. if there were no published releases on the 229 * requested branch). 230 */ 231 function project_release_check_supported_versions($pid, $tid, $major, $delete) { 232 // Remember if we updated {project_release_supported_versions} so we can 233 // return the value to our caller. 234 $did_update = FALSE; 235 236 // If we're being called as a release node is being edited and saved, and 237 // the site we're running on is using DB replication, we need to make sure 238 // we're talking to the primary DB so that all of this works. 239 if (function_exists('db_set_ignore_slave')) { 240 db_set_ignore_slave(); 241 } 242 243 // Regardless of if we're deleting, adding, or editing, we need to know the 244 // latest and recommended releases (if any) from the given branch. If 245 // there's no published release, these values will be 0. 246 list($latest_release, $recommended_release, $latest_security_release) = project_release_find_latest_releases($pid, $tid, $major); 247 248 if ($delete) { 249 // Make sure this isn't the last release node for the given major. 250 if (!empty($latest_release)) { 251 // Since the node we just deleted might have been the latest or 252 // recommended on the branch, update our record with the real values. 253 db_query("UPDATE {project_release_supported_versions} SET recommended_release = %d, latest_release = %d, latest_security_release = %d WHERE nid = %d AND tid = %d AND major = %d", $recommended_release, $latest_release, $latest_security_release, $pid, $tid, $major); 254 $did_update = TRUE; 255 } 256 else { 257 // No latest release -- remove the bogus record for this branch. 258 db_query("DELETE FROM {project_release_supported_versions} WHERE nid = %d AND tid = %d AND major = %d", $pid, $tid, $major); 259 260 $num_recommended = db_result(db_query("SELECT COUNT(*) FROM {project_release_supported_versions} WHERE nid = %d AND tid = %d AND supported = %d AND recommended = %d", $pid, $tid, 1, 1)); 261 if ($num_recommended > 1) { 262 // Something seriously bogus, clear out the values and start over. 263 db_query("UPDATE {project_release_supported_versions} SET recommended = %d WHERE nid = %d AND tid = %d", 0, $pid, $tid); 264 $num_recommended = 0; 265 } 266 } 267 } 268 else { 269 // Adding or editing a release. 270 if (!empty($latest_release)) { 271 // We have at least 1 published release, so make sure we have an entry 272 // for this major version in {project_release_supported_versions}. 273 $current_branches = db_query("SELECT major FROM {project_release_supported_versions} WHERE nid = %d AND tid = %d", $pid, $tid); 274 $have_current_branch = FALSE; 275 $num_branches = 0; 276 while (($branch = db_fetch_object($current_branches)) !== FALSE) { 277 $num_branches++; 278 if ($branch->major == $major) { 279 $have_current_branch = TRUE; 280 break; 281 } 282 } 283 if ($num_branches == 0 || !$have_current_branch) { 284 // First entry for this API tid/major version pair, so add a new 285 // record to the table as supported but not recommended. 286 db_query("INSERT INTO {project_release_supported_versions} (nid, tid, major, supported, recommended, snapshot, recommended_release, latest_release, latest_security_release) VALUES (%d, %d, %d, %d, %d, %d, %d, %d, %d)", $pid, $tid, $major, 1, 0, 0, $recommended_release, $latest_release, $latest_security_release); 287 } 288 else { 289 // We already have this branch in the table, but the latest_release 290 // and recommended_release fields might be stale based on whatever 291 // node was just added or edited. 292 db_query("UPDATE {project_release_supported_versions} SET recommended_release = %d, latest_release = %d, latest_security_release = %d WHERE nid = %d AND tid = %d AND major = %d", $recommended_release, $latest_release, $latest_security_release, $pid, $tid, $major); 293 } 294 $did_update = TRUE; 295 } 296 } 297 298 // Regardless of insert/edit/delete, we want to go through and recompute 299 // {project_release_nodes}.update_status for all records on this branch. 300 // Note: we end up doing the same query in here that we performed in 301 // project_release_find_latest_releases(), we just need to process the 302 // results differently. However, to keep the code sane, we invoke the query 303 // again. If this becomes a performance problem, we can always refactor. 304 project_release_compute_update_status($pid, $tid, $major); 305 306 // Either way, clear the cache for the release table, since what we want to 307 // display might have changed, too. 308 $cid = 'table:'. $pid .':'; 309 cache_clear_all($cid, 'cache_project_release', TRUE); 310 311 return $did_update; 312 } 313 314 /** 315 * Compute the {project_release_nodes}.update_status values for a given branch. 316 * 317 * For any given release node, there are three possible status values for if 318 * if the release needs an update or not: 319 * - 'current' (PROJECT_RELEASE_UPDATE_STATUS_CURRENT): It's the currently 320 * recommended release (without extra), or the latest possible release 321 * (including betas, rcs, etc). There is no need to upgrade this release at 322 * this time, it's the most up-to-date available. 323 * - 'not-current' (PROJECT_RELEASE_UPDATE_STATUS_NOT_CURRENT): Any release 324 * older than the recommended release, or any older release with extra from 325 * the same major/minor/patch as the latest release. 326 * - 'not-secure' (PROJECT_RELEASE_UPDATE_STATUS_NOT_SECURE): Any release 327 * older than the latest security update on this branch is considered not 328 * secure. Releases are only marked 'not-secure' on sites that define the 329 * 'project_release_security_update_tid' variable. 330 * 331 * For example, if 1.2.2 is the recommended release, 1.2.1 was a security 332 * update, and 1.2.2-beta2 is the latest release, here would be the following 333 * update status values for various releases: 334 * - 1.2.2-beta2: 'current' (since it's the latest release) 335 * - 1.2.2-beta1: 'not-current' (since beta2 is available) 336 * - 1.2.2: 'current' (recommended release, latest without "extra") 337 * - 1.2.2-rc1: 'not-current' (since 1.2.2 official is out) 338 * - 1.2.1: 'not-current' 339 * - 1.2.1-beta1: 'not-secure' (since 1.2.1 official was a security update) 340 * - 1.2.0: 'not-secure' 341 * 342 * This status is recorded in the {project_release_nodes}.update_status column 343 * in the database. Whenever a release is created, updated, or deleted, we 344 * need to inspect all the other releases on the same branch to potentially 345 * modify the update_status column as needed. 346 * 347 * This function walks through all the records in the {project_release_nodes} 348 * table matching the given branch (API compatibility term ID and major 349 * version) for a specified project in version order (as determined by 350 * project_release_query_releases_by_branch() which sorts by version_minor, 351 * version_patch, version_extra_weight and finally version_extra), and 352 * compares them with that branch's latest release, recommended release, and 353 * latest security release to compute their update status. If the release is 354 * the latest or recommended, it's 'current'. Otherwise, it's 'not-current' 355 * if we haven't passed a security update yet, or 'not-secure' once we find a 356 * security update. 357 * 358 * @param $pid 359 * The project ID. 360 * @param $api_tid 361 * The API compatibility term ID. 362 * @param $major 363 * The major version of the new/modified/deleted release. 364 * 365 * @return 366 * Void. This function directly updates the {project_release_nodes} table 367 * with the appropriate values. 368 * 369 * @see project_release_check_supported_versions() 370 * @see project_release_query_releases_by_branch() 371 * @see project_release_release_nodeapi() 372 */ 373 function project_release_compute_update_status($pid, $api_tid, $major) { 374 $latest_release = $recommended_release = $latest_security_release = 0; 375 $nid_update_map = array(); 376 $query = project_release_query_releases_by_branch($pid, $api_tid, $major); 377 while ($release = db_fetch_object($query)) { 378 // Clear out the status so we always start fresh with each release. 379 unset($update_status); 380 if (empty($latest_release)) { 381 $latest_release = $release->nid; 382 // If this is the latest release, it's current. 383 $update_status = PROJECT_RELEASE_UPDATE_STATUS_CURRENT; 384 } 385 if (empty($recommended_release) && empty($release->version_extra)) { 386 $recommended_release = $release->nid; 387 // If this is the recommended release, it's current. 388 $update_status = PROJECT_RELEASE_UPDATE_STATUS_CURRENT; 389 } 390 if (empty($latest_security_release) && !empty($release->security_update)) { 391 $latest_security_release = $release->nid; 392 } 393 394 // Based on what we've already seen, figure out the status. The only 395 // possible releases that can be "CURRENT" are the latest and recommended 396 // releases, and we already set the status for those. So, if we're here, 397 // we know it's not current, we just need to know if it's also not secure. 398 if (!isset($update_status)) { 399 // If we haven't found a security release yet, or the release we're on 400 // is the latest security update, this is just 'not_current'. 401 if (empty($latest_security_release) || $latest_security_release == $release->nid) { 402 $update_status = PROJECT_RELEASE_UPDATE_STATUS_NOT_CURRENT; 403 } 404 // Otherwise, we're past the latest security release, this is insecure. 405 else { 406 $update_status = PROJECT_RELEASE_UPDATE_STATUS_NOT_SECURE; 407 } 408 } 409 410 // If the status is different than what we have in the DB, remember that 411 // we need to update this nid in the DB. 412 if ($update_status != $release->update_status) { 413 $nid_update_map[$update_status][] = $release->nid; 414 } 415 } 416 417 if (!empty($nid_update_map)) { 418 foreach ($nid_update_map as $update_status => $nids) { 419 if (!empty($nids)) { 420 $placeholders = db_placeholders($nids); 421 db_query("UPDATE {project_release_nodes} SET update_status = %d WHERE nid IN ($placeholders)", array_merge(array($update_status), $nids)); 422 if ($update_status == PROJECT_RELEASE_UPDATE_STATUS_NOT_SECURE && module_exists('project_package')) { 423 project_package_check_update_status($nids); 424 } 425 } 426 } 427 } 428 } 429 430 /** 431 * Implementation of hook_delete(). 432 * @ingroup project_release_node 433 */ 434 function project_release_delete($node) { 435 if (!empty($node->project_release['files'])) { 436 foreach ($node->project_release['files'] as $fid => $file) { 437 project_release_file_delete($file); 438 } 439 } 440 db_query("DELETE FROM {project_release_package_errors} WHERE nid = %d", $node->nid); 441 db_query("DELETE FROM {project_release_nodes} WHERE nid = %d", $node->nid); 442 } 443 444 /** 445 * Deletes release files. 446 * 447 * @param $file 448 * The file object to delete. 449 */ 450 function project_release_file_delete($file) { 451 db_query("DELETE FROM {files} WHERE fid = %d", $file->fid); 452 db_query("DELETE FROM {project_release_file} WHERE fid = %d", $file->fid); 453 file_delete(file_create_path($file->filepath)); 454 } 455 456 /** 457 * @defgroup project_release_api Project release functions that other 458 * modules might want to use 459 */ 460 461 /** 462 * Returns the version format string for a given project 463 * @ingroup project_release_api 464 */ 465 function project_release_get_version_format($project) { 466 if (!empty($project->project_release['version_format'])) { 467 return $project->project_release['version_format']; 468 } 469 470 $db_format = db_result(db_query("SELECT version_format FROM {project_release_projects} WHERE nid = %d", $project->nid)); 471 if (!empty($db_format)) { 472 return $db_format; 473 } 474 475 return variable_get('project_release_default_version_format', PROJECT_RELEASE_DEFAULT_VERSION_FORMAT); 476 } 477 478 /** 479 * Validates a version format string. Only alphanumeric characters and 480 * [-_.] are allowed. Calls form_set_error() on error, else returns. 481 * @param $form_values Array of form values passed to validate hook. 482 * @param $element The name of the form element for the format string. 483 * @ingroup project_release_internal 484 */ 485 function _project_release_validate_format_string($form_values, $element) { 486 if (!preg_match('/^[a-zA-Z0-9_\-.!%#]+$/', $form_values[$element])) { 487 form_set_error($element, PROJECT_RELEASE_VERSION_FORMAT_VALID_MSG); 488 } 489 } 490 491 /** 492 * Returns the formatted version string for a given version object. 493 * 494 * @param $version 495 * Object containing the separate version-related fields, such as 496 * version_major, version_minor, etc. 497 * @param $project 498 * Optional project node that the version corresponds with. If not defined, 499 * the version object should at least include a "pid" field. 500 * 501 * @return 502 * The formatted version string for the given version and project info. 503 * 504 * @ingroup project_release_api 505 */ 506 function project_release_get_version($version, $project = NULL) { 507 if (isset($project)) { 508 $node = $project; 509 } 510 else { 511 $node->nid = $version->pid; 512 } 513 $variables = array(); 514 foreach (array('major', 'minor', 'patch', 'extra') as $field) { 515 $var = "version_$field"; 516 if (isset($version->$var) && $version->$var !== '') { 517 $variables["!$field"] = $version->$var; 518 $variables["%$field"] = '.'. $version->$var; 519 $variables["#$field"] = '-'. $version->$var; 520 } 521 else { 522 $variables["!$field"] = ''; 523 $variables["%$field"] = ''; 524 $variables["#$field"] = ''; 525 } 526 } 527 $vid = _project_release_get_api_vid(); 528 if (project_release_get_api_taxonomy() && isset($version->version_api_tid)) { 529 $term = taxonomy_get_term($version->version_api_tid); 530 $variables["!api"] = $term->name; 531 $variables["%api"] = '.'. $term->name; 532 $variables["#api"] = '-'. $term->name; 533 } 534 else { 535 $variables["!api"] = ''; 536 $variables["%api"] = ''; 537 $variables["#api"] = ''; 538 } 539 $version_format = project_release_get_version_format($node); 540 return strtr($version_format, $variables); 541 } 542 543 /** 544 * Implementation of hook_view(). 545 * @ingroup project_release_node 546 */ 547 function project_release_view($node, $teaser = FALSE, $page = FALSE) { 548 $node = node_prepare($node, $teaser); 549 $project = node_load($node->project_release['pid']); 550 551 if ($page) { 552 // Breadcrumb navigation 553 $breadcrumb[] = l($project->title, 'node/'. $project->nid); 554 $breadcrumb[] = l(t('Releases'), 'node/'. $project->nid .'/release'); 555 project_project_set_breadcrumb($project, $breadcrumb); 556 } 557 558 $output = ''; 559 $max_file_timestamp = 0; 560 if (!empty($node->project_release['files'])) { 561 $view_info = variable_get('project_release_files_view', 'project_release_files:default'); 562 list($view_name, $display_name) = split(':', $view_info); 563 $output .= views_embed_view($view_name, $display_name, $node->nid); 564 foreach ($node->project_release['files'] as $file) { 565 $max_file_timestamp = max($max_file_timestamp, $file->timestamp); 566 } 567 $node->content['release_file_info'] = array( 568 '#value' => '<div class="project-release-files">'. $output .'</div>', 569 '#weight' => -4, 570 ); 571 } 572 573 $output = ''; 574 if (project_use_cvs($project) && isset($node->project_release['tag'])) { 575 if (!empty($node->project_release['rebuild'])) { 576 $output .= t('Nightly development snapshot from CVS branch: @tag', array('@tag' => $node->project_release['tag'])) .'<br />'; 577 } 578 else { 579 $output .= t('Official release from CVS tag: @tag', array('@tag' => $node->project_release['tag'])) .'<br />'; 580 } 581 } 582 583 if (!empty($max_file_timestamp)) { 584 $output .= '<div class="last-updated">' . t('Last updated: !changed', array('!changed' => format_date($max_file_timestamp))) . '</div>'; 585 } 586 587 if (module_exists('project_usage') && user_access('view project usage')) { 588 $output .= '<div class="usage-statistics-link">'. l(t('View usage statistics for this release'), 'project/usage/'. $node->nid) .'</div>'; 589 } 590 $node->content['release_info'] = array( 591 '#value' => '<div class="project-release-info">'. $output .'</div>', 592 '#weight' => -3, 593 ); 594 595 if (module_exists('project_package')) { 596 $output = ''; 597 if (!empty($node->project_package['count'])) { 598 $view_info = variable_get('project_package_release_items_view', 'project_package_items:default'); 599 list($view_name, $display_name) = split(':', $view_info); 600 $output .= '<h3>' . t('In this package') . '</h3>'; 601 $output .= views_embed_view($view_name, $display_name, $node->nid); 602 } 603 if (!empty($output)) { 604 $node->content['release_package_items'] = array( 605 '#value' => '<div class="project-release-package-items">'. $output .'</div>', 606 '#weight' => -2, 607 ); 608 } 609 } 610 611 // Display packaging errors to admins. 612 if (project_check_admin_access($node->project_release['pid'])) { 613 $rows = array(); 614 $result = db_query('SELECT * FROM {project_release_package_errors} WHERE nid = %d', $node->nid); 615 $error = db_fetch_object($result); 616 if (!empty($error)) { 617 $rows = unserialize($error->messages); 618 if (!empty($rows)) { 619 $node->content['release_errors'] = array( 620 '#value' => theme('item_list', $rows, t('Packaging error messages')), 621 '#weight' => -1, 622 '#prefix' => '<div class="messages error">', 623 '#suffix' => '</div>', 624 ); 625 } 626 } 627 } 628 629 return $node; 630 } 631 632 function project_release_load_file($fid) { 633 return db_fetch_object(db_query("SELECT f.*, prf.filehash FROM {project_release_file} prf INNER JOIN {files} f ON prf.fid = f.fid WHERE f.fid = %d", $fid)); 634 } 635 636 function theme_project_release_download_file($file, $download_link = TRUE) { 637 $output = ''; 638 if ($download_link) { 639 $output .= '<small>'. t('Download: !file', array('!file' => theme('project_release_download_link', $file->filepath))) .'</small><br />'; 640 } 641 else { 642 $output .= '<small>'. t('File: @filepath', array('@filepath' => $file->filepath)) .'</small><br />'; 643 } 644 $output .= '<small>'. t('Size: !size', array('!size' => format_size($file->filesize))) .'</small><br />'; 645 $output .= '<small>'. t('md5_file hash: !filehash', array('!filehash' => $file->filehash)) .'</small><br />'; 646 $output .= '<small>'. t('Last updated: !changed', array('!changed' => format_date($file->timestamp))) .'</small><br />'; 647 return $output; 648 } 649 650 /* 651 @TODO: This function is used by project_issue, so we need to keep it here, 652 even though we're now creating the list of releases at node/XXX/release using 653 the views module. however, it might be nice if we could replace this function 654 with views as well just to use views's query builder. Maybe that's a bad 655 idea in terms of performance, however. 656 */ 657 /** 658 * Get an array of release nodes 659 * @ingroup project_release_api 660 * 661 * @param $project 662 * The project node object. 663 * @param $nodes 664 * If set, an array of release nodes will be returned. 665 * Otherwise only the version field will be returned in the array value. 666 * @param $sort_by 667 * This can be 'date' or 'version' and determines how the releases 668 * returned are to be sorted. 669 * @param $filter_by 670 * This can be 'all' to include all releases or 'files' to return 671 * only releases which have a file attached. 672 * @param $rids 673 * This is a special parameter that can be used to allow one or more 674 * releases to be returned even if the node itself is unpublished. 675 * This is useful when this function is called by the project_issue 676 * module to allow a user to keep the version of an issue unchanged 677 * even if the release represented by the version is now unpublished. 678 * @return 679 * An array of releases. The keys are the release node nids. The values 680 * will either be release objects or release version strings, depending 681 * on the value of the $nodes parameter. 682 */ 683 function project_release_get_releases($project, $nodes = TRUE, $sort_by = 'version', $filter_by = 'all', $rids = array()) { 684 if ($sort_by == 'date') { 685 $order_by = 'n.created'; 686 } 687 else { 688 $order_by = 'r.version'; 689 } 690 $where = ''; 691 $join = ''; 692 $args = array($project->nid); 693 if (!project_check_admin_access($project)) { 694 if (!empty($rids)) { 695 $where = "AND (n.status = %d OR n.nid IN (". db_placeholders($rids) ."))"; 696 $args[] = 1; 697 foreach ($rids as $rid) { 698 $args[] = $rid; 699 } 700 } 701 else { 702 $where = 'AND (n.status = %d)'; 703 $args[] = 1; 704 } 705 if ($filter_by == 'files') { 706 $join .= "INNER JOIN {project_release_file} prf ON n.nid = prf.nid"; 707 } 708 } 709 710 $result = db_query(db_rewrite_sql("SELECT n.nid, r.* FROM {node} n INNER JOIN {project_release_nodes} r $join ON r.nid = n.nid WHERE (r.pid = %d) $where ORDER BY $order_by DESC"), $args); 711 $releases = array(); 712 while ($obj = db_fetch_object($result)) { 713 if ($nodes) { 714 $releases[$obj->nid] = node_load($obj->nid); 715 } 716 else { 717 $releases[$obj->nid] = $obj->version; 718 } 719 } 720 return $releases; 721 } 722 723 724 /** 725 * @defgroup project_release_callback Menu callback functions 726 */ 727 728 /** 729 * Returns a listing of all active project release compatibility terms 730 * in the system. 731 * @ingroup project_release_api 732 */ 733 function project_release_compatibility_list() { 734 static $terms = array(); 735 if (empty($terms) && $tree = project_release_get_api_taxonomy()) { 736 $tids = variable_get('project_release_active_compatibility_tids', array()); 737 foreach ($tree as $term) { 738 if (($tids && !empty($tids[$term->tid])) || !$tids) { 739 $terms[$term->tid] = $term->name; 740 } 741 } 742 } 743 return $terms; 744 } 745 746 /** 747 * @defgroup project_release_fapi Form API hooks 748 */ 749 750 /** 751 * Implementation of hook_form_alter(). 752 * @ingroup project_release_fapi 753 */ 754 function project_release_form_alter(&$form, &$form_state, $form_id) { 755 if ($form_id == 'project_project_node_form') { 756 return project_release_alter_project_form($form, $form_state); 757 } 758 if ($form_id == 'project_release_node_form') { 759 return project_release_alter_release_form($form, $form_state); 760 } 761 } 762 763 /** 764 * Alters the project_project node form to add release settings. 765 * @ingroup project_release_fapi 766 * @see project_release_form_alter 767 */ 768 function project_release_alter_project_form(&$form) { 769 if (!empty($form['project_node']['project']['uri']['#description'])) { 770 $form['project_node']['project']['uri']['#description'] .= ' ' . t('This string is also used to generate the name of releases associated with this project.'); 771 } 772 else { 773 $form['project_node']['project']['uri']['#description'] = t('This string is used to generate the name of releases associated with this project.'); 774 } 775 } 776 777 /** 778 * Alters the project_release node form to handle the API taxonomy. 779 * If the vocabulary is empty, this removes the form elements. 780 * @ingroup project_release_fapi 781 * @see project_release_form_alter 782 */ 783 function project_release_alter_release_form(&$form, &$form_state) { 784 global $user; 785 $node = $form['#node']; 786 $tid = ''; 787 if (!empty($node->project_release['version_api_tid'])) { 788 $tid = $node->project_release['version_api_tid']; 789 } 790 $vid = _project_release_get_api_vid(); 791 if (!project_release_get_api_taxonomy() && isset($form['taxonomy'][$vid])) { 792 unset($form['taxonomy'][$vid]); 793 } 794 else { 795 if (!user_access('administer projects')) { 796 // The user doesn't have 'administer projects' permission, so 797 // we restrict their options for the compatibility taxonomy. 798 if (!empty($tid)) { 799 // If we already have the term, we want to force it to stay. 800 $indexes = form_get_options($form['taxonomy'][$vid], $tid); 801 if ($indexes !== FALSE) { 802 foreach ($indexes as $index) { 803 $options[] = $form['taxonomy'][$vid]['#options'][$index]; 804 } 805 } 806 $form['taxonomy'][$vid]['#default_value'] = $tid; 807 } 808 elseif ($tids = variable_get('project_release_active_compatibility_tids', array())) { 809 // We don't have the term since we're adding a new release. 810 // Restrict to the active terms (if any). 811 foreach (array_filter($tids) as $tid) { 812 $indexes = form_get_options($form['taxonomy'][$vid], $tid); 813 814 if ($indexes !== FALSE) { 815 foreach ($indexes as $index) { 816 $options[$index] = $form['taxonomy'][$vid]['#options'][$index]; 817 } 818 } 819 } 820 } 821 if (!empty($options)) { 822 $form['taxonomy'][$vid]['#options'] = $options; 823 } 824 else { 825 unset($form['taxonomy'][$vid]); 826 } 827 // If they're not project admins, remove the delete button (if any). 828 unset($form['delete']); 829 } 830 } 831 // If there are no children elements, we should unset the entire 832 // thing so we don't end up with an empty fieldset. 833 if (isset($form['taxonomy']) && !element_children($form['taxonomy'])) { 834 unset($form['taxonomy']); 835 } 836 837 $form['buttons']['submit']['#submit'][] = 'project_release_node_submit'; 838 } 839 840 841 /** 842 * @defgroup project_release_nodeapi Node API hooks 843 */ 844 845 /** 846 * hook_nodeapi() implementation. This just decides what type of node 847 * is being passed, and calls the appropriate type-specific hook. 848 * @ingroup project_release_nodeapi 849 * @see project_release_project_nodeapi(). 850 */ 851 function project_release_nodeapi(&$node, $op, $arg) { 852 switch ($node->type) { 853 case 'project_project': 854 project_release_project_nodeapi($node, $op, $arg); 855 break; 856 case 'project_release': 857 project_release_release_nodeapi($node, $op, $arg); 858 break; 859 } 860 } 861 862 /** 863 * hook_nodeapi implementation specific to "project_project" nodes 864 * (from the project.module) 865 * @ingroup project_release_nodeapi 866 * @see project_release_nodeapi(). 867 */ 868 function project_release_project_nodeapi(&$node, $op, $arg) { 869 switch ($op) { 870 case 'load': 871 project_release_project_nodeapi_load($node); 872 break; 873 874 case 'insert': 875 project_release_project_nodeapi_insert($node); 876 break; 877 878 case 'delete': 879 project_release_project_nodeapi_delete($node); 880 } 881 } 882 883 /** 884 * Loads project_release fields into the project node object. 885 */ 886 function project_release_project_nodeapi_load(&$node) { 887 $project = db_fetch_object(db_query('SELECT * FROM {project_release_projects} WHERE nid = %d', $node->nid)); 888 if (!empty($project)) { 889 $fields = array('releases', 'version_format'); 890 foreach ($fields as $field) { 891 $node->project_release[$field] = $project->$field; 892 } 893 $wants_snapshots = db_result(db_query('SELECT tid FROM {project_release_supported_versions} WHERE nid = %d AND snapshot = %d LIMIT %d', $node->nid, 1, 1)); 894 if (isset($wants_snapshots)) { 895 $node->project_release['project_release_show_snapshots'] = TRUE; 896 } 897 } 898 } 899 900 /** 901 * Insert release information about a project node. 902 */ 903 function project_release_project_nodeapi_insert(&$node) { 904 db_query("INSERT INTO {project_release_projects} (nid, releases, version_format) VALUES (%d, %d, '%s')", $node->nid, 1, ''); 905 } 906 907 /** 908 * Deletes release information when a project is deleted. 909 */ 910 function project_release_project_nodeapi_delete(&$node) { 911 // TODO: unpublish (delete?) all release nodes associated with 912 // this project, too. 913 db_query('DELETE FROM {project_release_projects} WHERE nid = %d', $node->nid); 914 915 } 916 917 /** 918 * hook_nodeapi implementation specific to "project_release" nodes. 919 * 920 * We use hook_nodeapi() for our own node type to trigger some code that has 921 * to happen after taxonomy_nodeapi() runs. project_release already has to be 922 * weighted heavier than taxonomy for other things to work. 923 * 924 * @ingroup project_release_nodeapi 925 * @see project_release_nodeapi(). 926 */ 927 function project_release_release_nodeapi(&$node, $op, $arg) { 928 switch ($op) { 929 case 'insert': 930 case 'update': 931 case 'delete': 932 // Since release nodes can be unpublished, we need to make sure that the 933 // recommended branch information is still up to date. 934 if (module_exists('taxonomy')) { 935 if (isset($node->project_release['version_api_tid'])) { 936 $tid = $node->project_release['version_api_tid']; 937 } 938 else { 939 $vid = _project_release_get_api_vid(); 940 if (isset($node->taxonomy[$vid])) { 941 $tid = $node->taxonomy[$vid]; 942 } 943 } 944 if (isset($tid)) { 945 project_release_check_supported_versions($node->project_release['pid'], $tid, $node->project_release['version_major'], ($op == 'delete' ? TRUE : FALSE)); 946 } 947 } 948 break; 949 950 case 'rss item': 951 // Prepend the table of release info whenever a release is in a feed. 952 if (isset($node->body)) { 953 $node->body = $node->content['release_info']['#value'] . $node->body; 954 } 955 if (isset($node->teaser)) { 956 $node->teaser = $node->content['release_info']['#value'] . $node->teaser; 957 } 958 // If the release node has a file, include an enclosure attribute for it. 959 if (!empty($node->project_release['files'])) { 960 // RSS will only take the first file. 961 $file = reset($node->project_release['files']); 962 $file_link = theme('project_release_download_link', $file->filepath, NULL, TRUE); 963 return array( 964 array( 965 'key' => 'enclosure', 966 'attributes' => array( 967 'url' => $file_link['href'], 968 'length' => $file->filesize, 969 'type' => 'application/octet-stream', 970 ) 971 ) 972 ); 973 } 974 break; 975 } 976 } 977 978 /** 979 * Fetch information about the current releases for a given project. 980 * 981 * This just queries the {project_release_supported_versions} table for either 982 * the latest release or the recommended release, and retrieves data about 983 * that release from the {node} and {project_release_nodes} tables. To 984 * actually recompute the latest and recommended releases for a given branch, 985 * you must use project_release_find_latest_releases(). 986 * 987 * @param $project_nid 988 * The nid of the project to find the current release for. 989 * @param $api_tid 990 * The API compatibility term ID you want to search. 991 * @param $recommended_major 992 * An optional major version to search. If not specified, the current 993 * recommended branch from {project_release_supported_versions} is used. 994 * @param $type 995 * String for what kind of release to get ('recommended' or 'latest'). 996 * 997 * @return 998 * An object containing all the fields from {project_release_nodes}, along 999 * with {node}.title and {node}.created, for the appropriate release; or 1000 * FALSE if no published releases exists that the caller can access on the 1001 * requested branch of the desired project. 1002 */ 1003 function project_release_get_current_recommended($project_nid, $api_tid, $recommended_major = NULL, $type = 'recommended') { 1004 // Compute the appropriate JOIN ON clauses based on the arguments. 1005 $prsv_joins[] = 'n.nid = ' . ($type == 'recommended' ? 'prsv.recommended_release' : 'prsv.latest_release'); 1006 $prsv_joins[] = 'prsv.nid = %d'; 1007 $join_params[] = $project_nid; 1008 $prsv_joins[] = 'prsv.tid = %d'; 1009 $join_params[] = $api_tid; 1010 if (!isset($recommended_major)) { 1011 $prsv_joins[] = 'prsv.recommended = %d'; 1012 $join_params[] = 1; 1013 } 1014 else { 1015 $prsv_joins[] = 'prsv.major = %d'; 1016 $join_params[] = $recommended_major; 1017 } 1018 // Build the actual JOIN ON string by AND'ing all the clauses together. 1019 $prsv_join = implode(' AND ', $prsv_joins); 1020 $result = db_query(db_rewrite_sql( 1021 "SELECT n.nid, n.title, n.created, r.* FROM {node} n ". 1022 "INNER JOIN {project_release_nodes} r ON r.nid = n.nid ". 1023 "INNER JOIN {project_release_supported_versions} prsv ON $prsv_join "), 1024 $join_params); 1025 return db_fetch_object($result); 1026 } 1027 1028 /** 1029 * Finds the latest and recommended releases for a given project and branch. 1030 * 1031 * The "latest" release just means the published release node with the highest 1032 * version string. The "recommended" release is the published release node 1033 * with the highest version string that doesn't have a "version_extra" field 1034 * (e.g. "beta1"). If all releases on the given branch have "extra", then the 1035 * recommended release will be the same as the latest release. 1036 * 1037 * @param $project_nid 1038 * The node ID of the project to find the latest and recommended releases of. 1039 * @param $api_tid 1040 * The API compatibility term ID to search. 1041 * @param $major 1042 * The {project_release_nodes}.version_major field of the branch to search. 1043 * @param $access 1044 * Optional boolean to indicate if node access checks should be enforced. 1045 * Defaults to FALSE since the caller might not actually have access to all 1046 * the releases or projects. However, this function usually has to compute 1047 * the accurate values regardless of access, and consumers of this data are 1048 * responsible for ensuring access. 1049 * 1050 * @return 1051 * An array containing the node ID (nid) of the latest and recommended 1052 * releases, and latest security update (if any) from the given branch. 1053 * 1054 * @see project_release_query_releases_by_branch() 1055 */ 1056 function project_release_find_latest_releases($project_nid, $api_tid, $major, $access = FALSE) { 1057 $latest_release = $recommended_release = $latest_security_release = 0; 1058 1059 $query = project_release_query_releases_by_branch($project_nid, $api_tid, $major, $access); 1060 while ($release = db_fetch_object($query)) { 1061 if (empty($latest_release)) { 1062 $latest_release = $release->nid; 1063 } 1064 if (empty($recommended_release) && empty($release->version_extra)) { 1065 $recommended_release = $release->nid; 1066 } 1067 if (empty($latest_security_release) && !empty($release->security_update)) { 1068 $latest_security_release = $release->nid; 1069 } 1070 1071 // If we've found everything we're looking for, break out of the loop and 1072 // stop inspecting release from this branch. $latest_release can't 1073 // possibly be empty here, so don't bother testing for it. 1074 if (!empty($recommended_release) && !empty($latest_security_release)) { 1075 break; 1076 } 1077 } 1078 1079 // If we found no releases without extra (e.g. a new branch that only has 1080 // betas), just call the latest release the recommended one). 1081 if (empty($recommended_release)) { 1082 $recommended_release = $latest_release; 1083 } 1084 1085 return array( 1086 $latest_release, 1087 $recommended_release, 1088 $latest_security_release, 1089 ); 1090 } 1091 1092 /** 1093 * Build a query for releases on a given branch, ordered by version. 1094 * 1095 * @param $project_nid 1096 * The project node ID. 1097 * @param $api_tid 1098 * The API compatibility term ID. 1099 * @param $major 1100 * The major version that defines the branch for the project and API term. 1101 * @param $access 1102 * Optional boolean to indicate if node access checks should be enforced. 1103 * Defaults to FALSE since the caller might not actually have access to all 1104 * the releases or projects. However, this function usually has to compute 1105 * the accurate values regardless of access, and consumers of this data are 1106 * responsible for ensuring access. 1107 * 1108 * @return 1109 * A database query result resource, as returned by db_query(). 1110 * 1111 * @see db_query() 1112 * @see project_release_find_latest_releases() 1113 */ 1114 function project_release_query_releases_by_branch($project_nid, $api_tid, $major, $access = FALSE) { 1115 $wheres = $params = $order_bys = array(); 1116 1117 $wheres[] = '(r.pid = %d)'; 1118 $params[] = $project_nid; 1119 1120 $wheres[] = '(r.version_api_tid = %d)'; 1121 $params[] = $api_tid; 1122 1123 $wheres[] = '(r.version_major = %d)'; 1124 $params[] = $major; 1125 1126 $wheres[] = '(n.status = %d)'; 1127 $params[] = 1; 1128 1129 $where = 'WHERE ' . implode(' AND ', $wheres); 1130 1131 // We always want the dev snapshots to show up last. 1132 $order_bys[] = 'r.rebuild'; 1133 // Sort by the obvious integer values along the branch (minor and patch). 1134 $order_bys[] = 'r.version_minor DESC'; 1135 $order_bys[] = 'r.version_patch DESC'; 1136 // To reliably sort release with version_extra, use version_extra_weight. 1137 $order_bys[] = 'r.version_extra_weight DESC'; 1138 // Within releases of the same version_extra_weight (e.g. rc1 vs. rc2), 1139 // sort by version_extra_delta. 1140 $order_bys[] = 'r.version_extra_delta DESC'; 1141 // Within releases of the same version_extra_weight and version_extra_delta, 1142 // sort alphabetically. This shouldn't normally happen, but just in case you 1143 // have multiple releases with the same delta (e.g. "alpha-one", "alpha-two" 1144 // etc), at least you'll get deterministic results. 1145 $order_bys[] = 'r.version_extra DESC'; 1146 1147 $order_by = 'ORDER BY '. implode(', ', $order_bys); 1148 1149 $sql = "SELECT n.nid, n.title, n.created, r.* FROM {node} n ". 1150 "INNER JOIN {project_release_nodes} r ON r.nid = n.nid ". 1151 "$where $order_by"; 1152 1153 // Only enforce node access via db_rewrite_sql() if the caller specifically 1154 // requested that behavior. 1155 if ($access) { 1156 $sql = db_rewrite_sql($sql); 1157 } 1158 1159 return db_query($sql, $params); 1160 } 1161 1162 /** 1163 * Theme the appropriate release download table for a project node. 1164 */ 1165 function theme_project_release_project_download_table($node) { 1166 if (empty($node->project_release['releases'])) { 1167 return; 1168 } 1169 $output = '<h3 id="downloads">'. t('Downloads') .'</h3>'; 1170 $view_args = array($node->nid); 1171 $displays = array( 1172 'attachment_1' => array( 1173 'class' => 'ok', 1174 'header' => t('Recommended releases'), 1175 ), 1176 'attachment_2' => array( 1177 'class' => 'warning', 1178 'header' => t('Other releases'), 1179 ), 1180 'attachment_3' => array( 1181 'class' => 'error', 1182 'header' => t('Development releases'), 1183 ), 1184 ); 1185 $number_of_tables = 0; 1186 $views_output = array(); 1187 foreach ($displays as $display => $info) { 1188 $view = views_get_view('project_release_download_table'); 1189 $view_output = $view->preview($display, $view_args); 1190 if (!empty($view->result)) { 1191 $views_output[$display] = $view_output; 1192 $number_of_tables++; 1193 } 1194 } 1195 1196 if ($number_of_tables > 0) { 1197 foreach ($displays as $display => $info) { 1198 if (!empty($views_output[$display])) { 1199 $classes = 'download-table download-table-' . $info['class']; 1200 $output .= '<div class="' . $classes . '">'; 1201 if ($number_of_tables > 1) { 1202 $output .= '<h4>' . $info['header'] . "</h4>\n"; 1203 } 1204 $output .= $views_output[$display]; 1205 $output .= "</div> <!-- .download-table -->\n"; 1206 } 1207 } 1208 } 1209 1210 return $output; 1211 } 1212 1213 /** 1214 * Implemenation of hook_project_page_link_alter(). 1215 * 1216 * Note: This is *not* an implementation of hook_link_alter(). 1217 */ 1218 function project_release_project_page_link_alter(&$links, $node) { 1219 if (empty($node->project_release['releases'])) { 1220 return; 1221 } 1222 $links['project_release'] = array( 1223 // NOTE: The 'name' element of this array is not defined here because 1224 // it's actually printed as part of the output of the 1225 // theme_project_release_project_download_table() function above. 1226 'weight' => 2, 1227 'clear' => TRUE, 1228 'links' => array( 1229 'view_all_releases' => l(t('View all releases'), 'node/'. $node->nid .'/release') . theme('project_feed_icon', url('node/'. $node->nid .'/release/feed'), t('RSS feed of all releases')) 1230 ), 1231 ); 1232 1233 if (project_check_admin_access($node->nid)) { 1234 $links['project_release']['links']['add_new_release'] = l(t('Add new release'), 'node/add/project_release/'. $node->nid); 1235 $links['project_release']['links']['administer_releases'] = l(t('Administer releases'), 'node/'. $node->nid .'/edit/releases'); 1236 } 1237 } 1238 1239 /** 1240 * Theme function that calls project_release_table(). 1241 * 1242 * The main purpose of this theme wrapper function is to make it easier 1243 * to display a different kind of table (for example, $tabel_type=all) 1244 * from the project_page_overview() function in project.module. 1245 * 1246 * The parameters are described at project_release_table(). 1247 * 1248 * @see project_page_overview() 1249 * @see project_release_table() 1250 */ 1251 function theme_project_release_table_overview($project, $table_type, $release_type, $title, $print_size) { 1252 return project_release_table($project, $table_type, $release_type, $title, $print_size); 1253 } 1254 1255 /** 1256 * Generate a table of releases for a given project. 1257 * 1258 * @param $project 1259 * The project object (as returned by node_load(), for example). 1260 * 1261 * @param $table_type 1262 * Indicates what kind of table should be generated. Possible options: 1263 * 'recommended': Only show the current recommended versions. 1264 * 'supported': Only show the latest release from each supported branch. 1265 * 'all': Include all releases. 1266 * 1267 * @param $release_type 1268 * Filter what kinds of releases are visible in the table. Possible options: 1269 * 'official': Only include offical releases. 1270 * 'snapshot': Only include development snapshots. 1271 * 'all': Include all releases. 1272 * 1273 * @param $title 1274 * The title of the first column in the table. Defaults to "Version" if NULL. 1275 * 1276 * @param $print_size 1277 * Should the table include the filesize of each release? 1278 * 1279 * @param $check_edit 1280 * Should the table check for and include edit links to user with access? 1281 */ 1282 function project_release_table($project, $table_type = 'recommended', $release_type = 'all', $title = NULL, $print_size = TRUE, $check_edit = TRUE) { 1283 if (empty($title)) { 1284 $title = t('Version'); 1285 } 1286 1287 // Can the current user edit releases for this project? 1288 $can_edit = $check_edit ? node_access('update', $project) : FALSE; 1289 1290 // Generate the cache ID. 1291 $cid = 'table:'. $project->nid .':'. $table_type .':'. $release_type .':'. $title .':'. (int)$print_size .':'. (int)$can_edit; 1292 if ($cached = cache_get($cid, 'cache_project_release')) { 1293 return $cached->data; 1294 } 1295 1296 $selects = array(); 1297 $join = $where = $order_by = ''; 1298 $args = array(); 1299 $tids = project_release_compatibility_list(); 1300 if (!empty($tids)) { 1301 $join = ' INNER JOIN {term_node} tn ON n.nid = tn.nid AND tn.tid in (' 1302 . db_placeholders($tids) .') ' 1303 .' INNER JOIN {term_data} td ON td.tid = tn.tid '; 1304 $args = array_keys($tids); 1305 $selects[] = 'tn.tid'; 1306 $selects[] = 'td.name as api_term_name'; 1307 $orderby[] = 'td.weight'; 1308 $orderby[] = 'td.name'; 1309 } 1310 1311 if ($tids) { 1312 $selects[] = 'prsv.supported'; 1313 $selects[] = 'prsv.recommended'; 1314 $selects[] = 'prsv.snapshot'; 1315 $join .= ' INNER JOIN {project_release_supported_versions} prsv ON prsv.nid = r.pid AND prsv.tid = tn.tid AND prsv.major = r.version_major '; 1316 if ($table_type == 'recommended') { 1317 $join .= 'AND prsv.recommended = %d '; 1318 $args[] = 1; 1319 } 1320 elseif ($table_type == 'supported') { 1321 $join .= 'AND prsv.supported = %d '; 1322 $args[] = 1; 1323 } 1324 } 1325 else { 1326 // TODO: someday (never?) when project_release doesn't require taxonomy. 1327 } 1328 $args[] = $project->nid; // Account for r.pid. 1329 $args[] = 1; // Account for n.status = 1. 1330 1331 switch ($release_type) { 1332 case 'official': 1333 $where = 'AND r.rebuild <> %d'; 1334 $args[] = 1; 1335 break; 1336 1337 case 'snapshot': 1338 // For snapshot tables, restrict to snapshot nodes from branches where 1339 // the maintainer wants the snapshot visible. 1340 $where = 'AND r.rebuild = %d'; 1341 $args[] = 1; 1342 if ($tids) { 1343 $where .= ' AND prsv.snapshot = %d'; 1344 $args[] = 1; 1345 } 1346 break; 1347 1348 case 'all': 1349 // If we're generating the default releases table, we want the 1350 // dev snapshots to be last in the query results, so that we 1351 // only show them if there's nothing else. 1352 if ($table_type == 'recommended') { 1353 $orderby[] = 'r.rebuild ASC'; 1354 } 1355 break; 1356 } 1357 1358 $orderby[] = 'r.version_major DESC'; 1359 $orderby[] = 'r.version_minor DESC'; 1360 $orderby[] = 'r.version_patch DESC'; 1361 $orderby[] = 'f.timestamp DESC'; 1362 1363 $order_by = !empty($orderby) ? (' ORDER BY '. implode(', ', $orderby)) : ''; 1364 $select = !empty($selects) ? (implode(', ', $selects) .',') : ''; 1365 1366 // TODO: we MUST rewrite this query when multiple files attachments 1367 // per release node lands, as it will return a non-unique result set. 1368 $result = db_query(db_rewrite_sql( 1369 "SELECT n.nid, n.created, f.filename, f.filepath, f.timestamp, ". 1370 "f.filesize, $select r.* FROM {node} n ". 1371 "INNER JOIN {project_release_nodes} r ON r.nid = n.nid ". 1372 "INNER JOIN {project_release_file} prf ON n.nid = prf.nid ". 1373 "INNER JOIN {files} f ON prf.fid = f.fid$join ". 1374 "WHERE (r.pid = %d) AND (n.status = %d) $where $order_by"), 1375 $args); 1376 1377 $rows = array(); // Rows for the download table. 1378 $seen = array(); // Keeps track of which versions we already saw. 1379 while ($release = db_fetch_object($result)) { 1380 $tid = $release->tid; 1381 $major = $release->version_major; 1382 $recommended = false; 1383 if ($table_type == 'supported') { 1384 // Supported version can be multiple majors per tid. 1385 if (empty($seen[$tid])) { 1386 $seen[$tid] = array(); 1387 } 1388 if (empty($seen[$tid][$major])) { 1389 $seen[$tid][$major] = 1; 1390 if ($release->recommended) { 1391 $recommended = true; 1392 } 1393 } 1394 else { 1395 // We already know the supported release for this tid/major, go on. 1396 continue; 1397 } 1398 } 1399 else { 1400 if (empty($seen[$tid])) { 1401 // Only one major per tid, so the row lives here. 1402 $seen[$tid] = 1; 1403 if ($release->recommended) { 1404 $recommended = true; 1405 } 1406 } 1407 elseif ($table_type == 'recommended') { 1408 // We already know the recommended release for this tid and that's all 1409 // we want in the table, so skip this release. 1410 continue; 1411 } 1412 } 1413 // If we're still here, we need to add the row to the table. 1414 $rows[] = theme('project_release_download_table_row', $release, $recommended, $can_edit, $print_size); 1415 } 1416 1417 $header = array( 1418 array( 1419 'class' => 'release-title', 1420 'data' => $title, 1421 ), 1422 array( 1423 'class' => 'release-date', 1424 'data' => t('Date'), 1425 ), 1426 ); 1427 if ($print_size) { 1428 $header[] = array( 1429 'class' => 'release-size', 1430 'data' => t('Size'), 1431 ); 1432 } 1433 $header[] = array( 1434 'class' => 'release-links', 1435 'data' => t('Links'), 1436 ); 1437 $header[] = array( 1438 'class' => 'release-status', 1439 'data' => t('Status'), 1440 'colspan' => 2, 1441 ); 1442 1443 $output = ''; 1444 if (!empty($rows)) { 1445 $output = theme('table', $header, $rows, array('class' => 'releases')); 1446 } 1447 // Default cache time is 12 hours - will be cleared by the packaging script 1448 cache_set($cid, $output, 'cache_project_release', time() + 43200); 1449 return $output; 1450 } 1451 1452 /** 1453 * Helper function to return an individual row for the download table. 1454 * 1455 * @param $release 1456 * The release object queried from the database. Since this is NOT a 1457 * fully-loaded $node object, so the release-related fields are not in a 1458 * 'project_release' sub-array. 1459 * @param $recommended 1460 * Boolean indicating if this release is the currently recommended one. 1461 * @param $can_edit 1462 * Boolean indicating if the current user can edit the release. 1463 * @param $print_size 1464 * Boolean indicating if the size of the download should be printed. 1465 */ 1466 function theme_project_release_download_table_row($release, $recommended = false, $can_edit = false, $print_size = true) { 1467 static $icons = array(); 1468 if (empty($icons)) { 1469 $icons = array( 1470 'ok' => 'misc/watchdog-ok.png', 1471 'warning' => 'misc/watchdog-warning.png', 1472 'error' => 'misc/watchdog-error.png', 1473 ); 1474 } 1475 $links = array(); 1476 if (!empty($release->filepath)) { 1477 $links['project_release_download'] = theme('project_release_download_link', $release->filepath, t('Download'), TRUE); 1478 } 1479 $links['project_release_notes'] = array( 1480 'title' => t('Release notes'), 1481 'href' => "node/$release->nid", 1482 ); 1483 if ($can_edit) { 1484 $links['project_release_edit'] = array( 1485 'title' => t('Edit'), 1486 'href' => "node/$release->nid/edit", 1487 ); 1488 } 1489 // Figure out the class for the table row 1490 $row_class = $release->rebuild ? 'release-dev' : 'release'; 1491 // Now, set the row color and help text, based on the release attributes. 1492 if (!$release->supported) { 1493 $text = theme('project_release_download_text_unsupported', $release, 'summary'); 1494 $message = theme('project_release_download_text_unsupported', $release, 'message'); 1495 $classification = 'error'; 1496 } 1497 elseif ($release->rebuild) { 1498 $reason = theme('project_release_download_text_snapshot', $release, 'summary'); 1499 $message = theme('project_release_download_text_snapshot', $release, 'message'); 1500 $classification = 'error'; 1501 } 1502 elseif ($recommended) { 1503 $reason = theme('project_release_download_text_recommended', $release, 'summary'); 1504 $message = theme('project_release_download_text_recommended', $release, 'message'); 1505 $classification = 'ok'; 1506 } 1507 else { 1508 // Supported, but not recommened, official release. 1509 $reason = theme('project_release_download_text_supported', $release, 'summary'); 1510 $message = theme('project_release_download_text_supported', $release, 'message'); 1511 $classification = 'warning'; 1512 } 1513 1514 $row = array( 1515 // class of <tr> 1516 'class' => $row_class .' '. $classification, 1517 'data' => array( 1518 array( 1519 'class' => 'release-title', 1520 'data' => l($release->version, "node/$release->nid"), 1521 ), 1522 array( 1523 'class' => 'release-date', 1524 'data' => !empty($release->filepath) ? format_date($release->timestamp, 'custom', 'Y-M-d') : format_date($release->created, 'custom', 'Y-M-d'), 1525 ), 1526 ), 1527 ); 1528 if ($print_size) { 1529 $row['data'][] = array( 1530 'class' => 'release-size', 1531 'data' => !empty($release->filepath) ? format_size($release->filesize) : t('n/a'), 1532 ); 1533 } 1534 $row['data'][] = array( 1535 'class' => 'release-links', 1536 'data' => theme('links', $links), 1537 ); 1538 $row['data'][] = array( 1539 'class' => 'release-reason', 1540 'data' => $reason, 1541 ); 1542 $row['data'][] = array( 1543 'class' => 'release-icon', 1544 'data' => theme('image', $icons[$classification], $message, $message), 1545 ); 1546 return $row; 1547 } 1548 1549 /** 1550 * Return the message text for recommended releases in the download table. 1551 * 1552 * @param $release 1553 * The release object queried from the database. Since this is NOT a 1554 * fully-loaded $node object, so the release-related fields are not in a 1555 * 'project_release' sub-array. 1556 * @param $text_type 1557 * What kind of text to render. Can be either 'summary' for the summary 1558 * text to include directly on the project node, or 'message' for the text 1559 * to put in the title and alt attributes of the icon. 1560 */ 1561 function theme_project_release_download_text_recommended($release, $text_type) { 1562 if ($text_type == 'summary') { 1563 return t('Recommended for %api_term_name', array('%api_term_name' => $release->api_term_name)); 1564 } 1565 return t('This is currently the recommended release for @api_term_name.', array('@api_term_name' => $release->api_term_name)); 1566 } 1567 1568 /** 1569 * Return the message text for supported releases in the download table. 1570 * 1571 * @see theme_project_release_download_text_recommended 1572 */ 1573 function theme_project_release_download_text_supported($release, $text_type) { 1574 if ($text_type == 'summary') { 1575 return t('Supported for %api_term_name', array('%api_term_name' => $release->api_term_name)); 1576 } 1577 return t('This release is supported but is not currently the recommended release for @api_term_name.', array('@api_term_name' => $release->api_term_name)); 1578 } 1579 1580 /** 1581 * Return the message text for snapshot releases in the download table. 1582 * 1583 * @see theme_project_release_download_text_recommended 1584 */ 1585 function theme_project_release_download_text_snapshot($release, $text_type) { 1586 if ($text_type == 'summary') { 1587 return t('Development snapshot'); 1588 } 1589 return t('Development snapshots are automatically regenerated and their contents can frequently change, so they are not recommended for production use.'); 1590 } 1591 1592 /** 1593 * Return the message text for snapshot releases in the download table. 1594 * 1595 * @see theme_project_release_download_text_recommended 1596 */ 1597 function theme_project_release_download_text_unsupported($release, $text_type) { 1598 if ($text_type == 'summary') { 1599 return t('Unsupported'); 1600 } 1601 return t('This release is not supported and may no longer work.'); 1602 } 1603 1604 /** 1605 * Implementation of hook_taxonomy(). 1606 */ 1607 function project_release_taxonomy($op, $type, $array = NULL) { 1608 if ($op == 'delete' && $type == 'vocabulary') { 1609 if ($array['vid'] == _project_release_get_api_vid()) { 1610 variable_del('project_release_api_vocabulary'); 1611 } 1612 elseif ($array['vid'] == _project_release_get_release_type_vid()) { 1613 variable_del('project_release_release_type_vid'); 1614 } 1615 } 1616 elseif ($type == 'term' && $array['vid'] == _project_release_get_api_vid()) { 1617 menu_rebuild(); 1618 } 1619 } 1620 1621 /** 1622 * If taxonomy is enabled, returns the taxonomy tree for the 1623 * API compatibility vocabulary, otherwise, it returns false. 1624 */ 1625 function project_release_get_api_taxonomy() { 1626 if (!module_exists('taxonomy')) { 1627 return false; 1628 } 1629 static $tree = NULL; 1630 if (!isset($tree)) { 1631 $tree = taxonomy_get_tree(_project_release_get_api_vid()); 1632 } 1633 return $tree; 1634 } 1635 1636 /** 1637 * Returns the vocabulary id for project release API 1638 */ 1639 function _project_release_get_api_vid() { 1640 return variable_get('project_release_api_vocabulary', ''); 1641 } 1642 1643 /** 1644 * Return the taxonomy tree for the release type vocabulary (if any). 1645 * 1646 * If taxonomy is disabled, this returns false. 1647 */ 1648 function project_release_get_release_type_vocabulary() { 1649 if (!module_exists('taxonomy')) { 1650 return false; 1651 } 1652 static $tree = NULL; 1653 if (!isset($tree)) { 1654 $tree = taxonomy_get_tree(_project_release_get_release_type_vid()); 1655 } 1656 return $tree; 1657 } 1658 1659 /** 1660 * Return the vocabulary id for project release type. 1661 */ 1662 function _project_release_get_release_type_vid() { 1663 return variable_get('project_release_release_type_vid', ''); 1664 } 1665 1666 function project_release_exists($version) { 1667 $fields = array('version_major', 'version_minor', 'version_patch', 'version_api_tid'); 1668 foreach ($fields as $field) { 1669 if (isset($version->$field) && is_numeric($version->$field)) { 1670 $types[$field] = "%d"; 1671 $values[$field] = $version->$field; 1672 $foo = $version->$field; 1673 } 1674 else { 1675 $null_types[] = $field; 1676 } 1677 } 1678 $fields = array('version', 'version_extra'); 1679 foreach ($fields as $field) { 1680 if (isset($version->$field) && $version->$field !== '') { 1681 $types[$field] = "'%s'"; 1682 $values[$field] = $version->$field; 1683 $str = $version->$field; 1684 } 1685 elseif ($field == 'version_extra') { 1686 $null_types[] = $field; 1687 } 1688 } 1689 if (empty($types) && empty($null_types)) { 1690 // We have nothing to query, yet... 1691 return false; 1692 } 1693 1694 $sql = 'SELECT COUNT(*) FROM {project_release_nodes} WHERE pid = %d'; 1695 if (!empty($types)) { 1696 foreach ($types as $field => $type) { 1697 $sql .= " AND $field = $type"; 1698 } 1699 } 1700 if (!empty($null_types)) { 1701 foreach ($null_types as $field) { 1702 $sql .= " AND $field IS NULL"; 1703 } 1704 } 1705 // we put pid as the first WHERE, so stick it on the front 1706 $values = array_merge(array('pid' => $version->pid), $values); 1707 return db_result(db_query($sql, $values)); 1708 } 1709 1710 /** 1711 * Generates the appropriate download link for a give file path. This 1712 * function takes the 'project_release_download_base' setting into 1713 * account, so it should be used everywhere a download link is made. 1714 * 1715 * @param $filepath 1716 * The path to the download file, as stored in the database. 1717 * @param $link_text 1718 * The text to use for the download link. If NULL, the basename 1719 * of the given $filepath is used. 1720 * @param $as_array 1721 * Should the link be returned as a structured array, or as raw HTML? 1722 * @return 1723 * The link itself, as a structured array. 1724 */ 1725 function theme_project_release_download_link($filepath, $link_text = NULL, $as_array = FALSE) { 1726 if (empty($link_text)) { 1727 $link_text = basename($filepath); 1728 } 1729 $download_base = variable_get('project_release_download_base', ''); 1730 if (!empty($download_base)) { 1731 $link_path = $download_base . $filepath; 1732 } 1733 else { 1734 $link_path = file_create_url($filepath); 1735 } 1736 if ($as_array) { 1737 return array( 1738 'title' => $link_text, 1739 'href' => $link_path, 1740 ); 1741 } 1742 else { 1743 return l($link_text, $link_path); 1744 } 1745 } 1746 1747 /** 1748 * Implementation of hook_file_download(). 1749 * 1750 * @param $filename 1751 * The name of the file to download. 1752 * @return 1753 * An array of header fields for the download. 1754 */ 1755 function project_release_file_download($filename) { 1756 $filepath = file_create_path($filename); 1757 $result = db_query("SELECT prf.nid FROM {project_release_file} prf INNER JOIN {files} f ON prf.fid = f.fid WHERE f.filepath = '%s'", $filepath); 1758 if ($nid = db_result($result)) { 1759 $node = node_load($nid); 1760 if (node_access('view', $node)) { 1761 return array( 1762 'Content-Type: application/octet-stream', 1763 'Content-Length: '. filesize($filepath), 1764 'Content-Disposition: attachment; filename="'. mime_header_encode($filename) .'"', 1765 ); 1766 } 1767 return -1; 1768 } 1769 } 1770 1771 /** 1772 * Implementation of hook_flush_caches(). 1773 */ 1774 function project_release_flush_caches() { 1775 return array('cache_project_release'); 1776 } 1777 1778 /** 1779 * Menu callback to select a project when creating a new release. 1780 */ 1781 function project_release_pick_project_page($type_name) { 1782 drupal_set_title(t('Submit @name', array('@name' => $type_name))); 1783 $project = arg(3); 1784 if (!empty($project)) { 1785 // If there's any argument at all and we hit this form, it's from a 1786 // non-numeric project id, which by definition is invalid. No one's ever 1787 // going to hit this code from clicking around in the normal UI, only if 1788 // they type in a URL manually. 1789 drupal_set_message(t('Specified argument (%project) is not a valid project ID number.', array('%project' => $project)), 'error'); 1790 return drupal_goto('/node/add/project-release'); 1791 } 1792 return drupal_get_form('project_release_pick_project_form'); 1793 } 1794 1795 /** 1796 * Implementation of hook_theme(). 1797 */ 1798 function project_release_theme() { 1799 return array( 1800 'project_release_download_file' => array( 1801 'arguments' => array( 1802 'file' => NULL, 1803 'download_link' => TRUE, 1804 ), 1805 ), 1806 'project_release_download_link' => array( 1807 'arguments' => array( 1808 'filepath' => NULL, 1809 'link_text' => NULL, 1810 'as_array' => FALSE, 1811 ), 1812 ), 1813 'project_release_download_table_row' => array( 1814 'arguments' => array( 1815 'release' => NULL, 1816 'recommended' => FALSE, 1817 'can_edit' => FALSE, 1818 'print_size' => TRUE, 1819 ), 1820 ), 1821 'project_release_download_text_recommended' => array( 1822 'arguments' => array( 1823 'release' => NULL, 1824 'text_type' => NULL, 1825 ), 1826 ), 1827 'project_release_download_text_snapshot' => array( 1828 'arguments' => array( 1829 'release' => NULL, 1830 'text_type' => NULL, 1831 ), 1832 ), 1833 'project_release_download_text_supported' => array( 1834 'arguments' => array( 1835 'release' => NULL, 1836 'text_type' => NULL, 1837 ), 1838 ), 1839 'project_release_download_text_unsupported' => array( 1840 'arguments' => array( 1841 'release' => NULL, 1842 'text_type' => NULL, 1843 ), 1844 ), 1845 'project_release_form_value' => array( 1846 'file' => 'includes/release_node_form.inc', 1847 'arguments' => array( 1848 'element' => NULL, 1849 ), 1850 ), 1851 'project_release_project_download_table' => array( 1852 'arguments' => array( 1853 'node' => NULL, 1854 ), 1855 ), 1856 'project_release_project_edit_form' => array( 1857 'file' => 'includes/release_node_form.inc', 1858 'arguments' => array( 1859 'form' => NULL, 1860 ), 1861 ), 1862 'project_release_table_overview' => array( 1863 'arguments' => array( 1864 'project' => NULL, 1865 'table_type' => NULL, 1866 'release_type' => NULL, 1867 'title' => NULL, 1868 'print_size' => NULL, 1869 ), 1870 ), 1871 'project_release_node_form_version_elements' => array( 1872 'arguments' => array( 1873 'form' => NULL, 1874 ), 1875 ), 1876 'project_release_update_status_icon' => array( 1877 'arguments' => array( 1878 'status' => NULL, 1879 ), 1880 ), 1881 ); 1882 } 1883 1884 function theme_project_release_node_form_version_elements($form) { 1885 $output = '<div class="version-elements">'; 1886 $output .= drupal_render($form); 1887 $output .= '</div>'; 1888 return $output; 1889 } 1890 1891 /** 1892 * Implement hook_token_list() (from token.module) 1893 */ 1894 function project_release_token_list($type) { 1895 if ($type == 'node') { 1896 $tokens['node'] = array( 1897 'project_release_pid' => t("A release's project nid"), 1898 'project_release_project_title' => t("A release's project title"), 1899 'project_release_project_title-raw' => t("A release's project title raw"), 1900 'project_release_project_shortname' => t("A release's project short name"), 1901 'project_release_version' => t("A release's version string"), 1902 'project_release_version_major' => t("A release's major version number"), 1903 'project_release_version_minor' => t("A release's minor version number"), 1904 'project_release_version_patch' => t("A release's patch version number"), 1905 'project_release_version_extra' => t("A release's extra version identifier"), 1906 ); 1907 if (project_release_compatibility_list()) { 1908 $vocab = taxonomy_vocabulary_load(_project_release_get_api_vid()); 1909 $tokens['node']['project_release_version_api_tid'] = t("A release's %api_compatibility term ID", array('%api_compatibility' => $vocab->name)); 1910 $tokens['node']['project_release_version_api_term'] = t("A release's %api_compatibility term name", array('%api_compatibility' => $vocab->name)); 1911 } 1912 return $tokens; 1913 } 1914 } 1915 1916 /** 1917 * Implement hook_token_values() (from token.module). 1918 */ 1919 function project_release_token_values($type = 'all', $object = NULL) { 1920 if ($type == 'node') { 1921 // Defaults in case it's not a release or we run into other problems. 1922 $values = array( 1923 'project_release_pid' => '', 1924 'project_release_project_title' => '', 1925 'project_release_project_title-raw' => '', 1926 'project_release_project_shortname' => '', 1927 'project_release_version' => '', 1928 'project_release_version_major' => '', 1929 'project_release_version_minor' => '', 1930 'project_release_version_patch' => '', 1931 'project_release_version_extra' => '', 1932 'project_release_version_api_tid' => '', 1933 'project_release_version_api_term' => '', 1934 ); 1935 if ($object->type == 'project_release') { 1936 if ($project = node_load($object->project_release['pid'])) { 1937 $values['project_release_pid'] = intval($object->project_release['pid']); 1938 $values['project_release_project_title'] = check_plain($project->title); 1939 $values['project_release_project_title-raw'] = $project->title; 1940 $values['project_release_project_shortname'] = check_plain($project->project['uri']); 1941 } 1942 $values['project_release_version'] = check_plain($object->project_release['version']); 1943 $values['project_release_version_major'] = check_plain($object->project_release['version_major']); 1944 $values['project_release_version_minor'] = check_plain($object->project_release['version_minor']); 1945 $values['project_release_version_patch'] = check_plain($object->project_release['version_patch']); 1946 $values['project_release_version_extra'] = check_plain($object->project_release['version_extra']); 1947 if (!empty($object->project_release['version_api_tid'])) { 1948 $term = taxonomy_get_term($object->project_release['version_api_tid']); 1949 $values['project_release_version_api_tid'] = check_plain($term->tid); 1950 $values['project_release_version_api_term'] = check_plain($term->name); 1951 } 1952 } 1953 return $values; 1954 } 1955 } 1956 1957 /** 1958 * Determines taxonomy-specific functionality for releases. 1959 */ 1960 function project_release_use_taxonomy() { 1961 return module_exists('taxonomy') && _project_release_get_api_vid(); 1962 } 1963 1964 /** 1965 * Implementation of hook_help(). 1966 */ 1967 function project_release_help($section) { 1968 switch ($section) { 1969 case 'admin/project/project-release-settings': 1970 if (project_release_use_taxonomy()) { 1971 return _project_release_taxonomy_help(); 1972 } 1973 break; 1974 } 1975 if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'taxonomy') { 1976 $vid = _project_release_get_api_vid(); 1977 if (arg(3) == $vid) { 1978 return _project_release_taxonomy_help($vid, FALSE); 1979 } 1980 } 1981 } 1982 1983 /** 1984 * Prints help message for release compatibility vocabulary configuration. 1985 * 1986 * @param $vid 1987 * Vocabulary ID of the project taxonomy. 1988 * @param $vocab_link 1989 * Boolean that controls if a link to the vocabulary admin page is added. 1990 */ 1991 function _project_release_taxonomy_help($vid = 0, $vocab_link = TRUE) { 1992 if (!$vid) { 1993 $vid = _project_release_get_api_vid(); 1994 } 1995 if (empty($vid)) { 1996 return; 1997 } 1998 $vocabulary = taxonomy_vocabulary_load($vid); 1999 $text = '<p>'. t('The Project release module makes special use of the taxonomy (category) system. A special vocabulary, %vocabulary_name, has been created automatically.', array('%vocabulary_name' => $vocabulary->name)) .'</p>'; 2000 $text .= '<p>'. t('To categorize project releases by their compatibility with a version of some outside software (eg. a library or API of some sort), add at least one term to this vocabulary. For example, you might add the following terms: "5.x", "6.x", "7.x".') .'</p>'; 2001 $text .='<p>'. t('For more information, please see !url.', array('!url' => l('http://drupal.org/node/116544', 'http://drupal.org/node/116544'))) .'</p>'; 2002 if ($vocab_link) { 2003 $text .= '<p>'. t('Use the <a href="@taxonomy-admin">vocabulary admininistration page</a> to view and add terms.', array('@taxonomy-admin' => url('admin/content/taxonomy/'. $vid))) .'</p>'; 2004 } 2005 return $text; 2006 } 2007 2008 /** 2009 * Implementation of hook_views_api(). 2010 */ 2011 function project_release_views_api() { 2012 return array( 2013 'api' => 2, 2014 'path' => drupal_get_path('module', 'project_release') .'/views', 2015 ); 2016 } 2017 2018 /** 2019 * Return the mapping of version_extra prefixes to version_extra_weight values. 2020 * 2021 * This mapping allows project_release to use SQL to sort releases by version, 2022 * even though direct string comparison doesn't work for the kinds of version 2023 * strings people might use (for example "1.0-unstable1" should be lower than 2024 * "1.0-alpha3", even though "u" comes higher in the alphabet than "a"). This 2025 * is similar to the logic version_compare() performs, only using this weight 2026 * field, we can do the comparison in SQL instead of in PHP. 2027 * 2028 * @return 2029 * Associative array mapping version_extra prefixes into weights. The 2030 * prefixes should be lowercase, since the query uses LOWER(version_extra) 2031 * inside _project_release_update_version_extra_weights(). The special-case 2032 * is the record with the key 'NULL' (should be uppercase) which doesn't 2033 * correspond to a literal version_extra field, but is used for releases 2034 * that do not define version_extra where the value is NULL in the database. 2035 * 2036 * @see version_compare() 2037 * @see _project_release_update_version_extra_weights() 2038 */ 2039 function project_release_get_version_extra_weight_map() { 2040 $default_map = array( 2041 'NULL' => 10, // Official releases without extra are always highest. 2042 'rc' => 4, 2043 'beta' => 3, 2044 'alpha' => 2, 2045 'unstable' => 1, 2046 // Anything that doesn't match will remain at weight 0, the default. 2047 ); 2048 return variable_get('project_release_version_extra_weights', $default_map); 2049 } 2050 2051 /** 2052 * Get the human-readable update status string, or an array of all statuses. 2053 * 2054 * @param $status 2055 * Optional status code to get the human-readable string for. If NULL, the 2056 * whole mapping of status codes to strings is returned. 2057 * 2058 * @return 2059 * If $status is defined, the human-readable string for that status, 2060 * otherwise, an associative array of status strings keyed by status code. 2061 */ 2062 function project_release_update_status($status = NULL) { 2063 $status_map = array( 2064 PROJECT_RELEASE_UPDATE_STATUS_CURRENT => t('Up to date'), 2065 PROJECT_RELEASE_UPDATE_STATUS_NOT_CURRENT => t('Update available'), 2066 PROJECT_RELEASE_UPDATE_STATUS_NOT_SECURE => t('Not secure'), 2067 ); 2068 return isset($status) ? $status_map[$status] : $status_map; 2069 } 2070 2071 /** 2072 * Render HTML for an icon approrpriate for the given release update status. 2073 * 2074 * @param $status 2075 * Update status code to get the icon for. 2076 * 2077 * @return 2078 * Icon to use for the given update status code. 2079 */ 2080 function theme_project_release_update_status_icon($status) { 2081 $label = project_release_update_status($status); 2082 $icon = ''; 2083 switch ($status) { 2084 case PROJECT_RELEASE_UPDATE_STATUS_CURRENT: 2085 $icon = theme('image', 'misc/watchdog-ok.png', $label, $label); 2086 break; 2087 2088 case PROJECT_RELEASE_UPDATE_STATUS_NOT_CURRENT: 2089 $icon = theme('image', 'misc/watchdog-warning.png', $label, $label); 2090 break; 2091 2092 case PROJECT_RELEASE_UPDATE_STATUS_NOT_SECURE: 2093 $icon = theme('image', 'misc/watchdog-error.png', $label, $label); 2094 break; 2095 } 2096 2097 return $icon; 2098 } 2099 2100 /** 2101 * Implement hook_preprocess_views_view_table(). 2102 * 2103 * Handles the logic for conditionally adding row classes based on release 2104 * update_status, and has a hack for hiding the update_status column entirely 2105 * on the project_release_download_table view if there's nothing to see. 2106 */ 2107 function project_release_preprocess_views_view_table($variables) { 2108 $view = $variables['view']; 2109 if ($view->plugin_name == 'project_release_table') { 2110 // TODO: this is a hack, we want something more flexible. 2111 $needs_status_column = FALSE; 2112 foreach ($view->result as $num => $result) { 2113 $variables['row_classes'][$num][] = "release-update-status-$result->project_release_nodes_update_status"; 2114 if (!empty($variables['rows'][$num]['update_status'])) { 2115 $needs_status_column = TRUE; 2116 } 2117 } 2118 if ($view->name == 'project_release_download_table' && !$needs_status_column) { 2119 unset($variables['header']['update_status']); 2120 foreach ($variables['rows'] as &$row) { 2121 unset($row['update_status']); 2122 } 2123 } 2124 $variables['class'] .= " project-release"; 2125 } 2126 } 2127
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Mar 24 11:18:33 2011 | Cross-referenced by PHPXref 0.7 |