cron.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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', !isset($_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. // Load the most important includes. In general, a background should be loading its own dependencies.
  57. require_once($sourcedir . '/Errors.php');
  58. require_once($sourcedir . '/Load.php');
  59. require_once($sourcedir . '/Subs.php');
  60. // Using an pre-PHP 5.1 version?
  61. if (version_compare(PHP_VERSION, '5.1', '<'))
  62. require_once($sourcedir . '/Subs-Compat.php');
  63. // Create a variable to store some SMF specific functions in.
  64. $smcFunc = array();
  65. // This is our general bootstrap, a la SSI.php but with a few differences.
  66. unset ($db_show_debug);
  67. loadDatabase();
  68. reloadSettings();
  69. // Just in case there's a problem...
  70. set_error_handler('error_handler_cron');
  71. $sc = '';
  72. $_SERVER['QUERY_STRING'] = '';
  73. $_SERVER['REQUEST_URL'] = FROM_CLI ? 'CLI cron.php' : $boardurl . '/cron.php';
  74. // Now 'clean the request' (or more accurately, ignore everything we're not going to use)
  75. cleanRequest_cron();
  76. // At this point we could reseed the RNG but I don't think we need to risk it being seeded *even more*.
  77. // Meanwhile, time we got on with the real business here.
  78. while ($task_details = fetch_task())
  79. {
  80. $result = perform_task($task_details);
  81. if ($result)
  82. {
  83. $smcFunc['db_query']('', '
  84. DELETE FROM {db_prefix}background_tasks
  85. WHERE id_task = {int:task}',
  86. array(
  87. 'task' => $task_details['id_task'],
  88. )
  89. );
  90. }
  91. }
  92. exit;
  93. // The heart of this cron handler...
  94. function fetch_task()
  95. {
  96. global $smcFunc;
  97. // Check we haven't run over our time limit.
  98. if (microtime(true) - TIME_START > MAX_CRON_TIME)
  99. return false;
  100. // Try to find a task. Specifically, try to find one that hasn't been claimed previously, or failing that,
  101. // a task that was claimed but failed for whatever reason and failed long enough ago. We should not care
  102. // what task it is, merely that it is one in the queue, the order is irrelevant.
  103. $request = $smcFunc['db_query']('', '
  104. SELECT id_task, task_file, task_class, task_data, claimed_time
  105. FROM {db_prefix}background_tasks
  106. WHERE claimed_time < {int:claim_limit}
  107. ORDER BY null
  108. LIMIT 1',
  109. array(
  110. 'claim_limit' => time() - MAX_CLAIM_THRESHOLD,
  111. )
  112. );
  113. if ($row = $smcFunc['db_fetch_assoc']($request))
  114. {
  115. // We found one. Let's try and claim it immediately.
  116. $smcFunc['db_free_result']($request);
  117. $smcFunc['db_query']('', '
  118. UPDATE {db_prefix}background_tasks
  119. SET claimed_time = {int:new_claimed}
  120. WHERE id_task = {int:task}
  121. AND claimed_time = {int:old_claimed}',
  122. array(
  123. 'new_claimed' => time(),
  124. 'task' => $row['id_task'],
  125. 'old_claimed' => $row['claimed_time'],
  126. )
  127. );
  128. // Could we claim it? If so, return it back.
  129. if ($smcFunc['db_affected_rows']() != 0)
  130. {
  131. // Update the time and go back.
  132. $row['claimed_time'] = time();
  133. return $row;
  134. }
  135. else
  136. {
  137. // Uh oh, we just missed it. Try to claim another one, and let it fall through if there aren't any.
  138. return fetch_task();
  139. }
  140. }
  141. else
  142. {
  143. // No dice. Clean up and go home.
  144. $smcFunc['db_free_result']($request);
  145. return false;
  146. }
  147. }
  148. function perform_task($task_details)
  149. {
  150. global $sourcedir, $boarddir;
  151. // This indicates the file to load.
  152. if (!empty($task_details['task_file']))
  153. {
  154. $include = strtr(trim($task_details['task_file']), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
  155. if (file_exists($include))
  156. require_once($include);
  157. }
  158. if (empty($task_details['task_class']))
  159. {
  160. // This would be nice to translate but the language files aren't loaded for any specific language.
  161. log_error('Invalid background task specified (no class, ' . (empty($task_details['task_file']) ? ' no file' : ' to load ' . $task_details['task_file']) . ')');
  162. return true; // So we clear it from the queue.
  163. }
  164. // All background tasks need to be classes.
  165. elseif (class_exists($task_details['task_class']) && is_subclass_of($task_details['task_class'], 'SMF_BackgroundTask'))
  166. {
  167. $details = empty($task_details['task_data']) ? array() : unserialize($task_details['task_data']);
  168. $bgtask = new $task_details['task_class']($details);
  169. return $bgtask->execute();
  170. }
  171. else
  172. {
  173. log_error('Invalid background task specified: (class: ' . $task_details['task_class'] . ', ' . (empty($task_details['task_file']) ? ' no file' : ' to load ' . $task_details['task_file']) . ')');
  174. return true; // So we clear it from the queue.
  175. }
  176. }
  177. // These are all our helper functions that resemble their big brother counterparts. These are not so important.
  178. function cleanRequest_cron()
  179. {
  180. global $scripturl, $boardurl;
  181. $scripturl = $boardurl . '/index.php';
  182. // These keys shouldn't be set...ever.
  183. if (isset($_REQUEST['GLOBALS']) || isset($_COOKIE['GLOBALS']))
  184. die('Invalid request variable.');
  185. // Save some memory.. (since we don't use these anyway.)
  186. unset($GLOBALS['HTTP_POST_VARS'], $GLOBALS['HTTP_POST_VARS']);
  187. unset($GLOBALS['HTTP_POST_FILES'], $GLOBALS['HTTP_POST_FILES']);
  188. unset($GLOBALS['_GET'], $GLOBALS['_POST'], $GLOBALS['_REQUEST'], $GLOBALS['_COOKIE'], $GLOBALS['_FILES']);
  189. }
  190. function error_handler_cron($error_level, $error_string, $file, $line)
  191. {
  192. global $modSettings, $db_show_debug;
  193. // Ignore errors if we're ignoring them or they are strict notices from PHP 5 (which cannot be solved without breaking PHP 4.)
  194. if (error_reporting() == 0 || (defined('E_STRICT') && $error_level == E_STRICT && !empty($modSettings['enableErrorLogging'])))
  195. return;
  196. $error_type = 'cron';
  197. log_error($error_level . ': ' . $error_string, $error_type, $file, $line);
  198. // If this is an E_ERROR or E_USER_ERROR.... die. Violently so.
  199. if ($error_level % 255 == E_ERROR)
  200. die('No direct access...');
  201. }
  202. function obExit_cron()
  203. {
  204. if (FROM_CLI)
  205. die(0);
  206. else
  207. {
  208. header('Content-Type: image/gif');
  209. 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");
  210. }
  211. }
  212. // We would like this to be defined, but we don't want to have to load more stuff than necessary.
  213. // Thus we declare it here, and any legitimate background task must implement this.
  214. abstract class SMF_BackgroundTask
  215. {
  216. protected $_details;
  217. public function __construct($details)
  218. {
  219. $this->_details = $details;
  220. }
  221. abstract public function execute();
  222. }
  223. ?>