Yaffs site version 1.1
[yaffs-website] / vendor / drush / drush / commands / pm / updatecode.pm.inc
1 <?php
2
3 /**
4  * @file
5  * pm-updatecode command implementation.
6  */
7
8 use Drush\Log\LogLevel;
9
10 /**
11  * Command callback. Displays update status info and allows to update installed projects.
12  *
13  * Pass specific projects as arguments, otherwise we update all that have
14  * candidate releases.
15  *
16  * This command prompts for confirmation before updating, so it is safe to run
17  * just to check on. In this case, say at the confirmation prompt.
18  */
19 function drush_pm_updatecode() {
20   // In --pipe mode, just run pm-updatestatus and exit.
21   if (drush_get_context('DRUSH_PIPE')) {
22     drush_set_option('strict', 0);
23     return drush_invoke('pm-updatestatus');
24   }
25
26   $update_status = drush_get_engine('update_status');
27
28   // Get specific requests.
29   $requests = pm_parse_arguments(func_get_args(), FALSE);
30
31   // Print report of modules to update, and record
32   // result of that function in $update_info.
33   $updatestatus_options = array();
34   foreach (array('lock', 'unlock', 'lock-message', 'update-backend', 'check-disabled', 'security-only') as $option) {
35     $value = drush_get_option($option, FALSE);
36     if ($value) {
37       $updatestatus_options[$option] = $value;
38     }
39   }
40   $backend_options = array(
41     'integrate' => FALSE,
42   );
43   $values = drush_invoke_process("@self", 'pm-updatestatus', func_get_args(), $updatestatus_options, $backend_options);
44   if (!is_array($values) || $values['error_status']) {
45     return drush_set_error('pm-updatestatus failed.');
46   }
47   $last = $update_status->lastCheck();
48   drush_print(dt('Update information last refreshed: ') . ($last  ? format_date($last) : dt('Never')));
49   drush_print($values['output']);
50
51   $update_info = $values['object'];
52
53   // Prevent update of core if --no-core was specified.
54   if (isset($update_info['drupal']) && drush_get_option('no-core', FALSE)) {
55     unset($update_info['drupal']);
56     drush_print(dt('Skipping core update (--no-core specified).'));
57   }
58
59   // Remove locked and non-updateable projects.
60   foreach ($update_info as $name => $project) {
61     if ((isset($project['locked']) && !isset($requests[$name])) || (!isset($project['updateable']) || !$project['updateable'])) {
62       unset($update_info[$name]);
63     }
64   }
65
66   // Do no updates in simulated mode.
67   if (drush_get_context('DRUSH_SIMULATE')) {
68     return drush_log(dt('No action taken in simulated mode.'), LogLevel::OK);
69     return TRUE;
70   }
71
72   $tmpfile = drush_tempnam('pm-updatecode.');
73
74   $core_update_available = FALSE;
75   if (isset($update_info['drupal'])) {
76     $drupal_project = $update_info['drupal'];
77     unset($update_info['drupal']);
78
79     // At present we need to update drupal core after non-core projects
80     // are updated.
81     if (empty($update_info)) {
82       return _pm_update_core($drupal_project, $tmpfile);
83     }
84     // If there are modules other than drupal core enabled, then update them
85     // first.
86     else {
87       $core_update_available = TRUE;
88       if ($drupal_project['status'] == DRUSH_UPDATESTATUS_NOT_SECURE) {
89         drush_print(dt("NOTE: A security update for the Drupal core is available."));
90       }
91       else {
92         drush_print(dt("NOTE: A code update for the Drupal core is available."));
93       }
94       drush_print(dt("Drupal core will be updated after all of the non-core projects are updated.\n"));
95     }
96   }
97
98   // If there are no releases to update, then print a final
99   // exit message.
100   if (empty($update_info)) {
101     if (drush_get_option('security-only')) {
102       return drush_log(dt('No security updates available.'), LogLevel::OK);
103     }
104     else {
105       return drush_log(dt('No code updates available.'), LogLevel::OK);
106     }
107   }
108
109   // Offer to update to the identified releases.
110   if (!pm_update_packages($update_info, $tmpfile)) {
111     return FALSE;
112   }
113
114   // After projects are updated we can update core.
115   if ($core_update_available) {
116     drush_print();
117     return _pm_update_core($drupal_project, $tmpfile);
118   }
119 }
120
121 /**
122  * Update drupal core, following interactive confirmation from the user.
123  *
124  * @param $project
125  *   The drupal project information from the drupal.org update service,
126  *   copied from $update_info['drupal'].  @see drush_pm_updatecode.
127  *
128  * @return bool
129  *   Success or failure. An error message will be logged.
130  */
131 function _pm_update_core(&$project, $tmpfile) {
132   $release_info = drush_get_engine('release_info');
133
134   drush_print(dt('Code updates will be made to drupal core.'));
135   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."));
136   drush_print(dt("Note: Updating core can potentially break your site. It is NOT recommended to update production sites without prior testing."));
137   drush_print();
138   if (drush_get_option('notes', FALSE)) {
139     drush_print('Obtaining release notes for above projects...');
140     #TODO# Build the $request array from info in $project.
141     $request = pm_parse_request('drupal');
142     $release_info->get($request)->getReleaseNotes(NULL, TRUE, $tmpfile);
143   }
144   if(!drush_confirm(dt('Do you really want to continue?'))) {
145     drush_print(dt('Rolling back all changes. Run again with --no-core to update modules only.'));
146     return drush_user_abort();
147   }
148
149   $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
150
151   // We need write permission on $drupal_root.
152   if (!is_writable($drupal_root)) {
153     return drush_set_error('DRUSH_PATH_NO_WRITABLE', dt('Drupal root path is not writable.'));
154   }
155
156   // Create a directory 'core' if it does not already exist.
157   $project['path'] = 'drupal-' . $project['candidate_version'];
158   $project['full_project_path'] = $drupal_root . '/' . $project['path'];
159   if (!is_dir($project['full_project_path'])) {
160     drush_mkdir($project['full_project_path']);
161   }
162
163   // Create a list of directories to exclude from the update process.
164   // On Drupal >=8 skip also directories in the document root.
165   if (drush_drupal_major_version() >= 8) {
166     $skip_list = array('sites', $project['path'], 'modules', 'profiles', 'themes');
167   }
168   else {
169     $skip_list = array('sites', $project['path']);
170   }
171   // Add non-writable directories: we can't move them around.
172   // We will also use $items_to_test later for $version_control check.
173   $items_to_test = drush_scan_directory($drupal_root, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'basename', 0, TRUE);
174   foreach (array_keys($items_to_test) as $item) {
175     if (is_dir($item) && !is_writable($item)) {
176       $skip_list[] = $item;
177       unset($items_to_test[$item]);
178     }
179     elseif (is_link($item)) {
180       $skip_list[] = $item;
181       unset($items_to_test[$item]);
182     }
183   }
184   $project['skip_list'] = $skip_list;
185
186   // Move all files and folders in $drupal_root to the new 'core' directory
187   // except for the items in the skip list
188   _pm_update_move_files($drupal_root, $project['full_project_path'], $project['skip_list']);
189
190   // Set a context variable to indicate that rollback should reverse
191   // the _pm_update_move_files above.
192   drush_set_context('DRUSH_PM_DRUPAL_CORE', $project);
193
194   if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) {
195     return FALSE;
196   }
197
198   // Check we have a version control system, and it clears its pre-flight.
199   if (!$version_control->pre_update($project, $items_to_test)) {
200     return FALSE;
201   }
202
203   // Update core.
204   if (pm_update_project($project, $version_control) === FALSE) {
205     return FALSE;
206   }
207
208   // Take the updated files in the 'core' directory that have been updated,
209   // and move all except for the items in the skip list back to
210   // the drupal root.
211   _pm_update_move_files($project['full_project_path'], $drupal_root, $project['skip_list']);
212   drush_delete_dir($project['full_project_path']);
213   $project['full_project_path'] = $drupal_root;
214
215   // If there is a backup target, then find items
216   // in the backup target that do not exist at the
217   // drupal root.  These are to be moved back.
218   if (array_key_exists('backup_target', $project)) {
219     _pm_update_move_files($project['backup_target'], $drupal_root, $project['skip_list'], FALSE);
220     _pm_update_move_files($project['backup_target'] . '/profiles', $drupal_root . '/profiles', array('default'), FALSE);
221   }
222
223   pm_update_finish($project, $version_control);
224
225   return TRUE;
226 }
227
228 /**
229  * Move some files from one location to another.
230  */
231 function _pm_update_move_files($src_dir, $dest_dir, $skip_list, $remove_conflicts = TRUE) {
232   $items_to_move = drush_scan_directory($src_dir, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'filename', 0, TRUE);
233   foreach ($items_to_move as $filename => $info) {
234     if ($remove_conflicts) {
235       drush_delete_dir($dest_dir . '/' . basename($filename));
236     }
237     if (!file_exists($dest_dir . '/' . basename($filename))) {
238       $move_result = drush_move_dir($filename,  $dest_dir . '/' . basename($filename));
239     }
240   }
241   return TRUE;
242 }
243
244 /**
245  * Update projects according to an array of releases and print the release notes
246  * for each project, following interactive confirmation from the user.
247  *
248  * @param $update_info
249  *   An array of projects from the drupal.org update service, with an additional
250  *   array key candidate_version that specifies the version to be installed.
251  */
252 function pm_update_packages($update_info, $tmpfile) {
253   $release_info = drush_get_engine('release_info');
254
255   $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
256
257   $print = '';
258   $status = array();
259   foreach($update_info as $project) {
260     $print .= $project['title'] . " [" . $project['name'] . '-' . $project['candidate_version'] . "], ";
261     $status[$project['status']] = $project['status'];
262   }
263   // We print the list of the projects that need to be updated.
264   if (isset($status[DRUSH_UPDATESTATUS_NOT_SECURE])) {
265     if (isset($status[DRUSH_UPDATESTATUS_NOT_CURRENT])) {
266       $title = (dt('Security and code updates will be made to the following projects:'));
267     }
268     else {
269       $title = (dt('Security updates will be made to the following projects:'));
270     }
271   }
272   else {
273     $title = (dt('Code updates will be made to the following projects:'));
274   }
275   $print = "$title " . (substr($print, 0, strlen($print)-2));
276   drush_print($print);
277   file_put_contents($tmpfile, "\n\n$print\n\n", FILE_APPEND);
278
279   // Print the release notes for projects to be updated.
280   if (drush_get_option('notes', FALSE)) {
281     drush_print('Obtaining release notes for above projects...');
282     #TODO# Build the $request array from info in $project.
283     foreach (array_keys($update_info) as $project_name) {
284       $request = pm_parse_request($project_name);
285       $release_info->get($request)->getReleaseNotes(NULL, TRUE, $tmpfile);
286     }
287   }
288
289   // We print some warnings before the user confirms the update.
290   drush_print();
291   if (drush_get_option('no-backup', FALSE)) {
292     drush_print(dt("Note: You have selected to not store backups."));
293   }
294   else {
295     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."));
296     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.'));
297   }
298   if(!drush_confirm(dt('Do you really want to continue with the update process?'))) {
299     return drush_user_abort();
300   }
301
302   // Now we start the actual updating.
303   foreach($update_info as $project) {
304     if (empty($project['path'])) {
305       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'])));
306     }
307     drush_log(dt('Starting to update !project code at !dir...', array('!project' => $project['title'], '!dir' => $project['path'])));
308
309     // Define and check the full path to project directory and base (parent) directory.
310     $project['full_project_path'] = $drupal_root . '/' . $project['path'];
311     if (stripos($project['path'], $project['project_type']) === FALSE || !is_dir($project['full_project_path'])) {
312       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'])));
313     }
314     if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) {
315       return FALSE;
316     }
317
318     // Check we have a version control system, and it clears its pre-flight.
319     if (!$version_control->pre_update($project)) {
320       return FALSE;
321     }
322
323     // Run update on one project.
324     if (pm_update_project($project, $version_control) === FALSE) {
325       return FALSE;
326     }
327     pm_update_finish($project, $version_control);
328   }
329
330   return TRUE;
331 }
332
333 /**
334  * Update one project -- a module, theme or Drupal core.
335  *
336  * @param $project
337  *   The project to upgrade.  $project['full_project_path'] must be set
338  *   to the location where this project is stored.
339  * @return bool
340  *   Success or failure. An error message will be logged.
341  */
342 function pm_update_project($project, $version_control) {
343   // 1. If the version control engine is a proper vcs we need to remove project
344   // files in order to not have orphan files after update.
345   // 2. If the package-handler is cvs or git, it will remove upstream removed
346   // files and no orphans will exist after update.
347   // So, we must remove all files previous update if the directory is not a
348   // working copy of cvs or git but we don't need to remove them if the version
349   // control engine is backup, as it did already move the project out to the
350   // backup directory.
351   if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) {
352     // Find and unlink all files but the ones in the vcs control directories.
353     $skip_list = array('.', '..');
354     $skip_list = array_merge($skip_list, drush_version_control_reserved_files());
355     drush_scan_directory($project['full_project_path'], '/.*/', $skip_list, 'unlink', TRUE, 'filename', 0, TRUE);
356   }
357
358   // Add the project to a context so we can roll back if needed.
359   $updated = drush_get_context('DRUSH_PM_UPDATED');
360   $updated[] = $project;
361   drush_set_context('DRUSH_PM_UPDATED', $updated);
362
363   if (!package_handler_update_project($project, $project['releases'][$project['candidate_version']])) {
364     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'])));
365   }
366
367   // If the version control engine is a proper vcs we also need to remove
368   // orphan directories.
369   if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) {
370     $files = drush_find_empty_directories($project['full_project_path'], $version_control->reserved_files());
371     array_map('drush_delete_dir', $files);
372   }
373
374   return TRUE;
375 }
376
377 /**
378  * Run the post-update hooks after updatecode is finished for one project.
379  */
380 function pm_update_finish($project, $version_control) {
381   drush_print(dt('Project !project was updated successfully. Installed version is now !version.', array('!project' => $project['name'], '!version' => $project['candidate_version'])));
382   drush_command_invoke_all('pm_post_update', $project['name'], $project['releases'][$project['candidate_version']], $project);
383   $version_control->post_update($project);
384 }
385
386 /**
387  * Rollback the update process.
388  */
389 function drush_pm_updatecode_rollback() {
390   $projects = array_reverse(drush_get_context('DRUSH_PM_UPDATED', array()));
391   foreach($projects as $project) {
392     drush_log(dt('Rolling back update of !project code ...', array('!project' => $project['title'])));
393
394     // Check we have a version control system, and it clears it's pre-flight.
395     if (!$version_control = drush_pm_include_version_control($project['path'])) {
396       return FALSE;
397     }
398     $version_control->rollback($project);
399   }
400
401   // Post rollback, we will do additional repair if the project is drupal core.
402   $drupal_core = drush_get_context('DRUSH_PM_DRUPAL_CORE', FALSE);
403   if ($drupal_core) {
404     $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
405     _pm_update_move_files($drupal_core['full_project_path'], $drupal_root, $drupal_core['skip_list']);
406     drush_delete_dir($drupal_core['full_project_path']);
407   }
408 }
409