Completed
Push — release-2.1 ( 6f6d35...abeae7 )
by Mathias
08:46
created

ManageMaintenance.php ➔ AdminBoardRecount()   F

Complexity

Conditions 46
Paths > 20000

Size

Total Lines 511
Code Lines 237

Duplication

Lines 222
Ratio 43.44 %

Importance

Changes 0
Metric Value
cc 46
eloc 237
nc 162648
nop 0
dl 222
loc 511
rs 2
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']]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
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']]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
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')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
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')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
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
	// If we're here, we must be done.
680
	$context['continue_percent'] = 100;
681
	$context['continue_get_data'] = '?action=admin;area=maintain;sa=database;done=convertentities';
682
	$context['last_step'] = true;
683
	$context['continue_countdown'] = -1;
684
}
685
686
/**
687
 * Optimizes all tables in the database and lists how much was saved.
688
 * It requires the admin_forum permission.
689
 * It shows as the maintain_forum admin area.
690
 * It is accessed from ?action=admin;area=maintain;sa=database;activity=optimize.
691
 * It also updates the optimize scheduled task such that the tables are not automatically optimized again too soon.
692
693
 * @uses the optimize sub template
694
 */
695
function OptimizeTables()
696
{
697
	global $db_prefix, $txt, $context, $smcFunc, $time_start;
698
699
	isAllowedTo('admin_forum');
700
701
	checkSession('request');
702
703
	if (!isset($_SESSION['optimized_tables']))
704
		validateToken('admin-maint');
705
	else
706
		validateToken('admin-optimize', 'post', false);
707
708
	ignore_user_abort(true);
709
	db_extend();
710
711
	$context['page_title'] = $txt['database_optimize'];
712
	$context['sub_template'] = 'optimize';
713
	$context['continue_post_data'] = '';
714
	$context['continue_countdown'] = 3;
715
716
	// Only optimize the tables related to this smf install, not all the tables in the db
717
	$real_prefix = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
718
719
	// Get a list of tables, as well as how many there are.
720
	$temp_tables = $smcFunc['db_list_tables'](false, $real_prefix . '%');
721
	$tables = array();
722
	foreach ($temp_tables as $table)
723
		$tables[] = array('table_name' => $table);
724
725
	// If there aren't any tables then I believe that would mean the world has exploded...
726
	$context['num_tables'] = count($tables);
727
	if ($context['num_tables'] == 0)
728
		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...
729
730
	$_REQUEST['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
731
732
	// Try for extra time due to large tables.
733
	@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...
734
735
	// For each table....
736
	$_SESSION['optimized_tables'] = !empty($_SESSION['optimized_tables']) ? $_SESSION['optimized_tables'] : array();
737
	for ($key = $_REQUEST['start']; $context['num_tables'] - 1; $key++)
738
	{
739
		if (empty($tables[$key]))
740
			break;
741
742
		// Continue?
743
		if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 10)
744
		{
745
			$_REQUEST['start'] = $key;
746
			$context['continue_get_data'] = '?action=admin;area=maintain;sa=database;activity=optimize;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
747
			$context['continue_percent'] = round(100 * $_REQUEST['start'] / $context['num_tables']);
748
			$context['sub_template'] = 'not_done';
749
			$context['page_title'] = $txt['not_done_title'];
750
751
			createToken('admin-optimize');
752
			$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-optimize_token_var'] . '" value="' . $context['admin-optimize_token'] . '">';
753
754
			if (function_exists('apache_reset_timeout'))
755
				apache_reset_timeout();
756
757
			return;
758
		}
759
760
		// Optimize the table!  We use backticks here because it might be a custom table.
761
		$data_freed = $smcFunc['db_optimize_table']($tables[$key]['table_name']);
762
763
		if ($data_freed > 0)
764
			$_SESSION['optimized_tables'][] = array(
765
				'name' => $tables[$key]['table_name'],
766
				'data_freed' => $data_freed,
767
			);
768
	}
769
770
	// Number of tables, etc...
771
	$txt['database_numb_tables'] = sprintf($txt['database_numb_tables'], $context['num_tables']);
772
	$context['num_tables_optimized'] = count($_SESSION['optimized_tables']);
773
	$context['optimized_tables'] = $_SESSION['optimized_tables'];
774
	unset($_SESSION['optimized_tables']);
775
}
776
777
/**
778
 * Recount many forum totals that can be recounted automatically without harm.
779
 * it requires the admin_forum permission.
780
 * It shows the maintain_forum admin area.
781
 *
782
 * Totals recounted:
783
 * - fixes for topics with wrong num_replies.
784
 * - updates for num_posts and num_topics of all boards.
785
 * - recounts instant_messages but not unread_messages.
786
 * - repairs messages pointing to boards with topics pointing to other boards.
787
 * - updates the last message posted in boards and children.
788
 * - updates member count, latest member, topic count, and message count.
789
 *
790
 * The function redirects back to ?action=admin;area=maintain when complete.
791
 * It is accessed via ?action=admin;area=maintain;sa=database;activity=recount.
792
 */
793
function AdminBoardRecount()
794
{
795
	global $txt, $context, $modSettings, $sourcedir;
796
	global $time_start, $smcFunc;
797
798
	isAllowedTo('admin_forum');
799
	checkSession('request');
800
801
	// validate the request or the loop
802
	if (!isset($_REQUEST['step']))
803
		validateToken('admin-maint');
804
	else
805
		validateToken('admin-boardrecount');
806
807
	$context['page_title'] = $txt['not_done_title'];
808
	$context['continue_post_data'] = '';
809
	$context['continue_countdown'] = 3;
810
	$context['sub_template'] = 'not_done';
811
812
	// Try for as much time as possible.
813
	@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...
814
815
	// Step the number of topics at a time so things don't time out...
816
	$request = $smcFunc['db_query']('', '
817
		SELECT MAX(id_topic)
818
		FROM {db_prefix}topics',
819
		array(
820
		)
821
	);
822
	list ($max_topics) = $smcFunc['db_fetch_row']($request);
823
	$smcFunc['db_free_result']($request);
824
825
	$increment = min(max(50, ceil($max_topics / 4)), 2000);
826
	if (empty($_REQUEST['start']))
827
		$_REQUEST['start'] = 0;
828
829
	$total_steps = 8;
830
831
	// Get each topic with a wrong reply count and fix it - let's just do some at a time, though.
832
	if (empty($_REQUEST['step']))
833
	{
834
		$_REQUEST['step'] = 0;
835
836
		while ($_REQUEST['start'] < $max_topics)
837
		{
838
			// Recount approved messages
839
			$request = $smcFunc['db_query']('', '
840
				SELECT /*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.num_replies) AS num_replies,
841
					CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END AS real_num_replies
842
				FROM {db_prefix}topics AS t
843
					LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = {int:is_approved})
844
				WHERE t.id_topic > {int:start}
845
					AND t.id_topic <= {int:max_id}
846
				GROUP BY t.id_topic
847
				HAVING CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END != MAX(t.num_replies)',
848
				array(
849
					'is_approved' => 1,
850
					'start' => $_REQUEST['start'],
851
					'max_id' => $_REQUEST['start'] + $increment,
852
				)
853
			);
854
			while ($row = $smcFunc['db_fetch_assoc']($request))
855
				$smcFunc['db_query']('', '
856
					UPDATE {db_prefix}topics
857
					SET num_replies = {int:num_replies}
858
					WHERE id_topic = {int:id_topic}',
859
					array(
860
						'num_replies' => $row['real_num_replies'],
861
						'id_topic' => $row['id_topic'],
862
					)
863
				);
864
			$smcFunc['db_free_result']($request);
865
866
			// Recount unapproved messages
867
			$request = $smcFunc['db_query']('', '
868
				SELECT /*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.unapproved_posts) AS unapproved_posts,
869
					COUNT(mu.id_msg) AS real_unapproved_posts
870
				FROM {db_prefix}topics AS t
871
					LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = {int:not_approved})
872
				WHERE t.id_topic > {int:start}
873
					AND t.id_topic <= {int:max_id}
874
				GROUP BY t.id_topic
875
				HAVING COUNT(mu.id_msg) != MAX(t.unapproved_posts)',
876
				array(
877
					'not_approved' => 0,
878
					'start' => $_REQUEST['start'],
879
					'max_id' => $_REQUEST['start'] + $increment,
880
				)
881
			);
882
			while ($row = $smcFunc['db_fetch_assoc']($request))
883
				$smcFunc['db_query']('', '
884
					UPDATE {db_prefix}topics
885
					SET unapproved_posts = {int:unapproved_posts}
886
					WHERE id_topic = {int:id_topic}',
887
					array(
888
						'unapproved_posts' => $row['real_unapproved_posts'],
889
						'id_topic' => $row['id_topic'],
890
					)
891
				);
892
			$smcFunc['db_free_result']($request);
893
894
			$_REQUEST['start'] += $increment;
895
896
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
897
			{
898
				createToken('admin-boardrecount');
899
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
900
901
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=0;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
902
				$context['continue_percent'] = round((100 * $_REQUEST['start'] / $max_topics) / $total_steps);
903
904
				return;
905
			}
906
		}
907
908
		$_REQUEST['start'] = 0;
909
	}
910
911
	// Update the post count of each board.
912 View Code Duplication
	if ($_REQUEST['step'] <= 1)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
913
	{
914
		if (empty($_REQUEST['start']))
915
			$smcFunc['db_query']('', '
916
				UPDATE {db_prefix}boards
917
				SET num_posts = {int:num_posts}
918
				WHERE redirect = {string:redirect}',
919
				array(
920
					'num_posts' => 0,
921
					'redirect' => '',
922
				)
923
			);
924
925
		while ($_REQUEST['start'] < $max_topics)
926
		{
927
			$request = $smcFunc['db_query']('', '
928
				SELECT /*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_num_posts
929
				FROM {db_prefix}messages AS m
930
				WHERE m.id_topic > {int:id_topic_min}
931
					AND m.id_topic <= {int:id_topic_max}
932
					AND m.approved = {int:is_approved}
933
				GROUP BY m.id_board',
934
				array(
935
					'id_topic_min' => $_REQUEST['start'],
936
					'id_topic_max' => $_REQUEST['start'] + $increment,
937
					'is_approved' => 1,
938
				)
939
			);
940
			while ($row = $smcFunc['db_fetch_assoc']($request))
941
				$smcFunc['db_query']('', '
942
					UPDATE {db_prefix}boards
943
					SET num_posts = num_posts + {int:real_num_posts}
944
					WHERE id_board = {int:id_board}',
945
					array(
946
						'id_board' => $row['id_board'],
947
						'real_num_posts' => $row['real_num_posts'],
948
					)
949
				);
950
			$smcFunc['db_free_result']($request);
951
952
			$_REQUEST['start'] += $increment;
953
954
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
955
			{
956
				createToken('admin-boardrecount');
957
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
958
959
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=1;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
960
				$context['continue_percent'] = round((200 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
961
962
				return;
963
			}
964
		}
965
966
		$_REQUEST['start'] = 0;
967
	}
968
969
	// Update the topic count of each board.
970 View Code Duplication
	if ($_REQUEST['step'] <= 2)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
971
	{
972
		if (empty($_REQUEST['start']))
973
			$smcFunc['db_query']('', '
974
				UPDATE {db_prefix}boards
975
				SET num_topics = {int:num_topics}',
976
				array(
977
					'num_topics' => 0,
978
				)
979
			);
980
981
		while ($_REQUEST['start'] < $max_topics)
982
		{
983
			$request = $smcFunc['db_query']('', '
984
				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_num_topics
985
				FROM {db_prefix}topics AS t
986
				WHERE t.approved = {int:is_approved}
987
					AND t.id_topic > {int:id_topic_min}
988
					AND t.id_topic <= {int:id_topic_max}
989
				GROUP BY t.id_board',
990
				array(
991
					'is_approved' => 1,
992
					'id_topic_min' => $_REQUEST['start'],
993
					'id_topic_max' => $_REQUEST['start'] + $increment,
994
				)
995
			);
996
			while ($row = $smcFunc['db_fetch_assoc']($request))
997
				$smcFunc['db_query']('', '
998
					UPDATE {db_prefix}boards
999
					SET num_topics = num_topics + {int:real_num_topics}
1000
					WHERE id_board = {int:id_board}',
1001
					array(
1002
						'id_board' => $row['id_board'],
1003
						'real_num_topics' => $row['real_num_topics'],
1004
					)
1005
				);
1006
			$smcFunc['db_free_result']($request);
1007
1008
			$_REQUEST['start'] += $increment;
1009
1010
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1011
			{
1012
				createToken('admin-boardrecount');
1013
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1014
1015
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=2;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1016
				$context['continue_percent'] = round((300 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1017
1018
				return;
1019
			}
1020
		}
1021
1022
		$_REQUEST['start'] = 0;
1023
	}
1024
1025
	// Update the unapproved post count of each board.
1026 View Code Duplication
	if ($_REQUEST['step'] <= 3)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1027
	{
1028
		if (empty($_REQUEST['start']))
1029
			$smcFunc['db_query']('', '
1030
				UPDATE {db_prefix}boards
1031
				SET unapproved_posts = {int:unapproved_posts}',
1032
				array(
1033
					'unapproved_posts' => 0,
1034
				)
1035
			);
1036
1037
		while ($_REQUEST['start'] < $max_topics)
1038
		{
1039
			$request = $smcFunc['db_query']('', '
1040
				SELECT /*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_unapproved_posts
1041
				FROM {db_prefix}messages AS m
1042
				WHERE m.id_topic > {int:id_topic_min}
1043
					AND m.id_topic <= {int:id_topic_max}
1044
					AND m.approved = {int:is_approved}
1045
				GROUP BY m.id_board',
1046
				array(
1047
					'id_topic_min' => $_REQUEST['start'],
1048
					'id_topic_max' => $_REQUEST['start'] + $increment,
1049
					'is_approved' => 0,
1050
				)
1051
			);
1052
			while ($row = $smcFunc['db_fetch_assoc']($request))
1053
				$smcFunc['db_query']('', '
1054
					UPDATE {db_prefix}boards
1055
					SET unapproved_posts = unapproved_posts + {int:unapproved_posts}
1056
					WHERE id_board = {int:id_board}',
1057
					array(
1058
						'id_board' => $row['id_board'],
1059
						'unapproved_posts' => $row['real_unapproved_posts'],
1060
					)
1061
				);
1062
			$smcFunc['db_free_result']($request);
1063
1064
			$_REQUEST['start'] += $increment;
1065
1066
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1067
			{
1068
				createToken('admin-boardrecount');
1069
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1070
1071
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=3;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1072
				$context['continue_percent'] = round((400 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1073
1074
				return;
1075
			}
1076
		}
1077
1078
		$_REQUEST['start'] = 0;
1079
	}
1080
1081
	// Update the unapproved topic count of each board.
1082 View Code Duplication
	if ($_REQUEST['step'] <= 4)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1083
	{
1084
		if (empty($_REQUEST['start']))
1085
			$smcFunc['db_query']('', '
1086
				UPDATE {db_prefix}boards
1087
				SET unapproved_topics = {int:unapproved_topics}',
1088
				array(
1089
					'unapproved_topics' => 0,
1090
				)
1091
			);
1092
1093
		while ($_REQUEST['start'] < $max_topics)
1094
		{
1095
			$request = $smcFunc['db_query']('', '
1096
				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_unapproved_topics
1097
				FROM {db_prefix}topics AS t
1098
				WHERE t.approved = {int:is_approved}
1099
					AND t.id_topic > {int:id_topic_min}
1100
					AND t.id_topic <= {int:id_topic_max}
1101
				GROUP BY t.id_board',
1102
				array(
1103
					'is_approved' => 0,
1104
					'id_topic_min' => $_REQUEST['start'],
1105
					'id_topic_max' => $_REQUEST['start'] + $increment,
1106
				)
1107
			);
1108
			while ($row = $smcFunc['db_fetch_assoc']($request))
1109
				$smcFunc['db_query']('', '
1110
					UPDATE {db_prefix}boards
1111
					SET unapproved_topics = unapproved_topics + {int:real_unapproved_topics}
1112
					WHERE id_board = {int:id_board}',
1113
					array(
1114
						'id_board' => $row['id_board'],
1115
						'real_unapproved_topics' => $row['real_unapproved_topics'],
1116
					)
1117
				);
1118
			$smcFunc['db_free_result']($request);
1119
1120
			$_REQUEST['start'] += $increment;
1121
1122
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1123
			{
1124
				createToken('admin-boardrecount');
1125
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1126
1127
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=4;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1128
				$context['continue_percent'] = round((500 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1129
1130
				return;
1131
			}
1132
		}
1133
1134
		$_REQUEST['start'] = 0;
1135
	}
1136
1137
	// Get all members with wrong number of personal messages.
1138
	if ($_REQUEST['step'] <= 5)
1139
	{
1140
		$request = $smcFunc['db_query']('', '
1141
			SELECT /*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num,
1142
				MAX(mem.instant_messages) AS instant_messages
1143
			FROM {db_prefix}members AS mem
1144
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (mem.id_member = pmr.id_member AND pmr.deleted = {int:is_not_deleted})
1145
			GROUP BY mem.id_member
1146
			HAVING COUNT(pmr.id_pm) != MAX(mem.instant_messages)',
1147
			array(
1148
				'is_not_deleted' => 0,
1149
			)
1150
		);
1151 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1152
			updateMemberData($row['id_member'], array('instant_messages' => $row['real_num']));
1153
		$smcFunc['db_free_result']($request);
1154
1155
		$request = $smcFunc['db_query']('', '
1156
			SELECT /*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num,
1157
				MAX(mem.unread_messages) AS unread_messages
1158
			FROM {db_prefix}members AS mem
1159
				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})
1160
			GROUP BY mem.id_member
1161
			HAVING COUNT(pmr.id_pm) != MAX(mem.unread_messages)',
1162
			array(
1163
				'is_not_deleted' => 0,
1164
				'is_not_read' => 0,
1165
			)
1166
		);
1167 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1168
			updateMemberData($row['id_member'], array('unread_messages' => $row['real_num']));
1169
		$smcFunc['db_free_result']($request);
1170
1171
		if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1172
		{
1173
			createToken('admin-boardrecount');
1174
			$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1175
1176
			$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=6;start=0;' . $context['session_var'] . '=' . $context['session_id'];
1177
			$context['continue_percent'] = round(700 / $total_steps);
1178
1179
			return;
1180
		}
1181
	}
1182
1183
	// Any messages pointing to the wrong board?
1184
	if ($_REQUEST['step'] <= 6)
1185
	{
1186
		while ($_REQUEST['start'] < $modSettings['maxMsgID'])
1187
		{
1188
			$request = $smcFunc['db_query']('', '
1189
				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, m.id_msg
1190
				FROM {db_prefix}messages AS m
1191
					INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic AND t.id_board != m.id_board)
1192
				WHERE m.id_msg > {int:id_msg_min}
1193
					AND m.id_msg <= {int:id_msg_max}',
1194
				array(
1195
					'id_msg_min' => $_REQUEST['start'],
1196
					'id_msg_max' => $_REQUEST['start'] + $increment,
1197
				)
1198
			);
1199
			$boards = array();
1200
			while ($row = $smcFunc['db_fetch_assoc']($request))
1201
				$boards[$row['id_board']][] = $row['id_msg'];
1202
			$smcFunc['db_free_result']($request);
1203
1204
			foreach ($boards as $board_id => $messages)
1205
				$smcFunc['db_query']('', '
1206
					UPDATE {db_prefix}messages
1207
					SET id_board = {int:id_board}
1208
					WHERE id_msg IN ({array_int:id_msg_array})',
1209
					array(
1210
						'id_msg_array' => $messages,
1211
						'id_board' => $board_id,
1212
					)
1213
				);
1214
1215
			$_REQUEST['start'] += $increment;
1216
1217
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1218
			{
1219
				createToken('admin-boardrecount');
1220
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1221
1222
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=6;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1223
				$context['continue_percent'] = round((700 + 100 * $_REQUEST['start'] / $modSettings['maxMsgID']) / $total_steps);
1224
1225
				return;
1226
			}
1227
		}
1228
1229
		$_REQUEST['start'] = 0;
1230
	}
1231
1232
	// Update the latest message of each board.
1233
	$request = $smcFunc['db_query']('', '
1234
		SELECT m.id_board, MAX(m.id_msg) AS local_last_msg
1235
		FROM {db_prefix}messages AS m
1236
		WHERE m.approved = {int:is_approved}
1237
		GROUP BY m.id_board',
1238
		array(
1239
			'is_approved' => 1,
1240
		)
1241
	);
1242
	$realBoardCounts = array();
1243
	while ($row = $smcFunc['db_fetch_assoc']($request))
1244
		$realBoardCounts[$row['id_board']] = $row['local_last_msg'];
1245
	$smcFunc['db_free_result']($request);
1246
1247
	$request = $smcFunc['db_query']('', '
1248
		SELECT /*!40001 SQL_NO_CACHE */ id_board, id_parent, id_last_msg, child_level, id_msg_updated
1249
		FROM {db_prefix}boards',
1250
		array(
1251
		)
1252
	);
1253
	$resort_me = array();
1254
	while ($row = $smcFunc['db_fetch_assoc']($request))
1255
	{
1256
		$row['local_last_msg'] = isset($realBoardCounts[$row['id_board']]) ? $realBoardCounts[$row['id_board']] : 0;
1257
		$resort_me[$row['child_level']][] = $row;
1258
	}
1259
	$smcFunc['db_free_result']($request);
1260
1261
	krsort($resort_me);
1262
1263
	$lastModifiedMsg = array();
1264
	foreach ($resort_me as $rows)
1265
		foreach ($rows as $row)
1266
		{
1267
			// The latest message is the latest of the current board and its children.
1268
			if (isset($lastModifiedMsg[$row['id_board']]))
1269
				$curLastModifiedMsg = max($row['local_last_msg'], $lastModifiedMsg[$row['id_board']]);
1270
			else
1271
				$curLastModifiedMsg = $row['local_last_msg'];
1272
1273
			// If what is and what should be the latest message differ, an update is necessary.
1274
			if ($row['local_last_msg'] != $row['id_last_msg'] || $curLastModifiedMsg != $row['id_msg_updated'])
1275
				$smcFunc['db_query']('', '
1276
					UPDATE {db_prefix}boards
1277
					SET id_last_msg = {int:id_last_msg}, id_msg_updated = {int:id_msg_updated}
1278
					WHERE id_board = {int:id_board}',
1279
					array(
1280
						'id_last_msg' => $row['local_last_msg'],
1281
						'id_msg_updated' => $curLastModifiedMsg,
1282
						'id_board' => $row['id_board'],
1283
					)
1284
				);
1285
1286
			// Parent boards inherit the latest modified message of their children.
1287
			if (isset($lastModifiedMsg[$row['id_parent']]))
1288
				$lastModifiedMsg[$row['id_parent']] = max($row['local_last_msg'], $lastModifiedMsg[$row['id_parent']]);
1289
			else
1290
				$lastModifiedMsg[$row['id_parent']] = $row['local_last_msg'];
1291
		}
1292
1293
	// Update all the basic statistics.
1294
	updateStats('member');
1295
	updateStats('message');
1296
	updateStats('topic');
1297
1298
	// Finally, update the latest event times.
1299
	require_once($sourcedir . '/ScheduledTasks.php');
1300
	CalculateNextTrigger();
1301
1302
	redirectexit('action=admin;area=maintain;sa=routine;done=recount');
1303
}
1304
1305
/**
1306
 * Perform a detailed version check.  A very good thing ;).
1307
 * The function parses the comment headers in all files for their version information,
1308
 * and outputs that for some javascript to check with simplemachines.org.
1309
 * It does not connect directly with simplemachines.org, but rather expects the client to.
1310
 *
1311
 * It requires the admin_forum permission.
1312
 * Uses the view_versions admin area.
1313
 * Accessed through ?action=admin;area=maintain;sa=routine;activity=version.
1314
 * @uses Admin template, view_versions sub-template.
1315
 */
1316
function VersionDetail()
1317
{
1318
	global $forum_version, $txt, $sourcedir, $context;
1319
1320
	isAllowedTo('admin_forum');
1321
1322
	// Call the function that'll get all the version info we need.
1323
	require_once($sourcedir . '/Subs-Admin.php');
1324
	$versionOptions = array(
1325
		'include_ssi' => true,
1326
		'include_subscriptions' => true,
1327
		'include_tasks' => true,
1328
		'sort_results' => true,
1329
	);
1330
	$version_info = getFileVersions($versionOptions);
1331
1332
	// Add the new info to the template context.
1333
	$context += array(
1334
		'file_versions' => $version_info['file_versions'],
1335
		'default_template_versions' => $version_info['default_template_versions'],
1336
		'template_versions' => $version_info['template_versions'],
1337
		'default_language_versions' => $version_info['default_language_versions'],
1338
		'default_known_languages' => array_keys($version_info['default_language_versions']),
1339
		'tasks_versions' => $version_info['tasks_versions'],
1340
	);
1341
1342
	// Make it easier to manage for the template.
1343
	$context['forum_version'] = $forum_version;
1344
1345
	$context['sub_template'] = 'view_versions';
1346
	$context['page_title'] = $txt['admin_version_check'];
1347
}
1348
1349
/**
1350
 * Re-attribute posts.
1351
 */
1352
function MaintainReattributePosts()
1353
{
1354
	global $sourcedir, $context, $txt;
1355
1356
	checkSession();
1357
1358
	// Find the member.
1359
	require_once($sourcedir . '/Subs-Auth.php');
1360
	$members = findMembers($_POST['to']);
1361
1362
	if (empty($members))
1363
		fatal_lang_error('reattribute_cannot_find_member');
1364
1365
	$memID = array_shift($members);
1366
	$memID = $memID['id'];
1367
1368
	$email = $_POST['type'] == 'email' ? $_POST['from_email'] : '';
1369
	$membername = $_POST['type'] == 'name' ? $_POST['from_name'] : '';
1370
1371
	// Now call the reattribute function.
1372
	require_once($sourcedir . '/Subs-Members.php');
1373
	reattributePosts($memID, $email, $membername, !empty($_POST['posts']));
1374
1375
	$context['maintenance_finished'] = $txt['maintain_reattribute_posts'];
1376
}
1377
1378
/**
1379
 * Removing old members. Done and out!
1380
 * @todo refactor
1381
 */
1382
function MaintainPurgeInactiveMembers()
1383
{
1384
	global $sourcedir, $context, $smcFunc, $txt;
1385
1386
	$_POST['maxdays'] = empty($_POST['maxdays']) ? 0 : (int) $_POST['maxdays'];
1387
	if (!empty($_POST['groups']) && $_POST['maxdays'] > 0)
1388
	{
1389
		checkSession();
1390
		validateToken('admin-maint');
1391
1392
		$groups = array();
1393
		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...
1394
			$groups[] = (int) $id;
1395
		$time_limit = (time() - ($_POST['maxdays'] * 24 * 3600));
1396
		$where_vars = array(
1397
			'time_limit' => $time_limit,
1398
		);
1399
		if ($_POST['del_type'] == 'activated')
1400
		{
1401
			$where = 'mem.date_registered < {int:time_limit} AND mem.is_activated = {int:is_activated}';
1402
			$where_vars['is_activated'] = 0;
1403
		}
1404
		else
1405
			$where = 'mem.last_login < {int:time_limit} AND (mem.last_login != 0 OR mem.date_registered < {int:time_limit})';
1406
1407
		// Need to get *all* groups then work out which (if any) we avoid.
1408
		$request = $smcFunc['db_query']('', '
1409
			SELECT id_group, group_name, min_posts
1410
			FROM {db_prefix}membergroups',
1411
			array(
1412
			)
1413
		);
1414
		while ($row = $smcFunc['db_fetch_assoc']($request))
1415
		{
1416
			// Avoid this one?
1417
			if (!in_array($row['id_group'], $groups))
1418
			{
1419
				// Post group?
1420
				if ($row['min_posts'] != -1)
1421
				{
1422
					$where .= ' AND mem.id_post_group != {int:id_post_group_' . $row['id_group'] . '}';
1423
					$where_vars['id_post_group_' . $row['id_group']] = $row['id_group'];
1424
				}
1425
				else
1426
				{
1427
					$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';
1428
					$where_vars['id_group_' . $row['id_group']] = $row['id_group'];
1429
				}
1430
			}
1431
		}
1432
		$smcFunc['db_free_result']($request);
1433
1434
		// If we have ungrouped unselected we need to avoid those guys.
1435
		if (!in_array(0, $groups))
1436
		{
1437
			$where .= ' AND (mem.id_group != 0 OR mem.additional_groups != {string:blank_add_groups})';
1438
			$where_vars['blank_add_groups'] = '';
1439
		}
1440
1441
		// Select all the members we're about to murder/remove...
1442
		$request = $smcFunc['db_query']('', '
1443
			SELECT mem.id_member, COALESCE(m.id_member, 0) AS is_mod
1444
			FROM {db_prefix}members AS mem
1445
				LEFT JOIN {db_prefix}moderators AS m ON (m.id_member = mem.id_member)
1446
			WHERE ' . $where,
1447
			$where_vars
1448
		);
1449
		$members = array();
1450
		while ($row = $smcFunc['db_fetch_assoc']($request))
1451
		{
1452
			if (!$row['is_mod'] || !in_array(3, $groups))
1453
				$members[] = $row['id_member'];
1454
		}
1455
		$smcFunc['db_free_result']($request);
1456
1457
		require_once($sourcedir . '/Subs-Members.php');
1458
		deleteMembers($members);
1459
	}
1460
1461
	$context['maintenance_finished'] = $txt['maintain_members'];
1462
	createToken('admin-maint');
1463
}
1464
1465
/**
1466
 * Removing old posts doesn't take much as we really pass through.
1467
 */
1468
function MaintainRemoveOldPosts()
1469
{
1470
	global $sourcedir;
1471
1472
	validateToken('admin-maint');
1473
1474
	// Actually do what we're told!
1475
	require_once($sourcedir . '/RemoveTopic.php');
1476
	RemoveOldTopics2();
1477
}
1478
1479
/**
1480
 * Removing old drafts
1481
 */
1482
function MaintainRemoveOldDrafts()
1483
{
1484
	global $sourcedir, $smcFunc;
1485
1486
	validateToken('admin-maint');
1487
1488
	$drafts = array();
1489
1490
	// Find all of the old drafts
1491
	$request = $smcFunc['db_query']('', '
1492
		SELECT id_draft
1493
		FROM {db_prefix}user_drafts
1494
		WHERE poster_time <= {int:poster_time_old}',
1495
		array(
1496
			'poster_time_old' => time() - (86400 * $_POST['draftdays']),
1497
		)
1498
	);
1499
1500
	while ($row = $smcFunc['db_fetch_row']($request))
1501
		$drafts[] = (int) $row[0];
1502
	$smcFunc['db_free_result']($request);
1503
1504
	// If we have old drafts, remove them
1505
	if (count($drafts) > 0)
1506
	{
1507
		require_once($sourcedir . '/Drafts.php');
1508
		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...
1509
	}
1510
}
1511
1512
/**
1513
 * Moves topics from one board to another.
1514
 *
1515
 * @uses not_done template to pause the process.
1516
 */
1517
function MaintainMassMoveTopics()
1518
{
1519
	global $smcFunc, $sourcedir, $context, $txt;
1520
1521
	// Only admins.
1522
	isAllowedTo('admin_forum');
1523
1524
	checkSession('request');
1525
	validateToken('admin-maint');
1526
1527
	// Set up to the context.
1528
	$context['page_title'] = $txt['not_done_title'];
1529
	$context['continue_countdown'] = 3;
1530
	$context['continue_post_data'] = '';
1531
	$context['continue_get_data'] = '';
1532
	$context['sub_template'] = 'not_done';
1533
	$context['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
1534
	$context['start_time'] = time();
1535
1536
	// First time we do this?
1537
	$id_board_from = isset($_REQUEST['id_board_from']) ? (int) $_REQUEST['id_board_from'] : 0;
1538
	$id_board_to = isset($_REQUEST['id_board_to']) ? (int) $_REQUEST['id_board_to'] : 0;
1539
	$max_days = isset($_REQUEST['maxdays']) ? (int) $_REQUEST['maxdays'] : 0;
1540
	$locked = isset($_POST['move_type_locked']) || isset($_GET['locked']);
1541
	$sticky = isset($_POST['move_type_sticky']) || isset($_GET['sticky']);
1542
1543
	// No boards then this is your stop.
1544
	if (empty($id_board_from) || empty($id_board_to))
1545
		return;
1546
1547
	// The big WHERE clause
1548
	$conditions = 'WHERE t.id_board = {int:id_board_from}
1549
		AND m.icon != {string:moved}';
1550
1551
	// DB parameters
1552
	$params = array(
1553
		'id_board_from' => $id_board_from,
1554
		'moved' => 'moved',
1555
	);
1556
1557
	// Only moving topics not posted in for x days?
1558
	if (!empty($max_days))
1559
	{
1560
		$conditions .= '
1561
			AND m.poster_time < {int:poster_time}';
1562
		$params['poster_time'] = time() - 3600 * 24 * $max_days;
1563
	}
1564
1565
	// Moving locked topics?
1566
	if ($locked)
1567
	{
1568
		$conditions .= '
1569
			AND t.locked = {int:locked}';
1570
		$params['locked'] = 1;
1571
	}
1572
1573
	// What about sticky topics?
1574
	if ($sticky)
1575
	{
1576
		$conditions .= '
1577
			AND t.is_sticky = {int:sticky}';
1578
		$params['sticky'] = 1;
1579
	}
1580
1581
	// How many topics are we converting?
1582
	if (!isset($_REQUEST['totaltopics']))
1583
	{
1584
		$request = $smcFunc['db_query']('', '
1585
			SELECT COUNT(*)
1586
			FROM {db_prefix}topics AS t
1587
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_last_msg)' .
1588
			$conditions,
1589
			$params
1590
		);
1591
		list ($total_topics) = $smcFunc['db_fetch_row']($request);
1592
		$smcFunc['db_free_result']($request);
1593
	}
1594
	else
1595
		$total_topics = (int) $_REQUEST['totaltopics'];
1596
1597
	// Seems like we need this here.
1598
	$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;
1599
1600
	if ($locked)
1601
		$context['continue_get_data'] .= ';locked';
1602
1603
	if ($sticky)
1604
		$context['continue_get_data'] .= ';sticky';
1605
1606
	$context['continue_get_data'] .= ';start=' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1607
1608
	// We have topics to move so start the process.
1609
	if (!empty($total_topics))
1610
	{
1611
		while ($context['start'] <= $total_topics)
1612
		{
1613
			// Lets get the topics.
1614
			$request = $smcFunc['db_query']('', '
1615
				SELECT t.id_topic
1616
				FROM {db_prefix}topics AS t
1617
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_last_msg)
1618
				' . $conditions . '
1619
				LIMIT 10',
1620
				$params
1621
			);
1622
1623
			// Get the ids.
1624
			$topics = array();
1625
			while ($row = $smcFunc['db_fetch_assoc']($request))
1626
				$topics[] = $row['id_topic'];
1627
1628
			// Just return if we don't have any topics left to move.
1629
			if (empty($topics))
1630
			{
1631
				cache_put_data('board-' . $id_board_from, null, 120);
1632
				cache_put_data('board-' . $id_board_to, null, 120);
1633
				redirectexit('action=admin;area=maintain;sa=topics;done=massmove');
1634
			}
1635
1636
			// Lets move them.
1637
			require_once($sourcedir . '/MoveTopic.php');
1638
			moveTopics($topics, $id_board_to);
1639
1640
			// We've done at least ten more topics.
1641
			$context['start'] += 10;
1642
1643
			// Lets wait a while.
1644
			if (time() - $context['start_time'] > 3)
1645
			{
1646
				// What's the percent?
1647
				$context['continue_percent'] = round(100 * ($context['start'] / $total_topics), 1);
1648
				$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'];
1649
1650
				// Let the template system do it's thang.
1651
				return;
1652
			}
1653
		}
1654
	}
1655
1656
	// Don't confuse admins by having an out of date cache.
1657
	cache_put_data('board-' . $id_board_from, null, 120);
1658
	cache_put_data('board-' . $id_board_to, null, 120);
1659
1660
	redirectexit('action=admin;area=maintain;sa=topics;done=massmove');
1661
}
1662
1663
/**
1664
 * Recalculate all members post counts
1665
 * it requires the admin_forum permission.
1666
 *
1667
 * - recounts all posts for members found in the message table
1668
 * - updates the members post count record in the members table
1669
 * - honors the boards post count flag
1670
 * - does not count posts in the recycle bin
1671
 * - zeros post counts for all members with no posts in the message table
1672
 * - runs as a delayed loop to avoid server overload
1673
 * - uses the not_done template in Admin.template
1674
 *
1675
 * The function redirects back to action=admin;area=maintain;sa=members when complete.
1676
 * It is accessed via ?action=admin;area=maintain;sa=members;activity=recountposts
1677
 */
1678
function MaintainRecountPosts()
1679
{
1680
	global $txt, $context, $modSettings, $smcFunc;
1681
1682
	// You have to be allowed in here
1683
	isAllowedTo('admin_forum');
1684
	checkSession('request');
1685
1686
	// Set up to the context.
1687
	$context['page_title'] = $txt['not_done_title'];
1688
	$context['continue_countdown'] = 3;
1689
	$context['continue_get_data'] = '';
1690
	$context['sub_template'] = 'not_done';
1691
1692
	// init
1693
	$increment = 200;
1694
	$_REQUEST['start'] = !isset($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
1695
1696
	// Ask for some extra time, on big boards this may take a bit
1697
	@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...
1698
1699
	// Only run this query if we don't have the total number of members that have posted
1700
	if (!isset($_SESSION['total_members']))
1701
	{
1702
		validateToken('admin-maint');
1703
1704
		$request = $smcFunc['db_query']('', '
1705
			SELECT COUNT(DISTINCT m.id_member)
1706
			FROM {db_prefix}messages AS m
1707
			JOIN {db_prefix}boards AS b on m.id_board = b.id_board
1708
			WHERE m.id_member != 0
1709
				AND b.count_posts = 0',
1710
			array(
1711
			)
1712
		);
1713
1714
		// save it so we don't do this again for this task
1715
		list ($_SESSION['total_members']) = $smcFunc['db_fetch_row']($request);
1716
		$smcFunc['db_free_result']($request);
1717
	}
1718
	else
1719
		validateToken('admin-recountposts');
1720
1721
	// Lets get a group of members and determine their post count (from the boards that have post count enabled of course).
1722
	$request = $smcFunc['db_query']('', '
1723
		SELECT /*!40001 SQL_NO_CACHE */ m.id_member, COUNT(m.id_member) AS posts
1724
		FROM {db_prefix}messages AS m
1725
			INNER JOIN {db_prefix}boards AS b ON m.id_board = b.id_board
1726
		WHERE m.id_member != {int:zero}
1727
			AND b.count_posts = {int:zero}
1728
			' . (!empty($modSettings['recycle_enable']) ? ' AND b.id_board != {int:recycle}' : '') . '
1729
		GROUP BY m.id_member
1730
		LIMIT {int:start}, {int:number}',
1731
		array(
1732
			'start' => $_REQUEST['start'],
1733
			'number' => $increment,
1734
			'recycle' => $modSettings['recycle_board'],
1735
			'zero' => 0,
1736
		)
1737
	);
1738
	$total_rows = $smcFunc['db_num_rows']($request);
1739
1740
	// Update the post count for this group
1741
	while ($row = $smcFunc['db_fetch_assoc']($request))
1742
	{
1743
		$smcFunc['db_query']('', '
1744
			UPDATE {db_prefix}members
1745
			SET posts = {int:posts}
1746
			WHERE id_member = {int:row}',
1747
			array(
1748
				'row' => $row['id_member'],
1749
				'posts' => $row['posts'],
1750
			)
1751
		);
1752
	}
1753
	$smcFunc['db_free_result']($request);
1754
1755
	// Continue?
1756
	if ($total_rows == $increment)
1757
	{
1758
		$_REQUEST['start'] += $increment;
1759
		$context['continue_get_data'] = '?action=admin;area=maintain;sa=members;activity=recountposts;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1760
		$context['continue_percent'] = round(100 * $_REQUEST['start'] / $_SESSION['total_members']);
1761
1762
		createToken('admin-recountposts');
1763
		$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-recountposts_token_var'] . '" value="' . $context['admin-recountposts_token'] . '">';
1764
1765
		if (function_exists('apache_reset_timeout'))
1766
			apache_reset_timeout();
1767
		return;
1768
	}
1769
1770
	// final steps ... made more difficult since we don't yet support sub-selects on joins
1771
	// place all members who have posts in the message table in a temp table
1772
	$createTemporary = $smcFunc['db_query']('', '
1773
		CREATE TEMPORARY TABLE {db_prefix}tmp_maint_recountposts (
1774
			id_member mediumint(8) unsigned NOT NULL default {string:string_zero},
1775
			PRIMARY KEY (id_member)
1776
		)
1777
		SELECT m.id_member
1778
		FROM {db_prefix}messages AS m
1779
			INNER JOIN {db_prefix}boards AS b ON m.id_board = b.id_board
1780
		WHERE m.id_member != {int:zero}
1781
			AND b.count_posts = {int:zero}
1782
			' . (!empty($modSettings['recycle_enable']) ? ' AND b.id_board != {int:recycle}' : '') . '
1783
		GROUP BY m.id_member',
1784
		array(
1785
			'zero' => 0,
1786
			'string_zero' => '0',
1787
			'db_error_skip' => true,
1788
			'recycle' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
1789
		)
1790
	) !== false;
1791
1792 View Code Duplication
	if ($createTemporary)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1793
	{
1794
		// outer join the members table on the temporary table finding the members that have a post count but no posts in the message table
1795
		$request = $smcFunc['db_query']('', '
1796
			SELECT mem.id_member, mem.posts
1797
			FROM {db_prefix}members AS mem
1798
			LEFT OUTER JOIN {db_prefix}tmp_maint_recountposts AS res
1799
			ON res.id_member = mem.id_member
1800
			WHERE res.id_member IS null
1801
				AND mem.posts != {int:zero}',
1802
			array(
1803
				'zero' => 0,
1804
			)
1805
		);
1806
1807
		// set the post count to zero for any delinquents we may have found
1808
		while ($row = $smcFunc['db_fetch_assoc']($request))
1809
		{
1810
			$smcFunc['db_query']('', '
1811
				UPDATE {db_prefix}members
1812
				SET posts = {int:zero}
1813
				WHERE id_member = {int:row}',
1814
				array(
1815
					'row' => $row['id_member'],
1816
					'zero' => 0,
1817
				)
1818
			);
1819
		}
1820
		$smcFunc['db_free_result']($request);
1821
	}
1822
1823
	// all done
1824
	unset($_SESSION['total_members']);
1825
	$context['maintenance_finished'] = $txt['maintain_recountposts'];
1826
	redirectexit('action=admin;area=maintain;sa=members;done=recountposts');
1827
}
1828
1829
/**
1830
 * Generates a list of integration hooks for display
1831
 * Accessed through ?action=admin;area=maintain;sa=hooks;
1832
 * Allows for removal or disabling of selected hooks
1833
 */
1834
function list_integration_hooks()
1835
{
1836
	global $sourcedir, $scripturl, $context, $txt;
1837
1838
	$context['filter_url'] = '';
1839
	$context['current_filter'] = '';
1840
	$currentHooks = get_integration_hooks();
1841
	if (isset($_GET['filter']) && in_array($_GET['filter'], array_keys($currentHooks)))
1842
	{
1843
		$context['filter_url'] = ';filter=' . $_GET['filter'];
1844
		$context['current_filter'] = $_GET['filter'];
1845
	}
1846
1847
	if (!empty($_REQUEST['do']) && isset($_REQUEST['hook']) && isset($_REQUEST['function']))
1848
	{
1849
		checkSession('request');
1850
		validateToken('admin-hook', 'request');
1851
1852
		if ($_REQUEST['do'] == 'remove')
1853
			remove_integration_function($_REQUEST['hook'], urldecode($_REQUEST['function']));
1854
1855
		else
1856
		{
1857
			$function_remove = urldecode($_REQUEST['function']) . (($_REQUEST['do'] == 'disable') ? '' : '!');
1858
			$function_add = urldecode($_REQUEST['function']) . (($_REQUEST['do'] == 'disable') ? '!' : '');
1859
1860
			remove_integration_function($_REQUEST['hook'], $function_remove);
1861
			add_integration_function($_REQUEST['hook'], $function_add);
1862
1863
			redirectexit('action=admin;area=maintain;sa=hooks' . $context['filter_url']);
1864
		}
1865
	}
1866
1867
	createToken('admin-hook', 'request');
1868
1869
	$list_options = array(
1870
		'id' => 'list_integration_hooks',
1871
		'title' => $txt['hooks_title_list'],
1872
		'items_per_page' => 20,
1873
		'base_href' => $scripturl . '?action=admin;area=maintain;sa=hooks' . $context['filter_url'] . ';' . $context['session_var'] . '=' . $context['session_id'],
1874
		'default_sort_col' => 'hook_name',
1875
		'get_items' => array(
1876
			'function' => 'get_integration_hooks_data',
1877
		),
1878
		'get_count' => array(
1879
			'function' => 'get_integration_hooks_count',
1880
		),
1881
		'no_items_label' => $txt['hooks_no_hooks'],
1882
		'columns' => array(
1883
			'hook_name' => array(
1884
				'header' => array(
1885
					'value' => $txt['hooks_field_hook_name'],
1886
				),
1887
				'data' => array(
1888
					'db' => 'hook_name',
1889
				),
1890
				'sort' =>  array(
1891
					'default' => 'hook_name',
1892
					'reverse' => 'hook_name DESC',
1893
				),
1894
			),
1895
			'function_name' => array(
1896
				'header' => array(
1897
					'value' => $txt['hooks_field_function_name'],
1898
				),
1899
				'data' => array(
1900
					'function' => function($data) use ($txt)
1901
					{
1902
						// Show a nice icon to indicate this is an instance.
1903
						$instance = (!empty($data['instance']) ? '<span class="generic_icons news" title="' . $txt['hooks_field_function_method'] . '"></span> ' : '');
1904
1905
						if (!empty($data['included_file']))
1906
							return $instance . $txt['hooks_field_function'] . ': ' . $data['real_function'] . '<br>' . $txt['hooks_field_included_file'] . ': ' . $data['included_file'];
1907
1908
						else
1909
							return $instance . $data['real_function'];
1910
					},
1911
				),
1912
				'sort' =>  array(
1913
					'default' => 'function_name',
1914
					'reverse' => 'function_name DESC',
1915
				),
1916
			),
1917
			'file_name' => array(
1918
				'header' => array(
1919
					'value' => $txt['hooks_field_file_name'],
1920
				),
1921
				'data' => array(
1922
					'db' => 'file_name',
1923
				),
1924
				'sort' =>  array(
1925
					'default' => 'file_name',
1926
					'reverse' => 'file_name DESC',
1927
				),
1928
			),
1929
			'status' => array(
1930
				'header' => array(
1931
					'value' => $txt['hooks_field_hook_exists'],
1932
					'style' => 'width:3%;',
1933
				),
1934
				'data' => array(
1935
					'function' => function($data) use ($txt, $scripturl, $context)
1936
					{
1937
						$change_status = array('before' => '', 'after' => '');
1938
1939
							$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">';
1940
							$change_status['after'] = '</a>';
1941
1942
						return $change_status['before'] . '<span class="generic_icons post_moderation_' . $data['status'] . '" title="' . $data['img_text'] . '"></span>';
1943
					},
1944
					'class' => 'centertext',
1945
				),
1946
				'sort' =>  array(
1947
					'default' => 'status',
1948
					'reverse' => 'status DESC',
1949
				),
1950
			),
1951
		),
1952
		'additional_rows' => array(
1953
			array(
1954
				'position' => 'after_title',
1955
				'value' => $txt['hooks_disable_instructions'] . '<br>
1956
					' . $txt['hooks_disable_legend'] . ':
1957
				<ul style="list-style: none;">
1958
					<li><span class="generic_icons post_moderation_allow"></span> ' . $txt['hooks_disable_legend_exists'] . '</li>
1959
					<li><span class="generic_icons post_moderation_moderate"></span> ' . $txt['hooks_disable_legend_disabled'] . '</li>
1960
					<li><span class="generic_icons post_moderation_deny"></span> ' . $txt['hooks_disable_legend_missing'] . '</li>
1961
				</ul>'
1962
			),
1963
		),
1964
	);
1965
1966
	$list_options['columns']['remove'] = array(
1967
		'header' => array(
1968
			'value' => $txt['hooks_button_remove'],
1969
			'style' => 'width:3%',
1970
		),
1971
		'data' => array(
1972
			'function' => function($data) use ($txt, $scripturl, $context)
1973
			{
1974
				if (!$data['hook_exists'])
1975
					return '
1976
					<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">
1977
						<span class="generic_icons delete" title="' . $txt['hooks_button_remove'] . '"></span>
1978
					</a>';
1979
			},
1980
			'class' => 'centertext',
1981
		),
1982
	);
1983
	$list_options['form'] = array(
1984
		'href' => $scripturl . '?action=admin;area=maintain;sa=hooks' . $context['filter_url'] . ';' . $context['session_var'] . '=' . $context['session_id'],
1985
		'name' => 'list_integration_hooks',
1986
	);
1987
1988
1989
	require_once($sourcedir . '/Subs-List.php');
1990
	createList($list_options);
1991
1992
	$context['page_title'] = $txt['hooks_title_list'];
1993
	$context['sub_template'] = 'show_list';
1994
	$context['default_list'] = 'list_integration_hooks';
1995
}
1996
1997
/**
1998
 * Gets all of the files in a directory and its children directories
1999
 *
2000
 * @param string $dir_path The path to the directory
2001
 * @return array An array containing information about the files found in the specified directory and its children
2002
 */
2003
function get_files_recursive($dir_path)
2004
{
2005
	$files = array();
2006
2007
	if ($dh = opendir($dir_path))
2008
	{
2009
		while (($file = readdir($dh)) !== false)
2010
		{
2011
			if ($file != '.' && $file != '..')
2012
			{
2013
				if (is_dir($dir_path . '/' . $file))
2014
					$files = array_merge($files, get_files_recursive($dir_path . '/' . $file));
2015
				else
2016
					$files[] = array('dir' => $dir_path, 'name' => $file);
2017
			}
2018
		}
2019
	}
2020
	closedir($dh);
2021
2022
	return $files;
2023
}
2024
2025
/**
2026
 * Callback function for the integration hooks list (list_integration_hooks)
2027
 * Gets all of the hooks in the system and their status
2028
 *
2029
 * @param int $start The item to start with (for pagination purposes)
2030
 * @param int $per_page How many items to display on each page
2031
 * @param string $sort A string indicating how to sort things
2032
 * @return array An array of information about the integration hooks
2033
 */
2034
function get_integration_hooks_data($start, $per_page, $sort)
2035
{
2036
	global $boarddir, $sourcedir, $settings, $txt, $context, $scripturl;
2037
2038
	$hooks = $temp_hooks = get_integration_hooks();
2039
	$hooks_data = $temp_data = $hook_status = array();
2040
2041
	$files = get_files_recursive($sourcedir);
2042
	if (!empty($files))
2043
	{
2044
		foreach ($files as $file)
2045
		{
2046
			if (is_file($file['dir'] . '/' . $file['name']) && substr($file['name'], -4) === '.php')
2047
			{
2048
				$fp = fopen($file['dir'] . '/' . $file['name'], 'rb');
2049
				$fc = fread($fp, filesize($file['dir'] . '/' . $file['name']));
2050
				fclose($fp);
2051
2052
				foreach ($temp_hooks as $hook => $allFunctions)
2053
				{
2054
					foreach ($allFunctions as $rawFunc)
2055
					{
2056
						// Get the hook info.
2057
						$hookParsedData = get_hook_info_from_raw($rawFunc);
2058
2059
						if (substr($hook, -8) === '_include')
2060
						{
2061
							$hook_status[$hook][$hookParsedData['pureFunc']]['exists'] = file_exists(strtr(trim($rawFunc), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])));
2062
							// I need to know if there is at least one function called in this file.
2063
							$temp_data['include'][$hookParsedData['pureFunc']] = array('hook' => $hook, 'function' => $hookParsedData['pureFunc']);
2064
							unset($temp_hooks[$hook][$rawFunc]);
2065
						}
2066
						elseif (strpos(str_replace(' (', '(', $fc), 'function ' . trim($hookParsedData['pureFunc']) . '(') !== false)
2067
						{
2068
							$hook_status[$hook][$hookParsedData['pureFunc']] = $hookParsedData;
2069
							$hook_status[$hook][$hookParsedData['pureFunc']]['exists'] = true;
2070
							$hook_status[$hook][$hookParsedData['pureFunc']]['in_file'] = (!empty($file['name']) ? $file['name'] : (!empty($hookParsedData['hookFile']) ? $hookParsedData['hookFile'] : ''));
2071
2072
							// Does the hook has its own file?
2073
							if (!empty($hookParsedData['hookFile']))
2074
								$temp_data['include'][$hookParsedData['pureFunc']] = array('hook' => $hook, 'function' => $hookParsedData['pureFunc']);
2075
2076
							// 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)
2077
							$temp_data['function'][$file['name']][$hookParsedData['pureFunc']] = $hookParsedData['enabled'];
2078
							unset($temp_hooks[$hook][$rawFunc]);
2079
						}
2080
					}
2081
				}
2082
			}
2083
		}
2084
	}
2085
2086
	$sort_types = array(
2087
		'hook_name' => array('hook', SORT_ASC),
2088
		'hook_name DESC' => array('hook', SORT_DESC),
2089
		'function_name' => array('function', SORT_ASC),
2090
		'function_name DESC' => array('function', SORT_DESC),
2091
		'file_name' => array('file_name', SORT_ASC),
2092
		'file_name DESC' => array('file_name', SORT_DESC),
2093
		'status' => array('status', SORT_ASC),
2094
		'status DESC' => array('status', SORT_DESC),
2095
	);
2096
2097
	$sort_options = $sort_types[$sort];
2098
	$sort = array();
2099
	$hooks_filters = array();
2100
2101
	foreach ($hooks as $hook => $functions)
2102
		$hooks_filters[] = '<option' . ($context['current_filter'] == $hook ? ' selected ' : '') . ' value="' . $hook . '">' . $hook . '</option>';
2103
2104
	if (!empty($hooks_filters))
2105
		$context['insert_after_template'] .= '
2106
		<script>
2107
			var hook_name_header = document.getElementById(\'header_list_integration_hooks_hook_name\');
2108
			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>') . ';
2109
		</script>';
2110
2111
	$temp_data = array();
2112
	$id = 0;
2113
2114
	foreach ($hooks as $hook => $functions)
2115
	{
2116
		if (empty($context['filter']) || (!empty($context['filter']) && $context['filter'] == $hook))
2117
		{
2118
			foreach ($functions as $rawFunc)
2119
			{
2120
				// Get the hook info.
2121
				$hookParsedData = get_hook_info_from_raw($rawFunc);
2122
2123
				$hook_exists = !empty($hook_status[$hook][$hookParsedData['pureFunc']]['exists']);
2124
				$sort[] = $sort_options[0];
2125
2126
				$temp_data[] = array(
2127
					'id' => 'hookid_' . $id++,
2128
					'hook_name' => $hook,
2129
					'function_name' => $hookParsedData['rawData'],
2130
					'real_function' => $hookParsedData['pureFunc'],
2131
					'included_file' => !empty($hookParsedData['absPath']) ? $hookParsedData['absPath'] : '',
2132
					'file_name' => (isset($hook_status[$hook][$hookParsedData['pureFunc']]['in_file']) ? $hook_status[$hook][$hookParsedData['pureFunc']]['in_file'] : (!empty($hookParsedData['hookFile']) ? $hookParsedData['hookFile'] : '')),
2133
					'instance' => $hookParsedData['object'],
2134
					'hook_exists' => $hook_exists,
2135
					'status' => $hook_exists ? ($hookParsedData['enabled'] ? 'allow' : 'moderate') : 'deny',
2136
					'img_text' => $txt['hooks_' . ($hook_exists ? ($hookParsedData['enabled'] ? 'active' : 'disabled') : 'missing')],
2137
					'enabled' => $hookParsedData['enabled'],
2138
					'can_be_disabled' => !isset($hook_status[$hook][$hookParsedData['pureFunc']]['enabled']),
2139
				);
2140
			}
2141
		}
2142
	}
2143
2144
	array_multisort($sort, $sort_options[1], $temp_data);
2145
2146
	$counter = 0;
2147
	$start++;
2148
2149
	foreach ($temp_data as $data)
2150
	{
2151
		if (++$counter < $start)
2152
			continue;
2153
		elseif ($counter == $start + $per_page)
2154
			break;
2155
2156
		$hooks_data[] = $data;
2157
	}
2158
2159
	return $hooks_data;
2160
}
2161
2162
/**
2163
 * Simply returns the total count of integration hooks
2164
 * Used by the integration hooks list function (list_integration_hooks)
2165
 *
2166
 * @return int The number of hooks currently in use
2167
 */
2168
function get_integration_hooks_count()
2169
{
2170
	global $context;
2171
2172
	$hooks = get_integration_hooks();
2173
	$hooks_count = 0;
2174
2175
	$context['filter'] = false;
2176
	if (isset($_GET['filter']))
2177
		$context['filter'] = $_GET['filter'];
2178
2179
	foreach ($hooks as $hook => $functions)
2180
	{
2181
		if (empty($context['filter']) || (!empty($context['filter']) && $context['filter'] == $hook))
2182
			$hooks_count += count($functions);
2183
	}
2184
2185
	return $hooks_count;
2186
}
2187
2188
/**
2189
 * Parses modSettings to create integration hook array
2190
 *
2191
 * @return array An array of information about the integration hooks
2192
 */
2193
function get_integration_hooks()
2194
{
2195
	global $modSettings;
2196
	static $integration_hooks;
2197
2198
	if (!isset($integration_hooks))
2199
	{
2200
		$integration_hooks = array();
2201
		foreach ($modSettings as $key => $value)
2202
		{
2203
			if (!empty($value) && substr($key, 0, 10) === 'integrate_')
2204
				$integration_hooks[$key] = explode(',', $value);
2205
		}
2206
	}
2207
2208
	return $integration_hooks;
2209
}
2210
2211
/**
2212
 * Parses each hook data and returns an array.
2213
 *
2214
 * @param string $rawData A string as it was saved to the DB.
2215
 * @return array everything found in the string itself
2216
 */
2217
function get_hook_info_from_raw($rawData)
2218
{
2219
	global $boarddir, $settings, $sourcedir;
2220
2221
	// A single string can hold tons of info!
2222
	$hookData = array(
2223
		'object' => false,
2224
		'enabled' => true,
2225
		'fileExists' => false,
2226
		'absPath' => '',
2227
		'hookFile' => '',
2228
		'pureFunc' => '',
2229
		'method' => '',
2230
		'class' => '',
2231
		'rawData' => $rawData,
2232
	);
2233
2234
	// Meh...
2235
	if (empty($rawData))
2236
		return $hookData;
2237
2238
	// For convenience purposes only!
2239
	$modFunc = $rawData;
2240
2241
	// Any files?
2242
	if (strpos($modFunc, '|') !== false)
2243
	{
2244
		list ($hookData['hookFile'], $modFunc) = explode('|', $modFunc);
2245
2246
		// Does the file exists? who knows!
2247
		if (empty($settings['theme_dir']))
2248
			$hookData['absPath'] = strtr(trim($hookData['hookFile']), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
2249
2250
		else
2251
			$hookData['absPath'] = strtr(trim($hookData['hookFile']), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2252
2253
		$hookData['fileExists'] = file_exists($hookData['absPath']);
2254
		$hookData['hookFile'] = basename($hookData['hookFile']);
2255
	}
2256
2257
	// Hook is an instance.
2258 View Code Duplication
	if (strpos($modFunc, '#') !== false)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2259
	{
2260
		$modFunc = str_replace('#', '', $modFunc);
2261
		$hookData['object'] = true;
2262
	}
2263
2264
	// Hook is "disabled"
2265 View Code Duplication
	if (strpos($modFunc, '!') !== false)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2266
	{
2267
		$modFunc = str_replace('!', '', $modFunc);
2268
		$hookData['enabled'] = false;
2269
	}
2270
2271
	// Handling methods?
2272
	if (strpos($modFunc, '::') !== false)
2273
	{
2274
		list ($hookData['class'], $hookData['method']) = explode('::', $modFunc);
2275
		$hookData['pureFunc'] = $hookData['method'];
2276
	}
2277
2278
	else
2279
		$hookData['pureFunc'] = $modFunc;
2280
2281
	return $hookData;
2282
}
2283
2284
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

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

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

Loading history...