Failed Conditions
Branch release-2.1 (4e22cf)
by Rick
06:39
created

ManageMaintenance.php ➔ MaintainPurgeInactiveMembers()   D

Complexity

Conditions 13
Paths 10

Size

Total Lines 82
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 43
nc 10
nop 0
dl 0
loc 82
rs 4.9922
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Forum maintenance. Important stuff.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Main dispatcher, the maintenance access point.
21
 * This, as usual, checks permissions, loads language files, and forwards to the actual workers.
22
 */
23
function ManageMaintenance()
24
{
25
	global $txt, $context;
26
27
	// You absolutely must be an admin by here!
28
	isAllowedTo('admin_forum');
29
30
	// Need something to talk about?
31
	loadLanguage('ManageMaintenance');
32
	loadTemplate('ManageMaintenance');
33
34
	// This uses admin tabs - as it should!
35
	$context[$context['admin_menu_name']]['tab_data'] = array(
36
		'title' => $txt['maintain_title'],
37
		'description' => $txt['maintain_info'],
38
		'tabs' => array(
39
			'routine' => array(),
40
			'database' => array(),
41
			'members' => array(),
42
			'topics' => array(),
43
		),
44
	);
45
46
	// So many things you can do - but frankly I won't let you - just these!
47
	$subActions = array(
48
		'routine' => array(
49
			'function' => 'MaintainRoutine',
50
			'template' => 'maintain_routine',
51
			'activities' => array(
52
				'version' => 'VersionDetail',
53
				'repair' => 'MaintainFindFixErrors',
54
				'recount' => 'AdminBoardRecount',
55
				'logs' => 'MaintainEmptyUnimportantLogs',
56
				'cleancache' => 'MaintainCleanCache',
57
			),
58
		),
59
		'database' => array(
60
			'function' => 'MaintainDatabase',
61
			'template' => 'maintain_database',
62
			'activities' => array(
63
				'optimize' => 'OptimizeTables',
64
				'convertentities' => 'ConvertEntities',
65
				'convertutf8' => 'ConvertUtf8',
66
				'convertmsgbody' => 'ConvertMsgBody',
67
			),
68
		),
69
		'members' => array(
70
			'function' => 'MaintainMembers',
71
			'template' => 'maintain_members',
72
			'activities' => array(
73
				'reattribute' => 'MaintainReattributePosts',
74
				'purgeinactive' => 'MaintainPurgeInactiveMembers',
75
				'recountposts' => 'MaintainRecountPosts',
76
			),
77
		),
78
		'topics' => array(
79
			'function' => 'MaintainTopics',
80
			'template' => 'maintain_topics',
81
			'activities' => array(
82
				'massmove' => 'MaintainMassMoveTopics',
83
				'pruneold' => 'MaintainRemoveOldPosts',
84
				'olddrafts' => 'MaintainRemoveOldDrafts',
85
			),
86
		),
87
		'hooks' => array(
88
			'function' => 'list_integration_hooks',
89
		),
90
		'destroy' => array(
91
			'function' => 'Destroy',
92
			'activities' => array(),
93
		),
94
	);
95
96
	call_integration_hook('integrate_manage_maintenance', array(&$subActions));
97
98
	// Yep, sub-action time!
99 View Code Duplication
	if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]))
100
		$subAction = $_REQUEST['sa'];
101
	else
102
		$subAction = 'routine';
103
104
	// Doing something special?
105 View Code Duplication
	if (isset($_REQUEST['activity']) && isset($subActions[$subAction]['activities'][$_REQUEST['activity']]))
106
		$activity = $_REQUEST['activity'];
107
108
	// Set a few things.
109
	$context['page_title'] = $txt['maintain_title'];
110
	$context['sub_action'] = $subAction;
111
	$context['sub_template'] = !empty($subActions[$subAction]['template']) ? $subActions[$subAction]['template'] : '';
112
113
	// Finally fall through to what we are doing.
114
	call_helper($subActions[$subAction]['function']);
115
116
	// Any special activity?
117
	if (isset($activity))
118
		call_helper($subActions[$subAction]['activities'][$activity]);
119
120
	//converted to UTF-8? show a small maintenance info
121 View Code Duplication
	if (isset($_GET['done']) && $_GET['done'] == 'convertutf8')
122
		$context['maintenance_finished'] = $txt['utf8_title'];
123
124
	// Create a maintenance token.  Kinda hard to do it any other way.
125
	createToken('admin-maint');
126
}
127
128
/**
129
 * Supporting function for the database maintenance area.
130
 */
131
function MaintainDatabase()
132
{
133
	global $context, $db_type, $db_character_set, $modSettings, $smcFunc, $txt;
134
135
	// Show some conversion options?
136
	$context['convert_utf8'] = ($db_type == 'mysql') && (!isset($db_character_set) || $db_character_set !== 'utf8' || empty($modSettings['global_character_set']) || $modSettings['global_character_set'] !== 'UTF-8') && version_compare('4.1.2', preg_replace('~\-.+?$~', '', $smcFunc['db_server_info']()), '<=');
137
	$context['convert_entities'] = ($db_type == 'mysql') && isset($db_character_set, $modSettings['global_character_set']) && $db_character_set === 'utf8' && $modSettings['global_character_set'] === 'UTF-8';
138
139
	if ($db_type == 'mysql')
140
	{
141
		db_extend('packages');
142
143
		$colData = $smcFunc['db_list_columns']('{db_prefix}messages', true);
144
		foreach ($colData as $column)
145
			if ($column['name'] == 'body')
146
				$body_type = $column['type'];
147
148
		$context['convert_to'] = $body_type == 'text' ? 'mediumtext' : 'text';
0 ignored issues
show
Bug introduced by
The variable $body_type does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
149
		$context['convert_to_suggest'] = ($body_type != 'text' && !empty($modSettings['max_messageLength']) && $modSettings['max_messageLength'] < 65536);
150
	}
151
152 View Code Duplication
	if (isset($_GET['done']) && $_GET['done'] == 'convertutf8')
153
		$context['maintenance_finished'] = $txt['utf8_title'];
154
	if (isset($_GET['done']) && $_GET['done'] == 'convertentities')
155
		$context['maintenance_finished'] = $txt['entity_convert_title'];
156
}
157
158
/**
159
 * Supporting function for the routine maintenance area.
160
 */
161
function MaintainRoutine()
162
{
163
	global $context, $txt;
164
165
	if (isset($_GET['done']) && $_GET['done'] == 'recount')
166
		$context['maintenance_finished'] = $txt['maintain_recount'];
167
}
168
169
/**
170
 * Supporting function for the members maintenance area.
171
 */
172
function MaintainMembers()
173
{
174
	global $context, $smcFunc, $txt;
175
176
	// Get membergroups - for deleting members and the like.
177
	$result = $smcFunc['db_query']('', '
178
		SELECT id_group, group_name
179
		FROM {db_prefix}membergroups',
180
		array(
181
		)
182
	);
183
	$context['membergroups'] = array(
184
		array(
185
			'id' => 0,
186
			'name' => $txt['maintain_members_ungrouped']
187
		),
188
	);
189
	while ($row = $smcFunc['db_fetch_assoc']($result))
190
	{
191
		$context['membergroups'][] = array(
192
			'id' => $row['id_group'],
193
			'name' => $row['group_name']
194
		);
195
	}
196
	$smcFunc['db_free_result']($result);
197
198
	if (isset($_GET['done']) && $_GET['done'] == 'recountposts')
199
		$context['maintenance_finished'] = $txt['maintain_recountposts'];
200
201
	loadJavaScriptFile('suggest.js', array('defer' => false), 'smf_suggest');
202
}
203
204
/**
205
 * Supporting function for the topics maintenance area.
206
 */
207
function MaintainTopics()
208
{
209
	global $context, $smcFunc, $txt, $sourcedir;
210
211
	// Let's load up the boards in case they are useful.
212
	$result = $smcFunc['db_query']('order_by_board_order', '
213
		SELECT b.id_board, b.name, b.child_level, c.name AS cat_name, c.id_cat
214
		FROM {db_prefix}boards AS b
215
			LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
216
		WHERE {query_see_board}
217
			AND redirect = {string:blank_redirect}',
218
		array(
219
			'blank_redirect' => '',
220
		)
221
	);
222
	$context['categories'] = array();
223
	while ($row = $smcFunc['db_fetch_assoc']($result))
224
	{
225
		if (!isset($context['categories'][$row['id_cat']]))
226
			$context['categories'][$row['id_cat']] = array(
227
				'name' => $row['cat_name'],
228
				'boards' => array()
229
			);
230
231
		$context['categories'][$row['id_cat']]['boards'][$row['id_board']] = array(
232
			'id' => $row['id_board'],
233
			'name' => $row['name'],
234
			'child_level' => $row['child_level']
235
		);
236
	}
237
	$smcFunc['db_free_result']($result);
238
239
	require_once($sourcedir . '/Subs-Boards.php');
240
	sortCategories($context['categories']);
241
242
	if (isset($_GET['done']) && $_GET['done'] == 'purgeold')
243
		$context['maintenance_finished'] = $txt['maintain_old'];
244
	elseif (isset($_GET['done']) && $_GET['done'] == 'massmove')
245
		$context['maintenance_finished'] = $txt['move_topics_maintenance'];
246
}
247
248
/**
249
 * Find and fix all errors on the forum.
250
 */
251
function MaintainFindFixErrors()
252
{
253
	global $sourcedir;
254
255
	// Honestly, this should be done in the sub function.
256
	validateToken('admin-maint');
257
258
	require_once($sourcedir . '/RepairBoards.php');
259
	RepairBoards();
260
}
261
262
/**
263
 * Wipes the whole cache directory.
264
 * This only applies to SMF's own cache directory, though.
265
 */
266
function MaintainCleanCache()
267
{
268
	global $context, $txt;
269
270
	checkSession();
271
	validateToken('admin-maint');
272
273
	// Just wipe the whole cache directory!
274
	clean_cache();
275
276
	$context['maintenance_finished'] = $txt['maintain_cache'];
277
}
278
279
/**
280
 * Empties all uninmportant logs
281
 */
282
function MaintainEmptyUnimportantLogs()
283
{
284
	global $context, $smcFunc, $txt;
285
286
	checkSession();
287
	validateToken('admin-maint');
288
289
	// No one's online now.... MUHAHAHAHA :P.
290
	$smcFunc['db_query']('', '
291
		DELETE FROM {db_prefix}log_online');
292
293
	// Dump the banning logs.
294
	$smcFunc['db_query']('', '
295
		DELETE FROM {db_prefix}log_banned');
296
297
	// Start id_error back at 0 and dump the error log.
298
	$smcFunc['db_query']('truncate_table', '
299
		TRUNCATE {db_prefix}log_errors');
300
301
	// Clear out the spam log.
302
	$smcFunc['db_query']('', '
303
		DELETE FROM {db_prefix}log_floodcontrol');
304
305
	// Last but not least, the search logs!
306
	$smcFunc['db_query']('truncate_table', '
307
		TRUNCATE {db_prefix}log_search_topics');
308
309
	$smcFunc['db_query']('truncate_table', '
310
		TRUNCATE {db_prefix}log_search_messages');
311
312
	$smcFunc['db_query']('truncate_table', '
313
		TRUNCATE {db_prefix}log_search_results');
314
315
	updateSettings(array('search_pointer' => 0));
316
317
	$context['maintenance_finished'] = $txt['maintain_logs'];
318
}
319
320
/**
321
 * Oh noes! I'd document this but that would give it away
322
 */
323
function Destroy()
324
{
325
	global $context;
326
327
	echo '<!DOCTYPE html>
328
		<html', $context['right_to_left'] ? ' dir="rtl"' : '', '><head><title>', $context['forum_name_html_safe'], ' deleted!</title></head>
329
		<body style="background-color: orange; font-family: arial, sans-serif; text-align: center;">
330
		<div style="margin-top: 8%; font-size: 400%; color: black;">Oh my, you killed ', $context['forum_name_html_safe'], '!</div>
331
		<div style="margin-top: 7%; font-size: 500%; color: red;"><strong>You lazy bum!</strong></div>
332
		</body></html>';
333
	obExit(false);
334
}
335
336
/**
337
 * Convert the column "body" of the table {db_prefix}messages from TEXT to MEDIUMTEXT and vice versa.
338
 * It requires the admin_forum permission.
339
 * This is needed only for MySQL.
340
 * During the conversion from MEDIUMTEXT to TEXT it check if any of the posts exceed the TEXT length and if so it aborts.
341
 * This action is linked from the maintenance screen (if it's applicable).
342
 * Accessed by ?action=admin;area=maintain;sa=database;activity=convertmsgbody.
343
 *
344
 * @uses the convert_msgbody sub template of the Admin template.
345
 */
346
function ConvertMsgBody()
347
{
348
	global $scripturl, $context, $txt, $db_type;
349
	global $modSettings, $smcFunc, $time_start;
350
351
	// Show me your badge!
352
	isAllowedTo('admin_forum');
353
354
	if ($db_type != 'mysql')
355
		return;
356
357
	db_extend('packages');
358
359
	$colData = $smcFunc['db_list_columns']('{db_prefix}messages', true);
360
	foreach ($colData as $column)
361
		if ($column['name'] == 'body')
362
			$body_type = $column['type'];
363
364
	$context['convert_to'] = $body_type == 'text' ? 'mediumtext' : 'text';
0 ignored issues
show
Bug introduced by
The variable $body_type does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
365
366
	if ($body_type == 'text' || ($body_type != 'text' && isset($_POST['do_conversion'])))
367
	{
368
		checkSession();
369
		validateToken('admin-maint');
370
371
		// Make it longer so we can do their limit.
372
		if ($body_type == 'text')
373
			$smcFunc['db_change_column']('{db_prefix}messages', 'body', array('type' => 'mediumtext'));
374
		// Shorten the column so we can have a bit (literally per record) less space occupied
375
		else
376
			$smcFunc['db_change_column']('{db_prefix}messages', 'body', array('type' => 'text'));
377
378
		// 3rd party integrations may be interested in knowning about this.
379
		call_integration_hook('integrate_convert_msgbody', array($body_type));
380
381
		$colData = $smcFunc['db_list_columns']('{db_prefix}messages', true);
382
		foreach ($colData as $column)
383
			if ($column['name'] == 'body')
384
				$body_type = $column['type'];
385
386
		$context['maintenance_finished'] = $txt[$context['convert_to'] . '_title'];
387
		$context['convert_to'] = $body_type == 'text' ? 'mediumtext' : 'text';
388
		$context['convert_to_suggest'] = ($body_type != 'text' && !empty($modSettings['max_messageLength']) && $modSettings['max_messageLength'] < 65536);
389
390
		return;
391
	}
392
	elseif ($body_type != 'text' && (!isset($_POST['do_conversion']) || isset($_POST['cont'])))
393
	{
394
		checkSession();
395
		if (empty($_REQUEST['start']))
396
			validateToken('admin-maint');
397
		else
398
			validateToken('admin-convertMsg');
399
400
		$context['page_title'] = $txt['not_done_title'];
401
		$context['continue_post_data'] = '';
402
		$context['continue_countdown'] = 3;
403
		$context['sub_template'] = 'not_done';
404
		$increment = 500;
405
		$id_msg_exceeding = isset($_POST['id_msg_exceeding']) ? explode(',', $_POST['id_msg_exceeding']) : array();
406
407
		$request = $smcFunc['db_query']('', '
408
			SELECT COUNT(*) as count
409
			FROM {db_prefix}messages',
410
			array()
411
		);
412
		list($max_msgs) = $smcFunc['db_fetch_row']($request);
413
		$smcFunc['db_free_result']($request);
414
415
		// Try for as much time as possible.
416
		@set_time_limit(600);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
417
418
		while ($_REQUEST['start'] < $max_msgs)
419
		{
420
			$request = $smcFunc['db_query']('', '
421
				SELECT /*!40001 SQL_NO_CACHE */ id_msg
422
				FROM {db_prefix}messages
423
				WHERE id_msg BETWEEN {int:start} AND {int:start} + {int:increment}
424
					AND LENGTH(body) > 65535',
425
				array(
426
					'start' => $_REQUEST['start'],
427
					'increment' => $increment - 1,
428
				)
429
			);
430
			while ($row = $smcFunc['db_fetch_assoc']($request))
431
				$id_msg_exceeding[] = $row['id_msg'];
432
			$smcFunc['db_free_result']($request);
433
434
			$_REQUEST['start'] += $increment;
435
436
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
437
			{
438
				createToken('admin-convertMsg');
439
				$context['continue_post_data'] = '
440
					<input type="hidden" name="' . $context['admin-convertMsg_token_var'] . '" value="' . $context['admin-convertMsg_token'] . '">
441
					<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '">
442
					<input type="hidden" name="id_msg_exceeding" value="' . implode(',', $id_msg_exceeding) . '">';
443
444
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=database;activity=convertmsgbody;start=' . $_REQUEST['start'];
445
				$context['continue_percent'] = round(100 * $_REQUEST['start'] / $max_msgs);
446
447
				return;
448
			}
449
		}
450
		createToken('admin-maint');
451
		$context['page_title'] = $txt[$context['convert_to'] . '_title'];
452
		$context['sub_template'] = 'convert_msgbody';
453
454
		if (!empty($id_msg_exceeding))
455
		{
456
			if (count($id_msg_exceeding) > 100)
457
			{
458
				$query_msg = array_slice($id_msg_exceeding, 0, 100);
459
				$context['exceeding_messages_morethan'] = sprintf($txt['exceeding_messages_morethan'], count($id_msg_exceeding));
460
			}
461
			else
462
				$query_msg = $id_msg_exceeding;
463
464
			$context['exceeding_messages'] = array();
465
			$request = $smcFunc['db_query']('', '
466
				SELECT id_msg, id_topic, subject
467
				FROM {db_prefix}messages
468
				WHERE id_msg IN ({array_int:messages})',
469
				array(
470
					'messages' => $query_msg,
471
				)
472
			);
473
			while ($row = $smcFunc['db_fetch_assoc']($request))
474
				$context['exceeding_messages'][] = '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'] . '">' . $row['subject'] . '</a>';
475
			$smcFunc['db_free_result']($request);
476
		}
477
	}
478
}
479
480
/**
481
 * Converts HTML-entities to their UTF-8 character equivalents.
482
 * This requires the admin_forum permission.
483
 * Pre-condition: UTF-8 has been set as database and global character set.
484
 *
485
 * It is divided in steps of 10 seconds.
486
 * This action is linked from the maintenance screen (if applicable).
487
 * It is accessed by ?action=admin;area=maintain;sa=database;activity=convertentities.
488
 *
489
 * @uses Admin template, convert_entities sub-template.
490
 */
491
function ConvertEntities()
492
{
493
	global $db_character_set, $modSettings, $context, $sourcedir, $smcFunc;
494
495
	isAllowedTo('admin_forum');
496
497
	// Check to see if UTF-8 is currently the default character set.
498
	if ($modSettings['global_character_set'] !== 'UTF-8' || !isset($db_character_set) || $db_character_set !== 'utf8')
499
		fatal_lang_error('entity_convert_only_utf8');
500
501
	// Some starting values.
502
	$context['table'] = empty($_REQUEST['table']) ? 0 : (int) $_REQUEST['table'];
503
	$context['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
504
505
	$context['start_time'] = time();
506
507
	$context['first_step'] = !isset($_REQUEST[$context['session_var']]);
508
	$context['last_step'] = false;
509
510
	// The first step is just a text screen with some explanation.
511
	if ($context['first_step'])
512
	{
513
		validateToken('admin-maint');
514
		createToken('admin-maint');
515
516
		$context['sub_template'] = 'convert_entities';
517
		return;
518
	}
519
	// Otherwise use the generic "not done" template.
520
	$context['sub_template'] = 'not_done';
521
	$context['continue_post_data'] = '';
522
	$context['continue_countdown'] = 3;
523
524
	// Now we're actually going to convert...
525
	checkSession('request');
526
	validateToken('admin-maint');
527
	createToken('admin-maint');
528
529
	// A list of tables ready for conversion.
530
	$tables = array(
531
		'ban_groups',
532
		'ban_items',
533
		'boards',
534
		'calendar',
535
		'calendar_holidays',
536
		'categories',
537
		'log_errors',
538
		'log_search_subjects',
539
		'membergroups',
540
		'members',
541
		'message_icons',
542
		'messages',
543
		'package_servers',
544
		'personal_messages',
545
		'pm_recipients',
546
		'polls',
547
		'poll_choices',
548
		'smileys',
549
		'themes',
550
	);
551
	$context['num_tables'] = count($tables);
552
553
	// Loop through all tables that need converting.
554
	for (; $context['table'] < $context['num_tables']; $context['table']++)
555
	{
556
		$cur_table = $tables[$context['table']];
557
		$primary_key = '';
558
		// Make sure we keep stuff unique!
559
		$primary_keys = array();
560
561
		if (function_exists('apache_reset_timeout'))
562
			@apache_reset_timeout();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
563
564
		// Get a list of text columns.
565
		$columns = array();
566
		$request = $smcFunc['db_query']('', '
567
			SHOW FULL COLUMNS
568
			FROM {db_prefix}{raw:cur_table}',
569
			array(
570
				'cur_table' => $cur_table,
571
			)
572
		);
573
		while ($column_info = $smcFunc['db_fetch_assoc']($request))
574
			if (strpos($column_info['Type'], 'text') !== false || strpos($column_info['Type'], 'char') !== false)
575
				$columns[] = strtolower($column_info['Field']);
576
577
		// Get the column with the (first) primary key.
578
		$request = $smcFunc['db_query']('', '
579
			SHOW KEYS
580
			FROM {db_prefix}{raw:cur_table}',
581
			array(
582
				'cur_table' => $cur_table,
583
			)
584
		);
585
		while ($row = $smcFunc['db_fetch_assoc']($request))
586
		{
587
			if ($row['Key_name'] === 'PRIMARY')
588
			{
589
				if (empty($primary_key) || ($row['Seq_in_index'] == 1 && !in_array(strtolower($row['Column_name']), $columns)))
590
					$primary_key = $row['Column_name'];
591
592
				$primary_keys[] = $row['Column_name'];
593
			}
594
		}
595
		$smcFunc['db_free_result']($request);
596
597
		// No primary key, no glory.
598
		// Same for columns. Just to be sure we've work to do!
599
		if (empty($primary_key) || empty($columns))
600
			continue;
601
602
		// Get the maximum value for the primary key.
603
		$request = $smcFunc['db_query']('', '
604
			SELECT MAX({identifier:key})
605
			FROM {db_prefix}{raw:cur_table}',
606
			array(
607
				'key' => $primary_key,
608
				'cur_table' => $cur_table,
609
			)
610
		);
611
		list($max_value) = $smcFunc['db_fetch_row']($request);
612
		$smcFunc['db_free_result']($request);
613
614
		if (empty($max_value))
615
			continue;
616
617
		while ($context['start'] <= $max_value)
618
		{
619
			// Retrieve a list of rows that has at least one entity to convert.
620
			$request = $smcFunc['db_query']('', '
621
				SELECT {raw:primary_keys}, {raw:columns}
622
				FROM {db_prefix}{raw:cur_table}
623
				WHERE {raw:primary_key} BETWEEN {int:start} AND {int:start} + 499
624
					AND {raw:like_compare}
625
				LIMIT 500',
626
				array(
627
					'primary_keys' => implode(', ', $primary_keys),
628
					'columns' => implode(', ', $columns),
629
					'cur_table' => $cur_table,
630
					'primary_key' => $primary_key,
631
					'start' => $context['start'],
632
					'like_compare' => '(' . implode(' LIKE \'%&#%\' OR ', $columns) . ' LIKE \'%&#%\')',
633
				)
634
			);
635
			while ($row = $smcFunc['db_fetch_assoc']($request))
636
			{
637
				$insertion_variables = array();
638
				$changes = array();
639
				foreach ($row as $column_name => $column_value)
640
					if ($column_name !== $primary_key && strpos($column_value, '&#') !== false)
641
					{
642
						$changes[] = $column_name . ' = {string:changes_' . $column_name . '}';
643
						$insertion_variables['changes_' . $column_name] = preg_replace_callback('~&#(\d{1,7}|x[0-9a-fA-F]{1,6});~', 'fixchar__callback', $column_value);
644
					}
645
646
				$where = array();
647
				foreach ($primary_keys as $key)
648
				{
649
					$where[] = $key . ' = {string:where_' . $key . '}';
650
					$insertion_variables['where_' . $key] = $row[$key];
651
				}
652
653
				// Update the row.
654
				if (!empty($changes))
655
					$smcFunc['db_query']('', '
656
						UPDATE {db_prefix}' . $cur_table . '
657
						SET
658
							' . implode(',
659
							', $changes) . '
660
						WHERE ' . implode(' AND ', $where),
661
						$insertion_variables
662
					);
663
			}
664
			$smcFunc['db_free_result']($request);
665
			$context['start'] += 500;
666
667
			// After ten seconds interrupt.
668
			if (time() - $context['start_time'] > 10)
669
			{
670
				// Calculate an approximation of the percentage done.
671
				$context['continue_percent'] = round(100 * ($context['table'] + ($context['start'] / $max_value)) / $context['num_tables'], 1);
672
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=database;activity=convertentities;table=' . $context['table'] . ';start=' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
673
				return;
674
			}
675
		}
676
		$context['start'] = 0;
677
	}
678
679
	// Make sure all serialized strings are all right.
680
	require_once($sourcedir . '/Subs-Charset.php');
681
	fix_serialized_columns();
682
683
	// If we're here, we must be done.
684
	$context['continue_percent'] = 100;
685
	$context['continue_get_data'] = '?action=admin;area=maintain;sa=database;done=convertentities';
686
	$context['last_step'] = true;
687
	$context['continue_countdown'] = -1;
688
}
689
690
/**
691
 * Optimizes all tables in the database and lists how much was saved.
692
 * It requires the admin_forum permission.
693
 * It shows as the maintain_forum admin area.
694
 * It is accessed from ?action=admin;area=maintain;sa=database;activity=optimize.
695
 * It also updates the optimize scheduled task such that the tables are not automatically optimized again too soon.
696
697
 * @uses the optimize sub template
698
 */
699
function OptimizeTables()
700
{
701
	global $db_prefix, $txt, $context, $smcFunc, $time_start;
702
703
	isAllowedTo('admin_forum');
704
705
	checkSession('request');
706
707
	if (!isset($_SESSION['optimized_tables']))
708
		validateToken('admin-maint');
709
	else
710
		validateToken('admin-optimize', 'post', false);
711
712
	ignore_user_abort(true);
713
	db_extend();
714
715
	$context['page_title'] = $txt['database_optimize'];
716
	$context['sub_template'] = 'optimize';
717
	$context['continue_post_data'] = '';
718
	$context['continue_countdown'] = 3;
719
720
	// Only optimize the tables related to this smf install, not all the tables in the db
721
	$real_prefix = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
722
723
	// Get a list of tables, as well as how many there are.
724
	$temp_tables = $smcFunc['db_list_tables'](false, $real_prefix . '%');
725
	$tables = array();
726
	foreach ($temp_tables as $table)
727
		$tables[] = array('table_name' => $table);
728
729
	// If there aren't any tables then I believe that would mean the world has exploded...
730
	$context['num_tables'] = count($tables);
731
	if ($context['num_tables'] == 0)
732
		fatal_error('You appear to be running SMF in a flat file mode... fantastic!', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
733
734
	$_REQUEST['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
735
736
	// Try for extra time due to large tables.
737
	@set_time_limit(100);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
738
739
	// For each table....
740
	$_SESSION['optimized_tables'] = !empty($_SESSION['optimized_tables']) ? $_SESSION['optimized_tables'] : array();
741
	for ($key = $_REQUEST['start']; $context['num_tables'] - 1; $key++)
742
	{
743
		if (empty($tables[$key]))
744
			break;
745
746
		// Continue?
747
		if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 10)
748
		{
749
			$_REQUEST['start'] = $key;
750
			$context['continue_get_data'] = '?action=admin;area=maintain;sa=database;activity=optimize;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
751
			$context['continue_percent'] = round(100 * $_REQUEST['start'] / $context['num_tables']);
752
			$context['sub_template'] = 'not_done';
753
			$context['page_title'] = $txt['not_done_title'];
754
755
			createToken('admin-optimize');
756
			$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-optimize_token_var'] . '" value="' . $context['admin-optimize_token'] . '">';
757
758
			if (function_exists('apache_reset_timeout'))
759
				apache_reset_timeout();
760
761
			return;
762
		}
763
764
		// Optimize the table!  We use backticks here because it might be a custom table.
765
		$data_freed = $smcFunc['db_optimize_table']($tables[$key]['table_name']);
766
767
		if ($data_freed > 0)
768
			$_SESSION['optimized_tables'][] = array(
769
				'name' => $tables[$key]['table_name'],
770
				'data_freed' => $data_freed,
771
			);
772
	}
773
774
	// Number of tables, etc...
775
	$txt['database_numb_tables'] = sprintf($txt['database_numb_tables'], $context['num_tables']);
776
	$context['num_tables_optimized'] = count($_SESSION['optimized_tables']);
777
	$context['optimized_tables'] = $_SESSION['optimized_tables'];
778
	unset($_SESSION['optimized_tables']);
779
}
780
781
/**
782
 * Recount many forum totals that can be recounted automatically without harm.
783
 * it requires the admin_forum permission.
784
 * It shows the maintain_forum admin area.
785
 *
786
 * Totals recounted:
787
 * - fixes for topics with wrong num_replies.
788
 * - updates for num_posts and num_topics of all boards.
789
 * - recounts instant_messages but not unread_messages.
790
 * - repairs messages pointing to boards with topics pointing to other boards.
791
 * - updates the last message posted in boards and children.
792
 * - updates member count, latest member, topic count, and message count.
793
 *
794
 * The function redirects back to ?action=admin;area=maintain when complete.
795
 * It is accessed via ?action=admin;area=maintain;sa=database;activity=recount.
796
 */
797
function AdminBoardRecount()
798
{
799
	global $txt, $context, $modSettings, $sourcedir;
800
	global $time_start, $smcFunc;
801
802
	isAllowedTo('admin_forum');
803
	checkSession('request');
804
805
	// validate the request or the loop
806
	if (!isset($_REQUEST['step']))
807
		validateToken('admin-maint');
808
	else
809
		validateToken('admin-boardrecount');
810
811
	$context['page_title'] = $txt['not_done_title'];
812
	$context['continue_post_data'] = '';
813
	$context['continue_countdown'] = 3;
814
	$context['sub_template'] = 'not_done';
815
816
	// Try for as much time as possible.
817
	@set_time_limit(600);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
818
819
	// Step the number of topics at a time so things don't time out...
820
	$request = $smcFunc['db_query']('', '
821
		SELECT MAX(id_topic)
822
		FROM {db_prefix}topics',
823
		array(
824
		)
825
	);
826
	list ($max_topics) = $smcFunc['db_fetch_row']($request);
827
	$smcFunc['db_free_result']($request);
828
829
	$increment = min(max(50, ceil($max_topics / 4)), 2000);
830
	if (empty($_REQUEST['start']))
831
		$_REQUEST['start'] = 0;
832
833
	$total_steps = 8;
834
835
	// Get each topic with a wrong reply count and fix it - let's just do some at a time, though.
836
	if (empty($_REQUEST['step']))
837
	{
838
		$_REQUEST['step'] = 0;
839
840
		while ($_REQUEST['start'] < $max_topics)
841
		{
842
			// Recount approved messages
843
			$request = $smcFunc['db_query']('', '
844
				SELECT /*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.num_replies) AS num_replies,
845
					CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END AS real_num_replies
846
				FROM {db_prefix}topics AS t
847
					LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = {int:is_approved})
848
				WHERE t.id_topic > {int:start}
849
					AND t.id_topic <= {int:max_id}
850
				GROUP BY t.id_topic
851
				HAVING CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END != MAX(t.num_replies)',
852
				array(
853
					'is_approved' => 1,
854
					'start' => $_REQUEST['start'],
855
					'max_id' => $_REQUEST['start'] + $increment,
856
				)
857
			);
858
			while ($row = $smcFunc['db_fetch_assoc']($request))
859
				$smcFunc['db_query']('', '
860
					UPDATE {db_prefix}topics
861
					SET num_replies = {int:num_replies}
862
					WHERE id_topic = {int:id_topic}',
863
					array(
864
						'num_replies' => $row['real_num_replies'],
865
						'id_topic' => $row['id_topic'],
866
					)
867
				);
868
			$smcFunc['db_free_result']($request);
869
870
			// Recount unapproved messages
871
			$request = $smcFunc['db_query']('', '
872
				SELECT /*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.unapproved_posts) AS unapproved_posts,
873
					COUNT(mu.id_msg) AS real_unapproved_posts
874
				FROM {db_prefix}topics AS t
875
					LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = {int:not_approved})
876
				WHERE t.id_topic > {int:start}
877
					AND t.id_topic <= {int:max_id}
878
				GROUP BY t.id_topic
879
				HAVING COUNT(mu.id_msg) != MAX(t.unapproved_posts)',
880
				array(
881
					'not_approved' => 0,
882
					'start' => $_REQUEST['start'],
883
					'max_id' => $_REQUEST['start'] + $increment,
884
				)
885
			);
886
			while ($row = $smcFunc['db_fetch_assoc']($request))
887
				$smcFunc['db_query']('', '
888
					UPDATE {db_prefix}topics
889
					SET unapproved_posts = {int:unapproved_posts}
890
					WHERE id_topic = {int:id_topic}',
891
					array(
892
						'unapproved_posts' => $row['real_unapproved_posts'],
893
						'id_topic' => $row['id_topic'],
894
					)
895
				);
896
			$smcFunc['db_free_result']($request);
897
898
			$_REQUEST['start'] += $increment;
899
900
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
901
			{
902
				createToken('admin-boardrecount');
903
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
904
905
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=0;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
906
				$context['continue_percent'] = round((100 * $_REQUEST['start'] / $max_topics) / $total_steps);
907
908
				return;
909
			}
910
		}
911
912
		$_REQUEST['start'] = 0;
913
	}
914
915
	// Update the post count of each board.
916 View Code Duplication
	if ($_REQUEST['step'] <= 1)
917
	{
918
		if (empty($_REQUEST['start']))
919
			$smcFunc['db_query']('', '
920
				UPDATE {db_prefix}boards
921
				SET num_posts = {int:num_posts}
922
				WHERE redirect = {string:redirect}',
923
				array(
924
					'num_posts' => 0,
925
					'redirect' => '',
926
				)
927
			);
928
929
		while ($_REQUEST['start'] < $max_topics)
930
		{
931
			$request = $smcFunc['db_query']('', '
932
				SELECT /*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_num_posts
933
				FROM {db_prefix}messages AS m
934
				WHERE m.id_topic > {int:id_topic_min}
935
					AND m.id_topic <= {int:id_topic_max}
936
					AND m.approved = {int:is_approved}
937
				GROUP BY m.id_board',
938
				array(
939
					'id_topic_min' => $_REQUEST['start'],
940
					'id_topic_max' => $_REQUEST['start'] + $increment,
941
					'is_approved' => 1,
942
				)
943
			);
944
			while ($row = $smcFunc['db_fetch_assoc']($request))
945
				$smcFunc['db_query']('', '
946
					UPDATE {db_prefix}boards
947
					SET num_posts = num_posts + {int:real_num_posts}
948
					WHERE id_board = {int:id_board}',
949
					array(
950
						'id_board' => $row['id_board'],
951
						'real_num_posts' => $row['real_num_posts'],
952
					)
953
				);
954
			$smcFunc['db_free_result']($request);
955
956
			$_REQUEST['start'] += $increment;
957
958
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
959
			{
960
				createToken('admin-boardrecount');
961
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
962
963
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=1;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
964
				$context['continue_percent'] = round((200 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
965
966
				return;
967
			}
968
		}
969
970
		$_REQUEST['start'] = 0;
971
	}
972
973
	// Update the topic count of each board.
974 View Code Duplication
	if ($_REQUEST['step'] <= 2)
975
	{
976
		if (empty($_REQUEST['start']))
977
			$smcFunc['db_query']('', '
978
				UPDATE {db_prefix}boards
979
				SET num_topics = {int:num_topics}',
980
				array(
981
					'num_topics' => 0,
982
				)
983
			);
984
985
		while ($_REQUEST['start'] < $max_topics)
986
		{
987
			$request = $smcFunc['db_query']('', '
988
				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_num_topics
989
				FROM {db_prefix}topics AS t
990
				WHERE t.approved = {int:is_approved}
991
					AND t.id_topic > {int:id_topic_min}
992
					AND t.id_topic <= {int:id_topic_max}
993
				GROUP BY t.id_board',
994
				array(
995
					'is_approved' => 1,
996
					'id_topic_min' => $_REQUEST['start'],
997
					'id_topic_max' => $_REQUEST['start'] + $increment,
998
				)
999
			);
1000
			while ($row = $smcFunc['db_fetch_assoc']($request))
1001
				$smcFunc['db_query']('', '
1002
					UPDATE {db_prefix}boards
1003
					SET num_topics = num_topics + {int:real_num_topics}
1004
					WHERE id_board = {int:id_board}',
1005
					array(
1006
						'id_board' => $row['id_board'],
1007
						'real_num_topics' => $row['real_num_topics'],
1008
					)
1009
				);
1010
			$smcFunc['db_free_result']($request);
1011
1012
			$_REQUEST['start'] += $increment;
1013
1014
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1015
			{
1016
				createToken('admin-boardrecount');
1017
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1018
1019
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=2;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1020
				$context['continue_percent'] = round((300 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1021
1022
				return;
1023
			}
1024
		}
1025
1026
		$_REQUEST['start'] = 0;
1027
	}
1028
1029
	// Update the unapproved post count of each board.
1030 View Code Duplication
	if ($_REQUEST['step'] <= 3)
1031
	{
1032
		if (empty($_REQUEST['start']))
1033
			$smcFunc['db_query']('', '
1034
				UPDATE {db_prefix}boards
1035
				SET unapproved_posts = {int:unapproved_posts}',
1036
				array(
1037
					'unapproved_posts' => 0,
1038
				)
1039
			);
1040
1041
		while ($_REQUEST['start'] < $max_topics)
1042
		{
1043
			$request = $smcFunc['db_query']('', '
1044
				SELECT /*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_unapproved_posts
1045
				FROM {db_prefix}messages AS m
1046
				WHERE m.id_topic > {int:id_topic_min}
1047
					AND m.id_topic <= {int:id_topic_max}
1048
					AND m.approved = {int:is_approved}
1049
				GROUP BY m.id_board',
1050
				array(
1051
					'id_topic_min' => $_REQUEST['start'],
1052
					'id_topic_max' => $_REQUEST['start'] + $increment,
1053
					'is_approved' => 0,
1054
				)
1055
			);
1056
			while ($row = $smcFunc['db_fetch_assoc']($request))
1057
				$smcFunc['db_query']('', '
1058
					UPDATE {db_prefix}boards
1059
					SET unapproved_posts = unapproved_posts + {int:unapproved_posts}
1060
					WHERE id_board = {int:id_board}',
1061
					array(
1062
						'id_board' => $row['id_board'],
1063
						'unapproved_posts' => $row['real_unapproved_posts'],
1064
					)
1065
				);
1066
			$smcFunc['db_free_result']($request);
1067
1068
			$_REQUEST['start'] += $increment;
1069
1070
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1071
			{
1072
				createToken('admin-boardrecount');
1073
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1074
1075
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=3;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1076
				$context['continue_percent'] = round((400 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1077
1078
				return;
1079
			}
1080
		}
1081
1082
		$_REQUEST['start'] = 0;
1083
	}
1084
1085
	// Update the unapproved topic count of each board.
1086 View Code Duplication
	if ($_REQUEST['step'] <= 4)
1087
	{
1088
		if (empty($_REQUEST['start']))
1089
			$smcFunc['db_query']('', '
1090
				UPDATE {db_prefix}boards
1091
				SET unapproved_topics = {int:unapproved_topics}',
1092
				array(
1093
					'unapproved_topics' => 0,
1094
				)
1095
			);
1096
1097
		while ($_REQUEST['start'] < $max_topics)
1098
		{
1099
			$request = $smcFunc['db_query']('', '
1100
				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_unapproved_topics
1101
				FROM {db_prefix}topics AS t
1102
				WHERE t.approved = {int:is_approved}
1103
					AND t.id_topic > {int:id_topic_min}
1104
					AND t.id_topic <= {int:id_topic_max}
1105
				GROUP BY t.id_board',
1106
				array(
1107
					'is_approved' => 0,
1108
					'id_topic_min' => $_REQUEST['start'],
1109
					'id_topic_max' => $_REQUEST['start'] + $increment,
1110
				)
1111
			);
1112
			while ($row = $smcFunc['db_fetch_assoc']($request))
1113
				$smcFunc['db_query']('', '
1114
					UPDATE {db_prefix}boards
1115
					SET unapproved_topics = unapproved_topics + {int:real_unapproved_topics}
1116
					WHERE id_board = {int:id_board}',
1117
					array(
1118
						'id_board' => $row['id_board'],
1119
						'real_unapproved_topics' => $row['real_unapproved_topics'],
1120
					)
1121
				);
1122
			$smcFunc['db_free_result']($request);
1123
1124
			$_REQUEST['start'] += $increment;
1125
1126
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1127
			{
1128
				createToken('admin-boardrecount');
1129
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1130
1131
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=4;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1132
				$context['continue_percent'] = round((500 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1133
1134
				return;
1135
			}
1136
		}
1137
1138
		$_REQUEST['start'] = 0;
1139
	}
1140
1141
	// Get all members with wrong number of personal messages.
1142
	if ($_REQUEST['step'] <= 5)
1143
	{
1144
		$request = $smcFunc['db_query']('', '
1145
			SELECT /*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num,
1146
				MAX(mem.instant_messages) AS instant_messages
1147
			FROM {db_prefix}members AS mem
1148
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (mem.id_member = pmr.id_member AND pmr.deleted = {int:is_not_deleted})
1149
			GROUP BY mem.id_member
1150
			HAVING COUNT(pmr.id_pm) != MAX(mem.instant_messages)',
1151
			array(
1152
				'is_not_deleted' => 0,
1153
			)
1154
		);
1155 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
1156
			updateMemberData($row['id_member'], array('instant_messages' => $row['real_num']));
1157
		$smcFunc['db_free_result']($request);
1158
1159
		$request = $smcFunc['db_query']('', '
1160
			SELECT /*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num,
1161
				MAX(mem.unread_messages) AS unread_messages
1162
			FROM {db_prefix}members AS mem
1163
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (mem.id_member = pmr.id_member AND pmr.deleted = {int:is_not_deleted} AND pmr.is_read = {int:is_not_read})
1164
			GROUP BY mem.id_member
1165
			HAVING COUNT(pmr.id_pm) != MAX(mem.unread_messages)',
1166
			array(
1167
				'is_not_deleted' => 0,
1168
				'is_not_read' => 0,
1169
			)
1170
		);
1171 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
1172
			updateMemberData($row['id_member'], array('unread_messages' => $row['real_num']));
1173
		$smcFunc['db_free_result']($request);
1174
1175
		if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1176
		{
1177
			createToken('admin-boardrecount');
1178
			$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1179
1180
			$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=6;start=0;' . $context['session_var'] . '=' . $context['session_id'];
1181
			$context['continue_percent'] = round(700 / $total_steps);
1182
1183
			return;
1184
		}
1185
	}
1186
1187
	// Any messages pointing to the wrong board?
1188
	if ($_REQUEST['step'] <= 6)
1189
	{
1190
		while ($_REQUEST['start'] < $modSettings['maxMsgID'])
1191
		{
1192
			$request = $smcFunc['db_query']('', '
1193
				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, m.id_msg
1194
				FROM {db_prefix}messages AS m
1195
					INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic AND t.id_board != m.id_board)
1196
				WHERE m.id_msg > {int:id_msg_min}
1197
					AND m.id_msg <= {int:id_msg_max}',
1198
				array(
1199
					'id_msg_min' => $_REQUEST['start'],
1200
					'id_msg_max' => $_REQUEST['start'] + $increment,
1201
				)
1202
			);
1203
			$boards = array();
1204
			while ($row = $smcFunc['db_fetch_assoc']($request))
1205
				$boards[$row['id_board']][] = $row['id_msg'];
1206
			$smcFunc['db_free_result']($request);
1207
1208
			foreach ($boards as $board_id => $messages)
1209
				$smcFunc['db_query']('', '
1210
					UPDATE {db_prefix}messages
1211
					SET id_board = {int:id_board}
1212
					WHERE id_msg IN ({array_int:id_msg_array})',
1213
					array(
1214
						'id_msg_array' => $messages,
1215
						'id_board' => $board_id,
1216
					)
1217
				);
1218
1219
			$_REQUEST['start'] += $increment;
1220
1221
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1222
			{
1223
				createToken('admin-boardrecount');
1224
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1225
1226
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=6;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1227
				$context['continue_percent'] = round((700 + 100 * $_REQUEST['start'] / $modSettings['maxMsgID']) / $total_steps);
1228
1229
				return;
1230
			}
1231
		}
1232
1233
		$_REQUEST['start'] = 0;
1234
	}
1235
1236
	// Update the latest message of each board.
1237
	$request = $smcFunc['db_query']('', '
1238
		SELECT m.id_board, MAX(m.id_msg) AS local_last_msg
1239
		FROM {db_prefix}messages AS m
1240
		WHERE m.approved = {int:is_approved}
1241
		GROUP BY m.id_board',
1242
		array(
1243
			'is_approved' => 1,
1244
		)
1245
	);
1246
	$realBoardCounts = array();
1247
	while ($row = $smcFunc['db_fetch_assoc']($request))
1248
		$realBoardCounts[$row['id_board']] = $row['local_last_msg'];
1249
	$smcFunc['db_free_result']($request);
1250
1251
	$request = $smcFunc['db_query']('', '
1252
		SELECT /*!40001 SQL_NO_CACHE */ id_board, id_parent, id_last_msg, child_level, id_msg_updated
1253
		FROM {db_prefix}boards',
1254
		array(
1255
		)
1256
	);
1257
	$resort_me = array();
1258
	while ($row = $smcFunc['db_fetch_assoc']($request))
1259
	{
1260
		$row['local_last_msg'] = isset($realBoardCounts[$row['id_board']]) ? $realBoardCounts[$row['id_board']] : 0;
1261
		$resort_me[$row['child_level']][] = $row;
1262
	}
1263
	$smcFunc['db_free_result']($request);
1264
1265
	krsort($resort_me);
1266
1267
	$lastModifiedMsg = array();
1268
	foreach ($resort_me as $rows)
1269
		foreach ($rows as $row)
1270
		{
1271
			// The latest message is the latest of the current board and its children.
1272
			if (isset($lastModifiedMsg[$row['id_board']]))
1273
				$curLastModifiedMsg = max($row['local_last_msg'], $lastModifiedMsg[$row['id_board']]);
1274
			else
1275
				$curLastModifiedMsg = $row['local_last_msg'];
1276
1277
			// If what is and what should be the latest message differ, an update is necessary.
1278
			if ($row['local_last_msg'] != $row['id_last_msg'] || $curLastModifiedMsg != $row['id_msg_updated'])
1279
				$smcFunc['db_query']('', '
1280
					UPDATE {db_prefix}boards
1281
					SET id_last_msg = {int:id_last_msg}, id_msg_updated = {int:id_msg_updated}
1282
					WHERE id_board = {int:id_board}',
1283
					array(
1284
						'id_last_msg' => $row['local_last_msg'],
1285
						'id_msg_updated' => $curLastModifiedMsg,
1286
						'id_board' => $row['id_board'],
1287
					)
1288
				);
1289
1290
			// Parent boards inherit the latest modified message of their children.
1291
			if (isset($lastModifiedMsg[$row['id_parent']]))
1292
				$lastModifiedMsg[$row['id_parent']] = max($row['local_last_msg'], $lastModifiedMsg[$row['id_parent']]);
1293
			else
1294
				$lastModifiedMsg[$row['id_parent']] = $row['local_last_msg'];
1295
		}
1296
1297
	// Update all the basic statistics.
1298
	updateStats('member');
1299
	updateStats('message');
1300
	updateStats('topic');
1301
1302
	// Finally, update the latest event times.
1303
	require_once($sourcedir . '/ScheduledTasks.php');
1304
	CalculateNextTrigger();
1305
1306
	redirectexit('action=admin;area=maintain;sa=routine;done=recount');
1307
}
1308
1309
/**
1310
 * Perform a detailed version check.  A very good thing ;).
1311
 * The function parses the comment headers in all files for their version information,
1312
 * and outputs that for some javascript to check with simplemachines.org.
1313
 * It does not connect directly with simplemachines.org, but rather expects the client to.
1314
 *
1315
 * It requires the admin_forum permission.
1316
 * Uses the view_versions admin area.
1317
 * Accessed through ?action=admin;area=maintain;sa=routine;activity=version.
1318
 * @uses Admin template, view_versions sub-template.
1319
 */
1320
function VersionDetail()
1321
{
1322
	global $forum_version, $txt, $sourcedir, $context;
1323
1324
	isAllowedTo('admin_forum');
1325
1326
	// Call the function that'll get all the version info we need.
1327
	require_once($sourcedir . '/Subs-Admin.php');
1328
	$versionOptions = array(
1329
		'include_ssi' => true,
1330
		'include_subscriptions' => true,
1331
		'include_tasks' => true,
1332
		'sort_results' => true,
1333
	);
1334
	$version_info = getFileVersions($versionOptions);
1335
1336
	// Add the new info to the template context.
1337
	$context += array(
1338
		'file_versions' => $version_info['file_versions'],
1339
		'default_template_versions' => $version_info['default_template_versions'],
1340
		'template_versions' => $version_info['template_versions'],
1341
		'default_language_versions' => $version_info['default_language_versions'],
1342
		'default_known_languages' => array_keys($version_info['default_language_versions']),
1343
		'tasks_versions' => $version_info['tasks_versions'],
1344
	);
1345
1346
	// Make it easier to manage for the template.
1347
	$context['forum_version'] = $forum_version;
1348
1349
	$context['sub_template'] = 'view_versions';
1350
	$context['page_title'] = $txt['admin_version_check'];
1351
}
1352
1353
/**
1354
 * Re-attribute posts.
1355
 */
1356
function MaintainReattributePosts()
1357
{
1358
	global $sourcedir, $context, $txt;
1359
1360
	checkSession();
1361
1362
	// Find the member.
1363
	require_once($sourcedir . '/Subs-Auth.php');
1364
	$members = findMembers($_POST['to']);
1365
1366
	if (empty($members))
1367
		fatal_lang_error('reattribute_cannot_find_member');
1368
1369
	$memID = array_shift($members);
1370
	$memID = $memID['id'];
1371
1372
	$email = $_POST['type'] == 'email' ? $_POST['from_email'] : '';
1373
	$membername = $_POST['type'] == 'name' ? $_POST['from_name'] : '';
1374
1375
	// Now call the reattribute function.
1376
	require_once($sourcedir . '/Subs-Members.php');
1377
	reattributePosts($memID, $email, $membername, !empty($_POST['posts']));
1378
1379
	$context['maintenance_finished'] = $txt['maintain_reattribute_posts'];
1380
}
1381
1382
/**
1383
 * Removing old members. Done and out!
1384
 * @todo refactor
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
1385
 */
1386
function MaintainPurgeInactiveMembers()
1387
{
1388
	global $sourcedir, $context, $smcFunc, $txt;
1389
1390
	$_POST['maxdays'] = empty($_POST['maxdays']) ? 0 : (int) $_POST['maxdays'];
1391
	if (!empty($_POST['groups']) && $_POST['maxdays'] > 0)
1392
	{
1393
		checkSession();
1394
		validateToken('admin-maint');
1395
1396
		$groups = array();
1397
		foreach ($_POST['groups'] as $id => $dummy)
0 ignored issues
show
Bug introduced by
The expression $_POST['groups'] of type integer is not traversable.
Loading history...
1398
			$groups[] = (int) $id;
1399
		$time_limit = (time() - ($_POST['maxdays'] * 24 * 3600));
1400
		$where_vars = array(
1401
			'time_limit' => $time_limit,
1402
		);
1403
		if ($_POST['del_type'] == 'activated')
1404
		{
1405
			$where = 'mem.date_registered < {int:time_limit} AND mem.is_activated = {int:is_activated}';
1406
			$where_vars['is_activated'] = 0;
1407
		}
1408
		else
1409
			$where = 'mem.last_login < {int:time_limit} AND (mem.last_login != 0 OR mem.date_registered < {int:time_limit})';
1410
1411
		// Need to get *all* groups then work out which (if any) we avoid.
1412
		$request = $smcFunc['db_query']('', '
1413
			SELECT id_group, group_name, min_posts
1414
			FROM {db_prefix}membergroups',
1415
			array(
1416
			)
1417
		);
1418
		while ($row = $smcFunc['db_fetch_assoc']($request))
1419
		{
1420
			// Avoid this one?
1421
			if (!in_array($row['id_group'], $groups))
1422
			{
1423
				// Post group?
1424
				if ($row['min_posts'] != -1)
1425
				{
1426
					$where .= ' AND mem.id_post_group != {int:id_post_group_' . $row['id_group'] . '}';
1427
					$where_vars['id_post_group_' . $row['id_group']] = $row['id_group'];
1428
				}
1429
				else
1430
				{
1431
					$where .= ' AND mem.id_group != {int:id_group_' . $row['id_group'] . '} AND FIND_IN_SET({int:id_group_' . $row['id_group'] . '}, mem.additional_groups) = 0';
1432
					$where_vars['id_group_' . $row['id_group']] = $row['id_group'];
1433
				}
1434
			}
1435
		}
1436
		$smcFunc['db_free_result']($request);
1437
1438
		// If we have ungrouped unselected we need to avoid those guys.
1439
		if (!in_array(0, $groups))
1440
		{
1441
			$where .= ' AND (mem.id_group != 0 OR mem.additional_groups != {string:blank_add_groups})';
1442
			$where_vars['blank_add_groups'] = '';
1443
		}
1444
1445
		// Select all the members we're about to murder/remove...
1446
		$request = $smcFunc['db_query']('', '
1447
			SELECT mem.id_member, COALESCE(m.id_member, 0) AS is_mod
1448
			FROM {db_prefix}members AS mem
1449
				LEFT JOIN {db_prefix}moderators AS m ON (m.id_member = mem.id_member)
1450
			WHERE ' . $where,
1451
			$where_vars
1452
		);
1453
		$members = array();
1454
		while ($row = $smcFunc['db_fetch_assoc']($request))
1455
		{
1456
			if (!$row['is_mod'] || !in_array(3, $groups))
1457
				$members[] = $row['id_member'];
1458
		}
1459
		$smcFunc['db_free_result']($request);
1460
1461
		require_once($sourcedir . '/Subs-Members.php');
1462
		deleteMembers($members);
1463
	}
1464
1465
	$context['maintenance_finished'] = $txt['maintain_members'];
1466
	createToken('admin-maint');
1467
}
1468
1469
/**
1470
 * Removing old posts doesn't take much as we really pass through.
1471
 */
1472
function MaintainRemoveOldPosts()
1473
{
1474
	global $sourcedir;
1475
1476
	validateToken('admin-maint');
1477
1478
	// Actually do what we're told!
1479
	require_once($sourcedir . '/RemoveTopic.php');
1480
	RemoveOldTopics2();
1481
}
1482
1483
/**
1484
 * Removing old drafts
1485
 */
1486
function MaintainRemoveOldDrafts()
1487
{
1488
	global $sourcedir, $smcFunc;
1489
1490
	validateToken('admin-maint');
1491
1492
	$drafts = array();
1493
1494
	// Find all of the old drafts
1495
	$request = $smcFunc['db_query']('', '
1496
		SELECT id_draft
1497
		FROM {db_prefix}user_drafts
1498
		WHERE poster_time <= {int:poster_time_old}',
1499
		array(
1500
			'poster_time_old' => time() - (86400 * $_POST['draftdays']),
1501
		)
1502
	);
1503
1504
	while ($row = $smcFunc['db_fetch_row']($request))
1505
		$drafts[] = (int) $row[0];
1506
	$smcFunc['db_free_result']($request);
1507
1508
	// If we have old drafts, remove them
1509
	if (count($drafts) > 0)
1510
	{
1511
		require_once($sourcedir . '/Drafts.php');
1512
		DeleteDraft($drafts, false);
0 ignored issues
show
Documentation introduced by
$drafts is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1513
	}
1514
}
1515
1516
/**
1517
 * Moves topics from one board to another.
1518
 *
1519
 * @uses not_done template to pause the process.
1520
 */
1521
function MaintainMassMoveTopics()
1522
{
1523
	global $smcFunc, $sourcedir, $context, $txt;
1524
1525
	// Only admins.
1526
	isAllowedTo('admin_forum');
1527
1528
	checkSession('request');
1529
	validateToken('admin-maint');
1530
1531
	// Set up to the context.
1532
	$context['page_title'] = $txt['not_done_title'];
1533
	$context['continue_countdown'] = 3;
1534
	$context['continue_post_data'] = '';
1535
	$context['continue_get_data'] = '';
1536
	$context['sub_template'] = 'not_done';
1537
	$context['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
1538
	$context['start_time'] = time();
1539
1540
	// First time we do this?
1541
	$id_board_from = isset($_REQUEST['id_board_from']) ? (int) $_REQUEST['id_board_from'] : 0;
1542
	$id_board_to = isset($_REQUEST['id_board_to']) ? (int) $_REQUEST['id_board_to'] : 0;
1543
	$max_days = isset($_REQUEST['maxdays']) ? (int) $_REQUEST['maxdays'] : 0;
1544
	$locked = isset($_POST['move_type_locked']) || isset($_GET['locked']);
1545
	$sticky = isset($_POST['move_type_sticky']) || isset($_GET['sticky']);
1546
1547
	// No boards then this is your stop.
1548
	if (empty($id_board_from) || empty($id_board_to))
1549
		return;
1550
1551
	// The big WHERE clause
1552
	$conditions = 'WHERE t.id_board = {int:id_board_from}
1553
		AND m.icon != {string:moved}';
1554
1555
	// DB parameters
1556
	$params = array(
1557
		'id_board_from' => $id_board_from,
1558
		'moved' => 'moved',
1559
	);
1560
1561
	// Only moving topics not posted in for x days?
1562
	if (!empty($max_days))
1563
	{
1564
		$conditions .= '
1565
			AND m.poster_time < {int:poster_time}';
1566
		$params['poster_time'] = time() - 3600 * 24 * $max_days;
1567
	}
1568
1569
	// Moving locked topics?
1570
	if ($locked)
1571
	{
1572
		$conditions .= '
1573
			AND t.locked = {int:locked}';
1574
		$params['locked'] = 1;
1575
	}
1576
1577
	// What about sticky topics?
1578
	if ($sticky)
1579
	{
1580
		$conditions .= '
1581
			AND t.is_sticky = {int:sticky}';
1582
		$params['sticky'] = 1;
1583
	}
1584
1585
	// How many topics are we converting?
1586
	if (!isset($_REQUEST['totaltopics']))
1587
	{
1588
		$request = $smcFunc['db_query']('', '
1589
			SELECT COUNT(*)
1590
			FROM {db_prefix}topics AS t
1591
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_last_msg)' .
1592
			$conditions,
1593
			$params
1594
		);
1595
		list ($total_topics) = $smcFunc['db_fetch_row']($request);
1596
		$smcFunc['db_free_result']($request);
1597
	}
1598
	else
1599
		$total_topics = (int) $_REQUEST['totaltopics'];
1600
1601
	// Seems like we need this here.
1602
	$context['continue_get_data'] = '?action=admin;area=maintain;sa=topics;activity=massmove;id_board_from=' . $id_board_from . ';id_board_to=' . $id_board_to . ';totaltopics=' . $total_topics . ';max_days=' . $max_days;
1603
1604
	if ($locked)
1605
		$context['continue_get_data'] .= ';locked';
1606
1607
	if ($sticky)
1608
		$context['continue_get_data'] .= ';sticky';
1609
1610
	$context['continue_get_data'] .= ';start=' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1611
1612
	// We have topics to move so start the process.
1613
	if (!empty($total_topics))
1614
	{
1615
		while ($context['start'] <= $total_topics)
1616
		{
1617
			// Lets get the topics.
1618
			$request = $smcFunc['db_query']('', '
1619
				SELECT t.id_topic
1620
				FROM {db_prefix}topics AS t
1621
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_last_msg)
1622
				' . $conditions . '
1623
				LIMIT 10',
1624
				$params
1625
			);
1626
1627
			// Get the ids.
1628
			$topics = array();
1629
			while ($row = $smcFunc['db_fetch_assoc']($request))
1630
				$topics[] = $row['id_topic'];
1631
1632
			// Just return if we don't have any topics left to move.
1633
			if (empty($topics))
1634
			{
1635
				cache_put_data('board-' . $id_board_from, null, 120);
1636
				cache_put_data('board-' . $id_board_to, null, 120);
1637
				redirectexit('action=admin;area=maintain;sa=topics;done=massmove');
1638
			}
1639
1640
			// Lets move them.
1641
			require_once($sourcedir . '/MoveTopic.php');
1642
			moveTopics($topics, $id_board_to);
1643
1644
			// We've done at least ten more topics.
1645
			$context['start'] += 10;
1646
1647
			// Lets wait a while.
1648
			if (time() - $context['start_time'] > 3)
1649
			{
1650
				// What's the percent?
1651
				$context['continue_percent'] = round(100 * ($context['start'] / $total_topics), 1);
1652
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=topics;activity=massmove;id_board_from=' . $id_board_from . ';id_board_to=' . $id_board_to . ';totaltopics=' . $total_topics . ';start=' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1653
1654
				// Let the template system do it's thang.
1655
				return;
1656
			}
1657
		}
1658
	}
1659
1660
	// Don't confuse admins by having an out of date cache.
1661
	cache_put_data('board-' . $id_board_from, null, 120);
1662
	cache_put_data('board-' . $id_board_to, null, 120);
1663
1664
	redirectexit('action=admin;area=maintain;sa=topics;done=massmove');
1665
}
1666
1667
/**
1668
 * Recalculate all members post counts
1669
 * it requires the admin_forum permission.
1670
 *
1671
 * - recounts all posts for members found in the message table
1672
 * - updates the members post count record in the members table
1673
 * - honors the boards post count flag
1674
 * - does not count posts in the recycle bin
1675
 * - zeros post counts for all members with no posts in the message table
1676
 * - runs as a delayed loop to avoid server overload
1677
 * - uses the not_done template in Admin.template
1678
 *
1679
 * The function redirects back to action=admin;area=maintain;sa=members when complete.
1680
 * It is accessed via ?action=admin;area=maintain;sa=members;activity=recountposts
1681
 */
1682
function MaintainRecountPosts()
1683
{
1684
	global $txt, $context, $modSettings, $smcFunc;
1685
1686
	// You have to be allowed in here
1687
	isAllowedTo('admin_forum');
1688
	checkSession('request');
1689
1690
	// Set up to the context.
1691
	$context['page_title'] = $txt['not_done_title'];
1692
	$context['continue_countdown'] = 3;
1693
	$context['continue_get_data'] = '';
1694
	$context['sub_template'] = 'not_done';
1695
1696
	// init
1697
	$increment = 200;
1698
	$_REQUEST['start'] = !isset($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
1699
1700
	// Ask for some extra time, on big boards this may take a bit
1701
	@set_time_limit(600);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1702
1703
	// Only run this query if we don't have the total number of members that have posted
1704
	if (!isset($_SESSION['total_members']))
1705
	{
1706
		validateToken('admin-maint');
1707
1708
		$request = $smcFunc['db_query']('', '
1709
			SELECT COUNT(DISTINCT m.id_member)
1710
			FROM {db_prefix}messages AS m
1711
			JOIN {db_prefix}boards AS b on m.id_board = b.id_board
1712
			WHERE m.id_member != 0
1713
				AND b.count_posts = 0',
1714
			array(
1715
			)
1716
		);
1717
1718
		// save it so we don't do this again for this task
1719
		list ($_SESSION['total_members']) = $smcFunc['db_fetch_row']($request);
1720
		$smcFunc['db_free_result']($request);
1721
	}
1722
	else
1723
		validateToken('admin-recountposts');
1724
1725
	// Lets get a group of members and determine their post count (from the boards that have post count enabled of course).
1726
	$request = $smcFunc['db_query']('', '
1727
		SELECT /*!40001 SQL_NO_CACHE */ m.id_member, COUNT(m.id_member) AS posts
1728
		FROM {db_prefix}messages AS m
1729
			INNER JOIN {db_prefix}boards AS b ON m.id_board = b.id_board
1730
		WHERE m.id_member != {int:zero}
1731
			AND b.count_posts = {int:zero}
1732
			' . (!empty($modSettings['recycle_enable']) ? ' AND b.id_board != {int:recycle}' : '') . '
1733
		GROUP BY m.id_member
1734
		LIMIT {int:start}, {int:number}',
1735
		array(
1736
			'start' => $_REQUEST['start'],
1737
			'number' => $increment,
1738
			'recycle' => $modSettings['recycle_board'],
1739
			'zero' => 0,
1740
		)
1741
	);
1742
	$total_rows = $smcFunc['db_num_rows']($request);
1743
1744
	// Update the post count for this group
1745
	while ($row = $smcFunc['db_fetch_assoc']($request))
1746
	{
1747
		$smcFunc['db_query']('', '
1748
			UPDATE {db_prefix}members
1749
			SET posts = {int:posts}
1750
			WHERE id_member = {int:row}',
1751
			array(
1752
				'row' => $row['id_member'],
1753
				'posts' => $row['posts'],
1754
			)
1755
		);
1756
	}
1757
	$smcFunc['db_free_result']($request);
1758
1759
	// Continue?
1760
	if ($total_rows == $increment)
1761
	{
1762
		$_REQUEST['start'] += $increment;
1763
		$context['continue_get_data'] = '?action=admin;area=maintain;sa=members;activity=recountposts;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1764
		$context['continue_percent'] = round(100 * $_REQUEST['start'] / $_SESSION['total_members']);
1765
1766
		createToken('admin-recountposts');
1767
		$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-recountposts_token_var'] . '" value="' . $context['admin-recountposts_token'] . '">';
1768
1769
		if (function_exists('apache_reset_timeout'))
1770
			apache_reset_timeout();
1771
		return;
1772
	}
1773
1774
	// final steps ... made more difficult since we don't yet support sub-selects on joins
1775
	// place all members who have posts in the message table in a temp table
1776
	$createTemporary = $smcFunc['db_query']('', '
1777
		CREATE TEMPORARY TABLE {db_prefix}tmp_maint_recountposts (
1778
			id_member mediumint(8) unsigned NOT NULL default {string:string_zero},
1779
			PRIMARY KEY (id_member)
1780
		)
1781
		SELECT m.id_member
1782
		FROM {db_prefix}messages AS m
1783
			INNER JOIN {db_prefix}boards AS b ON m.id_board = b.id_board
1784
		WHERE m.id_member != {int:zero}
1785
			AND b.count_posts = {int:zero}
1786
			' . (!empty($modSettings['recycle_enable']) ? ' AND b.id_board != {int:recycle}' : '') . '
1787
		GROUP BY m.id_member',
1788
		array(
1789
			'zero' => 0,
1790
			'string_zero' => '0',
1791
			'db_error_skip' => true,
1792
			'recycle' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
1793
		)
1794
	) !== false;
1795
1796 View Code Duplication
	if ($createTemporary)
1797
	{
1798
		// outer join the members table on the temporary table finding the members that have a post count but no posts in the message table
1799
		$request = $smcFunc['db_query']('', '
1800
			SELECT mem.id_member, mem.posts
1801
			FROM {db_prefix}members AS mem
1802
			LEFT OUTER JOIN {db_prefix}tmp_maint_recountposts AS res
1803
			ON res.id_member = mem.id_member
1804
			WHERE res.id_member IS null
1805
				AND mem.posts != {int:zero}',
1806
			array(
1807
				'zero' => 0,
1808
			)
1809
		);
1810
1811
		// set the post count to zero for any delinquents we may have found
1812
		while ($row = $smcFunc['db_fetch_assoc']($request))
1813
		{
1814
			$smcFunc['db_query']('', '
1815
				UPDATE {db_prefix}members
1816
				SET posts = {int:zero}
1817
				WHERE id_member = {int:row}',
1818
				array(
1819
					'row' => $row['id_member'],
1820
					'zero' => 0,
1821
				)
1822
			);
1823
		}
1824
		$smcFunc['db_free_result']($request);
1825
	}
1826
1827
	// all done
1828
	unset($_SESSION['total_members']);
1829
	$context['maintenance_finished'] = $txt['maintain_recountposts'];
1830
	redirectexit('action=admin;area=maintain;sa=members;done=recountposts');
1831
}
1832
1833
/**
1834
 * Generates a list of integration hooks for display
1835
 * Accessed through ?action=admin;area=maintain;sa=hooks;
1836
 * Allows for removal or disabling of selected hooks
1837
 */
1838
function list_integration_hooks()
1839
{
1840
	global $sourcedir, $scripturl, $context, $txt;
1841
1842
	$context['filter_url'] = '';
1843
	$context['current_filter'] = '';
1844
	$currentHooks = get_integration_hooks();
1845
	if (isset($_GET['filter']) && in_array($_GET['filter'], array_keys($currentHooks)))
1846
	{
1847
		$context['filter_url'] = ';filter=' . $_GET['filter'];
1848
		$context['current_filter'] = $_GET['filter'];
1849
	}
1850
1851
	if (!empty($_REQUEST['do']) && isset($_REQUEST['hook']) && isset($_REQUEST['function']))
1852
	{
1853
		checkSession('request');
1854
		validateToken('admin-hook', 'request');
1855
1856
		if ($_REQUEST['do'] == 'remove')
1857
			remove_integration_function($_REQUEST['hook'], urldecode($_REQUEST['function']));
1858
1859
		else
1860
		{
1861
			$function_remove = urldecode($_REQUEST['function']) . (($_REQUEST['do'] == 'disable') ? '' : '!');
1862
			$function_add = urldecode($_REQUEST['function']) . (($_REQUEST['do'] == 'disable') ? '!' : '');
1863
1864
			remove_integration_function($_REQUEST['hook'], $function_remove);
1865
			add_integration_function($_REQUEST['hook'], $function_add);
1866
1867
			redirectexit('action=admin;area=maintain;sa=hooks' . $context['filter_url']);
1868
		}
1869
	}
1870
1871
	createToken('admin-hook', 'request');
1872
1873
	$list_options = array(
1874
		'id' => 'list_integration_hooks',
1875
		'title' => $txt['hooks_title_list'],
1876
		'items_per_page' => 20,
1877
		'base_href' => $scripturl . '?action=admin;area=maintain;sa=hooks' . $context['filter_url'] . ';' . $context['session_var'] . '=' . $context['session_id'],
1878
		'default_sort_col' => 'hook_name',
1879
		'get_items' => array(
1880
			'function' => 'get_integration_hooks_data',
1881
		),
1882
		'get_count' => array(
1883
			'function' => 'get_integration_hooks_count',
1884
		),
1885
		'no_items_label' => $txt['hooks_no_hooks'],
1886
		'columns' => array(
1887
			'hook_name' => array(
1888
				'header' => array(
1889
					'value' => $txt['hooks_field_hook_name'],
1890
				),
1891
				'data' => array(
1892
					'db' => 'hook_name',
1893
				),
1894
				'sort' =>  array(
1895
					'default' => 'hook_name',
1896
					'reverse' => 'hook_name DESC',
1897
				),
1898
			),
1899
			'function_name' => array(
1900
				'header' => array(
1901
					'value' => $txt['hooks_field_function_name'],
1902
				),
1903
				'data' => array(
1904
					'function' => function($data) use ($txt)
1905
					{
1906
						// Show a nice icon to indicate this is an instance.
1907
						$instance = (!empty($data['instance']) ? '<span class="generic_icons news" title="' . $txt['hooks_field_function_method'] . '"></span> ' : '');
1908
1909
						if (!empty($data['included_file']))
1910
							return $instance . $txt['hooks_field_function'] . ': ' . $data['real_function'] . '<br>' . $txt['hooks_field_included_file'] . ': ' . $data['included_file'];
1911
1912
						else
1913
							return $instance . $data['real_function'];
1914
					},
1915
				),
1916
				'sort' =>  array(
1917
					'default' => 'function_name',
1918
					'reverse' => 'function_name DESC',
1919
				),
1920
			),
1921
			'file_name' => array(
1922
				'header' => array(
1923
					'value' => $txt['hooks_field_file_name'],
1924
				),
1925
				'data' => array(
1926
					'db' => 'file_name',
1927
				),
1928
				'sort' =>  array(
1929
					'default' => 'file_name',
1930
					'reverse' => 'file_name DESC',
1931
				),
1932
			),
1933
			'status' => array(
1934
				'header' => array(
1935
					'value' => $txt['hooks_field_hook_exists'],
1936
					'style' => 'width:3%;',
1937
				),
1938
				'data' => array(
1939
					'function' => function($data) use ($txt, $scripturl, $context)
1940
					{
1941
						$change_status = array('before' => '', 'after' => '');
1942
1943
							$change_status['before'] = '<a href="' . $scripturl . '?action=admin;area=maintain;sa=hooks;do=' . ($data['enabled'] ? 'disable' : 'enable') . ';hook=' . $data['hook_name'] . ';function=' . urlencode($data['function_name']) . $context['filter_url'] . ';' . $context['admin-hook_token_var'] . '=' . $context['admin-hook_token'] . ';' . $context['session_var'] . '=' . $context['session_id'] . '" data-confirm="' . $txt['quickmod_confirm'] . '" class="you_sure">';
1944
							$change_status['after'] = '</a>';
1945
1946
						return $change_status['before'] . '<span class="generic_icons post_moderation_' . $data['status'] . '" title="' . $data['img_text'] . '"></span>';
1947
					},
1948
					'class' => 'centertext',
1949
				),
1950
				'sort' =>  array(
1951
					'default' => 'status',
1952
					'reverse' => 'status DESC',
1953
				),
1954
			),
1955
		),
1956
		'additional_rows' => array(
1957
			array(
1958
				'position' => 'after_title',
1959
				'value' => $txt['hooks_disable_instructions'] . '<br>
1960
					' . $txt['hooks_disable_legend'] . ':
1961
				<ul style="list-style: none;">
1962
					<li><span class="generic_icons post_moderation_allow"></span> ' . $txt['hooks_disable_legend_exists'] . '</li>
1963
					<li><span class="generic_icons post_moderation_moderate"></span> ' . $txt['hooks_disable_legend_disabled'] . '</li>
1964
					<li><span class="generic_icons post_moderation_deny"></span> ' . $txt['hooks_disable_legend_missing'] . '</li>
1965
				</ul>'
1966
			),
1967
		),
1968
	);
1969
1970
	$list_options['columns']['remove'] = array(
1971
		'header' => array(
1972
			'value' => $txt['hooks_button_remove'],
1973
			'style' => 'width:3%',
1974
		),
1975
		'data' => array(
1976
			'function' => function($data) use ($txt, $scripturl, $context)
1977
			{
1978
				if (!$data['hook_exists'])
1979
					return '
1980
					<a href="' . $scripturl . '?action=admin;area=maintain;sa=hooks;do=remove;hook=' . $data['hook_name'] . ';function=' . urlencode($data['function_name']) . $context['filter_url'] . ';' . $context['admin-hook_token_var'] . '=' . $context['admin-hook_token'] . ';' . $context['session_var'] . '=' . $context['session_id'] . '" data-confirm="' . $txt['quickmod_confirm'] . '" class="you_sure">
1981
						<span class="generic_icons delete" title="' . $txt['hooks_button_remove'] . '"></span>
1982
					</a>';
1983
			},
1984
			'class' => 'centertext',
1985
		),
1986
	);
1987
	$list_options['form'] = array(
1988
		'href' => $scripturl . '?action=admin;area=maintain;sa=hooks' . $context['filter_url'] . ';' . $context['session_var'] . '=' . $context['session_id'],
1989
		'name' => 'list_integration_hooks',
1990
	);
1991
1992
1993
	require_once($sourcedir . '/Subs-List.php');
1994
	createList($list_options);
1995
1996
	$context['page_title'] = $txt['hooks_title_list'];
1997
	$context['sub_template'] = 'show_list';
1998
	$context['default_list'] = 'list_integration_hooks';
1999
}
2000
2001
/**
2002
 * Gets all of the files in a directory and its children directories
2003
 *
2004
 * @param string $dir_path The path to the directory
2005
 * @return array An array containing information about the files found in the specified directory and its children
2006
 */
2007
function get_files_recursive($dir_path)
2008
{
2009
	$files = array();
2010
2011
	if ($dh = opendir($dir_path))
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $dh. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2012
	{
2013
		while (($file = readdir($dh)) !== false)
2014
		{
2015
			if ($file != '.' && $file != '..')
2016
			{
2017
				if (is_dir($dir_path . '/' . $file))
2018
					$files = array_merge($files, get_files_recursive($dir_path . '/' . $file));
2019
				else
2020
					$files[] = array('dir' => $dir_path, 'name' => $file);
2021
			}
2022
		}
2023
	}
2024
	closedir($dh);
2025
2026
	return $files;
2027
}
2028
2029
/**
2030
 * Callback function for the integration hooks list (list_integration_hooks)
2031
 * Gets all of the hooks in the system and their status
2032
 *
2033
 * @param int $start The item to start with (for pagination purposes)
2034
 * @param int $per_page How many items to display on each page
2035
 * @param string $sort A string indicating how to sort things
2036
 * @return array An array of information about the integration hooks
2037
 */
2038
function get_integration_hooks_data($start, $per_page, $sort)
2039
{
2040
	global $boarddir, $sourcedir, $settings, $txt, $context, $scripturl;
2041
2042
	$hooks = $temp_hooks = get_integration_hooks();
2043
	$hooks_data = $temp_data = $hook_status = array();
2044
2045
	$files = get_files_recursive($sourcedir);
2046
	if (!empty($files))
2047
	{
2048
		foreach ($files as $file)
2049
		{
2050
			if (is_file($file['dir'] . '/' . $file['name']) && substr($file['name'], -4) === '.php')
2051
			{
2052
				$fp = fopen($file['dir'] . '/' . $file['name'], 'rb');
2053
				$fc = fread($fp, filesize($file['dir'] . '/' . $file['name']));
2054
				fclose($fp);
2055
2056
				foreach ($temp_hooks as $hook => $allFunctions)
2057
				{
2058
					foreach ($allFunctions as $rawFunc)
2059
					{
2060
						// Get the hook info.
2061
						$hookParsedData = get_hook_info_from_raw($rawFunc);
2062
2063
						if (substr($hook, -8) === '_include')
2064
						{
2065
							$hook_status[$hook][$hookParsedData['pureFunc']]['exists'] = file_exists(strtr(trim($rawFunc), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])));
2066
							// I need to know if there is at least one function called in this file.
2067
							$temp_data['include'][$hookParsedData['pureFunc']] = array('hook' => $hook, 'function' => $hookParsedData['pureFunc']);
2068
							unset($temp_hooks[$hook][$rawFunc]);
2069
						}
2070
						elseif (strpos(str_replace(' (', '(', $fc), 'function ' . trim($hookParsedData['pureFunc']) . '(') !== false)
2071
						{
2072
							$hook_status[$hook][$hookParsedData['pureFunc']] = $hookParsedData;
2073
							$hook_status[$hook][$hookParsedData['pureFunc']]['exists'] = true;
2074
							$hook_status[$hook][$hookParsedData['pureFunc']]['in_file'] = (!empty($file['name']) ? $file['name'] : (!empty($hookParsedData['hookFile']) ? $hookParsedData['hookFile'] : ''));
2075
2076
							// Does the hook has its own file?
2077
							if (!empty($hookParsedData['hookFile']))
2078
								$temp_data['include'][$hookParsedData['pureFunc']] = array('hook' => $hook, 'function' => $hookParsedData['pureFunc']);
2079
2080
							// I want to remember all the functions called within this file (to check later if they are enabled or disabled and decide if the integrare_*_include of that file can be disabled too)
2081
							$temp_data['function'][$file['name']][$hookParsedData['pureFunc']] = $hookParsedData['enabled'];
2082
							unset($temp_hooks[$hook][$rawFunc]);
2083
						}
2084
					}
2085
				}
2086
			}
2087
		}
2088
	}
2089
2090
	$sort_types = array(
2091
		'hook_name' => array('hook', SORT_ASC),
2092
		'hook_name DESC' => array('hook', SORT_DESC),
2093
		'function_name' => array('function', SORT_ASC),
2094
		'function_name DESC' => array('function', SORT_DESC),
2095
		'file_name' => array('file_name', SORT_ASC),
2096
		'file_name DESC' => array('file_name', SORT_DESC),
2097
		'status' => array('status', SORT_ASC),
2098
		'status DESC' => array('status', SORT_DESC),
2099
	);
2100
2101
	$sort_options = $sort_types[$sort];
2102
	$sort = array();
2103
	$hooks_filters = array();
2104
2105
	foreach ($hooks as $hook => $functions)
2106
		$hooks_filters[] = '<option' . ($context['current_filter'] == $hook ? ' selected ' : '') . ' value="' . $hook . '">' . $hook . '</option>';
2107
2108
	if (!empty($hooks_filters))
2109
		$context['insert_after_template'] .= '
2110
		<script>
2111
			var hook_name_header = document.getElementById(\'header_list_integration_hooks_hook_name\');
2112
			hook_name_header.innerHTML += ' . JavaScriptEscape('<select style="margin-left:15px;" onchange="window.location=(\'' . $scripturl . '?action=admin;area=maintain;sa=hooks\' + (this.value ? \';filter=\' + this.value : \'\'));"><option value="">' . $txt['hooks_reset_filter'] . '</option>' . implode('', $hooks_filters) . '</select>') . ';
2113
		</script>';
2114
2115
	$temp_data = array();
2116
	$id = 0;
2117
2118
	foreach ($hooks as $hook => $functions)
2119
	{
2120
		if (empty($context['filter']) || (!empty($context['filter']) && $context['filter'] == $hook))
2121
		{
2122
			foreach ($functions as $rawFunc)
2123
			{
2124
				// Get the hook info.
2125
				$hookParsedData = get_hook_info_from_raw($rawFunc);
2126
2127
				$hook_exists = !empty($hook_status[$hook][$hookParsedData['pureFunc']]['exists']);
2128
				$sort[] = $sort_options[0];
2129
2130
				$temp_data[] = array(
2131
					'id' => 'hookid_' . $id++,
0 ignored issues
show
Coding Style introduced by
Increment and decrement operators must be bracketed when used in string concatenation
Loading history...
2132
					'hook_name' => $hook,
2133
					'function_name' => $hookParsedData['rawData'],
2134
					'real_function' => $hookParsedData['pureFunc'],
2135
					'included_file' => !empty($hookParsedData['absPath']) ? $hookParsedData['absPath'] : '',
2136
					'file_name' => (isset($hook_status[$hook][$hookParsedData['pureFunc']]['in_file']) ? $hook_status[$hook][$hookParsedData['pureFunc']]['in_file'] : (!empty($hookParsedData['hookFile']) ? $hookParsedData['hookFile'] : '')),
2137
					'instance' => $hookParsedData['object'],
2138
					'hook_exists' => $hook_exists,
2139
					'status' => $hook_exists ? ($hookParsedData['enabled'] ? 'allow' : 'moderate') : 'deny',
2140
					'img_text' => $txt['hooks_' . ($hook_exists ? ($hookParsedData['enabled'] ? 'active' : 'disabled') : 'missing')],
2141
					'enabled' => $hookParsedData['enabled'],
2142
					'can_be_disabled' => !isset($hook_status[$hook][$hookParsedData['pureFunc']]['enabled']),
2143
				);
2144
			}
2145
		}
2146
	}
2147
2148
	array_multisort($sort, $sort_options[1], $temp_data);
2149
2150
	$counter = 0;
2151
	$start++;
2152
2153
	foreach ($temp_data as $data)
2154
	{
2155
		if (++$counter < $start)
2156
			continue;
2157
		elseif ($counter == $start + $per_page)
2158
			break;
2159
2160
		$hooks_data[] = $data;
2161
	}
2162
2163
	return $hooks_data;
2164
}
2165
2166
/**
2167
 * Simply returns the total count of integration hooks
2168
 * Used by the integration hooks list function (list_integration_hooks)
2169
 *
2170
 * @return int The number of hooks currently in use
2171
 */
2172
function get_integration_hooks_count()
2173
{
2174
	global $context;
2175
2176
	$hooks = get_integration_hooks();
2177
	$hooks_count = 0;
2178
2179
	$context['filter'] = false;
2180
	if (isset($_GET['filter']))
2181
		$context['filter'] = $_GET['filter'];
2182
2183
	foreach ($hooks as $hook => $functions)
2184
	{
2185
		if (empty($context['filter']) || (!empty($context['filter']) && $context['filter'] == $hook))
2186
			$hooks_count += count($functions);
2187
	}
2188
2189
	return $hooks_count;
2190
}
2191
2192
/**
2193
 * Parses modSettings to create integration hook array
2194
 *
2195
 * @return array An array of information about the integration hooks
2196
 */
2197
function get_integration_hooks()
2198
{
2199
	global $modSettings;
2200
	static $integration_hooks;
2201
2202
	if (!isset($integration_hooks))
2203
	{
2204
		$integration_hooks = array();
2205
		foreach ($modSettings as $key => $value)
2206
		{
2207
			if (!empty($value) && substr($key, 0, 10) === 'integrate_')
2208
				$integration_hooks[$key] = explode(',', $value);
2209
		}
2210
	}
2211
2212
	return $integration_hooks;
2213
}
2214
2215
/**
2216
 * Parses each hook data and returns an array.
2217
 *
2218
 * @param string $rawData A string as it was saved to the DB.
2219
 * @return array everything found in the string itself
2220
 */
2221
function get_hook_info_from_raw($rawData)
2222
{
2223
	global $boarddir, $settings, $sourcedir;
2224
2225
	// A single string can hold tons of info!
2226
	$hookData = array(
2227
		'object' => false,
2228
		'enabled' => true,
2229
		'fileExists' => false,
2230
		'absPath' => '',
2231
		'hookFile' => '',
2232
		'pureFunc' => '',
2233
		'method' => '',
2234
		'class' => '',
2235
		'rawData' => $rawData,
2236
	);
2237
2238
	// Meh...
2239
	if (empty($rawData))
2240
		return $hookData;
2241
2242
	// For convenience purposes only!
2243
	$modFunc = $rawData;
2244
2245
	// Any files?
2246
	if (strpos($modFunc, '|') !== false)
2247
	{
2248
		list ($hookData['hookFile'], $modFunc) = explode('|', $modFunc);
2249
2250
		// Does the file exists? who knows!
2251
		if (empty($settings['theme_dir']))
2252
			$hookData['absPath'] = strtr(trim($hookData['hookFile']), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
2253
2254
		else
2255
			$hookData['absPath'] = strtr(trim($hookData['hookFile']), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2256
2257
		$hookData['fileExists'] = file_exists($hookData['absPath']);
2258
		$hookData['hookFile'] = basename($hookData['hookFile']);
2259
	}
2260
2261
	// Hook is an instance.
2262 View Code Duplication
	if (strpos($modFunc, '#') !== false)
2263
	{
2264
		$modFunc = str_replace('#', '', $modFunc);
2265
		$hookData['object'] = true;
2266
	}
2267
2268
	// Hook is "disabled"
2269 View Code Duplication
	if (strpos($modFunc, '!') !== false)
2270
	{
2271
		$modFunc = str_replace('!', '', $modFunc);
2272
		$hookData['enabled'] = false;
2273
	}
2274
2275
	// Handling methods?
2276
	if (strpos($modFunc, '::') !== false)
2277
	{
2278
		list ($hookData['class'], $hookData['method']) = explode('::', $modFunc);
2279
		$hookData['pureFunc'] = $hookData['method'];
2280
	}
2281
2282
	else
2283
		$hookData['pureFunc'] = $modFunc;
2284
2285
	return $hookData;
2286
}
2287
2288
?>