batch.module

  1. 7.x modules/batch/batch.module
  2. 6.x modules/batch/batch.module
  3. 5.x modules/batch/batch.module

File

modules/batch/batch.module
View source
  1. <?php
  2. /**
  3. * The main module file for the Batch module.
  4. *
  5. * Credit where credit is due: This module is largely inspired by the excellent Batch processing functionality
  6. * baked into Drupal (like a lot of FlightPath).
  7. */
  8. /**
  9. * Implementation of hook_menu
  10. *
  11. */
  12. function batch_menu() {
  13. $items = array();
  14. // Our test batch process, for testing...
  15. $items["batch-test-form"] = array(
  16. "title" => "Batch Test - Form",
  17. "page_callback" => "fp_render_form",
  18. "page_arguments" => array("batch_test_form"),
  19. "access_arguments" => array("batch_run_test"),
  20. "type" => MENU_TYPE_CALLBACK,
  21. "file" => menu_get_module_path("batch") . "/batch.test.inc",
  22. );
  23. $items["batch-processing/%"] = array(
  24. "page_callback" => "batch_processing_page",
  25. "page_arguments" => array(1),
  26. "access_callback" => TRUE,
  27. "type" => MENU_TYPE_CALLBACK,
  28. );
  29. $items["batch-finished/%"] = array(
  30. "page_callback" => "batch_finished_page",
  31. "page_arguments" => array(1),
  32. "access_callback" => TRUE,
  33. "type" => MENU_TYPE_CALLBACK,
  34. );
  35. $items["batch-ajax-callback/%"] = array(
  36. "page_callback" => "batch_ajax_callback",
  37. "page_arguments" => array(1),
  38. "access_callback" => TRUE,
  39. "type" => MENU_TYPE_CALLBACK,
  40. );
  41. return $items;
  42. }
  43. /**
  44. * This function is called by ajax, and will trigger each run of the batch operation function, then
  45. * return appropriate results back to our javascript.
  46. *
  47. * @param unknown_type $batch_id
  48. */
  49. function batch_ajax_callback($batch_id) {
  50. $rtn = array();
  51. $batch = batch_get($batch_id);
  52. if (!$batch || $batch["token"] != batch_get_token()) {
  53. // Not allowed! Bad token.
  54. header('Content-Type: application/json');
  55. print json_encode(array("error" => t("An error has occured. You are not allowed to run this batch, or the batch does not exist.")));
  56. exit();
  57. }
  58. ///////////////////////////////////
  59. // Run the batch operation.
  60. if (isset($batch["file"]) && $batch["file"] != "") {
  61. require_once($batch["file"]);
  62. }
  63. $operation_callback = $batch["operation"][0];
  64. // If the operation callback doesn't exist, throw an error.
  65. if (!function_exists($operation_callback)) {
  66. header('Content-Type: application/json');
  67. print json_encode(array("error" => t("An error has occured: Cannot find operation callback function @function", array("@function" => addslashes($operation_callback)))));
  68. exit();
  69. }
  70. $operation_args = $batch["operation"][1];
  71. // Add our $batch to the beginning of the $args array.
  72. $params = array(0 => &$batch);
  73. foreach ($operation_args as $val) {
  74. $params[] = $val;
  75. }
  76. call_user_func_array($operation_callback, $params);
  77. // Coming out of this, $batch should be modified
  78. // Save it back to the database.
  79. db_query("UPDATE batch_queue
  80. SET batch_data = ?
  81. WHERE batch_id = ? ", serialize($batch), $batch_id);
  82. // Output relavant results....
  83. $current = $batch["results"]["current"];
  84. $total = $batch["results"]["total"];
  85. $percent = "";
  86. if ($total > 0) {
  87. $percent = round(($current / $total) * 100, 1) * 1;
  88. }
  89. $rtn["progress_message"] = t($batch["progress_message"], array("@current" => $current, "@total" => $total, "@percent" => $percent));
  90. $rtn["percent"] = $percent;
  91. $rtn["display_percent"] = $batch["display_percent"];
  92. $rtn["finished"] = "";
  93. if ($batch["results"]["finished"] == TRUE) {
  94. $rtn["finished"] = "finished";
  95. }
  96. header('Content-Type: application/json');
  97. $rtn["success"] = "SUCCESS"; // let javascript know we DID finish executing correctly.
  98. print json_encode($rtn);
  99. exit();
  100. }
  101. /**
  102. * This will append to a file, stored in custom/files/private, which
  103. * will use the batch id as its filename. If the file does not exist, it will be be created.
  104. * $batch is the array returned by @see batch_get()
  105. * $content is the string contents we wish to add to the file.
  106. * $filetype is the ext of the file, which gives us a clue of the file type.
  107. */
  108. function batch_append_to_batch_file($batch, $content, $filetype = 'csv') {
  109. $private_path = fp_get_files_path() . "/private";
  110. $batch_id = $batch['batch_id'];
  111. $created = $batch['created'];
  112. $filename = $private_path . '/batch_' . $batch_id . '__' . $created . '.' . $filetype . '.tmp';
  113. if (!file_exists($filename)) {
  114. file_put_contents($filename, ''); // Just create the file.
  115. }
  116. // Now, append to it...
  117. file_put_contents($filename, $content, FILE_APPEND);
  118. }
  119. /**
  120. * We redirect to this page when we have finished a batch.
  121. *
  122. * @param unknown_type $batch_id
  123. */
  124. function batch_finished_page($batch_id) {
  125. $batch = batch_get($batch_id);
  126. if ($batch["token"] != batch_get_token()) {
  127. return "<p>" . t("Sorry, there is a token mismatch or this batch no longer exists.") . "</p>";
  128. }
  129. // Otherwise, we can just return whatever their callback function is. If this is from a form submission, we will send them
  130. // to the form's original destination.
  131. if (isset($batch["file"]) && $batch["file"] != "") {
  132. require_once($batch["file"]);
  133. }
  134. $finished_callback = $batch["finished_callback"][0];
  135. $finished_args = array();
  136. if (isset($batch['finished_callback'][1])) {
  137. $finished_args = $batch["finished_callback"][1];
  138. }
  139. $params = array();
  140. // If we are NOT going to fp_goto, then add the $batch as the first argument.
  141. if ($finished_callback != "fp_goto") {
  142. // Add our $batch to the beginning of the $args array.
  143. //array_unshift($finished_args, $batch);
  144. $params = array (0 => &$batch);
  145. }
  146. foreach ($finished_args as $val) {
  147. $params[] = $val;
  148. }
  149. if (isset($batch["success_message"])) {
  150. fp_add_message($batch["success_message"]);
  151. }
  152. // Okay, let's call our function:
  153. return call_user_func_array($finished_callback, $params);
  154. }
  155. /**
  156. * This is the page the user sees while a batch is being processed.
  157. *
  158. * It will contain all the AJAX and such necessary to begin the batch process.
  159. *
  160. * @param unknown_type $batch_id
  161. */
  162. function batch_processing_page($batch_id) {
  163. $rtn = "";
  164. $batch = batch_get($batch_id);
  165. if ($batch["token"] != batch_get_token()) {
  166. return "<p>" . t("Sorry, there is a token mismatch or this batch no longer exists.") . "</p>";
  167. }
  168. fp_set_title($batch["title"]);
  169. fp_add_css(fp_get_module_path("batch") . "/css/batch.css");
  170. // Set up our setting...
  171. fp_add_js(array("batch_id" => $batch_id), "setting");
  172. // Add our general js file...
  173. fp_add_js(fp_get_module_path("batch") . "/js/batch.js");
  174. $rtn .= "<div class='batch-progress-bar-wrapper'>
  175. <div class='batch-progress-bar' id='batch-progress-bar'></div>
  176. </div>
  177. <div id='batch-progress-message'>" . t("Initializing... please wait...") . "</div>
  178. ";
  179. return $rtn;
  180. }
  181. /**
  182. * Implementation of hook_cron
  183. *
  184. * Delete old batches from the batch_queue table. These would be normal "old" batches we no longer need,
  185. * or ones that never finished for some reason and are just taking up space.
  186. *
  187. */
  188. function batch_cron() {
  189. $older_than = strtotime("2 HOURS AGO");
  190. db_query("DELETE FROM batch_queue WHERE created <= ? ", $older_than);
  191. // Should we check to see if there are any temp files to delete?
  192. if (time() > variable_get("batch_check_to_delete_temp_files", 0)) {
  193. // Also look for any "batch_" files which are too old.
  194. $private_path = fp_get_files_path() . "/private";
  195. $files = scandir($private_path);
  196. foreach ($files as $file) {
  197. if (str_starts_with($file, "batch_") && str_ends_with($file, ".tmp")) {
  198. $temp = explode("__", $file);
  199. $temp2 = explode(".", $temp[1]);
  200. $ts = intval($temp2[0]);
  201. if ($ts > 999) { // hopefully this is a valid timestamp
  202. if ($ts <= $older_than) {
  203. // Yes, we can delete now.
  204. watchdog("batch", "Delete old temp file: " . $file, array(), WATCHDOG_DEBUG);
  205. unlink($private_path . '/' . $file);
  206. }
  207. }
  208. }
  209. } // foreach file
  210. // Set a new time to check to delete temp files...
  211. variable_set("batch_check_to_delete_temp_files", strtotime("NOW + 2 HOURS"));
  212. } // if time > variable_get....
  213. }
  214. /**
  215. * Create a new batch process.
  216. *
  217. *
  218. *
  219. * @param $batch
  220. * This is an array containing everything we need to know about the batch we are creating.
  221. * The non-required fields will be substituted with default values if left out.
  222. *
  223. * - operation :[REQUIRED] an array containing the callback function to execute plus any arguments. Ex: "operation" => array('my_function_to_call', array($arg1, $arg2))
  224. * - finished_callback : an array containing the callback function and args to call once we are finished with the batch.
  225. * Ex: "finished_callback" => array('my_finished_function', array($arg1, $arg2))
  226. * Generally, this field is optional. Since batches are usually set up in a form's submit handler, the default behavior is to go to whatever the form's destination
  227. * was. You can override that here, however.
  228. * - title : A title to display to the user while the batch is running. Ex: 'title' => t('Processing Batch...')
  229. * - progress_message : A message to be displayed while the batch runs. It may use replacement patterns: @current, @total, @percent.
  230. * Ex: 'progress_message' => 'Processed @current out of @total.' // Do NOT run through t()! It will be done at run time.
  231. * - file : An optional file system path to a file where the batch's operation and/or finished functions reside. Ex: "file" => menu_get_module_path("my_example") . "/runme.inc"
  232. * - success_message : An optional message which will display to the user when the batch is complete. Ex: "The process is now complete."
  233. * - display_percent : boolean, default FALSE. If set to TRUE, display percentage value in progress bar.
  234. *
  235. *
  236. * @return $batch_id - the id from batch_queue table for this batch.
  237. */
  238. function batch_set($batch) {
  239. global $user;
  240. if (!isset($batch["title"])) {
  241. $batch["title"] = t("Processing Batch...");
  242. }
  243. if (!isset($batch["progress_message"])) {
  244. $batch["progress_message"] = t("Processed @current out of @total.");
  245. }
  246. if (!isset($batch["display_percent"])) {
  247. $batch["display_percent"] = FALSE;
  248. }
  249. // Add to the database
  250. $ser_batch = serialize($batch);
  251. $token = batch_get_token();
  252. db_query("INSERT INTO batch_queue (token, created, batch_data)
  253. VALUES ('?', '?', '?') ", $token, time(), $ser_batch);
  254. $batch_id = db_insert_id();
  255. // Set a session variable so we are aware of this batch process's existence, so we can call it from system's handle_form_submit if necessary.
  256. $_SESSION["fp_batch_id"] = $batch_id;
  257. // Return back the $bid for the next function to work with.
  258. return $batch_id;
  259. }
  260. /**
  261. * Return the batch_data array for this batch_id, or FALSE if it doesn't exist or can't be unserialized.
  262. *
  263. * @param unknown_type $batch_id
  264. */
  265. function batch_get($batch_id) {
  266. $res = db_query("SELECT * FROM batch_queue
  267. WHERE batch_id = ? ", $batch_id);
  268. $cur = db_fetch_array($res);
  269. if ($batch = unserialize($cur["batch_data"] ?? '')) {
  270. $batch["batch_id"] = $batch_id;
  271. $batch["token"] = $cur["token"];
  272. $batch["created"] = $cur["created"];
  273. return $batch;
  274. }
  275. return FALSE;
  276. }
  277. /**
  278. * Return a token for this user.
  279. *
  280. */
  281. function batch_get_token() {
  282. global $user;
  283. $uid = 0;
  284. if (isset($user->user_id)) {
  285. $uid = $user->user_id;
  286. }
  287. // Return back md5 of user_id + session_id.
  288. return md5($uid . session_id());
  289. }
  290. /**
  291. * Implementation of hook_perm
  292. *
  293. */
  294. function batch_perm() {
  295. $perms = array (
  296. "batch_run_test" => array(
  297. "title" => t("Run test batch function"),
  298. "description" => t("This is only useful for developers. It allows a user to execute a test batch process by visiting example.com/batch-test-form in their browser."),
  299. "admin_restricted" => TRUE, // means only appears for admin (user_id == 1)
  300. ),
  301. );
  302. return $perms;
  303. }
  304. /**
  305. * A batch process is being initiated from a form submission.
  306. *
  307. * @param unknown_type $batch_id
  308. */
  309. function batch_start_batch_from_form_submit($batch_id, $redirect_path = "", $redirect_query = "") {
  310. // We need to confirm that this user is allowed to access THIS batch.
  311. $current_user_token = batch_get_token();
  312. $batch = batch_get($batch_id);
  313. if ($batch["token"] == $current_user_token) {
  314. // Yes, we can proceed!
  315. // If there isn't a finished_callback set, we will set it using fp_goto() to return to the original form's redirect path and query.
  316. if (!isset($batch["finished_callback"])) {
  317. $batch["finished_callback"] = array("fp_goto", array($redirect_path, $redirect_query));
  318. // Update the database...
  319. db_query("UPDATE batch_queue
  320. SET batch_data = '?'
  321. WHERE batch_id = '?'", serialize($batch), $batch_id);
  322. }
  323. // Let's fp_goto to our batch-processing page...
  324. fp_goto("batch-processing/$batch_id");
  325. }
  326. else {
  327. // No... some problem.
  328. fp_add_message(t("Sorry, this batch process could not be initialized. Bad token, or batch does not exist?"), "error");
  329. return;
  330. }
  331. }

Functions

Namesort descending Description
batch_ajax_callback This function is called by ajax, and will trigger each run of the batch operation function, then return appropriate results back to our javascript.
batch_append_to_batch_file This will append to a file, stored in custom/files/private, which will use the batch id as its filename. If the file does not exist, it will be be created. $batch is the array returned by $content is the string contents we wish to add to the…
batch_cron Implementation of hook_cron
batch_finished_page We redirect to this page when we have finished a batch.
batch_get Return the batch_data array for this batch_id, or FALSE if it doesn't exist or can't be unserialized.
batch_get_token Return a token for this user.
batch_menu Implementation of hook_menu
batch_perm Implementation of hook_perm
batch_processing_page This is the page the user sees while a batch is being processed.
batch_set Create a new batch process.
batch_start_batch_from_form_submit A batch process is being initiated from a form submission.