cron.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. <?php
  2. /**
  3. * This is a slightly strange file. It is not designed to ever be run directly from within SMF's
  4. * conventional running, but called externally to facilitate background tasks. It can be called
  5. * either directly or via cron, and in either case will completely ignore anything supplied
  6. * via command line, or $_GET, $_POST, $_COOKIE etc. because those things should never affect the
  7. * running of this script.
  8. *
  9. * Because of the way this runs, etc. we do need some of SMF but not everything to try to keep this
  10. * running a little bit faster.
  11. *
  12. * Simple Machines Forum (SMF)
  13. *
  14. * @package SMF
  15. * @author Simple Machines http://www.simplemachines.org
  16. * @copyright 2014 Simple Machines and individual contributors
  17. * @license http://www.simplemachines.org/about/smf/license.php BSD
  18. *
  19. * @version 2.1 Alpha 1
  20. */
  21. define('SMF', 'BACKGROUND');
  22. define('FROM_CLI', empty($_SERVER['REQUEST_METHOD']));
  23. define('WIRELESS', false);
  24. // This one setting is worth bearing in mind. If you are running this from proper cron, make sure you
  25. // don't run this file any more frequently than indicated here. It might turn ugly if you do.
  26. // But on proper cron you can always increase this value provided you don't go beyond max_limit.
  27. define('MAX_CRON_TIME', 10);
  28. // If a task fails for whatever reason it will still be marked as claimed. This is the threshold
  29. // by which if a task has not completed in this time, the task should become available again.
  30. define('MAX_CLAIM_THRESHOLD', 300);
  31. // We're going to want a few globals... these are all set later.
  32. global $time_start, $maintenance, $msubject, $mmessage, $mbname, $language;
  33. global $boardurl, $boarddir, $sourcedir, $webmaster_email;
  34. global $db_server, $db_name, $db_user, $db_prefix, $db_persist, $db_error_send, $db_last_error;
  35. global $db_connection, $modSettings, $context, $sc, $user_info, $txt;
  36. global $smcFunc, $ssi_db_user, $scripturl, $db_passwd, $cachedir;
  37. define('TIME_START', microtime(true));
  38. // Just being safe...
  39. foreach (array('db_character_set', 'cachedir') as $variable)
  40. if (isset($GLOBALS[$variable]))
  41. unset($GLOBALS[$variable]);
  42. // Get the forum's settings for database and file paths.
  43. require_once(dirname(__FILE__) . '/Settings.php');
  44. // Make absolutely sure the cache directory is defined.
  45. if ((empty($cachedir) || !file_exists($cachedir)) && file_exists($boarddir . '/cache'))
  46. $cachedir = $boarddir . '/cache';
  47. // Don't do john didley if the forum's been shut down competely.
  48. if ($maintenance == 2)
  49. die($mmessage);
  50. // Fix for using the current directory as a path.
  51. if (substr($sourcedir, 0, 1) == '.' && substr($sourcedir, 1, 1) != '.')
  52. $sourcedir = dirname(__FILE__) . substr($sourcedir, 1);
  53. // Have we already turned this off? If so, exist gracefully.
  54. if (file_exists($cachedir . '/cron.lock'))
  55. obExit_cron();
  56. // Before we go any further, if this is not a CLI request, we need to do some checking.
  57. if (!FROM_CLI)
  58. {
  59. // We will clean up $_GET shortly. But we want to this ASAP.
  60. $ts = isset($_GET['ts']) ? (int) $_GET['ts'] : 0;
  61. if ($ts <= 0 || $ts % 15 != 0 || time() - $ts < 0 || time() - $ts > 20)
  62. obExit_cron();
  63. }
  64. // Load the most important includes. In general, a background should be loading its own dependencies.
  65. require_once($sourcedir . '/Errors.php');
  66. require_once($sourcedir . '/Load.php');
  67. require_once($sourcedir . '/Subs.php');
  68. // Using an pre-PHP 5.1 version?
  69. if (version_compare(PHP_VERSION, '5.1', '<'))
  70. require_once($sourcedir . '/Subs-Compat.php');
  71. // Create a variable to store some SMF specific functions in.
  72. $smcFunc = array();
  73. // This is our general bootstrap, a la SSI.php but with a few differences.
  74. unset ($db_show_debug);
  75. loadDatabase();
  76. reloadSettings();
  77. // Just in case there's a problem...
  78. set_error_handler('error_handler_cron');
  79. $sc = '';
  80. $_SERVER['QUERY_STRING'] = '';
  81. $_SERVER['REQUEST_URL'] = FROM_CLI ? 'CLI cron.php' : $boardurl . '/cron.php';
  82. // Now 'clean the request' (or more accurately, ignore everything we're not going to use)
  83. cleanRequest_cron();
  84. // At this point we could reseed the RNG but I don't think we need to risk it being seeded *even more*.
  85. // Meanwhile, time we got on with the real business here.
  86. while ($task_details = fetch_task())
  87. {
  88. $result = perform_task($task_details);
  89. if ($result)
  90. {
  91. $smcFunc['db_query']('', '
  92. DELETE FROM {db_prefix}background_tasks
  93. WHERE id_task = {int:task}',
  94. array(
  95. 'task' => $task_details['id_task'],
  96. )
  97. );
  98. }
  99. }
  100. obExit_cron();
  101. exit;
  102. // The heart of this cron handler...
  103. function fetch_task()
  104. {
  105. global $smcFunc;
  106. // Check we haven't run over our time limit.
  107. if (microtime(true) - TIME_START > MAX_CRON_TIME)
  108. return false;
  109. // Try to find a task. Specifically, try to find one that hasn't been claimed previously, or failing that,
  110. // a task that was claimed but failed for whatever reason and failed long enough ago. We should not care
  111. // what task it is, merely that it is one in the queue, the order is irrelevant.
  112. $request = $smcFunc['db_query']('', '
  113. SELECT id_task, task_file, task_class, task_data, claimed_time
  114. FROM {db_prefix}background_tasks
  115. WHERE claimed_time < {int:claim_limit}
  116. ORDER BY null
  117. LIMIT 1',
  118. array(
  119. 'claim_limit' => time() - MAX_CLAIM_THRESHOLD,
  120. )
  121. );
  122. if ($row = $smcFunc['db_fetch_assoc']($request))
  123. {
  124. // We found one. Let's try and claim it immediately.
  125. $smcFunc['db_free_result']($request);
  126. $smcFunc['db_query']('', '
  127. UPDATE {db_prefix}background_tasks
  128. SET claimed_time = {int:new_claimed}
  129. WHERE id_task = {int:task}
  130. AND claimed_time = {int:old_claimed}',
  131. array(
  132. 'new_claimed' => time(),
  133. 'task' => $row['id_task'],
  134. 'old_claimed' => $row['claimed_time'],
  135. )
  136. );
  137. // Could we claim it? If so, return it back.
  138. if ($smcFunc['db_affected_rows']() != 0)
  139. {
  140. // Update the time and go back.
  141. $row['claimed_time'] = time();
  142. return $row;
  143. }
  144. else
  145. {
  146. // Uh oh, we just missed it. Try to claim another one, and let it fall through if there aren't any.
  147. return fetch_task();
  148. }
  149. }
  150. else
  151. {
  152. // No dice. Clean up and go home.
  153. $smcFunc['db_free_result']($request);
  154. return false;
  155. }
  156. }
  157. function perform_task($task_details)
  158. {
  159. global $sourcedir, $boarddir;
  160. // This indicates the file to load.
  161. if (!empty($task_details['task_file']))
  162. {
  163. $include = strtr(trim($task_details['task_file']), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
  164. if (file_exists($include))
  165. require_once($include);
  166. }
  167. if (empty($task_details['task_class']))
  168. {
  169. // This would be nice to translate but the language files aren't loaded for any specific language.
  170. log_error('Invalid background task specified (no class, ' . (empty($task_details['task_file']) ? ' no file' : ' to load ' . $task_details['task_file']) . ')');
  171. return true; // So we clear it from the queue.
  172. }
  173. // All background tasks need to be classes.
  174. elseif (class_exists($task_details['task_class']) && is_subclass_of($task_details['task_class'], 'SMF_BackgroundTask'))
  175. {
  176. $details = empty($task_details['task_data']) ? array() : unserialize($task_details['task_data']);
  177. $bgtask = new $task_details['task_class']($details);
  178. return $bgtask->execute();
  179. }
  180. else
  181. {
  182. log_error('Invalid background task specified: (class: ' . $task_details['task_class'] . ', ' . (empty($task_details['task_file']) ? ' no file' : ' to load ' . $task_details['task_file']) . ')');
  183. return true; // So we clear it from the queue.
  184. }
  185. }
  186. // These are all our helper functions that resemble their big brother counterparts. These are not so important.
  187. function cleanRequest_cron()
  188. {
  189. global $scripturl, $boardurl;
  190. $scripturl = $boardurl . '/index.php';
  191. // These keys shouldn't be set...ever.
  192. if (isset($_REQUEST['GLOBALS']) || isset($_COOKIE['GLOBALS']))
  193. die('Invalid request variable.');
  194. // Save some memory.. (since we don't use these anyway.)
  195. unset($GLOBALS['HTTP_POST_VARS'], $GLOBALS['HTTP_POST_VARS']);
  196. unset($GLOBALS['HTTP_POST_FILES'], $GLOBALS['HTTP_POST_FILES']);
  197. unset($GLOBALS['_GET'], $GLOBALS['_POST'], $GLOBALS['_REQUEST'], $GLOBALS['_COOKIE'], $GLOBALS['_FILES']);
  198. }
  199. function error_handler_cron($error_level, $error_string, $file, $line)
  200. {
  201. global $modSettings;
  202. // Ignore errors if we're ignoring them or they are strict notices from PHP 5 (which cannot be solved without breaking PHP 4.)
  203. if (error_reporting() == 0 || (defined('E_STRICT') && $error_level == E_STRICT && !empty($modSettings['enableErrorLogging'])))
  204. return;
  205. $error_type = 'cron';
  206. log_error($error_level . ': ' . $error_string, $error_type, $file, $line);
  207. // If this is an E_ERROR or E_USER_ERROR.... die. Violently so.
  208. if ($error_level % 255 == E_ERROR)
  209. die('No direct access...');
  210. }
  211. function obExit_cron()
  212. {
  213. if (FROM_CLI)
  214. die(0);
  215. else
  216. {
  217. header('Content-Type: image/gif');
  218. die("\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B");
  219. }
  220. }
  221. // We would like this to be defined, but we don't want to have to load more stuff than necessary.
  222. // Thus we declare it here, and any legitimate background task must implement this.
  223. abstract class SMF_BackgroundTask
  224. {
  225. protected $_details;
  226. public function __construct($details)
  227. {
  228. $this->_details = $details;
  229. }
  230. abstract public function execute();
  231. }
  232. ?>