123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- <?php
- /**
- * This is a slightly strange file. It is not designed to ever be run directly from within SMF's
- * conventional running, but called externally to facilitate background tasks. It can be called
- * either directly or via cron, and in either case will completely ignore anything supplied
- * via command line, or $_GET, $_POST, $_COOKIE etc. because those things should never affect the
- * running of this script.
- *
- * Because of the way this runs, etc. we do need some of SMF but not everything to try to keep this
- * running a little bit faster.
- *
- * Simple Machines Forum (SMF)
- *
- * @package SMF
- * @author Simple Machines http://www.simplemachines.org
- * @copyright 2014 Simple Machines and individual contributors
- * @license http://www.simplemachines.org/about/smf/license.php BSD
- *
- * @version 2.1 Alpha 1
- */
- define('SMF', 'BACKGROUND');
- define('FROM_CLI', empty($_SERVER['REQUEST_METHOD']));
- define('WIRELESS', false);
- // This one setting is worth bearing in mind. If you are running this from proper cron, make sure you
- // don't run this file any more frequently than indicated here. It might turn ugly if you do.
- // But on proper cron you can always increase this value provided you don't go beyond max_limit.
- define('MAX_CRON_TIME', 10);
- // If a task fails for whatever reason it will still be marked as claimed. This is the threshold
- // by which if a task has not completed in this time, the task should become available again.
- define('MAX_CLAIM_THRESHOLD', 300);
- // We're going to want a few globals... these are all set later.
- global $time_start, $maintenance, $msubject, $mmessage, $mbname, $language;
- global $boardurl, $boarddir, $sourcedir, $webmaster_email;
- global $db_server, $db_name, $db_user, $db_prefix, $db_persist, $db_error_send, $db_last_error;
- global $db_connection, $modSettings, $context, $sc, $user_info, $txt;
- global $smcFunc, $ssi_db_user, $scripturl, $db_passwd, $cachedir;
- define('TIME_START', microtime(true));
- // Just being safe...
- foreach (array('db_character_set', 'cachedir') as $variable)
- if (isset($GLOBALS[$variable]))
- unset($GLOBALS[$variable]);
- // Get the forum's settings for database and file paths.
- require_once(dirname(__FILE__) . '/Settings.php');
- // Make absolutely sure the cache directory is defined.
- if ((empty($cachedir) || !file_exists($cachedir)) && file_exists($boarddir . '/cache'))
- $cachedir = $boarddir . '/cache';
- // Don't do john didley if the forum's been shut down competely.
- if ($maintenance == 2)
- die($mmessage);
- // Fix for using the current directory as a path.
- if (substr($sourcedir, 0, 1) == '.' && substr($sourcedir, 1, 1) != '.')
- $sourcedir = dirname(__FILE__) . substr($sourcedir, 1);
- // Have we already turned this off? If so, exist gracefully.
- if (file_exists($cachedir . '/cron.lock'))
- obExit_cron();
- // Before we go any further, if this is not a CLI request, we need to do some checking.
- if (!FROM_CLI)
- {
- // We will clean up $_GET shortly. But we want to this ASAP.
- $ts = isset($_GET['ts']) ? (int) $_GET['ts'] : 0;
- if ($ts <= 0 || $ts % 15 != 0 || time() - $ts < 0 || time() - $ts > 20)
- obExit_cron();
- }
- // Load the most important includes. In general, a background should be loading its own dependencies.
- require_once($sourcedir . '/Errors.php');
- require_once($sourcedir . '/Load.php');
- require_once($sourcedir . '/Subs.php');
- // Using an pre-PHP 5.1 version?
- if (version_compare(PHP_VERSION, '5.1', '<'))
- require_once($sourcedir . '/Subs-Compat.php');
- // Create a variable to store some SMF specific functions in.
- $smcFunc = array();
- // This is our general bootstrap, a la SSI.php but with a few differences.
- unset ($db_show_debug);
- loadDatabase();
- reloadSettings();
- // Just in case there's a problem...
- set_error_handler('error_handler_cron');
- $sc = '';
- $_SERVER['QUERY_STRING'] = '';
- $_SERVER['REQUEST_URL'] = FROM_CLI ? 'CLI cron.php' : $boardurl . '/cron.php';
- // Now 'clean the request' (or more accurately, ignore everything we're not going to use)
- cleanRequest_cron();
- // At this point we could reseed the RNG but I don't think we need to risk it being seeded *even more*.
- // Meanwhile, time we got on with the real business here.
- while ($task_details = fetch_task())
- {
- $result = perform_task($task_details);
- if ($result)
- {
- $smcFunc['db_query']('', '
- DELETE FROM {db_prefix}background_tasks
- WHERE id_task = {int:task}',
- array(
- 'task' => $task_details['id_task'],
- )
- );
- }
- }
- obExit_cron();
- exit;
- // The heart of this cron handler...
- function fetch_task()
- {
- global $smcFunc;
- // Check we haven't run over our time limit.
- if (microtime(true) - TIME_START > MAX_CRON_TIME)
- return false;
- // Try to find a task. Specifically, try to find one that hasn't been claimed previously, or failing that,
- // a task that was claimed but failed for whatever reason and failed long enough ago. We should not care
- // what task it is, merely that it is one in the queue, the order is irrelevant.
- $request = $smcFunc['db_query']('', '
- SELECT id_task, task_file, task_class, task_data, claimed_time
- FROM {db_prefix}background_tasks
- WHERE claimed_time < {int:claim_limit}
- ORDER BY null
- LIMIT 1',
- array(
- 'claim_limit' => time() - MAX_CLAIM_THRESHOLD,
- )
- );
- if ($row = $smcFunc['db_fetch_assoc']($request))
- {
- // We found one. Let's try and claim it immediately.
- $smcFunc['db_free_result']($request);
- $smcFunc['db_query']('', '
- UPDATE {db_prefix}background_tasks
- SET claimed_time = {int:new_claimed}
- WHERE id_task = {int:task}
- AND claimed_time = {int:old_claimed}',
- array(
- 'new_claimed' => time(),
- 'task' => $row['id_task'],
- 'old_claimed' => $row['claimed_time'],
- )
- );
- // Could we claim it? If so, return it back.
- if ($smcFunc['db_affected_rows']() != 0)
- {
- // Update the time and go back.
- $row['claimed_time'] = time();
- return $row;
- }
- else
- {
- // Uh oh, we just missed it. Try to claim another one, and let it fall through if there aren't any.
- return fetch_task();
- }
- }
- else
- {
- // No dice. Clean up and go home.
- $smcFunc['db_free_result']($request);
- return false;
- }
- }
- function perform_task($task_details)
- {
- global $sourcedir, $boarddir;
- // This indicates the file to load.
- if (!empty($task_details['task_file']))
- {
- $include = strtr(trim($task_details['task_file']), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
- if (file_exists($include))
- require_once($include);
- }
- if (empty($task_details['task_class']))
- {
- // This would be nice to translate but the language files aren't loaded for any specific language.
- log_error('Invalid background task specified (no class, ' . (empty($task_details['task_file']) ? ' no file' : ' to load ' . $task_details['task_file']) . ')');
- return true; // So we clear it from the queue.
- }
- // All background tasks need to be classes.
- elseif (class_exists($task_details['task_class']) && is_subclass_of($task_details['task_class'], 'SMF_BackgroundTask'))
- {
- $details = empty($task_details['task_data']) ? array() : unserialize($task_details['task_data']);
- $bgtask = new $task_details['task_class']($details);
- return $bgtask->execute();
- }
- else
- {
- log_error('Invalid background task specified: (class: ' . $task_details['task_class'] . ', ' . (empty($task_details['task_file']) ? ' no file' : ' to load ' . $task_details['task_file']) . ')');
- return true; // So we clear it from the queue.
- }
- }
- // These are all our helper functions that resemble their big brother counterparts. These are not so important.
- function cleanRequest_cron()
- {
- global $scripturl, $boardurl;
- $scripturl = $boardurl . '/index.php';
- // These keys shouldn't be set...ever.
- if (isset($_REQUEST['GLOBALS']) || isset($_COOKIE['GLOBALS']))
- die('Invalid request variable.');
- // Save some memory.. (since we don't use these anyway.)
- unset($GLOBALS['HTTP_POST_VARS'], $GLOBALS['HTTP_POST_VARS']);
- unset($GLOBALS['HTTP_POST_FILES'], $GLOBALS['HTTP_POST_FILES']);
- unset($GLOBALS['_GET'], $GLOBALS['_POST'], $GLOBALS['_REQUEST'], $GLOBALS['_COOKIE'], $GLOBALS['_FILES']);
- }
- function error_handler_cron($error_level, $error_string, $file, $line)
- {
- global $modSettings;
- // Ignore errors if we're ignoring them or they are strict notices from PHP 5 (which cannot be solved without breaking PHP 4.)
- if (error_reporting() == 0 || (defined('E_STRICT') && $error_level == E_STRICT && !empty($modSettings['enableErrorLogging'])))
- return;
- $error_type = 'cron';
- log_error($error_level . ': ' . $error_string, $error_type, $file, $line);
- // If this is an E_ERROR or E_USER_ERROR.... die. Violently so.
- if ($error_level % 255 == E_ERROR)
- die('No direct access...');
- }
- function obExit_cron()
- {
- if (FROM_CLI)
- die(0);
- else
- {
- header('Content-Type: image/gif');
- 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");
- }
- }
- // We would like this to be defined, but we don't want to have to load more stuff than necessary.
- // Thus we declare it here, and any legitimate background task must implement this.
- abstract class SMF_BackgroundTask
- {
- protected $_details;
- public function __construct($details)
- {
- $this->_details = $details;
- }
- abstract public function execute();
- }
- ?>
|