cron.php 8.5 KB

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