Completed
Push — release-2.1 ( d1d1b8...402acc )
by Mert
07:04
created

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * This is a slightly strange file. It is not designed to ever be run directly from within SMF's
5
 * conventional running, but called externally to facilitate background tasks. It can be called
6
 * either directly or via cron, and in either case will completely ignore anything supplied
7
 * via command line, or $_GET, $_POST, $_COOKIE etc. because those things should never affect the
8
 * running of this script.
9
 *
10
 * Because of the way this runs, etc. we do need some of SMF but not everything to try to keep this
11
 * running a little bit faster.
12
 *
13
 * Simple Machines Forum (SMF)
14
 *
15
 * @package SMF
16
 * @author Simple Machines http://www.simplemachines.org
17
 * @copyright 2015 Simple Machines and individual contributors
18
 * @license http://www.simplemachines.org/about/smf/license.php BSD
19
 *
20
 * @version 2.1 Beta 2
21
 */
22
23
define('SMF', 'BACKGROUND');
24
define('FROM_CLI', empty($_SERVER['REQUEST_METHOD']));
25
26
// This one setting is worth bearing in mind. If you are running this from proper cron, make sure you
27
// don't run this file any more frequently than indicated here. It might turn ugly if you do.
28
// But on proper cron you can always increase this value provided you don't go beyond max_limit.
29
define('MAX_CRON_TIME', 10);
30
// If a task fails for whatever reason it will still be marked as claimed. This is the threshold
31
// by which if a task has not completed in this time, the task should become available again.
32
define('MAX_CLAIM_THRESHOLD', 300);
33
34
// We're going to want a few globals... these are all set later.
35
global $time_start, $maintenance, $msubject, $mmessage, $mbname, $language;
36
global $boardurl, $boarddir, $sourcedir, $webmaster_email;
37
global $db_server, $db_name, $db_user, $db_prefix, $db_persist, $db_error_send, $db_last_error;
38
global $db_connection, $modSettings, $context, $sc, $user_info, $txt;
39
global $smcFunc, $ssi_db_user, $scripturl, $db_passwd, $cachedir;
40
41
define('TIME_START', microtime(true));
42
43
// Just being safe...
44 View Code Duplication
foreach (array('db_character_set', 'cachedir') as $variable)
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
45
	if (isset($GLOBALS[$variable]))
46
		unset($GLOBALS[$variable]);
47
48
// Get the forum's settings for database and file paths.
49
require_once(dirname(__FILE__) . '/Settings.php');
50
51
// Make absolutely sure the cache directory is defined.
52 View Code Duplication
if ((empty($cachedir) || !file_exists($cachedir)) && file_exists($boarddir . '/cache'))
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
53
	$cachedir = $boarddir . '/cache';
54
55
// Don't do john didley if the forum's been shut down competely.
56
if ($maintenance == 2)
57
	die($mmessage);
58
59
// Fix for using the current directory as a path.
60 View Code Duplication
if (substr($sourcedir, 0, 1) == '.' && substr($sourcedir, 1, 1) != '.')
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
61
	$sourcedir = dirname(__FILE__) . substr($sourcedir, 1);
62
63
// Have we already turned this off? If so, exist gracefully.
64
if (file_exists($cachedir . '/cron.lock'))
65
	obExit_cron();
66
67
// Before we go any further, if this is not a CLI request, we need to do some checking.
68
if (!FROM_CLI)
69
{
70
	// We will clean up $_GET shortly. But we want to this ASAP.
71
	$ts = isset($_GET['ts']) ? (int) $_GET['ts'] : 0;
72
	if ($ts <= 0 || $ts % 15 != 0 || time() - $ts < 0 || time() - $ts > 20)
73
		obExit_cron();
74
}
75
76
// Load the most important includes. In general, a background should be loading its own dependencies.
77
require_once($sourcedir . '/Errors.php');
78
require_once($sourcedir . '/Load.php');
79
require_once($sourcedir . '/Subs.php');
80
81
// Create a variable to store some SMF specific functions in.
82
$smcFunc = array();
83
84
// This is our general bootstrap, a la SSI.php but with a few differences.
85
unset ($db_show_debug);
86
loadDatabase();
87
reloadSettings();
88
89
// Just in case there's a problem...
90
set_error_handler('smf_error_handler_cron');
91
$sc = '';
92
$_SERVER['QUERY_STRING'] = '';
93
$_SERVER['REQUEST_URL'] = FROM_CLI ? 'CLI cron.php' : $boardurl . '/cron.php';
94
95
// Now 'clean the request' (or more accurately, ignore everything we're not going to use)
96
cleanRequest_cron();
97
98
// At this point we could reseed the RNG but I don't think we need to risk it being seeded *even more*.
99
// Meanwhile, time we got on with the real business here.
100
while ($task_details = fetch_task())
101
{
102
	$result = perform_task($task_details);
0 ignored issues
show
It seems like $task_details defined by fetch_task() on line 100 can also be of type boolean; however, perform_task() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
103
	if ($result)
104
	{
105
		$smcFunc['db_query']('', '
106
			DELETE FROM {db_prefix}background_tasks
107
			WHERE id_task = {int:task}',
108
			array(
109
				'task' => $task_details['id_task'],
110
			)
111
		);
112
	}
113
}
114
obExit_cron();
115
exit;
116
117
/**
118
 * The heart of this cron handler...
119
 * @return bool|array False if there's nothing to do or an array of info about the task
120
 */
121
function fetch_task()
122
{
123
	global $smcFunc;
124
125
	// Check we haven't run over our time limit.
126
	if (microtime(true) - TIME_START > MAX_CRON_TIME)
127
		return false;
128
129
	// Try to find a task. Specifically, try to find one that hasn't been claimed previously, or failing that,
130
	// a task that was claimed but failed for whatever reason and failed long enough ago. We should not care
131
	// what task it is, merely that it is one in the queue, the order is irrelevant.
132
	$request = $smcFunc['db_query']('cron_find_task', '
133
		SELECT id_task, task_file, task_class, task_data, claimed_time
134
		FROM {db_prefix}background_tasks
135
		WHERE claimed_time < {int:claim_limit}
136
		ORDER BY null
137
		LIMIT 1',
138
		array(
139
			'claim_limit' => time() - MAX_CLAIM_THRESHOLD,
140
		)
141
	);
142
	if ($row = $smcFunc['db_fetch_assoc']($request))
143
	{
144
		// We found one. Let's try and claim it immediately.
145
		$smcFunc['db_free_result']($request);
146
		$smcFunc['db_query']('', '
147
			UPDATE {db_prefix}background_tasks
148
			SET claimed_time = {int:new_claimed}
149
			WHERE id_task = {int:task}
150
				AND claimed_time = {int:old_claimed}',
151
			array(
152
				'new_claimed' => time(),
153
				'task' => $row['id_task'],
154
				'old_claimed' => $row['claimed_time'],
155
			)
156
		);
157
		// Could we claim it? If so, return it back.
158
		if ($smcFunc['db_affected_rows']() != 0)
159
		{
160
			// Update the time and go back.
161
			$row['claimed_time'] = time();
162
			return $row;
163
		}
164
		else
165
		{
166
			// Uh oh, we just missed it. Try to claim another one, and let it fall through if there aren't any.
167
			return fetch_task();
168
		}
169
	}
170
	else
171
	{
172
		// No dice. Clean up and go home.
173
		$smcFunc['db_free_result']($request);
174
		return false;
175
	}
176
}
177
178
/**
179
 * This actually handles the task
180
 * @param array $task_details An array of info about the task
181
 * @return bool|void True if the task is invalid; otherwise calls the function to execute the task
182
 */
183
function perform_task($task_details)
184
{
185
	global $sourcedir, $boarddir;
186
187
	// This indicates the file to load.
188
	if (!empty($task_details['task_file']))
189
	{
190
		$include = strtr(trim($task_details['task_file']), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
191
		if (file_exists($include))
192
			require_once($include);
193
	}
194
195
	if (empty($task_details['task_class']))
196
	{
197
		// This would be nice to translate but the language files aren't loaded for any specific language.
198
		log_error('Invalid background task specified (no class, ' . (empty($task_details['task_file']) ? ' no file' : ' to load ' . $task_details['task_file']) . ')');
199
		return true; // So we clear it from the queue.
200
	}
201
202
	// All background tasks need to be classes.
203
	elseif (class_exists($task_details['task_class']) && is_subclass_of($task_details['task_class'], 'SMF_BackgroundTask'))
204
	{
205
		$details = empty($task_details['task_data']) ? array() : json_decode($task_details['task_data'], true);
206
		$bgtask = new $task_details['task_class']($details);
207
		return $bgtask->execute();
208
	}
209
	else
210
	{
211
		log_error('Invalid background task specified: (class: ' . $task_details['task_class'] . ', ' . (empty($task_details['task_file']) ? ' no file' : ' to load ' . $task_details['task_file']) . ')');
212
		return true; // So we clear it from the queue.
213
	}
214
}
215
216
// These are all our helper functions that resemble their big brother counterparts. These are not so important.
217
/**
218
 * Cleans up the request variables
219
 * @return void
220
 */
221
function cleanRequest_cron()
222
{
223
	global $scripturl, $boardurl;
224
225
	$scripturl = $boardurl . '/index.php';
226
227
	// These keys shouldn't be set...ever.
228
	if (isset($_REQUEST['GLOBALS']) || isset($_COOKIE['GLOBALS']))
229
		die('Invalid request variable.');
230
231
	// Save some memory.. (since we don't use these anyway.)
232
	unset($GLOBALS['HTTP_POST_VARS'], $GLOBALS['HTTP_POST_VARS']);
233
	unset($GLOBALS['HTTP_POST_FILES'], $GLOBALS['HTTP_POST_FILES']);
234
	unset($GLOBALS['_GET'], $GLOBALS['_POST'], $GLOBALS['_REQUEST'], $GLOBALS['_COOKIE'], $GLOBALS['_FILES']);
235
}
236
237
/**
238
 * The error handling function
239
 * @param int $error_level One of the PHP error level constants (see )
240
 * @param string $error_string The error message
241
 * @param string $file The file where the error occurred
242
 * @param int $line What line of the specified file the error occurred on
243
 * @return void
244
 */
245
function smf_error_handler_cron($error_level, $error_string, $file, $line)
246
{
247
	global $modSettings;
248
249
	// Ignore errors if we're ignoring them or they are strict notices from PHP 5 (which cannot be solved without breaking PHP 4.)
250 View Code Duplication
	if (error_reporting() == 0 || (defined('E_STRICT') && $error_level == E_STRICT && !empty($modSettings['enableErrorLogging'])))
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
251
		return;
252
253
	$error_type = 'cron';
254
255
	log_error($error_level . ': ' . $error_string, $error_type, $file, $line);
256
257
	// If this is an E_ERROR or E_USER_ERROR.... die.  Violently so.
258
	if ($error_level % 255 == E_ERROR)
259
		die('No direct access...');
260
}
261
262
/**
263
 * The exit function
264
 */
265
function obExit_cron()
266
{
267
	if (FROM_CLI)
268
		die(0);
269
	else
270
	{
271
		header('Content-Type: image/gif');
272
		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");
273
	}
274
}
275
276
// We would like this to be defined, but we don't want to have to load more stuff than necessary.
277
// Thus we declare it here, and any legitimate background task must implement this.
278
/**
279
 * Class SMF_BackgroundTask
280
 */
281
abstract class SMF_BackgroundTask
282
{
283
284
	/**
285
	 * @var array Holds the details for the task
286
	 */
287
	protected $_details;
288
289
	/**
290
	 * The constructor.
291
	 * @param array $details The details for the task
292
	 */
293
	public function __construct($details)
294
	{
295
		$this->_details = $details;
296
	}
297
298
	/**
299
	 * The function to actually execute a task
300
	 * @return mixed
301
	 */
302
	abstract public function execute();
303
}
304
?>
0 ignored issues
show
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...