3 * @file Web server node type is defined here.
7 * Implementation of hook_menu().
9 function hosting_task_menu() {
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)
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
,
29 $items[$path] = array_merge($items[$path], $info);
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
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
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
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
);
75 function hosting_task_ajax_queue() {
76 $return['markup'] = hosting_task_queue_block();
81 function hosting_task_cancel($node) {
82 if ($node->type
== 'task') {
83 if ($node->task_status
== 0) {
84 $ref = node_load($node->rid
);
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
);
93 drupal_access_denied();
97 * Implementation of hook_access()
100 function hosting_task_cancel_access($node) {
101 // bring $user into scope, so we can test task ownership
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
)) {
110 // 'administer tasks' allows cancelling any and all tasks on the system
111 if (user_access('administer tasks')) {
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)) {
121 * Task access controls
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.
127 * the node object we're trying to access
130 * the task type we're trying to do on the $node
132 * @see hosting_task_menu()
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
)) {
140 if (($task == 'login-reset') && ($node->site_status
!= HOSTING_SITE_ENABLED
)) {
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') {
152 $site_enabled = (hosting_task_outstanding($node->nid
, 'enable') || ($node->site_status
== HOSTING_SITE_ENABLED
));
153 $deletable = ($task == "delete");
154 $enabable = ($task == "enable");
156 $delete_or_enable = ($deletable || $enabable);
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);
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
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'])) {
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
)) {
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');
186 // If the platform's unlocked, we can lock it, delete it or batch migrate sites
187 $platform_tasks = array('verify', 'lock', 'delete', 'migrate');
189 return (in_array($task, $platform_tasks));
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'])) {
196 // todo probably need more checks
204 * Access callback helper for hosting task menu items.
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.
210 * @see hosting_task_menu_access()
212 function hosting_task_menu_access_csrf($node, $task) {
215 $interactive_tasks = array('migrate', 'clone');
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
))) {
223 // Call the main menu access handler.
224 return hosting_task_menu_access($node, $task);
228 * Implementation of hook_node_info()
230 function hosting_task_node_info() {
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);
243 * Implementation of hook_access()
245 function hosting_task_access($op, $node, $account) {
246 if(user_access('administer tasks', $account)) {
252 * Implementation of hook_perm()
254 function hosting_task_perm() {
257 'create backup task',
258 'create restore task',
259 'create disable task',
260 'create enable task',
261 'create delete task',
262 'create verify task',
264 'create unlock task',
265 'create login-reset task',
266 'create backup-delete task',
270 'retry failed tasks',
276 * Implementation of hook_hosting_queues
278 * Return a list of queues that this module needs to manage.
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(),
296 * Insert an entry in the task log.
299 * The vid of the task to add an entry for.
301 * The type of log entry being added.
303 * The message for this log entry.
305 * (optional) The error code associated to this log entry.
307 * (optional) The UNIX timestamp of this log message, this defaults to the
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();
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));
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);
324 * Retry the given task
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
;
339 * Helper function to generate new task node
341 function hosting_add_task($nid, $type, $args = null
, $status = HOSTING_TASK_QUEUED
) {
344 $task = hosting_get_most_recent_task($nid, $type);
346 $node = db_fetch_object(db_query("SELECT nid, uid, title FROM {node} WHERE nid=%d", $nid));
348 $task = new
stdClass();
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
;
356 * fallback the owner of the task to the owner of the node we operate
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
362 $task->uid
= $user->uid ?
$user->uid
: $node->uid
;
364 $task->task_status
= $status;
365 if ($status == HOSTING_TASK_QUEUED
) {
366 $task->revision
= TRUE
;
368 #arguments, such as which backup to restore.
369 if (is_array($args)) {
370 $task->task_args
= $args;
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'))));
379 * Implementation of hook_form().
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
);
385 if (!isset($tasks[$task]['dialog']) || !$tasks[$task]['dialog']) {
386 hosting_add_task($node->nid
, $task);
387 drupal_goto('node/' .
$node->nid
);
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);
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;
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']);
407 // add an extra class to the actions to allow us to disable the cancel link via javascript for the modal dialog
409 $form['actions']['#prefix'] = '<div id="hosting-task-confirm-form-actions" class="container-inline">';
414 * Customize the task confirmation form for restore.
416 * This adds the backup listing to the confirmation dialog.
418 function hosting_task_restore_form($node) {
419 $list = hosting_site_backup_list($node->nid
);
421 $form['bid'] = array(
423 '#title' => t('Backups'),
429 $form['no_backups'] = array(
431 '#title' => t('Backups'),
432 '#value' => t('There are no valid backups available.')
439 * Implementation of hook_form_alter()
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.'));
448 * Implementation of hook_form_alter()
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.'));
454 if (!$form['parameters']['#post']['parameters']) {
455 form_set_error('no_backups', t('No backups were selected for deletion.'));
459 function hosting_task_backup_delete_form($node) {
460 $list = hosting_site_backup_list($node->nid
);
462 foreach ($list as
$bid => $info) {
463 $backup = hosting_site_get_backup($bid);
465 '#type' => 'checkbox',
467 '#return_value' => $backup['filename'],
472 $form['no_backups'] = array(
474 '#title' => t('Backups'),
475 '#value' => t('There are no valid backups available.')
482 * Generic form submit handler for tasks confirmation
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
488 * @see hosting_add_task()
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();
498 * Set the title of tasks automatically and in a consistent way
500 * Tasks should always be named 'task_type node_title'.
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
);
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
);
512 * Process the hosting task queue.
514 * Iterates through the list of outstanding tasks, and execute the commands on the back end.
516 function hosting_tasks_queue($count = 20) {
517 global $provision_errors;
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
));
527 * Determine whether there is an outstanding task of a specific type.
529 * This is used to ensure that there are not multiple tasks of the same type queued.
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
537 AND t.task_status = %d
538 AND t.task_type = '%s'
540 LIMIT 1", $nid, HOSTING_TASK_QUEUED
, $type));
545 * Return the amount of items still in the queue
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
));
552 * Return the amount of items running in the queue
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
));
559 * User-driven task descriptions
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.
564 * Modules can extend this list using hook_hosting_tasks()
566 * @see hook_hosting_tasks()
567 * @see hosting_task_menu_access()
569 function hosting_available_tasks($type, $reset = FALSE
) {
570 static
$cache = array();
572 if (!sizeof($cache) || $reset) {
573 $cache = module_invoke_all('hosting_tasks');
574 drupal_alter('hosting_tasks', $cache);
576 return $cache[$type];
581 * Implementation of hook_insert().
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
);
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);
595 hosting_task_set_title($node);
599 * Implementation of hook_update().
601 * As an existing node is being updated in the database, we need to do our own
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);
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);
626 * Implementation of hook_delete_revision()
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
);
635 * Implementation of hook_delete().
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
);
644 * Implementation of hook_load().
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
);
650 while ($arg = db_fetch_object($result)) {
651 $additions->task_args
[$arg->name
] = $arg->value
;
657 function hosting_task_retry_form($form_state, $nid) {
658 $form['#prefix'] = '<div class="hosting-task-retry">';
659 $form['task'] = array(
661 '#default_value' => $nid
663 $form['retry'] = array(
665 '#value' => t('Retry')
667 $form['#suffix'] = '</div>';
671 function hosting_task_retry_form_submit($form, &$form_state) {
672 hosting_task_retry($form_state['values']['task']);
673 modalframe_close_dialog();
677 * Implementation of hook_view().
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);
683 $ref = node_load($node->rid
);
685 hosting_set_breadcrumb($node);
686 $node->content
['info']['#prefix'] = '<div id="hosting-task-info" class="clear-block">';
687 $node->content
['info']['reference'] = array(
689 '#title' => drupal_ucfirst($ref->type
),
690 '#value' => _hosting_node_link($node->rid
),
693 if ($node->task_status
!= HOSTING_TASK_QUEUED
) {
694 if ($node->task_status
== HOSTING_TASK_PROCESSING
) {
695 $node->content
['info']['started'] = array(
697 '#title' => t('Started'),
698 '#value' => format_date($node->executed
),
701 $node->content
['info']['delta'] = array(
703 '#title' => t('Processing time'),
704 '#value' => format_interval(time() - $node->executed
),
709 $node->content
['info']['executed'] = array(
711 '#title' => t('Executed'),
712 '#value' => format_date($node->executed
),
715 $node->content
['info']['delta'] = array(
717 '#title' => t('Execution time'),
718 '#value' => format_interval($node->delta
),
723 $queues = hosting_get_queues();
724 $queue = $queues['tasks'];
725 $next = _hosting_queue_next_run($queue);
726 $node->content
['info']['notexecuted'] = array(
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'))),
732 if ($node->task_status
) {
733 $node->content
['info']['status'] = array(
735 '#title' => t('Status'),
736 '#value' => _hosting_parse_error_code($node->task_status
),
739 $node->content
['info']['#suffix'] = '</div>';
741 if (user_access('retry failed tasks') && ($node->task_status
== HOSTING_TASK_ERROR
)) {
742 $node->content
['retry'] = array(
744 '#value' => drupal_get_form('hosting_task_retry_form', $node->nid
),
749 if (user_access('access task logs')) {
750 if ($table = _hosting_task_log_table($node->vid
)) {
751 $node->content
['hosting_log'] = array(
762 * Display table containing the logged information for this task
764 function _hosting_task_log_table($vid) {
765 $result = db_query("SELECT * FROM {hosting_task_log} WHERE vid = %d ORDER BY lid", $vid);
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>';
774 $message = filter_xss($entry->message
);
776 $row = array(array('data' => $message, 'class' => 'hosting-status'));
777 $rows[] = array('data' => $row, 'class' => _hosting_task_log_class($entry->type
));
780 return theme("table", $header, (array) $rows, array('id' => 'hosting-task-log', 'class' => 'hosting-table'));
787 * Map entry statuses to coincide.
789 * @todo make this irrelevant.
791 function _hosting_task_log_class($type) {
792 switch (strtolower($type)) {
812 return 'hosting-' .
$type;
816 * Retrieve the latest task related to the specified platform, of a specific type
818 * This is used for documenting issues with verification.
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));
823 return node_load($nid);
829 * Retrieve tasks with specified criterias
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
836 function hosting_get_tasks($filter_by = null
, $filter_value = null
, $count = 5, $element = 0) {
841 if ($filter_by && $filter_value) {
842 $cond = ' AND t.' .
$filter_by .
' = %d';
843 $args[] = $filter_value;
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);
847 while ($row = db_fetch_object($result)) {
855 * Retrieve a list of outstanding tasks.
858 * The amount of items to return.
860 * An associative array containing task nodes, indexed by node id.
862 function _hosting_get_new_tasks($limit = 20) {
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
);
872 * @name Error status definitions
874 * Bitmask values used to generate the error code to return.
875 * @see drush_set_error(), drush_get_error(), drush_cmp_error()
879 * The task is being processed
881 define('HOSTING_TASK_PROCESSING', -1);
887 define('HOSTING_TASK_QUEUED', 0);
890 * The command completed succesfully.
892 define('HOSTING_TASK_SUCCESS', 1);
895 * The command was not successfully completed. This is the default error
898 define('HOSTING_TASK_ERROR', 2);
901 * @} End of "name Error status defintions".
905 * Turn bitmask integer error code into associative array
907 function _hosting_parse_error_code($code) {
909 HOSTING_TASK_SUCCESS
=> t('Successful'),
910 HOSTING_TASK_QUEUED
=> t('Queued'),
911 HOSTING_TASK_ERROR
=> t('Failed'),
912 HOSTING_TASK_PROCESSING
=> t('Processing'),
914 return $messages[$code];
918 * Return the status of the task matching the specification
920 function hosting_task_status($filter_by, $filter_value, $type = 'install') {
924 if ($filter_by && $filter_value) {
925 $cond = ' AND t.' .
$filter_by .
' = %d';
926 $args[] = $filter_value;
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'];
933 * Return the status of a task in human-readable form
935 * @see hosting_task_status()
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);
942 return $status; # should be NULL
947 * Display list of tasks
949 function hosting_task_list($filter_by = null
, $filter_value = null
) {
950 return _hosting_task_list($filter_by, $filter_value, 25, 12, 'title');
954 * Implementation of hosting_hook_summary()
956 function hosting_task_summary($filter_by = null
, $filter_value = null
) {
958 modalframe_parent_js();
959 $more_link = l(t('More tasks'), 'hosting/queues/tasks');
960 return hosting_task_queue_block() .
$more_link;
964 * Hosting task list queue block
966 function hosting_task_queue_block() {
967 drupal_add_js(drupal_get_path('module','hosting_task') .
'/hosting_task.js');
969 $settings['hostingTaskRefresh'] = array(
972 drupal_add_js($settings, 'setting');
974 $nodes = hosting_get_tasks('t.task-status', HOSTING_TASK_QUEUED
, 5);
976 $headers = array( t('Task'), t('Actions') );
978 foreach ($nodes as
$node) {
980 $row['type'] = array(
981 'data' => drupal_ucfirst(str_replace(array('_', '-'), ' ', $node->task_type
)) .
' ' .
_hosting_node_link($node->rid
),
982 'class' => 'hosting-status'
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
);
988 $row['actions'] = array(
989 'data' => $log_button,
990 'class' => 'hosting-actions',
992 $class = hosting_task_status_class($node->task_status
);
993 $rows[] = array('data' => $row, 'class' => $class);
995 return "<div id='hosting-task-queue-block'>" .
theme('table', $headers, $rows, array('class' => 'hosting-table')) .
"</div>" ;
1000 * A concise table listing of the tasks affecting this node
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
1006 function hosting_task_table($node) {
1010 $headers[] = t('Task');
1011 $headers[] = array('data' => t('Actions'), 'class' => 'hosting-actions');
1013 $tasklist = hosting_task_fetch_tasks($node->nid
);
1015 foreach ($tasklist as
$task => $info) {
1018 if (!isset($info['nid']) && !$info['task_permitted']) {
1019 // just don't show those tasks, since we'll not be able to run them
1023 $row['type'] = array('data' => $info['title'], 'class' => 'hosting-status');
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']);
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']);
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'
1039 $rows[] = array('data' => $row, 'class' => $info['class']);
1041 $output .
= theme('table', $headers, $rows, array('class' => 'hosting-table'));
1045 function _hosting_task_button($title, $link, $description, $class = '', $status = TRUE
, $dialog = FALSE
, $add_token = TRUE
) {
1049 $classes[] = 'hosting-button-enabled';
1050 if (!empty($class)) {
1051 $classes[] = $class;
1054 $classes[] = 'hosting-button-dialog';
1057 $options['attributes'] = array(
1058 'title' => $description,
1059 'class' => implode(" ", $classes),
1062 $options['query'] = array(
1063 'token' => drupal_get_token($user->uid
),
1066 return l($title, $link, $options);
1069 return "<span class='hosting-button-disabled'>" .
$title .
"</span>";
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);
1080 return t('No tasks available');
1083 $headers[t('Task')] = '';
1084 foreach ($nodes as
$node) {
1087 if ($field == 'title') {
1088 $data = drupal_ucfirst($node->task_type
) .
' ' .
_hosting_node_link($node->rid
);
1090 $data = $node->{$field};
1092 $row['type'] = array(
1094 'class' => 'hosting-status'
1097 if (!in_array('created', $skip)) {
1098 $row['created'] = t("@interval ago", array('@interval' => format_interval(time() - $node->created
, 1)));
1099 $headers[t('Created')] = '';
1102 $row['executed'] = t("@interval ago", array('@interval' => format_interval(time() - $node->changed
, 1)));
1103 $headers[t('Executed')] = '';
1105 $headers[t('Actions')] = '';
1107 $actions['log'] = l(t('View'), 'node/' .
$node->nid
, array('attributes' => array('class' => 'hosting-button-dialog hosting-button-enabled hosting-button-log')));
1109 $row['actions'] = array(
1110 'data' => $actions['log'],
1111 'class' => 'hosting-actions'
1114 $class = hosting_task_status_class($node->task_status
);
1116 $rows[] = array('data' => $row, 'class' => $class);
1119 $output = theme('table', array_keys($headers), $rows, array('class' => 'hosting-table'));
1120 if ($pager === TRUE
) {
1121 $output .
= theme('pager', NULL
, $count, $element);
1123 elseif (is_string($pager)) {
1130 function hosting_task_fetch_tasks($rid) {
1131 $node = node_load($rid);
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);
1137 while ($obj = db_fetch_object($result)) {
1138 $return[$obj->task_type
] = array('nid' => $obj->nid
, 'task_status' => $obj->task_status
, 'exists' => TRUE
);
1141 $tasks = hosting_available_tasks($node->type
);
1144 foreach ($tasks as
$type => $hook_task) {
1146 if (!isset($return[$type])) {
1147 $return[$type] = array();
1150 $access_callback = !empty($hook_task['access callback']) ?
$hook_task['access callback'] : 'hosting_task_menu_access';
1153 $task = array_merge($return[$type], $hook_task);
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
;
1159 $task['class'] = hosting_task_status_class($task['task_status']);
1161 $return[$type] = $task;
1167 function hosting_task_status_class($status = null
) {
1169 if (!is_null($status)) {
1171 case HOSTING_TASK_SUCCESS
:
1172 $class = 'hosting-success';
1174 case HOSTING_TASK_ERROR
:
1175 $class = 'hosting-error';
1177 case HOSTING_TASK_QUEUED
:
1178 $class = 'hosting-queue';
1180 case HOSTING_TASK_PROCESSING
:
1181 $class = 'hosting-processing';
1192 function hosting_task_views_api() {
1195 'path' => drupal_get_path('module', 'hosting_task'),