hosting_task.module

Web server node type is defined here.

Functions

Namesort descending Description
hosting_add_task Helper function to generate new task node
hosting_available_tasks Get a list of tasks that can be invoked by the user.
hosting_get_tasks Retrieve tasks with specified criterias
hosting_nodeapi_task_delete_revision Implements hook_delete_revision()
hosting_tasks_queue Process the hosting task queue.
hosting_task_action_info Implements hook_action_info().
hosting_task_ajax_command_hosting_table_append @todo Please document this function.
hosting_task_ajax_command_hosting_table_check @todo Please document this function.
hosting_task_ajax_list @todo Please document this function.
hosting_task_ajax_queue AJAX callback for refreshing task list.
hosting_task_cancel @todo Please document this function.
hosting_task_cancel_access @todo Please document this function.
hosting_task_confirm_form Implements hook_form().
hosting_task_confirm_form_submit Generic form submit handler for tasks confirmation
hosting_task_count Return the amount of items still in the queue
hosting_task_count_running Return the amount of items running in the queue
hosting_task_dangerous_task_is_allowed Guard against destructive tasks running on guarded Aegir entities.
hosting_task_delete Implements hook_delete().
hosting_task_delete_related_tasks Delete tasks related to a given site, platform, server, etc.
hosting_task_fetch_tasks @todo Please document this function.
hosting_task_get_dangerous_tasks Invoke hooks to build a list of dangerous tasks.
hosting_task_get_guarded_nodes Invoke hooks to build a list of NIDs to guard.
hosting_task_hosting_queues Implements hook_hosting_queues().
hosting_task_hosting_task_dangerous_tasks Implements hook_hosting_task_dangerous_tasks();
hosting_task_hosting_task_guarded_nodes Implements hook_hosting_task_guarded_nodes().
hosting_task_insert Implements hook_insert().
hosting_task_list Display list of tasks
hosting_task_load Implements hook_load().
hosting_task_log Insert an entry in the task log.
hosting_task_log_ajax Read additional task log data, served via AJAX.
hosting_task_menu Implements hook_menu().
hosting_task_menu_access Task access controls
hosting_task_menu_access_csrf Access callback helper for hosting task menu items.
hosting_task_node_access Implements hook_node_access().
hosting_task_node_info Implements hook_node_info().
hosting_task_outstanding Determine whether there is an outstanding task of a specific type.
hosting_task_overlay_paths Implements hook_overlay_paths().
hosting_task_parse_log Parse a task's log, and return its status accordingly.
hosting_task_permission Implements hook_permission().
hosting_task_preprocess_views_view_table Implements hook_preprocess_views_view_table().
hosting_task_restore_form Customize the task confirmation form for restore.
hosting_task_restore_form_validate Implements hook_form_alter(). TODO: Move restore functions to hosting_site, where backup functions are.
hosting_task_retry Retry the given task
hosting_task_retry_form Adds a retry button to failed task nodes.
hosting_task_retry_form_submit Submit handler for the task retry button.
hosting_task_set_title Set the title of tasks automatically and in a consistent way
hosting_task_status Return the status of the task matching the specification
hosting_task_status_class @todo Please document this function.
hosting_task_status_output Return the status of a task in human-readable form
hosting_task_table A concise table listing of the tasks affecting this node
hosting_task_update Implements hook_update().
hosting_task_update_status Set a task's status according to its log.
hosting_task_update_status_form Adds button to update the status of task nodes stuck in the 'processing' state.
hosting_task_update_status_form_submit Submit handler for the 'update status' button.
hosting_task_view Implements hook_view().
hosting_task_views_api Views integration
_hosting_get_new_tasks Retrieve a list of outstanding tasks.
_hosting_parse_error_code Turn bitmask integer error code into translatable label.
_hosting_task_button @todo Please document this function.
_hosting_task_error_codes Turn bitmask integer error code into associative array
_hosting_task_list Theme a task list
_hosting_task_log_class Map entry statuses to coincide with our CSS classes.
_hosting_task_log_table Display table containing the logged information for this task

Constants

Namesort descending Description
HOSTING_TASK_ERROR The command was not successfully completed. This is the default error status.
HOSTING_TASK_PROCESSING The task is being processed
HOSTING_TASK_QUEUED The task is queued
HOSTING_TASK_SUCCESS The command completed succesfully.
HOSTING_TASK_WARNING The command was completed successfully, but with warnings. This is requires attention.

File

task/hosting_task.module
View source
  1. <?php
  2. /**
  3. * @file
  4. * Web server node type is defined here.
  5. */
  6. /**
  7. * Implements hook_menu().
  8. */
  9. function hosting_task_menu() {
  10. $items = array();
  11. $tasks = hosting_available_tasks();
  12. foreach ($tasks as $type => $type_tasks) {
  13. if (empty($type_tasks)) {
  14. // This is to workaround problems in the upgrade path where the
  15. // hook returns nothing (e.g. for server).
  16. continue;
  17. }
  18. foreach ($type_tasks as $task => $info) {
  19. if (empty($info['hidden'])) {
  20. $path = sprintf("hosting_confirm/%%hosting_%s_wildcard/%s_%s", $type, $type, $task);
  21. $items[$path] = array(
  22. 'title' => $info['title'],
  23. 'description' => $info['description'],
  24. 'page callback' => 'drupal_get_form',
  25. 'page arguments' => array('hosting_task_confirm_form', 1, $task),
  26. 'access callback' => 'hosting_task_menu_access_csrf',
  27. 'access arguments' => array(1, $task),
  28. 'type' => MENU_CALLBACK,
  29. );
  30. // Complement the $items array with attributes from the task definition
  31. // e.g. a custom access callback.
  32. $items[$path] = array_merge($items[$path], $info);
  33. }
  34. }
  35. }
  36. $items['hosting/tasks/%node/list'] = array(
  37. 'title' => 'Task list',
  38. 'description' => 'AJAX callback for refreshing task list',
  39. 'page callback' => 'hosting_task_ajax_list',
  40. 'page arguments' => array(2),
  41. 'access callback' => 'node_access',
  42. 'access arguments' => array('view', 2),
  43. 'type' => MENU_CALLBACK,
  44. );
  45. $items['hosting/tasks/%node/cancel'] = array(
  46. 'title' => 'Task list',
  47. 'description' => '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. $items['hosting/tasks/queue'] = array(
  55. 'title' => 'Task list',
  56. 'description' => 'AJAX callback for refreshing task queue',
  57. 'page callback' => 'hosting_task_ajax_queue',
  58. 'access arguments' => array('access task logs'),
  59. 'type' => MENU_CALLBACK,
  60. );
  61. // Custom path to task node views for overlay.
  62. // See hosting_task_overlay_paths().
  63. $items['hosting/task/%node'] = array(
  64. 'page callback' => 'node_page_view',
  65. 'page arguments' => array(2),
  66. 'access arguments' => array('access task logs'),
  67. );
  68. $items['hosting/task/log/ajax/%node/%/%'] = array(
  69. 'page callback' => 'hosting_task_log_ajax',
  70. 'page arguments' => array(4, 5, 6),
  71. 'access arguments' => array('access task logs'),
  72. 'delivery callback' => 'ajax_deliver',
  73. );
  74. return $items;
  75. }
  76. /**
  77. * Read additional task log data, served via AJAX.
  78. */
  79. function hosting_task_log_ajax($node, $last_position, $id) {
  80. $commands = array();
  81. // Long polling for ten seconds.
  82. $expire = time() + 10;
  83. $table = FALSE;
  84. while ((empty($table) || !count($table['#rows'])) && (time() < $expire)) {
  85. usleep(200000);
  86. $table = _hosting_task_log_table($node, $last_position);
  87. }
  88. if (isset($table['#refresh_url'])) {
  89. $url = $table['#refresh_url'];
  90. }
  91. else {
  92. $url = url('hosting/task/log/ajax/' . $node->nid . '/' . $last_position);
  93. }
  94. if (!empty($table)) {
  95. unset($table['#header']);
  96. $commands[] = hosting_task_ajax_command_hosting_table_append('#' . $id, drupal_render($table));
  97. }
  98. $commands[] = hosting_task_ajax_command_hosting_table_check('#' . $id, $url);
  99. return array(
  100. '#type' => 'ajax',
  101. '#commands' => $commands,
  102. );
  103. }
  104. /**
  105. * @todo Please document this function.
  106. * @see http://drupal.org/node/1354
  107. */
  108. function hosting_task_ajax_command_hosting_table_append($selector, $html, $settings = NULL) {
  109. return array(
  110. 'command' => 'hosting_table_append',
  111. 'selector' => $selector,
  112. 'data' => $html,
  113. 'settings' => $settings,
  114. );
  115. }
  116. /**
  117. * @todo Please document this function.
  118. * @see http://drupal.org/node/1354
  119. */
  120. function hosting_task_ajax_command_hosting_table_check($selector, $url, $settings = NULL) {
  121. return array(
  122. 'command' => 'hosting_table_check',
  123. 'selector' => $selector,
  124. 'url' => $url,
  125. 'settings' => $settings,
  126. );
  127. }
  128. /**
  129. * @todo Please document this function.
  130. * @see http://drupal.org/node/1354
  131. */
  132. function hosting_task_ajax_list($node) {
  133. $return['markup'] = hosting_task_table($node);
  134. $return['changed'] = $node->changed;
  135. $return['navigate_url'] = url('node/' . $node->nid);
  136. drupal_json_output($return);
  137. exit();
  138. }
  139. /**
  140. * AJAX callback for refreshing task list.
  141. */
  142. function hosting_task_ajax_queue() {
  143. $view = views_get_view('hosting_task_list');
  144. $view->set_display('block');
  145. $view->pre_execute();
  146. $return['markup'] = $view->render('block');
  147. drupal_json_output($return);
  148. exit();
  149. }
  150. /**
  151. * @todo Please document this function.
  152. * @see http://drupal.org/node/1354
  153. */
  154. function hosting_task_cancel($node) {
  155. if ($node->type == 'task' && $node->task_status == HOSTING_TASK_QUEUED) {
  156. $node->task_status = HOSTING_TASK_WARNING;
  157. node_save($node);
  158. // Log the cancellation.
  159. hosting_task_log($node->vid, 'warning', t("Task was cancelled."));
  160. drupal_goto('node/' . $node->rid);
  161. }
  162. drupal_access_denied();
  163. }
  164. /*
  165. * Implements hook_access()
  166. */
  167. /**
  168. * @todo Please document this function.
  169. * @see http://drupal.org/node/1354
  170. */
  171. function hosting_task_cancel_access($node) {
  172. // Bring $user into scope, so we can test task ownership.
  173. global $user;
  174. // To prevent CSRF attacks, a unique token based upon user is used. Deny
  175. // access if the token is missing or invalid.
  176. if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $user->uid)) {
  177. return FALSE;
  178. }
  179. // 'administer tasks' allows cancelling any and all tasks on the system.
  180. if (user_access('administer tasks')) {
  181. return TRUE;
  182. }
  183. // 'cancel own tasks' allows cancelling any task the user *could have* created,
  184. // on nodes she can view.
  185. if (user_access('cancel own tasks') && user_access('create ' . $node->task_type . ' task') && node_access('view', $node)) {
  186. return TRUE;
  187. }
  188. }
  189. /**
  190. * Task access controls
  191. *
  192. * This function defines which tasks should be showed to the user but
  193. * especially which will be accessible to him, in the permissions system.
  194. *
  195. * @arg $node object
  196. * the node object we're trying to access
  197. *
  198. * @arg $task string
  199. * the task type we're trying to do on the $node
  200. *
  201. * @see hosting_task_menu()
  202. */
  203. function hosting_task_menu_access($node, $task) {
  204. if (user_access("create " . $task . " task")) {
  205. if ($node->type == 'site') {
  206. if (hosting_task_outstanding($node->nid, 'delete') || ($node->site_status == HOSTING_SITE_DELETED)) {
  207. return FALSE;
  208. }
  209. if (($task == 'login-reset') && ($node->site_status != HOSTING_SITE_ENABLED)) {
  210. return FALSE;
  211. }
  212. $safe_tasks = array('backup', 'backup-delete', 'verify', 'enable');
  213. if (!in_array($task, $safe_tasks)) {
  214. // Don't show certain tasks if the site is the 'special' main aegir site.
  215. $profile = node_load($node->profile);
  216. if ($profile->short_name == 'hostmaster') {
  217. return FALSE;
  218. }
  219. }
  220. $site_enabled = (hosting_task_outstanding($node->nid, 'enable') || ($node->site_status == HOSTING_SITE_ENABLED));
  221. $deletable = ($task == "delete");
  222. $enabable = ($task == "enable");
  223. $delete_or_enable = ($deletable || $enabable);
  224. // If the site is not enabled, we can either delete it, or enable it again
  225. if (!$site_enabled) {
  226. return ($delete_or_enable);
  227. }
  228. else {
  229. // Site is enabled
  230. return (
  231. // If we can just delete a site without disabling, allow that
  232. (!variable_get('hosting_require_disable_before_delete', TRUE)) && $deletable
  233. // Otherwise we must disable it first, hide the delete task and the enable task as well
  234. || !$delete_or_enable
  235. );
  236. }
  237. }
  238. if ($node->type == 'platform') {
  239. // if the user can't edit this node, he can't create tasks on it
  240. if (!node_access('update', $node, $GLOBALS['user'])) {
  241. return FALSE;
  242. }
  243. // If the platform is in a deleted state, nothing else can be done with it
  244. if (hosting_task_outstanding($node->nid, 'delete') || ($node->platform_status == HOSTING_PLATFORM_DELETED)) {
  245. return FALSE;
  246. }
  247. // If the platform's been locked, we can unlock it, delete, batch migrate existing sites or verify
  248. if ($node->platform_status == HOSTING_PLATFORM_LOCKED) {
  249. $platform_tasks = array('verify', 'unlock', 'delete', 'migrate');
  250. return (in_array($task, $platform_tasks));
  251. }
  252. else {
  253. // If the platform's unlocked, we can lock it, delete it or batch migrate sites
  254. $platform_tasks = array('verify', 'lock', 'delete', 'migrate');
  255. }
  256. return (in_array($task, $platform_tasks));
  257. }
  258. if ($node->type === 'server') {
  259. // If the user can't edit this node, he can't create tasks on it.
  260. if (!node_access('update', $node, $GLOBALS['user'])) {
  261. return FALSE;
  262. }
  263. // todo probably need more checks
  264. return TRUE;
  265. }
  266. }
  267. return FALSE;
  268. }
  269. /**
  270. * Access callback helper for hosting task menu items.
  271. *
  272. * Implemented as a helper function since we only want to validate the CSRF
  273. * token when the user accesses a certain path, not when (for example) building
  274. * the list of tasks a user has access to.
  275. *
  276. * @arg $node object
  277. * the node object we're trying to access
  278. *
  279. * @arg $task string
  280. * the task type we're trying to do on the $node
  281. *
  282. *
  283. * @see hosting_task_menu_access()
  284. */
  285. function hosting_task_menu_access_csrf($node, $task) {
  286. global $user;
  287. $interactive_tasks = array('migrate', 'clone');
  288. // To prevent CSRF attacks, a unique token based upon user is used. Deny
  289. // access if the token is missing or invalid. We only do this on
  290. // non-interactive tasks.
  291. if (!in_array($task, $interactive_tasks) && (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $user->uid))) {
  292. return FALSE;
  293. }
  294. // Call the main menu access handler.
  295. return hosting_task_menu_access($node, $task);
  296. }
  297. /**
  298. * Implements hook_node_info().
  299. */
  300. function hosting_task_node_info() {
  301. $types["task"] = array(
  302. "type" => 'task',
  303. "name" => t('Task'),
  304. 'base' => 'hosting_task',
  305. "has_title" => FALSE,
  306. "title_label" => '',
  307. "description" => hosting_node_help("task"),
  308. "has_body" => 0,
  309. "body_label" => '',
  310. "min_word_count" => 0,
  311. );
  312. return $types;
  313. }
  314. /**
  315. * Implements hook_node_access().
  316. */
  317. function hosting_task_node_access($node, $op, $account) {
  318. if (hosting_feature('client')) {
  319. // we rely on hosting_client_node_grants() instead of global configuration
  320. return NULL;
  321. }
  322. if (user_access('administer tasks', $account)) {
  323. return NODE_ACCESS_ALLOW;
  324. }
  325. }
  326. /**
  327. * Implements hook_permission().
  328. */
  329. function hosting_task_permission() {
  330. return array(
  331. 'administer tasks' => array(
  332. 'title' => t('administer tasks'),
  333. ),
  334. 'create backup task' => array(
  335. 'title' => t('create backup task'),
  336. ),
  337. 'create restore task' => array(
  338. 'title' => t('create restore task'),
  339. ),
  340. 'create disable task' => array(
  341. 'title' => t('create disable task'),
  342. ),
  343. 'create enable task' => array(
  344. 'title' => t('create enable task'),
  345. ),
  346. 'create delete task' => array(
  347. 'title' => t('create delete task'),
  348. ),
  349. 'create verify task' => array(
  350. 'title' => t('create verify task'),
  351. ),
  352. 'create lock task' => array(
  353. 'title' => t('create lock task'),
  354. ),
  355. 'create unlock task' => array(
  356. 'title' => t('create unlock task'),
  357. ),
  358. 'create login-reset task' => array(
  359. 'title' => t('create login-reset task'),
  360. ),
  361. 'create backup-delete task' => array(
  362. 'title' => t('create backup-delete task'),
  363. ),
  364. 'view own tasks' => array(
  365. 'title' => t('view own tasks'),
  366. ),
  367. 'view task' => array(
  368. 'title' => t('view task'),
  369. ),
  370. 'access task logs' => array(
  371. 'title' => t('access task logs'),
  372. ),
  373. 'retry failed tasks' => array(
  374. 'title' => t('retry failed tasks'),
  375. ),
  376. 'cancel own tasks' => array(
  377. 'title' => t('cancel own tasks'),
  378. ),
  379. 'update status of tasks' => array(
  380. 'title' => t('update status of tasks'),
  381. ),
  382. );
  383. }
  384. /**
  385. * Implements hook_hosting_queues().
  386. *
  387. * Return a list of queues that this module needs to manage.
  388. */
  389. function hosting_task_hosting_queues() {
  390. $queue['tasks'] = array(
  391. 'name' => t('Task queue'),
  392. 'description' => t('Process the queue of outstanding hosting tasks.'),
  393. 'type' => 'serial', // Run queue sequentially. always with the same parameters.
  394. 'frequency' => strtotime("1 minute", 0), # run queue every minute.
  395. 'items' => 5, # process 20 queue items per execution.
  396. 'total_items' => hosting_task_count(),
  397. 'singular' => t('task'),
  398. 'plural' => t('tasks'),
  399. 'running_items' => hosting_task_count_running(),
  400. );
  401. return $queue;
  402. }
  403. /**
  404. * Insert an entry in the task log.
  405. *
  406. * @param $vid
  407. * The vid of the task to add an entry for.
  408. * @param $type
  409. * The type of log entry being added.
  410. * @param $message
  411. * The message for this log entry.
  412. * @param $error
  413. * (optional) The error code associated to this log entry.
  414. * @param $timestamp
  415. * (optional) The UNIX timestamp of this log message, this defaults to the
  416. * current time.
  417. */
  418. function hosting_task_log($vid, $type, $message, $error = '', $timestamp = NULL) {
  419. // We keep track of nids we've looked up in this request, for faster lookups.
  420. static $nids = array();
  421. $timestamp = ($timestamp) ? $timestamp : REQUEST_TIME;
  422. // We need to insert the nid in addition to the vid, so look it up.
  423. if (!isset($nids[$vid])) {
  424. $nids[$vid] = (int) db_query('SELECT nid FROM {hosting_task} WHERE vid = :vid', array(':vid' => $vid))->fetchField();
  425. }
  426. $id = db_insert('hosting_task_log')
  427. ->fields(array(
  428. 'vid' => $vid,
  429. 'nid' => $nids[$vid],
  430. 'type' => $type,
  431. 'message' => $message,
  432. 'error' => isset($error) ? $error : '',
  433. 'timestamp' => $timestamp,
  434. ))
  435. ->execute();
  436. }
  437. /**
  438. * Retry the given task
  439. */
  440. function hosting_task_retry($task_id) {
  441. $node = node_load($task_id);
  442. if ($node->task_status != HOSTING_TASK_QUEUED) {
  443. drupal_set_message(t("The task is being retried and has been added to the hosting queue again"));
  444. hosting_task_log($node->vid, 'queue', t("The task is being retried and has been added to the hosting queue again"));
  445. $node->revision = TRUE;
  446. $node->changed = REQUEST_TIME;
  447. $node->task_status = HOSTING_TASK_QUEUED;
  448. node_save($node);
  449. }
  450. }
  451. /**
  452. * Helper function to generate new task node
  453. */
  454. function hosting_add_task($nid, $type, $args = NULL, $status = HOSTING_TASK_QUEUED) {
  455. global $user;
  456. // Guard against destructive tasks run on guarded nodes.
  457. if (!hosting_task_dangerous_task_is_allowed($type, $nid)) {
  458. return FALSE;
  459. }
  460. $node = db_query("SELECT nid, uid, title FROM {node} WHERE nid = :nid", array(':nid' => $nid))->fetchObject();
  461. $task = new stdClass();
  462. $task->language = LANGUAGE_NONE;
  463. $task->type = 'task';
  464. # TODO: make this pretty
  465. $task->title = t("!type !title", array('!type' => $type, '!title' => $node->title));
  466. $task->task_type = $type;
  467. $task->rid = $node->nid;
  468. /*
  469. * fallback the owner of the task to the owner of the node we operate
  470. * upon
  471. *
  472. * this is mostly for the signup form, which runs as the anonymous
  473. * user, but for which the node is set to the right user
  474. */
  475. $task->uid = $user->uid ? $user->uid : $node->uid;
  476. $task->status = 1;
  477. $task->task_status = $status;
  478. if ($status == HOSTING_TASK_QUEUED) {
  479. $task->revision = TRUE;
  480. }
  481. #arguments, such as which backup to restore.
  482. if (is_array($args)) {
  483. $task->task_args = $args;
  484. }
  485. node_save($task);
  486. return $task;
  487. }
  488. /**
  489. * Implements hook_form().
  490. */
  491. function hosting_task_confirm_form($form, $form_state, $node, $task) {
  492. drupal_add_js(drupal_get_path('module', 'hosting_task') . '/hosting_task.js');
  493. $tasks = hosting_available_tasks($node->type);
  494. if (!isset($tasks[$task]['dialog']) || !$tasks[$task]['dialog']) {
  495. hosting_add_task($node->nid, $task);
  496. if ($task == 'delete') {
  497. // We're deleting a Hosting object, and thus its corresponding node. If
  498. // we stay on the current page, we'll end up with a 404, so redirect to
  499. // the home page.
  500. drupal_set_message(t(':title has been queued for deletion.', array(':title'=>$node->title)));
  501. drupal_goto();
  502. }
  503. drupal_goto('node/' . $node->nid);
  504. }
  505. $form['help'] = array('#value' => $tasks[$task]['description']);
  506. $form['nid'] = array(
  507. '#type' => 'value',
  508. '#value' => $node->nid,
  509. );
  510. $form['task'] = array(
  511. '#type' => 'value',
  512. '#value' => $task,
  513. );
  514. $form['parameters'] = array('#tree' => TRUE);
  515. $func = 'hosting_task_' . str_replace('-', '_', $task) . '_form';
  516. if (function_exists($func)) {
  517. $form['parameters'] += $func($node);
  518. }
  519. $func = $func . '_validate';
  520. if (function_exists($func)) {
  521. $form['#validate'][] = $func;
  522. $form['#func_param_1'] = $node;
  523. $form['#func_param_2'] = $task;
  524. }
  525. $question = t("Are you sure you want to @task @object?", array('@task' => $task, '@object' => $node->title));
  526. $path = !empty($_REQUEST['destination'])? $_REQUEST['destination']: 'node/' . $node->nid;
  527. $form = confirm_form($form, $question, $path, '', $tasks[$task]['title']);
  528. // add an extra class to the actions to allow us to disable the cancel link via javascript for the modal dialog
  529. $form['actions']['#prefix'] = '<div id="hosting-task-confirm-form-actions" class="container-inline">';
  530. return $form;
  531. }
  532. /**
  533. * Customize the task confirmation form for restore.
  534. *
  535. * This adds the backup listing to the confirmation dialog.
  536. */
  537. function hosting_task_restore_form($node) {
  538. $list = hosting_site_backup_list($node->nid);
  539. if (sizeof($list)) {
  540. $form['bid'] = array(
  541. '#type' => 'radios',
  542. '#title' => t('Backups'),
  543. '#options' => $list,
  544. '#required' => TRUE,
  545. );
  546. }
  547. else {
  548. $form['no_backups'] = array(
  549. '#type' => 'item',
  550. '#title' => t('Backups'),
  551. '#value' => t('There are no valid backups available.'),
  552. );
  553. }
  554. return $form;
  555. }
  556. /**
  557. * Implements hook_form_alter().
  558. * TODO: Move restore functions to hosting_site, where backup functions are.
  559. */
  560. function hosting_task_restore_form_validate($form, &$form_state) {
  561. if (isset($form['parameters']['no_backups'])) {
  562. form_set_error('no_backups', t('There are no valid backups available.'));
  563. }
  564. }
  565. /**
  566. * Generic form submit handler for tasks confirmation
  567. *
  568. * This handler gets called after any task has been confirmed by the user. It
  569. * will inject a new task in the queue and redirect the user to the
  570. * originating node.
  571. *
  572. * @see hosting_add_task()
  573. */
  574. function hosting_task_confirm_form_submit($form, &$form_state) {
  575. $values = $form_state['values'];
  576. $parameters = isset($values['parameters']) ? $values['parameters'] : array();
  577. hosting_add_task($values['nid'], $values['task'], $parameters);
  578. $form_state['redirect'] = 'node/' . $values['nid'];
  579. if (module_exists('overlay')) {
  580. overlay_close_dialog();
  581. }
  582. }
  583. /**
  584. * Set the title of tasks automatically and in a consistent way
  585. *
  586. * Tasks should always be named 'task_type node_title'.
  587. */
  588. function hosting_task_set_title(&$node) {
  589. $ref = db_query("SELECT vid, nid, title, type FROM {node} WHERE nid = :nid", array(':nid' => $node->rid))->fetch();
  590. $tasks = hosting_available_tasks($ref->type);
  591. $node->title = drupal_ucfirst($tasks[$node->task_type]['title']) . ': ' . $ref->title;
  592. db_update('node')
  593. ->fields(array(
  594. 'title' => $node->title,
  595. ))
  596. ->condition('nid', $node->nid)
  597. ->execute();
  598. db_update('node_revision')
  599. ->fields(array(
  600. 'title' => $node->title,
  601. ))
  602. ->condition('vid', $node->vid)
  603. ->execute();
  604. }
  605. /**
  606. * Process the hosting task queue.
  607. *
  608. * Iterates through the list of outstanding tasks, and execute the commands on the back end.
  609. */
  610. function hosting_tasks_queue($count = 20) {
  611. global $provision_errors;
  612. drush_log(dt("Running tasks queue"));
  613. $tasks = _hosting_get_new_tasks($count);
  614. foreach ($tasks as $task) {
  615. drush_invoke_process('@self', "hosting-task", array($task->nid), array('strict' => FALSE), array('fork' => TRUE));
  616. }
  617. }
  618. /**
  619. * Determine whether there is an outstanding task of a specific type.
  620. *
  621. * This is used to ensure that there are not multiple tasks of the same type queued.
  622. */
  623. function hosting_task_outstanding($nid, $type) {
  624. $return = db_query("
  625. SELECT t.nid FROM {hosting_task} t
  626. INNER JOIN {node} n ON t.vid = n.vid
  627. WHERE
  628. t.rid = !rid
  629. AND t.task_status = !status
  630. AND t.task_type = '!type'
  631. ORDER BY t.vid DESC
  632. LIMIT 1", array('!rid' =>$nid, '!status' => HOSTING_TASK_QUEUED, '!type' => $type))->fetchField();
  633. return $return;
  634. }
  635. /**
  636. * Return the amount of items still in the queue
  637. */
  638. function hosting_task_count() {
  639. $result = db_query("SELECT COUNT(t.vid) FROM {hosting_task} t INNER JOIN {node} n ON t.vid = n.vid WHERE t.task_status = '!task_status'", array('task_status' => HOSTING_TASK_QUEUED))->fetchField();
  640. return $result;
  641. }
  642. /**
  643. * Return the amount of items running in the queue
  644. */
  645. function hosting_task_count_running() {
  646. return db_query("SELECT COUNT(t.nid) FROM {node} n INNER JOIN {hosting_task} t ON n.vid=t.vid WHERE type = !type AND t.task_status = !t.task_status", array('!type' => 'task', '!t.task_status' => HOSTING_TASK_PROCESSING))->fetchField();
  647. }
  648. /**
  649. * Get a list of tasks that can be invoked by the user.
  650. *
  651. * Note this does not check permissions or relevance of the tasks.
  652. *
  653. * Modules can extend this list using hook_hosting_tasks().
  654. *
  655. * @param $type
  656. * Tasks are grouped by the type of thing that the task is performed on. This
  657. * parameter should either be NULL, or a string indicating the type of thing
  658. * that you want possible tasks to operate on.
  659. * @param $reset
  660. * This function has an internal static cache, to reset the cache, pass in
  661. * TRUE.
  662. *
  663. * @return array
  664. * Depending on the value of the $type parameter, you will either be returned:
  665. * - If $type is NULL: an associative array whose keys are things that can be
  666. * acted upon by the tasks defined in the corresponding values.
  667. * - If $type is a string, then an associative array whose keys are task types
  668. * and whose values are definitions for those tasks.
  669. *
  670. * @see hook_hosting_tasks()
  671. * @see hook_hosting_tasks_alter()
  672. * @see hosting_task_menu_access()
  673. */
  674. function hosting_available_tasks($type = NULL, $reset = FALSE) {
  675. static $cache = array();
  676. if (!sizeof($cache) || $reset) {
  677. $cache = module_invoke_all('hosting_tasks');
  678. drupal_alter('hosting_tasks', $cache);
  679. }
  680. if (isset($type)) {
  681. return $cache[$type];
  682. }
  683. else {
  684. return $cache;
  685. }
  686. }
  687. /**
  688. * Implements hook_action_info().
  689. */
  690. function hosting_task_action_info() {
  691. $available_tasks = hosting_available_tasks();
  692. $actions = array();
  693. foreach ($available_tasks as $module => $tasks) {
  694. foreach ($tasks as $task => $task_info) {
  695. // Search for dashes an replace accordingly.
  696. $task = str_replace('-', '_', $task);
  697. $function = 'hosting_' . $module . '_' . $task . '_action';
  698. if (function_exists($function)) {
  699. $label = ucwords($module . ': ' . $task);
  700. $actions[$function] = array(
  701. 'type' => 'node',
  702. 'label' => t($label),
  703. 'configurable' => FALSE,
  704. );
  705. }
  706. }
  707. }
  708. return $actions;
  709. }
  710. /**
  711. * Implements hook_insert().
  712. */
  713. function hosting_task_insert($node) {
  714. $node->executed = isset($node->executed) ? $node->executed : NULL;
  715. $node->delta = isset($node->delta) ? $node->delta : NULL;
  716. $id = db_insert('hosting_task')
  717. ->fields(array(
  718. 'vid' => $node->vid,
  719. 'nid' => $node->nid,
  720. 'task_type' => $node->task_type,
  721. 'task_status' => $node->task_status,
  722. 'rid' => $node->rid,
  723. 'executed' => $node->executed,
  724. 'delta' => $node->delta,
  725. ))
  726. ->execute();
  727. if (isset($node->task_args) && is_array($node->task_args)) {
  728. foreach ($node->task_args as $key => $value) {
  729. $id = db_insert('hosting_task_arguments')
  730. ->fields(array(
  731. 'vid' => $node->vid,
  732. 'nid' => $node->nid,
  733. 'name' => $key,
  734. 'value' => $value,
  735. ))
  736. ->execute();
  737. }
  738. }
  739. hosting_task_set_title($node);
  740. }
  741. /**
  742. * Implements hook_update().
  743. *
  744. * As an existing node is being updated in the database, we need to do our own
  745. * database updates.
  746. */
  747. function hosting_task_update($node) {
  748. // if this is a new node or we're adding a new revision,
  749. if (!empty($node->revision)) {
  750. hosting_task_insert($node);
  751. }
  752. else {
  753. hosting_task_set_title($node);
  754. db_update('hosting_task')
  755. ->fields(array(
  756. 'nid' => $node->nid,
  757. 'task_type' => $node->task_type,
  758. 'task_status' => $node->task_status,
  759. 'rid' => $node->rid,
  760. 'executed' => $node->executed,
  761. 'delta' => $node->delta,
  762. ))
  763. ->condition('vid', $node->vid)
  764. ->execute();
  765. if (isset($node->task_args) && is_array($node->task_args)) {
  766. # Wipe out old arguments first, since arguments could theoretically be removed.
  767. db_delete('hosting_task_arguments')
  768. ->condition('vid', $node->vid)
  769. ->execute();
  770. foreach ($node->task_args as $key => $value) {
  771. $id = db_insert('hosting_task_arguments')
  772. ->fields(array(
  773. 'vid' => $node->vid,
  774. 'nid' => $node->nid,
  775. 'name' => $key,
  776. 'value' => $value,
  777. ))
  778. ->execute();
  779. }
  780. }
  781. }
  782. }
  783. /**
  784. * Implements hook_delete_revision()
  785. */
  786. function hosting_nodeapi_task_delete_revision(&$node) {
  787. db_delete('hosting_task')
  788. ->condition('vid', $node->vid)
  789. ->execute();
  790. db_delete('hosting_task_arguments')
  791. ->condition('vid', $node->vid)
  792. ->execute();
  793. db_delete('hosting_task_log')
  794. ->condition('vid', $node->vid)
  795. ->execute();
  796. }
  797. /**
  798. * Implements hook_delete().
  799. */
  800. function hosting_task_delete($node) {
  801. db_delete('hosting_task')
  802. ->condition('nid', $node->nid)
  803. ->execute();
  804. db_delete('hosting_task_arguments')
  805. ->condition('nid', $node->nid)
  806. ->execute();
  807. db_delete('hosting_task_log')
  808. ->condition('nid', $node->nid)
  809. ->execute();
  810. }
  811. /**
  812. * Delete tasks related to a given site, platform, server, etc.
  813. */
  814. function hosting_task_delete_related_tasks($nid) {
  815. $result = db_query("SELECT distinct nid FROM {hosting_task} WHERE rid = :rid", array(':rid' => $nid));
  816. foreach ($result as $node) {
  817. node_delete($node->nid);
  818. }
  819. }
  820. /**
  821. * Implements hook_load().
  822. */
  823. function hosting_task_load($nodes) {
  824. foreach ($nodes as $nid => &$node) {
  825. $additions = db_query('SELECT task_type, executed, delta, rid, task_status FROM {hosting_task} WHERE vid = :vid', array(':vid' => $node->vid))->fetch();
  826. $result = db_query("SELECT name, value FROM {hosting_task_arguments} WHERE vid = :vid", array(':vid' => $node->vid));
  827. if ($result) {
  828. while ($arg = $result->fetch()) {
  829. $additions->task_args[$arg->name] = $arg->value;
  830. }
  831. }
  832. foreach ($additions as $property => &$value) {
  833. $node->$property = $value;
  834. }
  835. }
  836. }
  837. /**
  838. * Adds a retry button to failed task nodes.
  839. */
  840. function hosting_task_retry_form($form, $form_state, $nid) {
  841. $form['#prefix'] = '<div class="hosting-task-retry">';
  842. $form['task'] = array(
  843. '#type' => 'hidden',
  844. '#default_value' => $nid,
  845. );
  846. $form['retry'] = array(
  847. '#type' => 'submit',
  848. '#value' => t('Retry'),
  849. );
  850. $form['#suffix'] = '</div>';
  851. return $form;
  852. }
  853. /**
  854. * Submit handler for the task retry button.
  855. */
  856. function hosting_task_retry_form_submit($form, &$form_state) {
  857. hosting_task_retry($form_state['values']['task']);
  858. if (module_exists('overlay')) {
  859. overlay_close_dialog();
  860. }
  861. }
  862. /**
  863. * Adds button to update the status of task nodes stuck in the 'processing' state.
  864. */
  865. function hosting_task_update_status_form($form, &$form_state, $vid) {
  866. $form['#prefix'] = '<div class="hosting-task-retry">';
  867. $form['task'] = array(
  868. '#type' => 'hidden',
  869. '#default_value' => $vid
  870. );
  871. $form['update-status'] = array(
  872. '#type' => 'submit',
  873. '#value' => t('Update status')
  874. );
  875. $form['#suffix'] = '</div>';
  876. return $form;
  877. }
  878. /**
  879. * Submit handler for the 'update status' button.
  880. */
  881. function hosting_task_update_status_form_submit($form, &$form_state) {
  882. hosting_task_update_status($form_state['values']['task']);
  883. if (module_exists('overlay')) {
  884. overlay_close_dialog();
  885. }
  886. }
  887. /**
  888. * Implements hook_view().
  889. */
  890. function hosting_task_view($node, $teaser = FALSE, $page = FALSE) {
  891. drupal_add_js(drupal_get_path('module', 'hosting') . '/hosting.js');
  892. //$node = node_prepare($node, $teaser);
  893. $ref = node_load($node->rid);
  894. hosting_set_breadcrumb($node);
  895. $node->content['info']['#prefix'] = '<div id="hosting-task-info" class="clear-block">';
  896. $node->content['info']['reference'] = array(
  897. '#type' => 'item',
  898. '#title' => drupal_ucfirst($ref->type),
  899. '#markup' => _hosting_node_link($node->rid),
  900. );
  901. if ($node->task_status != HOSTING_TASK_QUEUED) {
  902. if ($node->task_status == HOSTING_TASK_PROCESSING) {
  903. $node->content['info']['started'] = array(
  904. '#type' => 'item',
  905. '#title' => t('Started'),
  906. '#markup' => format_date($node->executed),
  907. '#weight' => 1,
  908. );
  909. $node->content['info']['delta'] = array(
  910. '#type' => 'item',
  911. '#title' => t('Processing time'),
  912. '#markup' => format_interval(REQUEST_TIME - $node->executed),
  913. '#weight' => 2,
  914. );
  915. }
  916. else {
  917. $node->content['info']['executed'] = array(
  918. '#type' => 'item',
  919. '#title' => t('Executed'),
  920. '#markup' => format_date($node->executed),
  921. '#weight' => 1,
  922. );
  923. $node->content['info']['delta'] = array(
  924. '#type' => 'item',
  925. '#title' => t('Execution time'),
  926. '#markup' => format_interval($node->delta),
  927. '#weight' => 2,
  928. );
  929. }
  930. }
  931. else {
  932. $queues = hosting_get_queues();
  933. $queue = $queues['tasks'];
  934. $next = _hosting_queue_next_run($queue);
  935. $node->content['info']['notexecuted'] = array(
  936. '#type' => 'item',
  937. '#title' => t('This task has not been processed yet'),
  938. '#markup' => 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(REQUEST_TIME, 'custom', 'H:i:sO'))),
  939. );
  940. }
  941. if ($node->task_status) {
  942. $node->content['info']['status'] = array(
  943. '#type' => 'item',
  944. '#title' => t('Status'),
  945. '#markup' => _hosting_parse_error_code($node->task_status),
  946. );
  947. }
  948. $node->content['info']['#suffix'] = '</div>';
  949. if (user_access('retry failed tasks') && ($node->task_status == HOSTING_TASK_ERROR)) {
  950. $node->content['retry'] = array(
  951. 'form' => drupal_get_form('hosting_task_retry_form', $node->nid),
  952. '#weight' => 5,
  953. );
  954. }
  955. if (user_access('update status of tasks') && ($node->task_status == HOSTING_TASK_PROCESSING)) {
  956. $node->content['update-status'] = array(
  957. 'form' => drupal_get_form('hosting_task_update_status_form', $node->vid),
  958. '#weight' => 5,
  959. );
  960. }
  961. if (user_access('access task logs')) {
  962. if (in_array($node->task_status, array(HOSTING_TASK_ERROR, HOSTING_TASK_WARNING))) {
  963. $url_options = array(
  964. 'attributes' => array(
  965. 'class' => array(
  966. 'hosting-button-enabled',
  967. ),
  968. 'target' => '_self',
  969. ),
  970. 'fragment' => 'warning',
  971. );
  972. if (module_exists('overlay') && overlay_get_mode() == 'child') {
  973. $url_options['query'] = array('render' => 'overlay');
  974. }
  975. $node->content['jump-link-warning'] = array(
  976. '#markup' => '<div>' . l('Jump to first warning', request_path(), $url_options) . '</div>',
  977. '#weight' => 8,
  978. );
  979. }
  980. if ($node->task_status == HOSTING_TASK_ERROR) {
  981. $url_options['fragment'] = 'error';
  982. $node->content['jump-link-error'] = array(
  983. '#markup' => '<div>' . l('Jump to first error', request_path(), $url_options) . '</div>',
  984. '#weight' => 9,
  985. );
  986. }
  987. if ($table = _hosting_task_log_table($node)) {
  988. $node->content['hosting_log'] = array(
  989. '#weight' => 10,
  990. 'table' => $table,
  991. );
  992. }
  993. }
  994. return $node;
  995. }
  996. /**
  997. * Display table containing the logged information for this task
  998. */
  999. function _hosting_task_log_table($node, $last_position = 0) {
  1000. $result = db_query("SELECT * FROM {hosting_task_log} WHERE vid = :vid AND lid > :lid ORDER BY lid", array(':vid' => $node->vid, ':lid' => $last_position));
  1001. if ($result) {
  1002. $headers = array('data' => 'Log message', 'execution_time' => 'Execution time');
  1003. $rows = array();
  1004. $last_lid = $last_position;
  1005. $last_timestamp = 0;
  1006. $exec_time = '';
  1007. $row_count = -1;
  1008. foreach ($result as $entry) {
  1009. if (strlen($entry->message) > 300) {
  1010. $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>';
  1011. $message = $summary . "<span class='hosting-task-full'>" . filter_xss($entry->message) . '</span>';
  1012. }
  1013. else {
  1014. $message = filter_xss($entry->message);
  1015. }
  1016. // Add error and warning anchors, so we can provide a quick link to them.
  1017. if ($entry->type == 'error') {
  1018. $message = '<a name="error"></a>' . $message;
  1019. }
  1020. elseif ($entry->type == 'warning') {
  1021. $message = '<a name="warning"></a>' . $message;
  1022. }
  1023. // Add the exec_time to the previous row
  1024. $exec_time = $entry->timestamp - $last_timestamp;
  1025. // "1" is unreliable because timestamps don't allow sub-second granularity
  1026. if ($exec_time < 1) {
  1027. $exec_time = '<div>-</div>';
  1028. }
  1029. else if ($exec_time == 1) {
  1030. $exec_time = '<div title="Many tasks take less than 1 second to perform. This duration represents an aggregate of the preceding tasks\' duration."><strong>' . $exec_time . ' s.</strong></div>';
  1031. }
  1032. else {
  1033. $exec_time = '<div><strong>' . $exec_time . ' s.</strong></div>';
  1034. }
  1035. if ($row_count > -1) {
  1036. $rows[$row_count]['data'][] = array(
  1037. 'data' => $exec_time,
  1038. );
  1039. }
  1040. $row_count++;
  1041. $last_timestamp = $entry->timestamp;
  1042. $row = array(
  1043. array(
  1044. 'data' => $message,
  1045. 'class' => array('hosting-status'),
  1046. ),
  1047. );
  1048. $rows[] = array(
  1049. 'data' => $row,
  1050. 'class' => array(_hosting_task_log_class($entry->type)),
  1051. );
  1052. // Record that we've seen this log row.
  1053. $last_lid = $entry->lid;
  1054. }
  1055. return array(
  1056. '#theme' => "table",
  1057. '#header' => $headers,
  1058. '#rows' => $rows,
  1059. '#attributes' => array(
  1060. 'id' => 'hosting-task-log',
  1061. 'class' => array(
  1062. 'hosting-table',
  1063. ),
  1064. ),
  1065. '#refresh_url' => url('hosting/task/log/ajax/' . $node->nid . '/' . $last_lid),
  1066. '#attached' => array(
  1067. 'js' => array(
  1068. array(
  1069. 'data' => array(
  1070. 'hosting_task' => array(
  1071. 'refresh_url' => url('hosting/task/log/ajax/' . $node->nid . '/' . $last_lid),
  1072. ),
  1073. ),
  1074. 'type' => 'setting',
  1075. ),
  1076. drupal_get_path('module', 'hosting_task') . '/js/hosting_task.table.js',
  1077. ),
  1078. 'library' => array(
  1079. array('system', 'drupal.ajax'),
  1080. ),
  1081. ),
  1082. );
  1083. }
  1084. return FALSE;
  1085. }
  1086. /**
  1087. * Map entry statuses to coincide with our CSS classes.
  1088. *
  1089. * @todo make this irrelevant.
  1090. * @see _drush_print_log().
  1091. */
  1092. function _hosting_task_log_class($type) {
  1093. switch (strtolower($type)) {
  1094. case 'warning':
  1095. case 'cancel':
  1096. case 'rollback': // aegir-specific
  1097. $type = 'warning';
  1098. break;
  1099. case 'failed':
  1100. case 'error':
  1101. $type = 'error';
  1102. break;
  1103. case 'queue': // aegir-specific
  1104. $type = 'queue';
  1105. break;
  1106. case 'ok':
  1107. case 'completed':
  1108. case 'success':
  1109. $type = 'success';
  1110. break;
  1111. case 'notice':
  1112. case 'info':
  1113. case 'message':
  1114. default:
  1115. $type = 'info';
  1116. }
  1117. return 'hosting-' . $type;
  1118. }
  1119. /**
  1120. * Retrieve tasks with specified criterias
  1121. *
  1122. * @arg $filter_by string a field to filter the list with, unchecked
  1123. * @arg $filter_value string what to restrict the field to, checked
  1124. * @arg $count integer the number of tasks to return
  1125. * @arg $element integer which element to start from
  1126. */
  1127. function hosting_get_tasks($filter_by = NULL, $filter_value = NULL, $count = 5, $element = 0) {
  1128. $nodes = array();
  1129. // TODO: Validate this DBTNG conversion.
  1130. /*
  1131. $args[] = 'task';
  1132. $cond = '';
  1133. if ($filter_by && $filter_value) {
  1134. $cond = ' AND t.' . $filter_by . ' = %d';
  1135. $args[] = $filter_value;
  1136. }
  1137. $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);
  1138. */
  1139. $query = db_select('node', 'n')
  1140. ->extend('PagerDefault');
  1141. $query->element($element);
  1142. $query->join('hosting_task', 't', 'n.vid=t.vid');
  1143. $query = $query
  1144. ->fields('n')
  1145. ->fields('t', array('task_status', 'task_type', 'rid'))
  1146. ->condition('type', 'task')
  1147. ->orderBy('n.vid', 'DESC')
  1148. ->limit($count)
  1149. ->addTag('node_access');
  1150. if ($filter_by && $filter_value) {
  1151. $query = $query->condition($filter_by, $filter_value);
  1152. }
  1153. $result = $query->execute();
  1154. foreach ($result as $row) {
  1155. $nodes[] = $row;
  1156. }
  1157. return $nodes;
  1158. }
  1159. /**
  1160. * Retrieve a list of outstanding tasks.
  1161. *
  1162. * @param int $limit
  1163. * The amount of items to return.
  1164. * @return array
  1165. * An associative array containing task nodes, indexed by node id.
  1166. */
  1167. function _hosting_get_new_tasks($limit = 20) {
  1168. $return = array();
  1169. $result = db_query_range("SELECT t.nid
  1170. FROM {hosting_task} t
  1171. INNER JOIN {node} n
  1172. ON t.vid = n.vid
  1173. WHERE t.task_status = :task_status
  1174. GROUP BY t.rid
  1175. ORDER BY n.changed, n.nid ASC",
  1176. 0,
  1177. $limit,
  1178. array(
  1179. ':task_status' => HOSTING_TASK_QUEUED,
  1180. ));
  1181. foreach ($result as $node) {
  1182. $return[$node->nid] = node_load($node->nid);
  1183. }
  1184. return $return;
  1185. }
  1186. /**
  1187. * @name Error status definitions
  1188. * @{
  1189. * Bitmask values used to generate the error code to return.
  1190. * @see drush_set_error(), drush_get_error(), drush_cmp_error()
  1191. */
  1192. /**
  1193. * The task is being processed
  1194. */
  1195. define('HOSTING_TASK_PROCESSING', -1);
  1196. /**
  1197. * The task is queued
  1198. */
  1199. define('HOSTING_TASK_QUEUED', 0);
  1200. /**
  1201. * The command completed succesfully.
  1202. */
  1203. define('HOSTING_TASK_SUCCESS', 1);
  1204. /**
  1205. * The command was not successfully completed. This is the default error
  1206. * status.
  1207. */
  1208. define('HOSTING_TASK_ERROR', 2);
  1209. /**
  1210. * The command was completed successfully, but with warnings. This is requires
  1211. * attention.
  1212. */
  1213. define('HOSTING_TASK_WARNING', 3);
  1214. /**
  1215. * @} End of "name Error status definitions".
  1216. */
  1217. /**
  1218. * Turn bitmask integer error code into associative array
  1219. */
  1220. function _hosting_task_error_codes() {
  1221. $codes = array(
  1222. HOSTING_TASK_SUCCESS => t('Successful'),
  1223. HOSTING_TASK_QUEUED => t('Queued'),
  1224. HOSTING_TASK_ERROR => t('Failed'),
  1225. HOSTING_TASK_PROCESSING => t('Processing'),
  1226. HOSTING_TASK_WARNING => t('Warning'),
  1227. );
  1228. return $codes;
  1229. }
  1230. /**
  1231. * Turn bitmask integer error code into translatable label.
  1232. */
  1233. function _hosting_parse_error_code($code) {
  1234. $messages = _hosting_task_error_codes();
  1235. return $messages[$code];
  1236. }
  1237. /**
  1238. * Return the status of the task matching the specification
  1239. */
  1240. function hosting_task_status($filter_by, $filter_value, $type = 'install') {
  1241. $query = db_select('node', 'n')
  1242. ->join('hosting_task', 't', 'n.vid = t.vid')
  1243. ->fields('t', array('task_status'))
  1244. ->condition('n.type', 'task')
  1245. ->condition('t.task_type', $type);
  1246. if ($filter_by && $filter_value) {
  1247. $query->condition('t.' . $filter_by, $filter_value);
  1248. }
  1249. $query->orderBy('t.vid', 'DESC');
  1250. return $query->execute()->fetchField();
  1251. }
  1252. /**
  1253. * Return the status of a task in human-readable form
  1254. *
  1255. * @see hosting_task_status()
  1256. */
  1257. function hosting_task_status_output($filter_by, $filter_value, $type = 'install') {
  1258. $status = hosting_task_status($filter_by, $filter_value, $type);
  1259. if (is_int($status)) {
  1260. return _hosting_parse_error_code($status);
  1261. }
  1262. else {
  1263. return $status; # should be NULL
  1264. }
  1265. }
  1266. /**
  1267. * Display list of tasks
  1268. */
  1269. function hosting_task_list($filter_by = NULL, $filter_value = NULL) {
  1270. return _hosting_task_list($filter_by, $filter_value, 25, 12, 'title');
  1271. }
  1272. /**
  1273. * A concise table listing of the tasks affecting this node
  1274. *
  1275. * This shows a table view of the tasks relevant to this node. It will show
  1276. * tasks that can be executed as well as tasks that have been in a single
  1277. * simple interface.
  1278. */
  1279. function hosting_task_table($node) {
  1280. $output = '';
  1281. $headers[] = t('Task');
  1282. $headers[] = array(
  1283. 'data' => t('Actions'),
  1284. 'class' => array('hosting-actions'),
  1285. );
  1286. $tasklist = hosting_task_fetch_tasks($node->nid);
  1287. $rows = array();
  1288. foreach ($tasklist as $task => $info) {
  1289. $row = array();
  1290. if (!isset($info['nid']) && !$info['task_permitted']) {
  1291. // just don't show those tasks, since we'll not be able to run them
  1292. continue;
  1293. }
  1294. $row['type'] = array(
  1295. 'data' => $info['title'],
  1296. 'class' => array('hosting-status'),
  1297. );
  1298. $actions = array();
  1299. if (isset($info['task_status']) && ($info['task_status'] == 0)) {
  1300. $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']);
  1301. }
  1302. else {
  1303. $actions['run'] = _hosting_task_button(t('Run'), sprintf("hosting_confirm/%d/%s_%s", $node->nid, $node->type, $task), $info['description'], 'hosting-button-run', $info['task_permitted'], $info['dialog']);
  1304. }
  1305. $actions['log'] = _hosting_task_button(t('View'), isset($info['nid']) ? 'hosting/task/' . $info['nid'] : '<front>', t("Display the task log"), 'hosting-button-log', isset($info['nid']) && user_access('access task logs'), TRUE, FALSE);
  1306. $row['actions'] = array(
  1307. 'data' => implode('', $actions),
  1308. 'class' => array('hosting-actions'),
  1309. );
  1310. $rows[] = array(
  1311. 'data' => $row,
  1312. 'class' => array($info['class']),
  1313. );
  1314. }
  1315. $output .= theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('class' => array('hosting-table'))));
  1316. return $output;
  1317. }
  1318. /**
  1319. * @todo Please document this function.
  1320. * @see http://drupal.org/node/1354
  1321. */
  1322. function _hosting_task_button($title, $link, $description, $class = '', $status = TRUE, $dialog = FALSE, $add_token = TRUE) {
  1323. global $user;
  1324. if ($status) {
  1325. $classes[] = 'hosting-button-enabled';
  1326. if (!empty($class)) {
  1327. $classes[] = $class;
  1328. }
  1329. if ($dialog) {
  1330. $classes[] = 'hosting-button-dialog';
  1331. }
  1332. $options['attributes'] = array(
  1333. 'title' => $description,
  1334. 'class' => $classes,
  1335. );
  1336. if ($add_token) {
  1337. $options['query'] = array(
  1338. 'token' => drupal_get_token($user->uid),
  1339. );
  1340. }
  1341. return l($title, $link, $options);
  1342. }
  1343. else {
  1344. return "<span class='hosting-button-disabled'>" . $title . "</span>";
  1345. }
  1346. }
  1347. /**
  1348. * Theme a task list
  1349. */
  1350. function _hosting_task_list($filter_by, $filter_value, $count = 5, $element = 0, $field = 'title', $skip = array(), $pager = TRUE) {
  1351. $nodes = hosting_get_tasks($filter_by, $filter_value, $count, $element);
  1352. if (!$nodes) {
  1353. return t('No tasks available');
  1354. }
  1355. else {
  1356. $headers[t('Task')] = '';
  1357. foreach ($nodes as $node) {
  1358. $row = array();
  1359. if ($field == 'title') {
  1360. $data = drupal_ucfirst($node->task_type) . ' ' . _hosting_node_link($node->rid);
  1361. }
  1362. else {
  1363. $data = $node->{$field};
  1364. }
  1365. $row['type'] = array(
  1366. 'data' => $data,
  1367. 'class' => array('hosting-status'),
  1368. );
  1369. if (!in_array('created', $skip)) {
  1370. $row['created'] = t("@interval ago", array('@interval' => format_interval(REQUEST_TIME - $node->created, 1)));
  1371. $headers[t('Created')] = '';
  1372. }
  1373. if ($node->task_status == 0) {
  1374. $row['executed'] = '';
  1375. }
  1376. else {
  1377. $row['executed'] = t("@interval ago", array('@interval' => format_interval(REQUEST_TIME - $node->changed, 1)));
  1378. }
  1379. $headers[t('Executed')] = '';
  1380. $headers[t('Actions')] = '';
  1381. $actions['log'] = l(t('View'), 'node/' . $node->nid, array('attributes' => array('class' => array('hosting-button-dialog', 'hosting-button-enabled', 'hosting-button-log'))));
  1382. $row['actions'] = array(
  1383. 'data' => $actions['log'],
  1384. 'class' => array('hosting-actions'),
  1385. );
  1386. $class = hosting_task_status_class($node->task_status);
  1387. $rows[] = array(
  1388. 'data' => $row,
  1389. 'class' => array($class),
  1390. );
  1391. }
  1392. $output = theme('table', array('header' => array_keys($headers), 'rows' => $rows, 'attributes' => array('class' => array('hosting-table'))));
  1393. if ($pager === TRUE) {
  1394. $output .= theme('pager', array('tags' => NULL, 'element' => $element));
  1395. }
  1396. elseif (is_string($pager)) {
  1397. $output .= $pager;
  1398. }
  1399. return $output;
  1400. }
  1401. }
  1402. /**
  1403. * @todo Please document this function.
  1404. * @see http://drupal.org/node/1354
  1405. */
  1406. function hosting_task_fetch_tasks($rid) {
  1407. $node = node_load($rid);
  1408. $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
  1409. WHERE n.type = :ntype AND t.rid = :trid
  1410. ORDER BY n.changed ASC", array(':ntype' => 'task', ':trid' => $rid));
  1411. foreach ($result as $obj) {
  1412. $return[$obj->task_type] = array(
  1413. 'nid' => $obj->nid,
  1414. 'task_status' => $obj->task_status,
  1415. 'exists' => TRUE,
  1416. );
  1417. }
  1418. $tasks = hosting_available_tasks($node->type);
  1419. ksort($tasks);
  1420. foreach ($tasks as $type => $hook_task) {
  1421. if (!isset($return[$type])) {
  1422. $return[$type] = array();
  1423. }
  1424. $access_callback = !empty($hook_task['access callback']) ? $hook_task['access callback'] : 'hosting_task_menu_access';
  1425. $task = array();
  1426. $task = array_merge($return[$type], $hook_task);
  1427. $allowed = (isset($task['exists']) && !in_array($task['task_status'], array(HOSTING_TASK_QUEUED, HOSTING_TASK_PROCESSING))) || !isset($task['exists']);
  1428. if ($allowed && empty($task['hidden']) && $access_callback($node, $type)) {
  1429. $task['task_permitted'] = TRUE;
  1430. }
  1431. else {
  1432. $task['task_permitted'] = FALSE;
  1433. }
  1434. // @TODO: Use task defaults array to prevent notices.
  1435. if (!isset($task['dialog'])) {
  1436. $task['dialog'] = FALSE;
  1437. }
  1438. if (!isset($task['task_status'])) {
  1439. $task['task_status'] = NULL;
  1440. }
  1441. $task['class'] = hosting_task_status_class($task['task_status']);
  1442. $return[$type] = $task;
  1443. }
  1444. return $return;
  1445. }
  1446. /**
  1447. * @todo Please document this function.
  1448. * @see http://drupal.org/node/1354
  1449. */
  1450. function hosting_task_status_class($status = NULL) {
  1451. $class = NULL;
  1452. if (!is_null($status)) {
  1453. switch ($status) {
  1454. case HOSTING_TASK_SUCCESS:
  1455. $class = 'hosting-success';
  1456. break;
  1457. case HOSTING_TASK_ERROR:
  1458. $class = 'hosting-error';
  1459. break;
  1460. case HOSTING_TASK_QUEUED:
  1461. $class = 'hosting-queue';
  1462. break;
  1463. case HOSTING_TASK_PROCESSING:
  1464. $class = 'hosting-processing';
  1465. break;
  1466. case HOSTING_TASK_WARNING:
  1467. $class = 'hosting-warning';
  1468. break;
  1469. }
  1470. }
  1471. return $class;
  1472. }
  1473. /**
  1474. * Views integration
  1475. */
  1476. function hosting_task_views_api() {
  1477. return array(
  1478. 'api' => 3,
  1479. 'path' => drupal_get_path('module', 'hosting_task') . '/includes/views',
  1480. );
  1481. }
  1482. /**
  1483. * Implements hook_overlay_paths().
  1484. */
  1485. function hosting_task_overlay_paths() {
  1486. $paths = array(
  1487. 'hosting/task/*' => array(
  1488. 'width' => 600,
  1489. ),
  1490. 'hosting_confirm/*' => array(
  1491. 'width' => 600,
  1492. ),
  1493. );
  1494. return $paths;
  1495. }
  1496. /**
  1497. * Implements hook_preprocess_views_view_table().
  1498. */
  1499. function hosting_task_preprocess_views_view_table(&$vars) {
  1500. $id = "{$vars['view']->name}-{$vars['view']->current_display}";
  1501. switch ($id) {
  1502. case 'hosting_task_list-block':
  1503. //modalframe_parent_js();
  1504. drupal_add_js(drupal_get_path('module', 'hosting_task') . '/hosting_task.js');
  1505. $settings['hostingTaskRefresh'] = array(
  1506. 'queueBlock' => 1
  1507. );
  1508. drupal_add_js($settings, 'setting');
  1509. break;
  1510. }
  1511. }
  1512. /**
  1513. * Set a task's status according to its log.
  1514. *
  1515. * @param A task object or the revision ID of a task node.
  1516. *
  1517. * @return The task's status error code.
  1518. */
  1519. function hosting_task_update_status($task) {
  1520. if (is_numeric($task)) {
  1521. $vid = $task;
  1522. }
  1523. else {
  1524. $vid = $task->vid;
  1525. }
  1526. // Get the status of the task by parsing the log.
  1527. $status = hosting_task_parse_log($vid);
  1528. db_update('hosting_task')
  1529. ->fields(array(
  1530. 'task_status' => $status,
  1531. ))
  1532. ->condition('vid', $vid)
  1533. ->execute();
  1534. // Invoke hook_hosting_task_update_status()
  1535. module_invoke_all('hosting_task_update_status', $task, $status);
  1536. return $status;
  1537. }
  1538. /**
  1539. * Parse a task's log, and return its status accordingly.
  1540. *
  1541. * @param The revision ID of a task node.
  1542. *
  1543. * @return The task's status error code.
  1544. */
  1545. function hosting_task_parse_log($vid) {
  1546. // Assume the best
  1547. $update = HOSTING_TASK_SUCCESS;
  1548. $result = db_query('SELECT type FROM {hosting_task_log} WHERE vid = :vid', array(':vid' => $vid));
  1549. foreach ($result as $status) {
  1550. if ($status->type == 'error' || $status->type == 'failed') {
  1551. $update = HOSTING_TASK_ERROR;
  1552. }
  1553. // Don't override a pre-existing error
  1554. elseif ($update != HOSTING_TASK_ERROR && $status->type == 'warning') {
  1555. $update = HOSTING_TASK_WARNING;
  1556. }
  1557. }
  1558. return $update;
  1559. }
  1560. /**
  1561. * Invoke hooks to build a list of NIDs to guard.
  1562. */
  1563. function hosting_task_get_guarded_nodes() {
  1564. $guarded_nids = &drupal_static(__FUNCTION__);
  1565. if (!isset($guarded_nids)) {
  1566. $guarded_nids = module_invoke_all('hosting_task_guarded_nodes');
  1567. drupal_alter('hosting_task_guarded_nodes', $guarded_nids);
  1568. }
  1569. return $guarded_nids;
  1570. }
  1571. /**
  1572. * Implements hook_hosting_task_guarded_nodes().
  1573. */
  1574. function hosting_task_hosting_task_guarded_nodes() {
  1575. // Guard against destructive tasks run on the hostmaster site or platform.
  1576. $hostmaster_site_nid = hosting_get_hostmaster_site_nid();
  1577. $hostmaster_platform_nid = hosting_get_hostmaster_platform_nid();
  1578. $guarded_nids = array(
  1579. $hostmaster_site_nid,
  1580. $hostmaster_platform_nid,
  1581. );
  1582. return $guarded_nids;
  1583. }
  1584. /**
  1585. * Invoke hooks to build a list of dangerous tasks.
  1586. */
  1587. function hosting_task_get_dangerous_tasks() {
  1588. $dangerous_tasks = &drupal_static(__FUNCTION__);
  1589. if (!isset($dangerous_tasks)) {
  1590. $dangerous_tasks = module_invoke_all('hosting_task_dangerous_tasks');
  1591. drupal_alter('hosting_task_dangerous_tasks', $dangerous_tasks);
  1592. }
  1593. return $dangerous_tasks;
  1594. }
  1595. /**
  1596. * Implements hook_hosting_task_dangerous_tasks();
  1597. */
  1598. function hosting_task_hosting_task_dangerous_tasks() {
  1599. $dangerous_tasks = array(
  1600. 'disable',
  1601. 'delete',
  1602. );
  1603. return $dangerous_tasks;
  1604. }
  1605. /**
  1606. * Guard against destructive tasks running on guarded Aegir entities.
  1607. */
  1608. function hosting_task_dangerous_task_is_allowed($task_type, $nid) {
  1609. $allowed = TRUE;
  1610. $guarded_nids = hosting_task_get_guarded_nodes();
  1611. if (in_array($nid, $guarded_nids)) {
  1612. $dangerous_tasks = hosting_task_get_dangerous_tasks();
  1613. if (in_array($task_type, $dangerous_tasks)) {
  1614. $node = node_load($nid);
  1615. $map = array(
  1616. ':task' => $task_type,
  1617. ':title' => $node->title,
  1618. ':type' => $node->type,
  1619. );
  1620. drupal_set_message(t('You cannot run dangerous :task task on guarded :type :title.', $map), 'warning');
  1621. watchdog('hostmaster', t('Detected attempt to run dangerous :task task on guarded :type :title.', $map), array(), WATCHDOG_WARNING);
  1622. $allowed = FALSE;
  1623. }
  1624. }
  1625. return $allowed;
  1626. }