Revert "Re-applying patch from http://drupal.org/files/views_integration-588728-11...
[sandbox/ergonlogic/1226310.git] / modules / hosting / task / hosting_task.module
1 <?php
2 /**
3 * @file Web server node type is defined here.
4 */
5
6 /**
7 * Implementation of hook_menu().
8 */
9 function hosting_task_menu() {
10 $items = array();
11 foreach (array('site', 'platform', 'server') as $type) {
12 if (!($tasks = hosting_available_tasks($type))) {
13 // this is to workaround problems in the upgrade path where the
14 // hook returns nothing (e.g. for server)
15 continue;
16 }
17 foreach ($tasks as $task => $info) {
18 if (empty($info['hidden'])) {
19 $path = sprintf("node/%%hosting_%s_node/%s_%s", $type, $type, $task);
20 $items[$path] = array(
21 'title' => $info['title'],
22 'description' => $info['description'],
23 'page callback' => 'drupal_get_form',
24 'page arguments' => array('hosting_task_confirm_form', 1, $task),
25 'access callback' => 'hosting_task_menu_access_csrf',
26 'access arguments' => array(1, $task),
27 'type' => MENU_CALLBACK,
28 );
29 $items[$path] = array_merge($items[$path], $info);
30 }
31 }
32 }
33
34 $items['hosting/tasks/%node/list'] = array(
35 'title' => t('Task list'),
36 'description' => t('AJAX callback for refreshing task list'),
37 'page callback' => 'hosting_task_ajax_list',
38 'page arguments' => array(2),
39 'access callback' => 'node_access',
40 'access arguments' => array('view', 2),
41 'type' => MENU_CALLBACK
42 );
43
44
45 $items['hosting/tasks/%node/cancel'] = array(
46 'title' => t('Task list'),
47 'description' => t('Callback for stopping tasks'),
48 'page callback' => 'hosting_task_cancel',
49 'page arguments' => array(2),
50 'access callback' => 'hosting_task_cancel_access',
51 'access arguments' => array(2),
52 'type' => MENU_CALLBACK
53 );
54
55
56 $items['hosting/tasks/queue'] = array(
57 'title' => t('Task list'),
58 'description' => t('AJAX callback for refreshing task queue'),
59 'page callback' => 'hosting_task_ajax_queue',
60 'access arguments' => array('access task logs'),
61 'type' => MENU_CALLBACK
62 );
63
64 return $items;
65 }
66
67 function hosting_task_ajax_list($node) {
68 $return['markup'] = hosting_task_table($node);
69 $return['changed'] = $node->changed;
70 $return['navigate_url'] = url('node/' . $node->nid);
71 drupal_json($return);
72 exit();
73 }
74
75 function hosting_task_ajax_queue() {
76 $return['markup'] = hosting_task_queue_block();
77 drupal_json($return);
78 exit();
79 }
80
81 function hosting_task_cancel($node) {
82 if ($node->type == 'task') {
83 if ($node->task_status == 0) {
84 $ref = node_load($node->rid);
85
86 hosting_add_task($node->rid, $node->task_type, array(), HOSTING_TASK_ERROR);
87 // Log the cancellation
88 hosting_task_log($node->vid, 'error', t("Task was cancelled."));
89 drupal_goto('node/' . $node->rid);
90 }
91 }
92
93 drupal_access_denied();
94 }
95
96 /*
97 * Implementation of hook_access()
98 */
99
100 function hosting_task_cancel_access($node) {
101 // bring $user into scope, so we can test task ownership
102 global $user;
103
104 // To prevent CSRF attacks, a unique token based upon user is used. Deny
105 // access if the token is missing or invalid.
106 if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $user->uid)) {
107 return FALSE;
108 }
109
110 // 'administer tasks' allows cancelling any and all tasks on the system
111 if (user_access('administer tasks')) {
112 return TRUE;
113 }
114 // 'cancel own tasks' allows cancelling any task the user *could have* created, on nodes she can view
115 if (user_access('cancel own tasks') && user_access('create ' . $node->task_type . ' task') && node_access('view', $node)) {
116 return TRUE;
117 }
118 }
119
120 /**
121 * Task access controls
122 *
123 * This function defines which tasks should be showed to the user but
124 * especially which will be accessible to him, in the permissions system.
125 *
126 * @arg $node object
127 * the node object we're trying to access
128 *
129 * @arg $task string
130 * the task type we're trying to do on the $node
131 *
132 * @see hosting_task_menu()
133 */
134 function hosting_task_menu_access($node, $task) {
135 if (user_access("create " . $task . " task")) {
136 if ($node->type == 'site') {
137 if (hosting_task_outstanding($node->nid, 'delete') || ($node->site_status == HOSTING_SITE_DELETED)) {
138 return FALSE;
139 }
140 if (($task == 'login-reset') && ($node->site_status != HOSTING_SITE_ENABLED)) {
141 return FALSE;
142 }
143 $safe_tasks = array('backup', 'backup-delete', 'verify', 'enable');
144 if (!in_array($task, $safe_tasks)) {
145 // Don't show certain tasks if the site is the 'special' main aegir site
146 $profile = node_load($node->profile);
147 if ($profile->short_name == 'hostmaster') {
148 return FALSE;
149 }
150 }
151
152 $site_enabled = (hosting_task_outstanding($node->nid, 'enable') || ($node->site_status == HOSTING_SITE_ENABLED));
153 $deletable = ($task == "delete");
154 $enabable = ($task == "enable");
155
156 $delete_or_enable = ($deletable || $enabable);
157
158 // If the site is not enabled, we can either delete it, or enable it again
159 if (!$site_enabled) {
160 return ($delete_or_enable);
161 }
162 else {
163 // Site is enabled
164 return (
165 // If we can just delete a site without disabling, allow that
166 (!variable_get('hosting_require_disable_before_delete', TRUE)) && $deletable
167 // Otherwise we must disable it first, hide the delete task and the enable task as well
168 || !$delete_or_enable
169 );
170 }
171 }
172 if ($node->type == 'platform') {
173 // if the user can't edit this node, he can't create tasks on it
174 if (!node_access('update', $node, $GLOBALS['user'])) {
175 return FALSE;
176 }
177 // If the platform is in a deleted state, nothing else can be done with it
178 if (hosting_task_outstanding($node->nid, 'delete') || ($node->platform_status == HOSTING_PLATFORM_DELETED)) {
179 return FALSE;
180 }
181 // If the platform's been locked, we can unlock it, delete, batch migrate existing sites or verify
182 if ($node->platform_status == HOSTING_PLATFORM_LOCKED) {
183 $platform_tasks = array('verify', 'unlock', 'delete', 'migrate');
184 }
185 else {
186 // If the platform's unlocked, we can lock it, delete it or batch migrate sites
187 $platform_tasks = array('verify', 'lock', 'delete', 'migrate');
188 }
189 return (in_array($task, $platform_tasks));
190 }
191 if ($node->type === 'server') {
192 // If the user can't edit this node, he can't create tasks on it.
193 if (!node_access('update', $node, $GLOBALS['user'])) {
194 return FALSE;
195 }
196 // todo probably need more checks
197 return TRUE;
198 }
199 }
200 return FALSE;
201 }
202
203 /**
204 * Access callback helper for hosting task menu items.
205 *
206 * Implemented as a helper function since we only want to validate the CSRF
207 * token when the user accesses a certain path, not when (for example) building
208 * the list of tasks a user has access to.
209 *
210 * @see hosting_task_menu_access()
211 */
212 function hosting_task_menu_access_csrf($node, $task) {
213 global $user;
214
215 $interactive_tasks = array('migrate', 'clone');
216
217 // To prevent CSRF attacks, a unique token based upon user is used. Deny
218 // access if the token is missing or invalid. We only do this on
219 // non-interactive tasks.
220 if (!in_array($task, $interactive_tasks) && (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $user->uid))) {
221 return FALSE;
222 }
223 // Call the main menu access handler.
224 return hosting_task_menu_access($node, $task);
225 }
226
227 /**
228 * Implementation of hook_node_info()
229 */
230 function hosting_task_node_info() {
231 #management
232 $types["task"] = array(
233 "type" => 'task', "name" => 'Task',
234 "module" => 'hosting_task',
235 "has_title" => FALSE, "title_label" => '',
236 "description" => hosting_node_help("task"),
237 "has_body" => 0, "body_label" => '', "min_word_count" => 0);
238
239 return $types;
240 }
241
242 /**
243 * Implementation of hook_access()
244 */
245 function hosting_task_access($op, $node, $account) {
246 if(user_access('administer tasks', $account)) {
247 return TRUE;
248 }
249 }
250
251 /**
252 * Implementation of hook_perm()
253 */
254 function hosting_task_perm() {
255 return array(
256 'administer tasks',
257 'create backup task',
258 'create restore task',
259 'create disable task',
260 'create enable task',
261 'create delete task',
262 'create verify task',
263 'create lock task',
264 'create unlock task',
265 'create login-reset task',
266 'create backup-delete task',
267 'view own tasks',
268 'view task',
269 'access task logs',
270 'retry failed tasks',
271 'cancel own tasks',
272 );
273 }
274
275 /**
276 * Implementation of hook_hosting_queues
277 *
278 * Return a list of queues that this module needs to manage.
279 */
280 function hosting_task_hosting_queues() {
281 $queue['tasks'] = array(
282 'name' => t('Task queue'),
283 'description' => t('Process the queue of outstanding hosting tasks.'),
284 'type' => 'serial', # run queue sequentially. always with the same parameters.
285 'frequency' => strtotime("1 minute", 0), # run queue every minute.
286 'items' => 5, # process 20 queue items per execution.
287 'total_items' => hosting_task_count(),
288 'singular' => t('task'),
289 'plural' => t('tasks'),
290 'running_items' => hosting_task_count_running(),
291 );
292 return $queue;
293 }
294
295 /**
296 * Insert an entry in the task log.
297 *
298 * @param $vid
299 * The vid of the task to add an entry for.
300 * @param $type
301 * The type of log entry being added.
302 * @param $message
303 * The message for this log entry.
304 * @param $error
305 * (optional) The error code associated to this log entry.
306 * @param $timestamp
307 * (optional) The UNIX timestamp of this log message, this defaults to the
308 * current time.
309 */
310 function hosting_task_log($vid, $type, $message, $error = '', $timestamp = NULL) {
311 // We keep track of nids we've looked up in this request, for faster lookups.
312 static $nids = array();
313 $timestamp = ($timestamp) ? $timestamp : time();
314
315 // We need to insert the nid in addition to the vid, so look it up.
316 if (!isset($nids[$vid])) {
317 $nids[$vid] = (int)db_result(db_query('SELECT nid FROM {hosting_task} WHERE vid = %d', $vid));
318 }
319
320 db_query("INSERT INTO {hosting_task_log} (vid, nid, type, message, error, timestamp) VALUES (%d, %d, '%s', '%s', '%s', %d)", $vid, $nids[$vid], $type, $message, $error, $timestamp);
321 }
322
323 /**
324 * Retry the given task
325 */
326 function hosting_task_retry($task_id) {
327 $node = node_load($task_id);
328 if ($node->task_status != HOSTING_TASK_QUEUED) {
329 drupal_set_message(t("The task is being retried and has been added to the hosting queue again"));
330 hosting_task_log($node->vid, 'queue', t("The task is being retried and has been added to the hosting queue again"));
331 $node->revision = TRUE;
332 $node->changed = time();
333 $node->task_status = HOSTING_TASK_QUEUED;
334 node_save($node);
335 }
336 }
337
338 /**
339 * Helper function to generate new task node
340 */
341 function hosting_add_task($nid, $type, $args = null, $status = HOSTING_TASK_QUEUED) {
342 global $user;
343
344 $task = hosting_get_most_recent_task($nid, $type);
345
346 $node = db_fetch_object(db_query("SELECT nid, uid, title FROM {node} WHERE nid=%d", $nid));
347 if (!$task) {
348 $task = new stdClass();
349 }
350 $task->type = 'task';
351 # TODO: make this pretty
352 $task->title = t("!type !title", array('!type' => $type, '!title' => $node->title));
353 $task->task_type = $type;
354 $task->rid = $node->nid;
355 /*
356 * fallback the owner of the task to the owner of the node we operate
357 * upon
358 *
359 * this is mostly for the signup form, which runs as the anonymous
360 * user, but for which the node is set to the right user
361 */
362 $task->uid = $user->uid ? $user->uid : $node->uid;
363 $task->status = 1;
364 $task->task_status = $status;
365 if ($status == HOSTING_TASK_QUEUED) {
366 $task->revision = TRUE;
367 }
368 #arguments, such as which backup to restore.
369 if (is_array($args)) {
370 $task->task_args = $args;
371 }
372 node_save($task);
373
374 drupal_set_message(t("Task %type was added to the queue. Next queue run is %date, server time is %time.", array('%type' => $type, '%date' => format_date(_hosting_queue_next_run('tasks'), 'custom', 'H:i:sO'), '%time' => format_date(time(), 'custom', 'H:i:sO'))));
375
376 return $task;
377 }
378 /**
379 * Implementation of hook_form().
380 */
381 function hosting_task_confirm_form($form_state, $node, $task) {
382 drupal_add_js(drupal_get_path('module','hosting_task') . '/hosting_task.js');
383 $tasks = hosting_available_tasks($node->type);
384
385 if (!isset($tasks[$task]['dialog']) || !$tasks[$task]['dialog']) {
386 hosting_add_task($node->nid, $task);
387 drupal_goto('node/' . $node->nid);
388 }
389
390 $form['help'] = array('#value' => $tasks[$task]['description']);
391 $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
392 $form['task'] = array('#type' => 'value', '#value' => $task);
393 $form['parameters'] = array('#tree' => TRUE);
394 $func = 'hosting_task_' . str_replace('-', '_', $task) . '_form';
395 if (function_exists($func)) {
396 $form['parameters'] += $func($node);
397 }
398 $func = $func . '_validate';
399 if (function_exists($func)) {
400 $form['#validate'][] = $func;
401 $form['#func_param_1'] = $node;
402 $form['#func_param_2'] = $task;
403 }
404 $question = t("Are you sure you want to @task @object?", array('@task' => $task, '@object' => $node->title));
405 $form = confirm_form($form, $question, 'node/' . $node->nid, '', $tasks[$task]['title']);
406
407 // add an extra class to the actions to allow us to disable the cancel link via javascript for the modal dialog
408
409 $form['actions']['#prefix'] = '<div id="hosting-task-confirm-form-actions" class="container-inline">';
410 return $form;
411 }
412
413 /**
414 * Customize the task confirmation form for restore.
415 *
416 * This adds the backup listing to the confirmation dialog.
417 */
418 function hosting_task_restore_form($node) {
419 $list = hosting_site_backup_list($node->nid);
420 if (sizeof($list)) {
421 $form['bid'] = array(
422 '#type' => 'radios',
423 '#title' => t('Backups'),
424 '#options' => $list,
425 '#required' => TRUE
426 );
427 }
428 else {
429 $form['no_backups'] = array(
430 '#type' => 'item',
431 '#title' => t('Backups'),
432 '#value' => t('There are no valid backups available.')
433 );
434 }
435 return $form;
436 }
437
438 /**
439 * Implementation of hook_form_alter()
440 */
441 function hosting_task_restore_form_validate($form, &$form_state) {
442 if ($form['parameters']['no_backups']) {
443 form_set_error('no_backups', t('There are no valid backups available.'));
444 }
445 }
446
447 /**
448 * Implementation of hook_form_alter()
449 */
450 function hosting_task_backup_delete_form_validate($form, &$form_state) {
451 if ($form['parameters']['no_backups']) {
452 form_set_error('no_backups', t('There are no valid backups available.'));
453 }
454 if (!$form['parameters']['#post']['parameters']) {
455 form_set_error('no_backups', t('No backups were selected for deletion.'));
456 }
457 }
458
459 function hosting_task_backup_delete_form($node) {
460 $list = hosting_site_backup_list($node->nid);
461 if (sizeof($list)) {
462 foreach ($list as $bid => $info) {
463 $backup = hosting_site_get_backup($bid);
464 $form[$bid] = array(
465 '#type' => 'checkbox',
466 '#title' => $info,
467 '#return_value' => $backup['filename'],
468 );
469 }
470 }
471 else {
472 $form['no_backups'] = array(
473 '#type' => 'item',
474 '#title' => t('Backups'),
475 '#value' => t('There are no valid backups available.')
476 );
477 }
478 return $form;
479 }
480
481 /**
482 * Generic form submit handler for tasks confirmation
483 *
484 * This handler gets called after any task has been confirmed by the user. It
485 * will inject a new task in the queue and redirect the user to the
486 * originating node.
487 *
488 * @see hosting_add_task()
489 */
490 function hosting_task_confirm_form_submit($form, &$form_state) {
491 $values = $form_state['values'];
492 hosting_add_task($values['nid'], $values['task'], $values['parameters']);
493 $form_state['redirect'] = 'node/' . $values['nid'];
494 modalframe_close_dialog();
495 }
496
497 /**
498 * Set the title of tasks automatically and in a consistent way
499 *
500 * Tasks should always be named 'task_type node_title'.
501 */
502 function hosting_task_set_title(&$node) {
503 $ref = db_fetch_object(db_query("SELECT vid, nid, title, type FROM {node} WHERE nid=%d", $node->rid));
504 $tasks = hosting_available_tasks($ref->type);
505
506 $node->title = drupal_ucfirst($tasks[$node->task_type]['title']) . ': ' . $ref->title;
507 db_query("UPDATE {node} SET title='%s' WHERE nid=%d", $node->title, $node->nid);
508 db_query("UPDATE {node_revisions} SET title='%s' WHERE vid=%d", $node->title, $node->vid);
509 }
510
511 /**
512 * Process the hosting task queue.
513 *
514 * Iterates through the list of outstanding tasks, and execute the commands on the back end.
515 */
516 function hosting_tasks_queue($count = 20) {
517 global $provision_errors;
518
519 drush_log(dt("Running tasks queue"));
520 $tasks = _hosting_get_new_tasks($count);
521 foreach ($tasks as $task) {
522 drush_invoke_process('@self', "hosting-task", array($task->nid), array('invoke' => TRUE), array('fork' => TRUE));
523 }
524 }
525
526 /**
527 * Determine whether there is an outstanding task of a specific type.
528 *
529 * This is used to ensure that there are not multiple tasks of the same type queued.
530 */
531 function hosting_task_outstanding($nid, $type) {
532 $return = db_result(db_query("
533 SELECT t.nid FROM {hosting_task} t
534 INNER JOIN {node} n ON t.vid = n.vid
535 WHERE
536 t.rid = %d
537 AND t.task_status = %d
538 AND t.task_type = '%s'
539 ORDER BY t.vid DESC
540 LIMIT 1", $nid, HOSTING_TASK_QUEUED, $type));
541 return $return;
542 }
543
544 /**
545 * Return the amount of items still in the queue
546 */
547 function hosting_task_count() {
548 return db_result(db_query("SELECT COUNT(t.vid) FROM {hosting_task} t INNER JOIN {node} n ON t.vid = n.vid WHERE t.task_status = %d", HOSTING_TASK_QUEUED));
549 }
550
551 /**
552 * Return the amount of items running in the queue
553 */
554 function hosting_task_count_running() {
555 return db_result(db_query("SELECT COUNT(t.nid) FROM {node} n INNER JOIN {hosting_task} t ON n.vid=t.vid WHERE type='task' AND t.task_status = '%d'", HOSTING_TASK_PROCESSING));
556 }
557
558 /**
559 * User-driven task descriptions
560 *
561 * This is the list of tasks that can be invoked by the user. This doesn't
562 * check permissions or relevance of the tasks.
563 *
564 * Modules can extend this list using hook_hosting_tasks()
565 *
566 * @see hook_hosting_tasks()
567 * @see hosting_task_menu_access()
568 */
569 function hosting_available_tasks($type, $reset = FALSE) {
570 static $cache = array();
571
572 if (!sizeof($cache) || $reset) {
573 $cache = module_invoke_all('hosting_tasks');
574 drupal_alter('hosting_tasks', $cache);
575 }
576 return $cache[$type];
577
578 }
579
580 /**
581 * Implementation of hook_insert().
582 */
583 function hosting_task_insert($node) {
584 $node->executed = isset($node->executed) ? $node->executed : NULL;
585 $node->delta = isset($node->delta) ? $node->delta : NULL;
586 db_query("INSERT INTO {hosting_task} (vid, nid, task_type, task_status, rid, executed, delta) VALUES (%d, %d, '%s', %d, %d, %d, %d)",
587 $node->vid, $node->nid, $node->task_type, $node->task_status, $node->rid, $node->executed, $node->delta);
588
589 if (isset($node->task_args) && is_array($node->task_args)) {
590 foreach ($node->task_args as $key => $value) {
591 db_query("INSERT INTO {hosting_task_arguments} (vid, nid, name, value) VALUES (%d, %d, '%s', '%s')",
592 $node->vid, $node->nid, $key, $value);
593 }
594 }
595 hosting_task_set_title($node);
596 }
597
598 /**
599 * Implementation of hook_update().
600 *
601 * As an existing node is being updated in the database, we need to do our own
602 * database updates.
603 */
604 function hosting_task_update($node) {
605 // if this is a new node or we're adding a new revision,
606 if (!empty($node->revision)) {
607 hosting_task_insert($node);
608 }
609 else {
610 hosting_task_set_title($node);
611 db_query("UPDATE {hosting_task} SET nid=%d, task_type = '%s', task_status = %d, rid = %d, executed=%d, delta=%d WHERE vid=%d",
612 $node->nid, $node->task_type, $node->task_status, $node->rid, $node->executed, $node->delta, $node->vid);
613 if (isset($node->task_args) && is_array($node->task_args)) {
614 # Wipe out old arguments first, since arguments could theoretically be removed.
615 db_query("DELETE FROM {hosting_task_arguments} WHERE vid=%d", $node->vid);
616 foreach ($node->task_args as $key => $value) {
617 db_query("INSERT INTO {hosting_task_arguments} (vid, nid, name, value) VALUES (%d, %d, '%s', '%s')",
618 $node->vid, $node->nid, $key, $value);
619 }
620 }
621
622 }
623 }
624
625 /**
626 * Implementation of hook_delete_revision()
627 */
628 function hosting_nodeapi_task_delete_revision(&$node) {
629 db_query('DELETE FROM {hosting_task} WHERE vid = %d', $node->vid);
630 db_query('DELETE FROM {hosting_task_arguments} WHERE vid = %d', $node->vid);
631 db_query('DELETE FROM {hosting_task_log} WHERE vid = %d', $node->vid);
632 }
633
634 /**
635 * Implementation of hook_delete().
636 */
637 function hosting_task_delete($node) {
638 db_query('DELETE FROM {hosting_task} WHERE nid = %d', $node->nid);
639 db_query('DELETE FROM {hosting_task_arguments} WHERE nid = %d', $node->nid);
640 db_query('DELETE FROM {hosting_task_log} WHERE nid = %d', $node->nid);
641 }
642
643 /**
644 * Implementation of hook_load().
645 */
646 function hosting_task_load($node) {
647 $additions = db_fetch_object(db_query('SELECT task_type, executed, delta, rid, task_status FROM {hosting_task} WHERE vid = %d', $node->vid));
648 $result = db_query("SELECT name, value FROM {hosting_task_arguments} WHERE vid=%d", $node->vid);
649 if ($result) {
650 while ($arg = db_fetch_object($result)) {
651 $additions->task_args[$arg->name] = $arg->value;
652 }
653 }
654 return $additions;
655 }
656
657 function hosting_task_retry_form($form_state, $nid) {
658 $form['#prefix'] = '<div class="hosting-task-retry">';
659 $form['task'] = array(
660 '#type' => 'hidden',
661 '#default_value' => $nid
662 );
663 $form['retry'] = array(
664 '#type' => 'submit',
665 '#value' => t('Retry')
666 );
667 $form['#suffix'] = '</div>';
668 return $form;
669 }
670
671 function hosting_task_retry_form_submit($form, &$form_state) {
672 hosting_task_retry($form_state['values']['task']);
673 modalframe_close_dialog();
674 }
675
676 /**
677 * Implementation of hook_view().
678 */
679 function hosting_task_view($node, $teaser = FALSE, $page = FALSE) {
680 drupal_add_js(drupal_get_path('module', 'hosting') . '/hosting.js');
681 $node = node_prepare($node, $teaser);
682
683 $ref = node_load($node->rid);
684
685 hosting_set_breadcrumb($node);
686 $node->content['info']['#prefix'] = '<div id="hosting-task-info" class="clear-block">';
687 $node->content['info']['reference'] = array(
688 '#type' => 'item',
689 '#title' => drupal_ucfirst($ref->type),
690 '#value' => _hosting_node_link($node->rid),
691 );
692
693 if ($node->task_status != HOSTING_TASK_QUEUED) {
694 if ($node->task_status == HOSTING_TASK_PROCESSING) {
695 $node->content['info']['started'] = array(
696 '#type' => 'item',
697 '#title' => t('Started'),
698 '#value' => format_date($node->executed),
699 '#weight' => 1,
700 );
701 $node->content['info']['delta'] = array(
702 '#type' => 'item',
703 '#title' => t('Processing time'),
704 '#value' => format_interval(time() - $node->executed),
705 '#weight' => 2,
706 );
707 }
708 else {
709 $node->content['info']['executed'] = array(
710 '#type' => 'item',
711 '#title' => t('Executed'),
712 '#value' => format_date($node->executed),
713 '#weight' => 1,
714 );
715 $node->content['info']['delta'] = array(
716 '#type' => 'item',
717 '#title' => t('Execution time'),
718 '#value' => format_interval($node->delta),
719 '#weight' => 2,
720 );
721 }
722 } else {
723 $queues = hosting_get_queues();
724 $queue = $queues['tasks'];
725 $next = _hosting_queue_next_run($queue);
726 $node->content['info']['notexecuted'] = array(
727 '#type' => 'item',
728 '#title' => t('This task has not been processed yet'),
729 '#value' => t('It will be processed around %date, if the queue is not too crowded. The queue is currently run every %freq, was last run %last and processes %items items at a time. Server time is %time.', array('%freq' => format_interval($queue['frequency']), '%date' => format_date($next, 'custom', 'H:i:sO'), '%last' => hosting_format_interval($queue['last_run']), '%items' => $queue['items'], '%time' => format_date(time(), 'custom', 'H:i:sO'))),
730 );
731 }
732 if ($node->task_status) {
733 $node->content['info']['status'] = array(
734 '#type' => 'item',
735 '#title' => t('Status'),
736 '#value' => _hosting_parse_error_code($node->task_status),
737 );
738 }
739 $node->content['info']['#suffix'] = '</div>';
740
741 if (user_access('retry failed tasks') && ($node->task_status == HOSTING_TASK_ERROR)) {
742 $node->content['retry'] = array(
743 '#type' => 'markup',
744 '#value' => drupal_get_form('hosting_task_retry_form', $node->nid),
745 '#weight' => 5,
746 );
747 }
748
749 if (user_access('access task logs')) {
750 if ($table = _hosting_task_log_table($node->vid)) {
751 $node->content['hosting_log'] = array(
752 '#weight' => 10,
753 '#type' => 'item',
754 '#value' => $table
755 );
756 }
757 }
758 return $node;
759 }
760
761 /**
762 * Display table containing the logged information for this task
763 */
764 function _hosting_task_log_table($vid) {
765 $result = db_query("SELECT * FROM {hosting_task_log} WHERE vid = %d ORDER BY lid", $vid);
766 if ($result) {
767 $header = array('data' => 'Log message');
768 while ($entry = db_fetch_object($result)) {
769 if (strlen($entry->message) > 300) {
770 $summary = "<span class='hosting-task-summary'>" . filter_xss(substr($entry->message, 0, 75), array()) . "... <a href='javascript:void(0)' class='hosting-summary-expand modalframe-exclude'>(" . t('Expand') . ')</a></span>';
771 $message = $summary . "<span class='hosting-task-full'>" . filter_xss($entry->message) . '</span>';
772 }
773 else {
774 $message = filter_xss($entry->message);
775 }
776 $row = array(array('data' => $message, 'class' => 'hosting-status'));
777 $rows[] = array('data' => $row, 'class' => _hosting_task_log_class($entry->type));
778 }
779
780 return theme("table", $header, (array) $rows, array('id' => 'hosting-task-log', 'class' => 'hosting-table'));
781 }
782
783 return false;
784 }
785
786 /**
787 * Map entry statuses to coincide.
788 *
789 * @todo make this irrelevant.
790 */
791 function _hosting_task_log_class($type) {
792 switch (strtolower($type)) {
793 case "warning":
794 $type = "warning";
795 break;
796 case "error":
797 case "failed":
798 $type = "error";
799 break;
800 case "queue":
801 $type = "queue";
802 break;
803 case "command":
804 case "notice":
805 $type = "info";
806 break;
807 default:
808 $type = 'success';
809 break;
810 }
811
812 return 'hosting-' . $type;
813 }
814
815 /**
816 * Retrieve the latest task related to the specified platform, of a specific type
817 *
818 * This is used for documenting issues with verification.
819 */
820 function hosting_get_most_recent_task($rid, $type) {
821 $nid = db_result(db_query("SELECT t.nid FROM {hosting_task} t INNER JOIN {node} n ON n.vid = t.vid WHERE task_type='%s' and t.rid=%d ORDER BY t.vid DESC limit 1", $type, $rid));
822 if ($nid) {
823 return node_load($nid);
824 }
825 return false;
826 }
827
828 /**
829 * Retrieve tasks with specified criterias
830 *
831 * @arg $filter_by string a field to filter the list with, unchecked
832 * @arg $filter_value string what to restrict the field to, checked
833 * @arg $count integer the number of tasks to return
834 * @arg $element integer which element to start from
835 */
836 function hosting_get_tasks($filter_by = null, $filter_value = null, $count = 5, $element = 0) {
837 $nodes = array();
838 $args[] = 'task';
839 $cond = '';
840
841 if ($filter_by && $filter_value) {
842 $cond = ' AND t.' . $filter_by . ' = %d';
843 $args[] = $filter_value;
844 }
845 $result = pager_query(db_rewrite_sql("SELECT n.*, t.task_status, t.task_type, t.rid FROM {node} n INNER JOIN {hosting_task} t on n.vid=t.vid WHERE type='%s'" . $cond . " ORDER BY n.vid DESC"), $count, $element, NULL, $args);
846
847 while ($row = db_fetch_object($result)) {
848 $nodes[] = $row;
849 }
850
851 return $nodes;
852 }
853
854 /**
855 * Retrieve a list of outstanding tasks.
856 *
857 * @param limit
858 * The amount of items to return.
859 * @return
860 * An associative array containing task nodes, indexed by node id.
861 */
862 function _hosting_get_new_tasks($limit = 20) {
863 $return = array();
864 $result = db_query("SELECT t.nid FROM {hosting_task} t INNER JOIN {node} n ON t.vid = n.vid WHERE t.task_status = %d GROUP BY t.rid ORDER BY n.changed, n.nid ASC LIMIT %d", 0, $limit);
865 while ($node = db_fetch_object($result)) {
866 $return[$node->nid] = node_load($node->nid);
867 }
868 return $return;
869 }
870
871 /**
872 * @name Error status definitions
873 * @{
874 * Bitmask values used to generate the error code to return.
875 * @see drush_set_error(), drush_get_error(), drush_cmp_error()
876 */
877
878 /**
879 * The task is being processed
880 */
881 define('HOSTING_TASK_PROCESSING', -1);
882
883
884 /**
885 * The task is queued
886 */
887 define('HOSTING_TASK_QUEUED', 0);
888
889 /**
890 * The command completed succesfully.
891 */
892 define('HOSTING_TASK_SUCCESS', 1);
893
894 /**
895 * The command was not successfully completed. This is the default error
896 * status.
897 */
898 define('HOSTING_TASK_ERROR', 2);
899
900 /**
901 * @} End of "name Error status defintions".
902 */
903
904 /**
905 * Turn bitmask integer error code into associative array
906 */
907 function _hosting_parse_error_code($code) {
908 $messages = array(
909 HOSTING_TASK_SUCCESS => t('Successful'),
910 HOSTING_TASK_QUEUED => t('Queued'),
911 HOSTING_TASK_ERROR => t('Failed'),
912 HOSTING_TASK_PROCESSING => t('Processing'),
913 );
914 return $messages[$code];
915 }
916
917 /**
918 * Return the status of the task matching the specification
919 */
920 function hosting_task_status($filter_by, $filter_value, $type = 'install') {
921 $args[] = 'task';
922 $args[] = $type;
923 $cond = '';
924 if ($filter_by && $filter_value) {
925 $cond = ' AND t.' . $filter_by . ' = %d';
926 $args[] = $filter_value;
927 }
928 $result = db_fetch_array(db_query("SELECT t.task_status AS status FROM {node} n INNER JOIN {hosting_task} t on n.vid=t.vid WHERE n.type='%s' AND t.task_type='%s' " . $cond . " ORDER BY t.vid DESC", $args));
929 return $result['status'];
930 }
931
932 /**
933 * Return the status of a task in human-readable form
934 *
935 * @see hosting_task_status()
936 */
937 function hosting_task_status_output($filter_by, $filter_value, $type = 'install') {
938 $status = hosting_task_status($filter_by, $filter_value, $type);
939 if (is_int($status)) {
940 return _hosting_parse_error_code($status);
941 } else {
942 return $status; # should be NULL
943 }
944 }
945
946 /**
947 * Display list of tasks
948 */
949 function hosting_task_list($filter_by = null, $filter_value = null) {
950 return _hosting_task_list($filter_by, $filter_value, 25, 12, 'title');
951 }
952
953 /**
954 * Implementation of hosting_hook_summary()
955 */
956 function hosting_task_summary($filter_by = null, $filter_value = null) {
957
958 modalframe_parent_js();
959 $more_link = l(t('More tasks'), 'hosting/queues/tasks');
960 return hosting_task_queue_block() . $more_link;
961 }
962
963 /**
964 * Hosting task list queue block
965 */
966 function hosting_task_queue_block() {
967 drupal_add_js(drupal_get_path('module','hosting_task') . '/hosting_task.js');
968
969 $settings['hostingTaskRefresh'] = array(
970 'queueBlock' => 1
971 );
972 drupal_add_js($settings, 'setting');
973
974 $nodes = hosting_get_tasks('t.task-status', HOSTING_TASK_QUEUED, 5);
975
976 $headers = array( t('Task'), t('Actions') );
977 $rows[] = array();
978 foreach ($nodes as $node) {
979 $row = array();
980 $row['type'] = array(
981 'data' => drupal_ucfirst(str_replace(array('_', '-'), ' ', $node->task_type)) . ' ' . _hosting_node_link($node->rid),
982 'class' => 'hosting-status'
983 );
984
985
986 $log_button = _hosting_task_button(t('View'), 'node/' . $node->nid, t("Display the task log"), 'hosting-button-log', isset($node->nid) && user_access('access task logs'), true, false);
987
988 $row['actions'] = array(
989 'data' => $log_button,
990 'class' => 'hosting-actions',
991 );
992 $class = hosting_task_status_class($node->task_status);
993 $rows[] = array('data' => $row, 'class' => $class);
994 }
995 return "<div id='hosting-task-queue-block'>" . theme('table', $headers, $rows, array('class' => 'hosting-table')) . "</div>" ;
996
997 }
998
999 /**
1000 * A concise table listing of the tasks affecting this node
1001 *
1002 * This shows a table view of the tasks relevant to this node. It will show
1003 * tasks that can be executed as well as tasks that have been in a single
1004 * simple interface.
1005 */
1006 function hosting_task_table($node) {
1007 $output = '';
1008
1009
1010 $headers[] = t('Task');
1011 $headers[] = array('data' => t('Actions'), 'class' => 'hosting-actions');
1012
1013 $tasklist = hosting_task_fetch_tasks($node->nid);
1014
1015 foreach ($tasklist as $task => $info) {
1016 $row = array();
1017
1018 if (!isset($info['nid']) && !$info['task_permitted']) {
1019 // just don't show those tasks, since we'll not be able to run them
1020 continue;
1021 }
1022
1023 $row['type'] = array('data' => $info['title'], 'class' => 'hosting-status');
1024 $actions = array();
1025
1026 if (isset($info['task_status']) && ($info['task_status'] == 0)) {
1027 $actions['cancel'] = _hosting_task_button(t('Cancel'), sprintf("hosting/tasks/%d/cancel", $info['nid']), t("Cancel the task and remove it from the queue"), 'hosting-button-stop', !$info['task_permitted']);
1028 }
1029 else {
1030 $actions['run'] = _hosting_task_button(t('Run'), sprintf("node/%d/%s_%s", $node->nid, $node->type, $task), $info['description'], 'hosting-button-run', $info['task_permitted'], $info['dialog']);
1031 }
1032
1033 $actions['log'] = _hosting_task_button(t('View'), 'node/' . $info['nid'], t("Display the task log"), 'hosting-button-log', isset($info['nid']) && user_access('access task logs'), TRUE, FALSE);
1034 $row['actions'] = array(
1035 'data' => implode('', $actions),
1036 'class' => 'hosting-actions'
1037 );
1038
1039 $rows[] = array('data' => $row, 'class' => $info['class']);
1040 }
1041 $output .= theme('table', $headers, $rows, array('class' => 'hosting-table'));
1042 return $output;
1043 }
1044
1045 function _hosting_task_button($title, $link, $description, $class = '', $status = TRUE, $dialog = FALSE, $add_token = TRUE) {
1046 global $user;
1047
1048 if ($status) {
1049 $classes[] = 'hosting-button-enabled';
1050 if (!empty($class)) {
1051 $classes[] = $class;
1052 }
1053 if ($dialog) {
1054 $classes[] = 'hosting-button-dialog';
1055 }
1056
1057 $options['attributes'] = array(
1058 'title' => $description,
1059 'class' => implode(" ", $classes),
1060 );
1061 if ($add_token) {
1062 $options['query'] = array(
1063 'token' => drupal_get_token($user->uid),
1064 );
1065 }
1066 return l($title, $link, $options);
1067 }
1068 else {
1069 return "<span class='hosting-button-disabled'>" . $title . "</span>";
1070 }
1071 }
1072
1073 /**
1074 * Theme a task list
1075 */
1076 function _hosting_task_list($filter_by, $filter_value, $count = 5, $element = 0, $field = 'title', $skip = array(), $pager = TRUE) {
1077 $nodes = hosting_get_tasks($filter_by, $filter_value, $count, $element);
1078
1079 if (!$nodes) {
1080 return t('No tasks available');
1081 }
1082 else {
1083 $headers[t('Task')] = '';
1084 foreach ($nodes as $node) {
1085 $row = array();
1086
1087 if ($field == 'title') {
1088 $data = drupal_ucfirst($node->task_type) . ' ' . _hosting_node_link($node->rid);
1089 } else {
1090 $data = $node->{$field};
1091 }
1092 $row['type'] = array(
1093 'data' => $data,
1094 'class' => 'hosting-status'
1095 );
1096
1097 if (!in_array('created', $skip)) {
1098 $row['created'] = t("@interval ago", array('@interval' => format_interval(time() - $node->created, 1)));
1099 $headers[t('Created')] = '';
1100 }
1101
1102 $row['executed'] = t("@interval ago", array('@interval' => format_interval(time() - $node->changed, 1)));
1103 $headers[t('Executed')] = '';
1104
1105 $headers[t('Actions')] = '';
1106
1107 $actions['log'] = l(t('View'), 'node/' . $node->nid, array('attributes' => array('class' => 'hosting-button-dialog hosting-button-enabled hosting-button-log')));
1108
1109 $row['actions'] = array(
1110 'data' => $actions['log'],
1111 'class' => 'hosting-actions'
1112 );
1113
1114 $class = hosting_task_status_class($node->task_status);
1115
1116 $rows[] = array('data' => $row, 'class' => $class);
1117 }
1118
1119 $output = theme('table', array_keys($headers), $rows, array('class' => 'hosting-table'));
1120 if ($pager === TRUE) {
1121 $output .= theme('pager', NULL, $count, $element);
1122 }
1123 elseif (is_string($pager)) {
1124 $output .= $pager;
1125 }
1126 return $output;
1127 }
1128 }
1129
1130 function hosting_task_fetch_tasks($rid) {
1131 $node = node_load($rid);
1132
1133 $result = db_query("SELECT n.nid, t.task_type, t.task_status FROM {node} n INNER JOIN {hosting_task} t ON n.vid = t.vid
1134 WHERE n.type = 'task' AND t.rid = %d
1135 ORDER BY t.task_status ASC, n.changed DESC", $rid);
1136
1137 while ($obj = db_fetch_object($result)) {
1138 $return[$obj->task_type] = array('nid' => $obj->nid, 'task_status' => $obj->task_status, 'exists' => TRUE);
1139 }
1140
1141 $tasks = hosting_available_tasks($node->type);
1142 ksort($tasks);
1143
1144 foreach ($tasks as $type => $hook_task) {
1145
1146 if (!isset($return[$type])) {
1147 $return[$type] = array();
1148 }
1149
1150 $access_callback = !empty($hook_task['access callback']) ? $hook_task['access callback'] : 'hosting_task_menu_access';
1151
1152 $task = array();
1153 $task = array_merge($return[$type], $hook_task);
1154
1155 $allowed = (isset($task['exists']) && !in_array($task['task_status'], array(HOSTING_TASK_QUEUED, HOSTING_TASK_PROCESSING))) || !isset($task['exists']);
1156 if ($allowed && !$task['hidden'] && $access_callback($node, $type)) {
1157 $task['task_permitted'] = TRUE;
1158 }
1159 $task['class'] = hosting_task_status_class($task['task_status']);
1160
1161 $return[$type] = $task;
1162 }
1163
1164 return $return;
1165 }
1166
1167 function hosting_task_status_class($status = null) {
1168 $class = null;
1169 if (!is_null($status)) {
1170 switch($status) {
1171 case HOSTING_TASK_SUCCESS :
1172 $class = 'hosting-success';
1173 break;
1174 case HOSTING_TASK_ERROR :
1175 $class = 'hosting-error';
1176 break;
1177 case HOSTING_TASK_QUEUED :
1178 $class = 'hosting-queue';
1179 break;
1180 case HOSTING_TASK_PROCESSING :
1181 $class = 'hosting-processing';
1182 break;
1183 }
1184 }
1185
1186 return $class;
1187 }
1188
1189 /**
1190 * Views integration
1191 */
1192 function hosting_task_views_api() {
1193 return array(
1194 'api' => 2,
1195 'path' => drupal_get_path('module', 'hosting_task'),
1196 );
1197 }