updatecode.pm.inc 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. <?php
  2. /**
  3. * Command callback. Displays update status info and allows to update installed
  4. * projects.
  5. * Pass specific projects as arguments, otherwise we update all that have
  6. * candidate releases.
  7. *
  8. * This command prompts for confirmation before updating, so it is safe to run
  9. * just to check on. In this case, say at the confirmation prompt.
  10. */
  11. function drush_pm_updatecode() {
  12. // We don't provide for other options here, so we supply an explicit path.
  13. drush_include_engine('update_info', 'drupal', NULL, DRUSH_BASE_PATH . '/commands/pm/update_info');
  14. // Find only security updates?
  15. $security_only = drush_get_option('security-only');
  16. // Get specific requests.
  17. $requests = _convert_csv_to_array(func_get_args());
  18. // Parse out project name and version.
  19. $requests = pm_parse_project_version($requests);
  20. // Get installed extensions and projects.
  21. $extensions = drush_get_extensions();
  22. $projects = drush_get_projects($extensions);
  23. // Get update status information.
  24. $update_info = _pm_get_update_info($projects);
  25. // Process locks specified on the command line.
  26. $locked_list = drush_pm_update_lock($update_info, drush_get_option_list('lock'), drush_get_option_list('unlock'), drush_get_option('lock-message'));
  27. foreach ($extensions as $name => $extension) {
  28. // Add an item to $update_info for each enabled extension which was obtained
  29. // from cvs or git and its project is unknown (because of cvs_deploy or
  30. // git_deploy is not enabled).
  31. if (!isset($extension->info['project'])) {
  32. if ((isset($extension->vcs)) && ($extension->status)) {
  33. $update_info[$name] = array(
  34. 'title' => $extension->info['name'],
  35. 'existing_version' => 'Unknown',
  36. 'status' => DRUSH_PM_REQUESTED_PROJECT_NOT_PACKAGED,
  37. 'status_msg' => dt('Project was not packaged by drupal.org but obtained from !vcs. You need to enable !vcs_deploy module', array('!vcs' => $extension->vcs))
  38. );
  39. // The user may have requested to update a project matching this
  40. // extension. If it was by coincidence or error we don't mind as we've
  41. // already added an item to $update_info. Just clean up $requests.
  42. if (isset($requests[$name])) {
  43. unset($requests[$name]);
  44. }
  45. }
  46. }
  47. // Aditionally if the extension name is distinct to the project name and
  48. // the user asked to update the extension, fix the request.
  49. elseif ((isset($requests[$name])) && ($extension->name != $extension->info['project'])) {
  50. $requests[$extension->info['project']] = $requests[$name];
  51. unset($requests[$name]);
  52. }
  53. }
  54. // Add an item to $update_info for each request not present in $update_info.
  55. foreach ($requests as $name => $request) {
  56. if (!isset($update_info[$name])) {
  57. // Disabled projects.
  58. if ((isset($projects[$name])) && ($projects[$name]['status'] == 0)) {
  59. $update_info[$name] = array(
  60. 'title' => $name,
  61. 'existing_version' => $projects[$name]['version'],
  62. 'status' => DRUSH_PM_REQUESTED_PROJECT_NOT_UPDATEABLE
  63. );
  64. unset($requests[$name]);
  65. }
  66. // At this point we are unable to find matching installed project.
  67. // It does not exist at all or it is mispelled,...
  68. else {
  69. $update_info[$name] = array(
  70. 'title' => $name,
  71. 'existing_version' => 'Unknown',
  72. 'status'=> DRUSH_PM_REQUESTED_PROJECT_NOT_FOUND,
  73. );
  74. }
  75. }
  76. }
  77. // If specific versions were requested, match the requested release.
  78. foreach ($requests as $name => $request) {
  79. if (!empty($request['version'])) {
  80. $release = pm_get_release($request, $update_info[$name]);
  81. if (!$release) {
  82. $update_info[$name]['status'] = DRUSH_PM_REQUESTED_VERSION_NOT_FOUND;
  83. }
  84. else if ($release['version'] == $update_info[$name]['existing_version']) {
  85. $update_info[$name]['status'] = DRUSH_PM_REQUESTED_CURRENT;
  86. }
  87. else {
  88. $update_info[$name]['status'] = DRUSH_PM_REQUESTED_UPDATE;
  89. }
  90. // Set the candidate version to the requested release.
  91. $update_info[$name]['candidate_version'] = $release['version'];
  92. }
  93. }
  94. // Table headers.
  95. $rows[] = array(dt('Name'), dt('Installed version'), dt('Proposed version'), dt('Status'));
  96. // Process releases, notifying user of status and
  97. // building a list of proposed updates.
  98. $updateable = pm_project_filter($update_info, $rows, $security_only);
  99. // Pipe preparation.
  100. if (drush_get_context('DRUSH_PIPE')) {
  101. $pipe = "";
  102. foreach($updateable as $project) {
  103. $pipe .= $project['name']. " ";
  104. $pipe .= $project['existing_version']. " ";
  105. $pipe .= $project['candidate_version']. " ";
  106. $pipe .= str_replace(' ', '-', pm_update_filter($project)). "\n";
  107. }
  108. drush_print_pipe($pipe);
  109. // Automatically curtail update process if in pipe mode.
  110. $updateable = array();
  111. }
  112. $tmpfile = drush_tempnam('pm-updatecode.');
  113. $last = pm_update_last_check();
  114. drush_print(dt('Update information last refreshed: ') . ($last ? format_date($last) : dt('Never')));
  115. drush_print();
  116. drush_print(dt("Update status information on all installed and enabled Drupal projects:"));
  117. drush_print_table($rows, TRUE, array(3 => 40), $tmpfile);
  118. $contents = file_get_contents($tmpfile);
  119. drush_print($contents);
  120. drush_print();
  121. // If specific project updates were requested then remove releases for all
  122. // others.
  123. if (!empty($requests)) {
  124. foreach ($updateable as $name => $project) {
  125. if (!isset($requests[$name])) {
  126. unset($updateable[$name]);
  127. }
  128. }
  129. }
  130. // Prevent update of core if --no-core was specified.
  131. if (isset($updateable['drupal']) && drush_get_option('no-core', FALSE)) {
  132. unset($updateable['drupal']);
  133. drush_print(dt('Skipping core update (--no-core specified).'));
  134. }
  135. // If there are any locked projects that were not requested, then remove them.
  136. if (!empty($locked_list)) {
  137. foreach ($updateable as $name => $project) {
  138. if ((isset($locked_list[$name])) && (!isset($requests[$name]))) {
  139. unset($updateable[$name]);
  140. }
  141. }
  142. }
  143. // First check to see if there is a newer drush.
  144. $drush_update_available = NULL;
  145. if (drush_get_option('self-update', TRUE)) {
  146. $drush_update_available = drush_check_self_update();
  147. }
  148. // Do no updates in simulated mode.
  149. if (drush_get_context('DRUSH_SIMULATE')) {
  150. return drush_log(dt('No action taken in simulated mode.'), 'ok');
  151. return TRUE;
  152. }
  153. $core_update_available = FALSE;
  154. if (isset($updateable['drupal'])) {
  155. $drupal_project = $updateable['drupal'];
  156. unset($update_info['drupal']);
  157. unset($updateable['drupal']);
  158. // At present we need to update drupal core after non-core projects
  159. // are updated.
  160. if (empty($updateable)) {
  161. return _pm_update_core($drupal_project, $tmpfile);
  162. }
  163. // If there are modules other than drupal core enabled, then update them
  164. // first.
  165. else {
  166. $core_update_available = TRUE;
  167. if ($drupal_project['status'] == UPDATE_NOT_SECURE) {
  168. drush_print(dt("NOTE: A security update for the Drupal core is available."));
  169. }
  170. else {
  171. drush_print(dt("NOTE: A code update for the Drupal core is available."));
  172. }
  173. drush_print(dt("Drupal core will be updated after all of the non-core modules are updated.\n"));
  174. }
  175. }
  176. // If there are no releases to update, then print a final
  177. // exit message. Supress the message if we already printed
  178. // a message about a drush update being available.
  179. if (empty($updateable)) {
  180. if ($drush_update_available === TRUE) {
  181. return FALSE;
  182. }
  183. if ($security_only) {
  184. return drush_log(dt('No security updates available.'), 'ok');
  185. }
  186. else {
  187. return drush_log(dt('No code updates available.'), 'ok');
  188. }
  189. }
  190. // Offer to update to the identified releases.
  191. if (!pm_update_packages($updateable, $tmpfile)) {
  192. return FALSE;
  193. }
  194. // After projects are updated we can update core.
  195. if ($core_update_available) {
  196. drush_print();
  197. return _pm_update_core($drupal_project, $tmpfile);
  198. }
  199. }
  200. /**
  201. * Update drupal core, following interactive confirmation from the user.
  202. *
  203. * @param $project
  204. * The drupal project information from the drupal.org update service,
  205. * copied from $update_info['drupal']. @see drush_pm_updatecode.
  206. */
  207. function _pm_update_core(&$project, $tmpfile) {
  208. drush_include_engine('package_handler', drush_get_option('package-handler', 'wget'));
  209. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  210. drush_print(dt('Code updates will be made to drupal core.'));
  211. drush_print(dt("WARNING: Updating core will discard any modifications made to Drupal core files, most noteworthy among these are .htaccess and robots.txt. If you have made any modifications to these files, please back them up before updating so that you can re-create your modifications in the updated version of the file."));
  212. drush_print(dt("Note: Updating core can potentially break your site. It is NOT recommended to update production sites without prior testing."));
  213. drush_print();
  214. if (drush_get_option('notes', FALSE)) {
  215. drush_print('Obtaining release notes for above projects...');
  216. _drush_pm_releasenotes(array('drupal'), TRUE, $tmpfile);
  217. }
  218. if(!drush_confirm(dt('Do you really want to continue?'))) {
  219. drush_print(dt('Rolling back all changes. Run again with --no-core to update modules only.'));
  220. return drush_user_abort();
  221. }
  222. // We need write permission on $drupal_root.
  223. if (!is_writable($drupal_root)) {
  224. return drush_set_error('DRUSH_PATH_NO_WRITABLE', dt('Drupal root path is not writable.'));
  225. }
  226. // Create a directory 'core' if it does not already exist.
  227. $project['path'] = 'drupal-' . $project['candidate_version'];
  228. $project['full_project_path'] = $drupal_root . '/' . $project['path'];
  229. if (!is_dir($project['full_project_path'])) {
  230. drush_mkdir($project['full_project_path']);
  231. }
  232. // Create a list of directories to exclude from the update process.
  233. $skip_list = array('sites', $project['path']);
  234. // Add non-writable directories: we can't move them around.
  235. // We will also use $items_to_test later for $version_control check.
  236. $items_to_test = drush_scan_directory($drupal_root, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'basename', 0, TRUE);
  237. foreach (array_keys($items_to_test) as $item) {
  238. if (is_dir($item) && !is_writable($item)) {
  239. $skip_list[] = $item;
  240. unset($items_to_test[$item]);
  241. }
  242. }
  243. $project['skip_list'] = $skip_list;
  244. // Move all files and folders in $drupal_root to the new 'core' directory
  245. // except for the items in the skip list
  246. _pm_update_move_files($drupal_root, $project['full_project_path'], $project['skip_list']);
  247. // Set a context variable to indicate that rollback should reverse
  248. // the _pm_update_move_files above.
  249. drush_set_context('DRUSH_PM_DRUPAL_CORE', $project);
  250. if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) {
  251. return FALSE;
  252. }
  253. $project['base_project_path'] = dirname($project['full_project_path']);
  254. // Check we have a version control system, and it clears its pre-flight.
  255. if (!$version_control->pre_update($project, $items_to_test)) {
  256. return FALSE;
  257. }
  258. // Package handlers want the project directory in project_dir.
  259. $project['project_dir'] = $project['path'];
  260. // Update core.
  261. if (pm_update_project($project, $version_control) === FALSE) {
  262. return FALSE;
  263. }
  264. // Take the updated files in the 'core' directory that have been updated,
  265. // and move all except for the items in the skip list back to
  266. // the drupal root
  267. _pm_update_move_files($project['full_project_path'], $drupal_root, $project['skip_list']);
  268. drush_delete_dir($project['full_project_path']);
  269. // Version control engines expect full_project_path to exist and be accurate.
  270. $project['full_project_path'] = $project['base_project_path'];
  271. // If there is a backup target, then find items
  272. // in the backup target that do not exist at the
  273. // drupal root. These are to be moved back.
  274. if (array_key_exists('backup_target', $project)) {
  275. _pm_update_move_files($project['backup_target'], $drupal_root, $project['skip_list'], FALSE);
  276. _pm_update_move_files($project['backup_target'] . '/profiles', $drupal_root . '/profiles', array('default'), FALSE);
  277. }
  278. pm_update_complete($project, $version_control);
  279. return TRUE;
  280. }
  281. /**
  282. * Move some files from one location to another
  283. */
  284. function _pm_update_move_files($src_dir, $dest_dir, $skip_list, $remove_conflicts = TRUE) {
  285. $items_to_move = drush_scan_directory($src_dir, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'filename', 0, TRUE);
  286. foreach ($items_to_move as $filename => $info) {
  287. if ($remove_conflicts) {
  288. drush_delete_dir($dest_dir . '/' . basename($filename));
  289. }
  290. if (!file_exists($dest_dir . '/' . basename($filename))) {
  291. $move_result = drush_move_dir($filename, $dest_dir . '/' . basename($filename));
  292. }
  293. }
  294. return TRUE;
  295. }
  296. /**
  297. * Update projects according to an array of releases and print the release notes
  298. * for each project, following interactive confirmation from the user.
  299. *
  300. * @param $update_info
  301. * An array of projects from the drupal.org update service, with an additional
  302. * array key candidate_version that specifies the version to be installed.
  303. */
  304. function pm_update_packages($update_info, $tmpfile) {
  305. drush_include_engine('package_handler', drush_get_option('package-handler', 'wget'));
  306. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  307. $print = '';
  308. $status = array();
  309. foreach($update_info as $project) {
  310. $print .= $project['title'] . " [" . $project['name'] . '-' . $project['candidate_version'] . "], ";
  311. $status[$project['status']] = $project['status'];
  312. }
  313. // We print the list of the projects that need to be updated.
  314. if (isset($status[UPDATE_NOT_SECURE])) {
  315. if (isset($status[UPDATE_NOT_CURRENT])) {
  316. $title = (dt('Security and code updates will be made to the following projects:'));
  317. }
  318. else {
  319. $title = (dt('Security updates will be made to the following projects:'));
  320. }
  321. }
  322. else {
  323. $title = (dt('Code updates will be made to the following projects:'));
  324. }
  325. $print = "$title " . (substr($print, 0, strlen($print)-2));
  326. drush_print($print);
  327. file_put_contents($tmpfile, "\n\n$print\n\n", FILE_APPEND);
  328. // Print the release notes for projects to be updated.
  329. if (drush_get_option('notes', FALSE)) {
  330. drush_print('Obtaining release notes for above projects...');
  331. _drush_pm_releasenotes(array_keys($update_info), TRUE, $tmpfile);
  332. }
  333. // We print some warnings before the user confirms the update.
  334. drush_print();
  335. if (drush_get_option('no-backup', FALSE)) {
  336. drush_print(dt("Note: You have selected to not store backups."));
  337. }
  338. else {
  339. drush_print(dt("Note: A backup of your project will be stored to backups directory if it is not managed by a supported version control system."));
  340. drush_print(dt('Note: If you have made any modifications to any file that belongs to one of these projects, you will have to migrate those modifications after updating.'));
  341. }
  342. if(!drush_confirm(dt('Do you really want to continue with the update process?'))) {
  343. return drush_user_abort();
  344. }
  345. // Now we start the actual updating.
  346. foreach($update_info as $project) {
  347. if (empty($project['path'])) {
  348. return drush_set_error('DRUSH_PM_UPDATING_NO_PROJECT_PATH', dt('The !project project path is not available, perhaps the !type is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type'])));
  349. }
  350. drush_log(dt('Starting to update !project code at !dir...', array('!project' => $project['title'], '!dir' => $project['path'])));
  351. // Create the projects directory and base (parent) directory.
  352. $project['full_project_path'] = $drupal_root . '/' . $project['path'];
  353. // Check that the directory exists, and is where we expect it to be.
  354. if (stripos($project['path'], $project['project_type']) === FALSE || !is_dir($project['full_project_path'])) {
  355. return drush_set_error('DRUSH_PM_UPDATING_PATH_NOT_FOUND', dt('The !project directory could not be found within the !types directory at !full_project_path, perhaps the project is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type'], '!full_project_path' => $project['full_project_path'])));
  356. }
  357. if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) {
  358. return FALSE;
  359. }
  360. $project['base_project_path'] = dirname($project['full_project_path']);
  361. // Check we have a version control system, and it clears its pre-flight.
  362. if (!$version_control->pre_update($project)) {
  363. return FALSE;
  364. }
  365. // Package handlers want the name of the directory in project_dir.
  366. // It may be different to the project name for pm-download.
  367. // Perhaps we want here filename($project['full_project_path']).
  368. $project['project_dir'] = $project['name'];
  369. // Run update on one project.
  370. if (pm_update_project($project, $version_control) === FALSE) {
  371. return FALSE;
  372. }
  373. pm_update_complete($project, $version_control);
  374. }
  375. return TRUE;
  376. }
  377. /**
  378. * Update one project -- a module, theme or Drupal core.
  379. *
  380. * @param $project
  381. * The project to upgrade. $project['full_project_path'] must be set
  382. * to the location where this project is stored.
  383. */
  384. function pm_update_project($project, $version_control) {
  385. // 1. If the version control engine is a proper vcs we need to remove project
  386. // files in order to not have orphan files after update.
  387. // 2. If the package-handler is cvs or git, it will remove upstream removed
  388. // files and no orphans will exist after update.
  389. // So, we must remove all files previous update if the directory is not a
  390. // working copy of cvs or git but we don't need to remove them if the version
  391. // control engine is backup, as it did already move the project out to the
  392. // backup directory.
  393. if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) {
  394. // Find and unlink all files but the ones in the vcs control directories.
  395. $skip_list = array('.', '..');
  396. $skip_list = array_merge($skip_list, drush_version_control_reserved_files());
  397. drush_scan_directory($project['full_project_path'], '/.*/', $skip_list, 'unlink', TRUE, 'filename', 0, TRUE);
  398. }
  399. // Add the project to a context so we can roll back if needed.
  400. $updated = drush_get_context('DRUSH_PM_UPDATED');
  401. $updated[] = $project;
  402. drush_set_context('DRUSH_PM_UPDATED', $updated);
  403. if (!package_handler_update_project($project, $project['releases'][$project['candidate_version']])) {
  404. return drush_set_error('DRUSH_PM_UPDATING_FAILED', dt('Updating project !project failed. Attempting to roll back to previously installed version.', array('!project' => $project['name'])));
  405. }
  406. // If the version control engine is a proper vcs we also need to remove
  407. // orphan directories.
  408. if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) {
  409. $files = drush_find_empty_directories($project['full_project_path'], $version_control->reserved_files());
  410. array_map('drush_delete_dir', $files);
  411. }
  412. return TRUE;
  413. }
  414. /**
  415. * Run the post-update hooks after updatecode is complete for one project.
  416. */
  417. function pm_update_complete($project, $version_control) {
  418. drush_print(dt('Project !project was updated successfully. Installed version is now !version.', array('!project' => $project['name'], '!version' => $project['candidate_version'])));
  419. drush_command_invoke_all('pm_post_update', $project['name'], $project['releases'][$project['candidate_version']]);
  420. $version_control->post_update($project);
  421. }
  422. function drush_pm_updatecode_rollback() {
  423. $projects = array_reverse(drush_get_context('DRUSH_PM_UPDATED', array()));
  424. foreach($projects as $project) {
  425. drush_log(dt('Rolling back update of !project code ...', array('!project' => $project['title'])));
  426. // Check we have a version control system, and it clears it's pre-flight.
  427. if (!$version_control = drush_pm_include_version_control($project['path'])) {
  428. return FALSE;
  429. }
  430. $version_control->rollback($project);
  431. }
  432. // Post rollback, we will do additional repair if the project is drupal core.
  433. $drupal_core = drush_get_context('DRUSH_PM_DRUPAL_CORE', FALSE);
  434. if ($drupal_core) {
  435. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  436. _pm_update_move_files($drupal_core['full_project_path'], $drupal_root, $drupal_core['skip_list']);
  437. drush_delete_dir($drupal_core['full_project_path']);
  438. }
  439. }
  440. /**
  441. * Return an array of updateable projects and fill $rows.
  442. *
  443. * Array of updateable projects is obtained from calculated project update
  444. * status and $security_only flag.
  445. */
  446. function pm_project_filter(&$update_info, &$rows, $security_only) {
  447. $updateable = array();
  448. foreach ($update_info as $key => $project) {
  449. if (empty($project['title'])) {
  450. continue;
  451. }
  452. switch($project['status']) {
  453. case DRUSH_PM_REQUESTED_UPDATE:
  454. $status = dt('Specified version available');
  455. $project['updateable'] = TRUE;
  456. break;
  457. case DRUSH_PM_REQUESTED_CURRENT:
  458. $status = dt('Specified version already installed');
  459. break;
  460. case DRUSH_PM_REQUESTED_PROJECT_NOT_PACKAGED:
  461. $status = $project['status_msg'];
  462. break;
  463. case DRUSH_PM_REQUESTED_VERSION_NOT_FOUND:
  464. $status = dt('Specified version not found');
  465. break;
  466. case DRUSH_PM_REQUESTED_PROJECT_NOT_FOUND:
  467. $status = dt('Specified project not found');
  468. break;
  469. case DRUSH_PM_REQUESTED_PROJECT_NOT_UPDATEABLE:
  470. $status = dt('Project has no enabled extensions and can\'t be updated');
  471. break;
  472. default:
  473. $status = pm_update_filter($project);
  474. break;
  475. }
  476. // Special checking: if drush decides that the candidate version is older
  477. // than the installed version, then we will set the candidate version to
  478. // the installed version.
  479. if (isset($project['candidate_version'], $project['releases'][$project['candidate_version']], $project['releases'][$project['existing_version']])) {
  480. if ($project['releases'][$project['candidate_version']]['date'] < $project['releases'][$project['existing_version']]['date']) {
  481. $project['candidate_version'] = $project['existing_version'];
  482. }
  483. }
  484. if (isset($project['locked'])) {
  485. $status = $project['locked'] . " ($status)";
  486. }
  487. // Persist candidate_version in $update_info (plural).
  488. if (empty($project['candidate_version'])) {
  489. $update_info[$key]['candidate_version'] = $project['existing_version']; // Default to no change
  490. }
  491. else {
  492. $update_info[$key]['candidate_version'] = $project['candidate_version'];
  493. }
  494. if (!empty($project['updateable'])) {
  495. $updateable[$key] = $project;
  496. // Find only security updates
  497. if ($security_only && ($project['status'] != UPDATE_NOT_SECURE)) {
  498. unset($updateable[$key]);
  499. }
  500. }
  501. $rows[] = array($project['title'], $project['existing_version'], $update_info[$key]['candidate_version'], $status);
  502. }
  503. return $updateable;
  504. }
  505. /**
  506. * Set a release to a recommended version (if available), and set as updateable.
  507. */
  508. function pm_release_recommended(&$project) {
  509. if (isset($project['recommended'])) {
  510. $project['candidate_version'] = $project['recommended'];
  511. $project['updateable'] = TRUE;
  512. }
  513. }
  514. /**
  515. * Get the a best release match for a requested update.
  516. *
  517. * @param $request A information array for the requested project
  518. * @param $project A project information array for this project, as returned by an update service from pm_get_extensions()
  519. */
  520. function pm_get_release($request, $project) {
  521. $minor = '';
  522. $version_patch_changed = '';
  523. if ($request['version']) {
  524. // The user specified a specific version - try to find that exact version
  525. foreach($project['releases'] as $version => $release) {
  526. // Ignore unpublished releases.
  527. if ($release['status'] != 'published') {
  528. continue;
  529. }
  530. // Straight match
  531. if (!isset($recommended_version) && $release['version'] == $request['version']) {
  532. $recommended_version = $version;
  533. }
  534. }
  535. }
  536. else {
  537. // No version specified - try to find the best version we can
  538. foreach($project['releases'] as $version => $release) {
  539. // Ignore unpublished releases.
  540. if ($release['status'] != 'published') {
  541. continue;
  542. }
  543. // If we haven't found a recommended version yet, put the dev
  544. // version as recommended and hope it gets overwritten later.
  545. // Look for the 'latest version' if we haven't found it yet.
  546. // Latest version is defined as the most recent version for the
  547. // default major version.
  548. if (!isset($latest_version) && $release['version_major'] == $project['default_major']) {
  549. $latest_version = $version;
  550. }
  551. if (!isset($recommended_version) && $release['version_major'] == $project['default_major']) {
  552. if ($minor != $release['version_patch']) {
  553. $minor = $release['version_patch'];
  554. $version_patch_changed = $version;
  555. }
  556. if (empty($release['version_extra']) && $minor == $release['version_patch']) {
  557. $recommended_version = $version_patch_changed;
  558. }
  559. continue;
  560. }
  561. }
  562. }
  563. if (isset($recommended_version)) {
  564. return $project['releases'][$recommended_version];
  565. }
  566. else if (isset($latest_version)) {
  567. return $project['releases'][$latest_version];
  568. }
  569. else {
  570. return false;
  571. }
  572. }