SproutCMS

This is the code documentation for the SproutCMS project

source of /sprout/Helpers/Cron.php

  1. <?php
  2. /*
  3.  * Copyright (C) 2017 Karmabunny Pty Ltd.
  4.  *
  5.  * This file is a part of SproutCMS.
  6.  *
  7.  * SproutCMS is free software: you can redistribute it and/or modify it under the terms
  8.  * of the GNU General Public License as published by the Free Software Foundation, either
  9.  * version 2 of the License, or (at your option) any later version.
  10.  *
  11.  * For more information, visit <http://getsproutcms.com>.
  12.  */
  13.  
  14. namespace Sprout\Helpers;
  15.  
  16. use Kohana;
  17.  
  18.  
  19. /**
  20.  * Methods for initialising, recording the progress of, and finalising and cron scripts
  21.  */
  22. class Cron
  23. {
  24. protected static $pdo;
  25. protected static $stmt_message;
  26. private static $job_id;
  27. private static $job_name;
  28.  
  29.  
  30. /**
  31.   * Called at the beginning of a cron job.
  32.   *
  33.   * Checks for appropriate access permissions.
  34.   * Creates a database record for the logging messages.
  35.   *
  36.   * Note that this function opens an additional database connection, allowing logging
  37.   * to continue to work even if the main script is using transactions - which it should be.
  38.   **/
  39. public static function start($job_name)
  40. {
  41. self::$job_name = $job_name;
  42.  
  43. // Require admin auth for browser-based requests. These *should* be tunneled via
  44. // the CronJobAdminController's UI but it's possible to call the methods directly
  45. // via a route if the calling code doesn't need CSRF protection.
  46. if (PHP_SAPI !== 'cli') {
  47. $_ENV['CRON'] = 1;
  48. AdminAuth::checkLogin();
  49. Kohana::closeBuffers();
  50. header('Content-type: text/plain');
  51. }
  52.  
  53. ini_set('memory_limit', '128M');
  54.  
  55. set_exception_handler(array('Sprout\\Helpers\\Cron', 'exceptionHandler'));
  56. register_shutdown_function(array('Sprout\\Helpers\\Cron', 'shutdown'));
  57.  
  58. $update_fields = array();
  59. $update_fields['name'] = $job_name;
  60. $update_fields['log'] = '';
  61. $update_fields['status'] = 'Running';
  62. $update_fields['date_added'] = Pdb::now();
  63. $update_fields['date_modified'] = Pdb::now();
  64.  
  65. self::$job_id = Pdb::insert('cron_jobs', $update_fields);
  66.  
  67. self::$pdo = Pdb::connect('default');
  68. $pf = Pdb::prefix();
  69.  
  70. // Prepare a statement for message updating; this is lots faster than direct queries
  71. $q = "UPDATE {$pf}cron_jobs
  72. SET
  73. log = CONCAT(log, '[', :date, '] ', :message, '\n'), date_modified = NOW()
  74. WHERE
  75. id = " . self::$job_id;
  76. self::$stmt_message = self::$pdo->prepare($q);
  77.  
  78. self::message('Starting job ' . $job_name);
  79.  
  80. return true;
  81. }
  82.  
  83.  
  84. /**
  85.   * Save and output log message for the currently running cron job
  86.   *
  87.   * @param string $message The message to log
  88.   * @return void
  89.   **/
  90. public static function message($message = '')
  91. {
  92. echo $message, "\n";
  93. flush();
  94.  
  95. if (!self::$job_id) return;
  96.  
  97. // Only do the splitting by newline if required, to save string copies and therefore RAM.
  98. if (strpos($message, "\n") === false) {
  99. self::$stmt_message->execute([
  100. ':date' => date('h:i:s a'),
  101. ':message' => $message,
  102. ]);
  103. } else {
  104. // Multiline messages are inserted multiple times as individual lines
  105. // Only compute the date and memory usage once though
  106. $args = [
  107. ':date' => date('h:i:s a'),
  108. ];
  109. foreach (explode("\n", $message) as $ln) {
  110. $args[':message'] = $ln;
  111. self::$stmt_message->execute($args);
  112. }
  113. }
  114. }
  115.  
  116.  
  117. /**
  118.   * Report that a cron job failed.
  119.   * Should be used with a return statement to end the execution of the job.
  120.   **/
  121. public static function failure($message = '')
  122. {
  123. if ($message != '') {
  124. self::message($message);
  125. }
  126.  
  127. if (self::$job_id) {
  128. $pf = Pdb::prefix();
  129. $q = "UPDATE {$pf}cron_jobs SET
  130. status = 'Failed', date_modified = NOW()
  131. WHERE
  132. id = " . self::$job_id;
  133. self::$pdo->query($q);
  134. }
  135.  
  136. echo 'Cron job failed. See log entry #' . self::$job_id . ' for more info';
  137. exit(1);
  138. }
  139.  
  140.  
  141. /**
  142.   * Report the successful completion of the cron job.
  143.   **/
  144. public static function success()
  145. {
  146. self::message('Done.');
  147.  
  148. if (self::$job_id) {
  149. $pf = Pdb::prefix();
  150. $q = "UPDATE {$pf}cron_jobs SET
  151. status = 'Success', date_modified = NOW()
  152. WHERE
  153. id = " . self::$job_id;
  154. self::$pdo->query($q);
  155. }
  156.  
  157. exit(0);
  158. }
  159.  
  160.  
  161. /**
  162.   * Exception and error handling for CRON jobs
  163.   **/
  164. public static function exceptionHandler($exception)
  165. {
  166. $log_id = Kohana::logException($exception);
  167. self::message('EXCEPTION ' . get_class($exception));
  168. self::message('Log ID: ' . $log_id);
  169. self::message('Message: ' . $exception->getMessage());
  170. self::message('File: ' . $exception->getFile());
  171. self::message('Line: ' . $exception->getLine());
  172. self::message('');
  173. self::message($exception->getTraceAsString());
  174. self::failure();
  175. }
  176.  
  177.  
  178. /**
  179.   * Shutdown function, for catching fatal errors
  180.   **/
  181. public static function shutdown()
  182. {
  183. $error = error_get_last();
  184. if ($error['type'] == 1) {
  185. self::failure('FATAL ERROR: ' . $error['message']);
  186. }
  187. }
  188.  
  189. }
  190.  
  191.