Completed
Push — release-2.1 ( 4c82a0...64d581 )
by Rick
09:29
created

ManageMaintenance.php ➔ ConvertEntities()   F

Complexity

Conditions 28
Paths 3088

Size

Total Lines 198
Code Lines 114

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 28
eloc 114
nc 3088
nop 0
dl 0
loc 198
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 2016 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 3
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' || $db_type == 'mysqli') && (!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' || $db_type == 'mysqli') && isset($db_character_set, $modSettings['global_character_set']) && $db_character_set === 'utf8' && $modSettings['global_character_set'] === 'UTF-8';
138
139
	if ($db_type == 'mysql' || $db_type == 'mysqli')
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' && $db_type != 'mysqli')
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
		redirectexit('action=admin;area=maintain;sa=database');
0 ignored issues
show
Unused Code introduced by
redirectexit('action=adm...maintain;sa=database'); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
392
	}
393
	elseif ($body_type != 'text' && (!isset($_POST['do_conversion']) || isset($_POST['cont'])))
394
	{
395
		checkSession();
396
		if (empty($_REQUEST['start']))
397
			validateToken('admin-maint');
398
		else
399
			validateToken('admin-convertMsg');
400
401
		$context['page_title'] = $txt['not_done_title'];
402
		$context['continue_post_data'] = '';
403
		$context['continue_countdown'] = 3;
404
		$context['sub_template'] = 'not_done';
405
		$increment = 500;
406
		$id_msg_exceeding = isset($_POST['id_msg_exceeding']) ? explode(',', $_POST['id_msg_exceeding']) : array();
407
408
		$request = $smcFunc['db_query']('', '
409
			SELECT COUNT(*) as count
410
			FROM {db_prefix}messages',
411
			array()
412
		);
413
		list($max_msgs) = $smcFunc['db_fetch_row']($request);
414
		$smcFunc['db_free_result']($request);
415
416
		// Try for as much time as possible.
417
		@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...
418
419
		while ($_REQUEST['start'] < $max_msgs)
420
		{
421
			$request = $smcFunc['db_query']('', '
422
				SELECT /*!40001 SQL_NO_CACHE */ id_msg
423
				FROM {db_prefix}messages
424
				WHERE id_msg BETWEEN {int:start} AND {int:start} + {int:increment}
425
					AND LENGTH(body) > 65535',
426
				array(
427
					'start' => $_REQUEST['start'],
428
					'increment' => $increment - 1,
429
				)
430
			);
431
			while ($row = $smcFunc['db_fetch_assoc']($request))
432
				$id_msg_exceeding[] = $row['id_msg'];
433
			$smcFunc['db_free_result']($request);
434
435
			$_REQUEST['start'] += $increment;
436
437
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
438
			{
439
				createToken('admin-convertMsg');
440
				$context['continue_post_data'] = '
441
					<input type="hidden" name="' . $context['admin-convertMsg_token_var'] . '" value="' . $context['admin-convertMsg_token'] . '">
442
					<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '">
443
					<input type="hidden" name="id_msg_exceeding" value="' . implode(',', $id_msg_exceeding) . '">';
444
445
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=database;activity=convertmsgbody;start=' . $_REQUEST['start'];
446
				$context['continue_percent'] = round(100 * $_REQUEST['start'] / $max_msgs);
447
448
				return;
449
			}
450
		}
451
		createToken('admin-maint');
452
		$context['page_title'] = $txt[$context['convert_to'] . '_title'];
453
		$context['sub_template'] = 'convert_msgbody';
454
455
		if (!empty($id_msg_exceeding))
456
		{
457
			if (count($id_msg_exceeding) > 100)
458
			{
459
				$query_msg = array_slice($id_msg_exceeding, 0, 100);
460
				$context['exceeding_messages_morethan'] = sprintf($txt['exceeding_messages_morethan'], count($id_msg_exceeding));
461
			}
462
			else
463
				$query_msg = $id_msg_exceeding;
464
465
			$context['exceeding_messages'] = array();
466
			$request = $smcFunc['db_query']('', '
467
				SELECT id_msg, id_topic, subject
468
				FROM {db_prefix}messages
469
				WHERE id_msg IN ({array_int:messages})',
470
				array(
471
					'messages' => $query_msg,
472
				)
473
			);
474
			while ($row = $smcFunc['db_fetch_assoc']($request))
475
				$context['exceeding_messages'][] = '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'] . '">' . $row['subject'] . '</a>';
476
			$smcFunc['db_free_result']($request);
477
		}
478
	}
479
}
480
481
/**
482
 * Converts HTML-entities to their UTF-8 character equivalents.
483
 * This requires the admin_forum permission.
484
 * Pre-condition: UTF-8 has been set as database and global character set.
485
 *
486
 * It is divided in steps of 10 seconds.
487
 * This action is linked from the maintenance screen (if applicable).
488
 * It is accessed by ?action=admin;area=maintain;sa=database;activity=convertentities.
489
 *
490
 * @uses Admin template, convert_entities sub-template.
491
 */
492
function ConvertEntities()
493
{
494
	global $db_character_set, $modSettings, $context, $sourcedir, $smcFunc;
495
496
	isAllowedTo('admin_forum');
497
498
	// Check to see if UTF-8 is currently the default character set.
499
	if ($modSettings['global_character_set'] !== 'UTF-8' || !isset($db_character_set) || $db_character_set !== 'utf8')
500
		fatal_lang_error('entity_convert_only_utf8');
501
502
	// Some starting values.
503
	$context['table'] = empty($_REQUEST['table']) ? 0 : (int) $_REQUEST['table'];
504
	$context['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
505
506
	$context['start_time'] = time();
507
508
	$context['first_step'] = !isset($_REQUEST[$context['session_var']]);
509
	$context['last_step'] = false;
510
511
	// The first step is just a text screen with some explanation.
512
	if ($context['first_step'])
513
	{
514
		validateToken('admin-maint');
515
		createToken('admin-maint');
516
517
		$context['sub_template'] = 'convert_entities';
518
		return;
519
	}
520
	// Otherwise use the generic "not done" template.
521
	$context['sub_template'] = 'not_done';
522
	$context['continue_post_data'] = '';
523
	$context['continue_countdown'] = 3;
524
525
	// Now we're actually going to convert...
526
	checkSession('request');
527
	validateToken('admin-maint');
528
	createToken('admin-maint');
529
530
	// A list of tables ready for conversion.
531
	$tables = array(
532
		'ban_groups',
533
		'ban_items',
534
		'boards',
535
		'calendar',
536
		'calendar_holidays',
537
		'categories',
538
		'log_errors',
539
		'log_search_subjects',
540
		'membergroups',
541
		'members',
542
		'message_icons',
543
		'messages',
544
		'package_servers',
545
		'personal_messages',
546
		'pm_recipients',
547
		'polls',
548
		'poll_choices',
549
		'smileys',
550
		'themes',
551
	);
552
	$context['num_tables'] = count($tables);
553
554
	// Loop through all tables that need converting.
555
	for (; $context['table'] < $context['num_tables']; $context['table']++)
556
	{
557
		$cur_table = $tables[$context['table']];
558
		$primary_key = '';
559
		// Make sure we keep stuff unique!
560
		$primary_keys = array();
561
562
		if (function_exists('apache_reset_timeout'))
563
			@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...
564
565
		// Get a list of text columns.
566
		$columns = array();
567
		$request = $smcFunc['db_query']('', '
568
			SHOW FULL COLUMNS
569
			FROM {db_prefix}{raw:cur_table}',
570
			array(
571
				'cur_table' => $cur_table,
572
			)
573
		);
574
		while ($column_info = $smcFunc['db_fetch_assoc']($request))
575
			if (strpos($column_info['Type'], 'text') !== false || strpos($column_info['Type'], 'char') !== false)
576
				$columns[] = strtolower($column_info['Field']);
577
578
		// Get the column with the (first) primary key.
579
		$request = $smcFunc['db_query']('', '
580
			SHOW KEYS
581
			FROM {db_prefix}{raw:cur_table}',
582
			array(
583
				'cur_table' => $cur_table,
584
			)
585
		);
586
		while ($row = $smcFunc['db_fetch_assoc']($request))
587
		{
588
			if ($row['Key_name'] === 'PRIMARY')
589
			{
590
				if (empty($primary_key) || ($row['Seq_in_index'] == 1 && !in_array(strtolower($row['Column_name']), $columns)))
591
					$primary_key = $row['Column_name'];
592
593
				$primary_keys[] = $row['Column_name'];
594
			}
595
		}
596
		$smcFunc['db_free_result']($request);
597
598
		// No primary key, no glory.
599
		// Same for columns. Just to be sure we've work to do!
600
		if (empty($primary_key) || empty($columns))
601
			continue;
602
603
		// Get the maximum value for the primary key.
604
		$request = $smcFunc['db_query']('', '
605
			SELECT MAX({identifier:key})
606
			FROM {db_prefix}{raw:cur_table}',
607
			array(
608
				'key' => $primary_key,
609
				'cur_table' => $cur_table,
610
			)
611
		);
612
		list($max_value) = $smcFunc['db_fetch_row']($request);
613
		$smcFunc['db_free_result']($request);
614
615
		if (empty($max_value))
616
			continue;
617
618
		while ($context['start'] <= $max_value)
619
		{
620
			// Retrieve a list of rows that has at least one entity to convert.
621
			$request = $smcFunc['db_query']('', '
622
				SELECT {raw:primary_keys}, {raw:columns}
623
				FROM {db_prefix}{raw:cur_table}
624
				WHERE {raw:primary_key} BETWEEN {int:start} AND {int:start} + 499
625
					AND {raw:like_compare}
626
				LIMIT 500',
627
				array(
628
					'primary_keys' => implode(', ', $primary_keys),
629
					'columns' => implode(', ', $columns),
630
					'cur_table' => $cur_table,
631
					'primary_key' => $primary_key,
632
					'start' => $context['start'],
633
					'like_compare' => '(' . implode(' LIKE \'%&#%\' OR ', $columns) . ' LIKE \'%&#%\')',
634
				)
635
			);
636
			while ($row = $smcFunc['db_fetch_assoc']($request))
637
			{
638
				$insertion_variables = array();
639
				$changes = array();
640
				foreach ($row as $column_name => $column_value)
641
					if ($column_name !== $primary_key && strpos($column_value, '&#') !== false)
642
					{
643
						$changes[] = $column_name . ' = {string:changes_' . $column_name . '}';
644
						$insertion_variables['changes_' . $column_name] = preg_replace_callback('~&#(\d{1,7}|x[0-9a-fA-F]{1,6});~', 'fixchar__callback', $column_value);
645
					}
646
647
				$where = array();
648
				foreach ($primary_keys as $key)
649
				{
650
					$where[] = $key . ' = {string:where_' . $key . '}';
651
					$insertion_variables['where_' . $key] = $row[$key];
652
				}
653
654
				// Update the row.
655
				if (!empty($changes))
656
					$smcFunc['db_query']('', '
657
						UPDATE {db_prefix}' . $cur_table . '
658
						SET
659
							' . implode(',
660
							', $changes) . '
661
						WHERE ' . implode(' AND ', $where),
662
						$insertion_variables
663
					);
664
			}
665
			$smcFunc['db_free_result']($request);
666
			$context['start'] += 500;
667
668
			// After ten seconds interrupt.
669
			if (time() - $context['start_time'] > 10)
670
			{
671
				// Calculate an approximation of the percentage done.
672
				$context['continue_percent'] = round(100 * ($context['table'] + ($context['start'] / $max_value)) / $context['num_tables'], 1);
673
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=database;activity=convertentities;table=' . $context['table'] . ';start=' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
674
				return;
675
			}
676
		}
677
		$context['start'] = 0;
678
	}
679
680
	// Make sure all serialized strings are all right.
681
	require_once($sourcedir . '/Subs-Charset.php');
682
	fix_serialized_columns();
683
684
	// If we're here, we must be done.
685
	$context['continue_percent'] = 100;
686
	$context['continue_get_data'] = '?action=admin;area=maintain;sa=database;done=convertentities';
687
	$context['last_step'] = true;
688
	$context['continue_countdown'] = -1;
689
}
690
691
/**
692
 * Optimizes all tables in the database and lists how much was saved.
693
 * It requires the admin_forum permission.
694
 * It shows as the maintain_forum admin area.
695
 * It is accessed from ?action=admin;area=maintain;sa=database;activity=optimize.
696
 * It also updates the optimize scheduled task such that the tables are not automatically optimized again too soon.
697
698
 * @uses the optimize sub template
699
 */
700
function OptimizeTables()
701
{
702
	global $db_prefix, $txt, $context, $smcFunc, $time_start;
703
704
	isAllowedTo('admin_forum');
705
706
	checkSession('request');
707
708
	if (!isset($_SESSION['optimized_tables']))
709
		validateToken('admin-maint');
710
	else
711
		validateToken('admin-optimize', 'post', false);
712
713
	ignore_user_abort(true);
714
	db_extend();
715
716
	// Start with no tables optimized.
717
	$opttab = 0;
0 ignored issues
show
Unused Code introduced by
$opttab is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
718
719
	$context['page_title'] = $txt['database_optimize'];
720
	$context['sub_template'] = 'optimize';
721
	$context['continue_post_data'] = '';
722
	$context['continue_countdown'] = 3;
723
724
	// Only optimize the tables related to this smf install, not all the tables in the db
725
	$real_prefix = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
726
727
	// Get a list of tables, as well as how many there are.
728
	$temp_tables = $smcFunc['db_list_tables'](false, $real_prefix . '%');
729
	$tables = array();
730
	foreach ($temp_tables as $table)
731
		$tables[] = array('table_name' => $table);
732
733
	// If there aren't any tables then I believe that would mean the world has exploded...
734
	$context['num_tables'] = count($tables);
735
	if ($context['num_tables'] == 0)
736
		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...
737
738
	$_REQUEST['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
739
740
	// Try for extra time due to large tables.
741
	@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...
742
743
	// For each table....
744
	$_SESSION['optimized_tables'] = !empty($_SESSION['optimized_tables']) ? $_SESSION['optimized_tables'] : array();
745
	for ($key=$_REQUEST['start']; $context['num_tables']-1; $key++)
746
	{
747
		if (empty($tables[$key]))
748
			break;
749
750
		// Continue?
751
		if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 10)
752
		{
753
			$_REQUEST['start'] = $key;
754
			$context['continue_get_data'] = '?action=admin;area=maintain;sa=database;activity=optimize;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
755
			$context['continue_percent'] = round(100 * $_REQUEST['start'] / $context['num_tables']);
756
			$context['sub_template'] = 'not_done';
757
			$context['page_title'] = $txt['not_done_title'];
758
759
			createToken('admin-optimize');
760
			$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-optimize_token_var'] . '" value="' . $context['admin-optimize_token'] . '">';
761
762
			if (function_exists('apache_reset_timeout'))
763
				apache_reset_timeout();
764
765
			return;
766
		}
767
768
		// Optimize the table!  We use backticks here because it might be a custom table.
769
		$data_freed = $smcFunc['db_optimize_table']($tables[$key]['table_name']);
770
771
		if ($data_freed > 0)
772
			$_SESSION['optimized_tables'][] = array(
773
				'name' => $tables[$key]['table_name'],
774
				'data_freed' => $data_freed,
775
			);
776
	}
777
778
	// Number of tables, etc...
779
	$txt['database_numb_tables'] = sprintf($txt['database_numb_tables'], $context['num_tables']);
780
	$context['num_tables_optimized'] = count($_SESSION['optimized_tables']);
781
	$context['optimized_tables'] = $_SESSION['optimized_tables'];
782
	unset($_SESSION['optimized_tables']);
783
}
784
785
/**
786
 * Recount many forum totals that can be recounted automatically without harm.
787
 * it requires the admin_forum permission.
788
 * It shows the maintain_forum admin area.
789
 *
790
 * Totals recounted:
791
 * - fixes for topics with wrong num_replies.
792
 * - updates for num_posts and num_topics of all boards.
793
 * - recounts instant_messages but not unread_messages.
794
 * - repairs messages pointing to boards with topics pointing to other boards.
795
 * - updates the last message posted in boards and children.
796
 * - updates member count, latest member, topic count, and message count.
797
 *
798
 * The function redirects back to ?action=admin;area=maintain when complete.
799
 * It is accessed via ?action=admin;area=maintain;sa=database;activity=recount.
800
 */
801
function AdminBoardRecount()
802
{
803
	global $txt, $context, $modSettings, $sourcedir;
804
	global $time_start, $smcFunc;
805
806
	isAllowedTo('admin_forum');
807
	checkSession('request');
808
809
	// validate the request or the loop
810
	if (!isset($_REQUEST['step']))
811
		validateToken('admin-maint');
812
	else
813
		validateToken('admin-boardrecount');
814
815
	$context['page_title'] = $txt['not_done_title'];
816
	$context['continue_post_data'] = '';
817
	$context['continue_countdown'] = 3;
818
	$context['sub_template'] = 'not_done';
819
820
	// Try for as much time as possible.
821
	@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...
822
823
	// Step the number of topics at a time so things don't time out...
824
	$request = $smcFunc['db_query']('', '
825
		SELECT MAX(id_topic)
826
		FROM {db_prefix}topics',
827
		array(
828
		)
829
	);
830
	list ($max_topics) = $smcFunc['db_fetch_row']($request);
831
	$smcFunc['db_free_result']($request);
832
833
	$increment = min(max(50, ceil($max_topics / 4)), 2000);
834
	if (empty($_REQUEST['start']))
835
		$_REQUEST['start'] = 0;
836
837
	$total_steps = 8;
838
839
	// Get each topic with a wrong reply count and fix it - let's just do some at a time, though.
840
	if (empty($_REQUEST['step']))
841
	{
842
		$_REQUEST['step'] = 0;
843
844
		while ($_REQUEST['start'] < $max_topics)
845
		{
846
			// Recount approved messages
847
			$request = $smcFunc['db_query']('', '
848
				SELECT /*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.num_replies) AS num_replies,
849
					CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END AS real_num_replies
850
				FROM {db_prefix}topics AS t
851
					LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = {int:is_approved})
852
				WHERE t.id_topic > {int:start}
853
					AND t.id_topic <= {int:max_id}
854
				GROUP BY t.id_topic
855
				HAVING CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END != MAX(t.num_replies)',
856
				array(
857
					'is_approved' => 1,
858
					'start' => $_REQUEST['start'],
859
					'max_id' => $_REQUEST['start'] + $increment,
860
				)
861
			);
862
			while ($row = $smcFunc['db_fetch_assoc']($request))
863
				$smcFunc['db_query']('', '
864
					UPDATE {db_prefix}topics
865
					SET num_replies = {int:num_replies}
866
					WHERE id_topic = {int:id_topic}',
867
					array(
868
						'num_replies' => $row['real_num_replies'],
869
						'id_topic' => $row['id_topic'],
870
					)
871
				);
872
			$smcFunc['db_free_result']($request);
873
874
			// Recount unapproved messages
875
			$request = $smcFunc['db_query']('', '
876
				SELECT /*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.unapproved_posts) AS unapproved_posts,
877
					COUNT(mu.id_msg) AS real_unapproved_posts
878
				FROM {db_prefix}topics AS t
879
					LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = {int:not_approved})
880
				WHERE t.id_topic > {int:start}
881
					AND t.id_topic <= {int:max_id}
882
				GROUP BY t.id_topic
883
				HAVING COUNT(mu.id_msg) != MAX(t.unapproved_posts)',
884
				array(
885
					'not_approved' => 0,
886
					'start' => $_REQUEST['start'],
887
					'max_id' => $_REQUEST['start'] + $increment,
888
				)
889
			);
890
			while ($row = $smcFunc['db_fetch_assoc']($request))
891
				$smcFunc['db_query']('', '
892
					UPDATE {db_prefix}topics
893
					SET unapproved_posts = {int:unapproved_posts}
894
					WHERE id_topic = {int:id_topic}',
895
					array(
896
						'unapproved_posts' => $row['real_unapproved_posts'],
897
						'id_topic' => $row['id_topic'],
898
					)
899
				);
900
			$smcFunc['db_free_result']($request);
901
902
			$_REQUEST['start'] += $increment;
903
904
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
905
			{
906
				createToken('admin-boardrecount');
907
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
908
909
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=0;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
910
				$context['continue_percent'] = round((100 * $_REQUEST['start'] / $max_topics) / $total_steps);
911
912
				return;
913
			}
914
		}
915
916
		$_REQUEST['start'] = 0;
917
	}
918
919
	// Update the post count of each board.
920 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...
921
	{
922
		if (empty($_REQUEST['start']))
923
			$smcFunc['db_query']('', '
924
				UPDATE {db_prefix}boards
925
				SET num_posts = {int:num_posts}
926
				WHERE redirect = {string:redirect}',
927
				array(
928
					'num_posts' => 0,
929
					'redirect' => '',
930
				)
931
			);
932
933
		while ($_REQUEST['start'] < $max_topics)
934
		{
935
			$request = $smcFunc['db_query']('', '
936
				SELECT /*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_num_posts
937
				FROM {db_prefix}messages AS m
938
				WHERE m.id_topic > {int:id_topic_min}
939
					AND m.id_topic <= {int:id_topic_max}
940
					AND m.approved = {int:is_approved}
941
				GROUP BY m.id_board',
942
				array(
943
					'id_topic_min' => $_REQUEST['start'],
944
					'id_topic_max' => $_REQUEST['start'] + $increment,
945
					'is_approved' => 1,
946
				)
947
			);
948
			while ($row = $smcFunc['db_fetch_assoc']($request))
949
				$smcFunc['db_query']('', '
950
					UPDATE {db_prefix}boards
951
					SET num_posts = num_posts + {int:real_num_posts}
952
					WHERE id_board = {int:id_board}',
953
					array(
954
						'id_board' => $row['id_board'],
955
						'real_num_posts' => $row['real_num_posts'],
956
					)
957
				);
958
			$smcFunc['db_free_result']($request);
959
960
			$_REQUEST['start'] += $increment;
961
962
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
963
			{
964
				createToken('admin-boardrecount');
965
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
966
967
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=1;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
968
				$context['continue_percent'] = round((200 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
969
970
				return;
971
			}
972
		}
973
974
		$_REQUEST['start'] = 0;
975
	}
976
977
	// Update the topic count of each board.
978 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...
979
	{
980
		if (empty($_REQUEST['start']))
981
			$smcFunc['db_query']('', '
982
				UPDATE {db_prefix}boards
983
				SET num_topics = {int:num_topics}',
984
				array(
985
					'num_topics' => 0,
986
				)
987
			);
988
989
		while ($_REQUEST['start'] < $max_topics)
990
		{
991
			$request = $smcFunc['db_query']('', '
992
				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_num_topics
993
				FROM {db_prefix}topics AS t
994
				WHERE t.approved = {int:is_approved}
995
					AND t.id_topic > {int:id_topic_min}
996
					AND t.id_topic <= {int:id_topic_max}
997
				GROUP BY t.id_board',
998
				array(
999
					'is_approved' => 1,
1000
					'id_topic_min' => $_REQUEST['start'],
1001
					'id_topic_max' => $_REQUEST['start'] + $increment,
1002
				)
1003
			);
1004
			while ($row = $smcFunc['db_fetch_assoc']($request))
1005
				$smcFunc['db_query']('', '
1006
					UPDATE {db_prefix}boards
1007
					SET num_topics = num_topics + {int:real_num_topics}
1008
					WHERE id_board = {int:id_board}',
1009
					array(
1010
						'id_board' => $row['id_board'],
1011
						'real_num_topics' => $row['real_num_topics'],
1012
					)
1013
				);
1014
			$smcFunc['db_free_result']($request);
1015
1016
			$_REQUEST['start'] += $increment;
1017
1018
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1019
			{
1020
				createToken('admin-boardrecount');
1021
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1022
1023
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=2;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1024
				$context['continue_percent'] = round((300 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1025
1026
				return;
1027
			}
1028
		}
1029
1030
		$_REQUEST['start'] = 0;
1031
	}
1032
1033
	// Update the unapproved post count of each board.
1034 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...
1035
	{
1036
		if (empty($_REQUEST['start']))
1037
			$smcFunc['db_query']('', '
1038
				UPDATE {db_prefix}boards
1039
				SET unapproved_posts = {int:unapproved_posts}',
1040
				array(
1041
					'unapproved_posts' => 0,
1042
				)
1043
			);
1044
1045
		while ($_REQUEST['start'] < $max_topics)
1046
		{
1047
			$request = $smcFunc['db_query']('', '
1048
				SELECT /*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_unapproved_posts
1049
				FROM {db_prefix}messages AS m
1050
				WHERE m.id_topic > {int:id_topic_min}
1051
					AND m.id_topic <= {int:id_topic_max}
1052
					AND m.approved = {int:is_approved}
1053
				GROUP BY m.id_board',
1054
				array(
1055
					'id_topic_min' => $_REQUEST['start'],
1056
					'id_topic_max' => $_REQUEST['start'] + $increment,
1057
					'is_approved' => 0,
1058
				)
1059
			);
1060
			while ($row = $smcFunc['db_fetch_assoc']($request))
1061
				$smcFunc['db_query']('', '
1062
					UPDATE {db_prefix}boards
1063
					SET unapproved_posts = unapproved_posts + {int:unapproved_posts}
1064
					WHERE id_board = {int:id_board}',
1065
					array(
1066
						'id_board' => $row['id_board'],
1067
						'unapproved_posts' => $row['real_unapproved_posts'],
1068
					)
1069
				);
1070
			$smcFunc['db_free_result']($request);
1071
1072
			$_REQUEST['start'] += $increment;
1073
1074
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1075
			{
1076
				createToken('admin-boardrecount');
1077
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1078
1079
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=3;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1080
				$context['continue_percent'] = round((400 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1081
1082
				return;
1083
			}
1084
		}
1085
1086
		$_REQUEST['start'] = 0;
1087
	}
1088
1089
	// Update the unapproved topic count of each board.
1090 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...
1091
	{
1092
		if (empty($_REQUEST['start']))
1093
			$smcFunc['db_query']('', '
1094
				UPDATE {db_prefix}boards
1095
				SET unapproved_topics = {int:unapproved_topics}',
1096
				array(
1097
					'unapproved_topics' => 0,
1098
				)
1099
			);
1100
1101
		while ($_REQUEST['start'] < $max_topics)
1102
		{
1103
			$request = $smcFunc['db_query']('', '
1104
				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_unapproved_topics
1105
				FROM {db_prefix}topics AS t
1106
				WHERE t.approved = {int:is_approved}
1107
					AND t.id_topic > {int:id_topic_min}
1108
					AND t.id_topic <= {int:id_topic_max}
1109
				GROUP BY t.id_board',
1110
				array(
1111
					'is_approved' => 0,
1112
					'id_topic_min' => $_REQUEST['start'],
1113
					'id_topic_max' => $_REQUEST['start'] + $increment,
1114
				)
1115
			);
1116
			while ($row = $smcFunc['db_fetch_assoc']($request))
1117
				$smcFunc['db_query']('', '
1118
					UPDATE {db_prefix}boards
1119
					SET unapproved_topics = unapproved_topics + {int:real_unapproved_topics}
1120
					WHERE id_board = {int:id_board}',
1121
					array(
1122
						'id_board' => $row['id_board'],
1123
						'real_unapproved_topics' => $row['real_unapproved_topics'],
1124
					)
1125
				);
1126
			$smcFunc['db_free_result']($request);
1127
1128
			$_REQUEST['start'] += $increment;
1129
1130
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1131
			{
1132
				createToken('admin-boardrecount');
1133
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1134
1135
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=4;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1136
				$context['continue_percent'] = round((500 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1137
1138
				return;
1139
			}
1140
		}
1141
1142
		$_REQUEST['start'] = 0;
1143
	}
1144
1145
	// Get all members with wrong number of personal messages.
1146
	if ($_REQUEST['step'] <= 5)
1147
	{
1148
		$request = $smcFunc['db_query']('', '
1149
			SELECT /*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num,
1150
				MAX(mem.instant_messages) AS instant_messages
1151
			FROM {db_prefix}members AS mem
1152
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (mem.id_member = pmr.id_member AND pmr.deleted = {int:is_not_deleted})
1153
			GROUP BY mem.id_member
1154
			HAVING COUNT(pmr.id_pm) != MAX(mem.instant_messages)',
1155
			array(
1156
				'is_not_deleted' => 0,
1157
			)
1158
		);
1159 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...
1160
			updateMemberData($row['id_member'], array('instant_messages' => $row['real_num']));
1161
		$smcFunc['db_free_result']($request);
1162
1163
		$request = $smcFunc['db_query']('', '
1164
			SELECT /*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num,
1165
				MAX(mem.unread_messages) AS unread_messages
1166
			FROM {db_prefix}members AS mem
1167
				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})
1168
			GROUP BY mem.id_member
1169
			HAVING COUNT(pmr.id_pm) != MAX(mem.unread_messages)',
1170
			array(
1171
				'is_not_deleted' => 0,
1172
				'is_not_read' => 0,
1173
			)
1174
		);
1175 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...
1176
			updateMemberData($row['id_member'], array('unread_messages' => $row['real_num']));
1177
		$smcFunc['db_free_result']($request);
1178
1179
		if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1180
		{
1181
			createToken('admin-boardrecount');
1182
			$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1183
1184
			$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=6;start=0;' . $context['session_var'] . '=' . $context['session_id'];
1185
			$context['continue_percent'] = round(700 / $total_steps);
1186
1187
			return;
1188
		}
1189
	}
1190
1191
	// Any messages pointing to the wrong board?
1192
	if ($_REQUEST['step'] <= 6)
1193
	{
1194
		while ($_REQUEST['start'] < $modSettings['maxMsgID'])
1195
		{
1196
			$request = $smcFunc['db_query']('', '
1197
				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, m.id_msg
1198
				FROM {db_prefix}messages AS m
1199
					INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic AND t.id_board != m.id_board)
1200
				WHERE m.id_msg > {int:id_msg_min}
1201
					AND m.id_msg <= {int:id_msg_max}',
1202
				array(
1203
					'id_msg_min' => $_REQUEST['start'],
1204
					'id_msg_max' => $_REQUEST['start'] + $increment,
1205
				)
1206
			);
1207
			$boards = array();
1208
			while ($row = $smcFunc['db_fetch_assoc']($request))
1209
				$boards[$row['id_board']][] = $row['id_msg'];
1210
			$smcFunc['db_free_result']($request);
1211
1212
			foreach ($boards as $board_id => $messages)
1213
				$smcFunc['db_query']('', '
1214
					UPDATE {db_prefix}messages
1215
					SET id_board = {int:id_board}
1216
					WHERE id_msg IN ({array_int:id_msg_array})',
1217
					array(
1218
						'id_msg_array' => $messages,
1219
						'id_board' => $board_id,
1220
					)
1221
				);
1222
1223
			$_REQUEST['start'] += $increment;
1224
1225
			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1226
			{
1227
				createToken('admin-boardrecount');
1228
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1229
1230
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=6;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1231
				$context['continue_percent'] = round((700 + 100 * $_REQUEST['start'] / $modSettings['maxMsgID']) / $total_steps);
1232
1233
				return;
1234
			}
1235
		}
1236
1237
		$_REQUEST['start'] = 0;
1238
	}
1239
1240
	// Update the latest message of each board.
1241
	$request = $smcFunc['db_query']('', '
1242
		SELECT m.id_board, MAX(m.id_msg) AS local_last_msg
1243
		FROM {db_prefix}messages AS m
1244
		WHERE m.approved = {int:is_approved}
1245
		GROUP BY m.id_board',
1246
		array(
1247
			'is_approved' => 1,
1248
		)
1249
	);
1250
	$realBoardCounts = array();
1251
	while ($row = $smcFunc['db_fetch_assoc']($request))
1252
		$realBoardCounts[$row['id_board']] = $row['local_last_msg'];
1253
	$smcFunc['db_free_result']($request);
1254
1255
	$request = $smcFunc['db_query']('', '
1256
		SELECT /*!40001 SQL_NO_CACHE */ id_board, id_parent, id_last_msg, child_level, id_msg_updated
1257
		FROM {db_prefix}boards',
1258
		array(
1259
		)
1260
	);
1261
	$resort_me = array();
1262
	while ($row = $smcFunc['db_fetch_assoc']($request))
1263
	{
1264
		$row['local_last_msg'] = isset($realBoardCounts[$row['id_board']]) ? $realBoardCounts[$row['id_board']] : 0;
1265
		$resort_me[$row['child_level']][] = $row;
1266
	}
1267
	$smcFunc['db_free_result']($request);
1268
1269
	krsort($resort_me);
1270
1271
	$lastModifiedMsg = array();
1272
	foreach ($resort_me as $rows)
1273
		foreach ($rows as $row)
1274
		{
1275
			// The latest message is the latest of the current board and its children.
1276
			if (isset($lastModifiedMsg[$row['id_board']]))
1277
				$curLastModifiedMsg = max($row['local_last_msg'], $lastModifiedMsg[$row['id_board']]);
1278
			else
1279
				$curLastModifiedMsg = $row['local_last_msg'];
1280
1281
			// If what is and what should be the latest message differ, an update is necessary.
1282
			if ($row['local_last_msg'] != $row['id_last_msg'] || $curLastModifiedMsg != $row['id_msg_updated'])
1283
				$smcFunc['db_query']('', '
1284
					UPDATE {db_prefix}boards
1285
					SET id_last_msg = {int:id_last_msg}, id_msg_updated = {int:id_msg_updated}
1286
					WHERE id_board = {int:id_board}',
1287
					array(
1288
						'id_last_msg' => $row['local_last_msg'],
1289
						'id_msg_updated' => $curLastModifiedMsg,
1290
						'id_board' => $row['id_board'],
1291
					)
1292
				);
1293
1294
			// Parent boards inherit the latest modified message of their children.
1295
			if (isset($lastModifiedMsg[$row['id_parent']]))
1296
				$lastModifiedMsg[$row['id_parent']] = max($row['local_last_msg'], $lastModifiedMsg[$row['id_parent']]);
1297
			else
1298
				$lastModifiedMsg[$row['id_parent']] = $row['local_last_msg'];
1299
		}
1300
1301
	// Update all the basic statistics.
1302
	updateStats('member');
1303
	updateStats('message');
1304
	updateStats('topic');
1305
1306
	// Finally, update the latest event times.
1307
	require_once($sourcedir . '/ScheduledTasks.php');
1308
	CalculateNextTrigger();
1309
1310
	redirectexit('action=admin;area=maintain;sa=routine;done=recount');
1311
}
1312
1313
/**
1314
 * Perform a detailed version check.  A very good thing ;).
1315
 * The function parses the comment headers in all files for their version information,
1316
 * and outputs that for some javascript to check with simplemachines.org.
1317
 * It does not connect directly with simplemachines.org, but rather expects the client to.
1318
 *
1319
 * It requires the admin_forum permission.
1320
 * Uses the view_versions admin area.
1321
 * Accessed through ?action=admin;area=maintain;sa=routine;activity=version.
1322
 * @uses Admin template, view_versions sub-template.
1323
 */
1324
function VersionDetail()
1325
{
1326
	global $forum_version, $txt, $sourcedir, $context;
1327
1328
	isAllowedTo('admin_forum');
1329
1330
	// Call the function that'll get all the version info we need.
1331
	require_once($sourcedir . '/Subs-Admin.php');
1332
	$versionOptions = array(
1333
		'include_ssi' => true,
1334
		'include_subscriptions' => true,
1335
		'include_tasks' => true,
1336
		'sort_results' => true,
1337
	);
1338
	$version_info = getFileVersions($versionOptions);
1339
1340
	// Add the new info to the template context.
1341
	$context += array(
1342
		'file_versions' => $version_info['file_versions'],
1343
		'default_template_versions' => $version_info['default_template_versions'],
1344
		'template_versions' => $version_info['template_versions'],
1345
		'default_language_versions' => $version_info['default_language_versions'],
1346
		'default_known_languages' => array_keys($version_info['default_language_versions']),
1347
		'tasks_versions' => $version_info['tasks_versions'],
1348
	);
1349
1350
	// Make it easier to manage for the template.
1351
	$context['forum_version'] = $forum_version;
1352
1353
	$context['sub_template'] = 'view_versions';
1354
	$context['page_title'] = $txt['admin_version_check'];
1355
}
1356
1357
/**
1358
 * Re-attribute posts.
1359
 */
1360
function MaintainReattributePosts()
1361
{
1362
	global $sourcedir, $context, $txt;
1363
1364
	checkSession();
1365
1366
	// Find the member.
1367
	require_once($sourcedir . '/Subs-Auth.php');
1368
	$members = findMembers($_POST['to']);
1369
1370
	if (empty($members))
1371
		fatal_lang_error('reattribute_cannot_find_member');
1372
1373
	$memID = array_shift($members);
1374
	$memID = $memID['id'];
1375
1376
	$email = $_POST['type'] == 'email' ? $_POST['from_email'] : '';
1377
	$membername = $_POST['type'] == 'name' ? $_POST['from_name'] : '';
1378
1379
	// Now call the reattribute function.
1380
	require_once($sourcedir . '/Subs-Members.php');
1381
	reattributePosts($memID, $email, $membername, !empty($_POST['posts']));
1382
1383
	$context['maintenance_finished'] = $txt['maintain_reattribute_posts'];
1384
}
1385
1386
/**
1387
 * Removing old members. Done and out!
1388
 * @todo refactor
1389
 */
1390
function MaintainPurgeInactiveMembers()
1391
{
1392
	global $sourcedir, $context, $smcFunc, $txt;
1393
1394
	$_POST['maxdays'] = empty($_POST['maxdays']) ? 0 : (int) $_POST['maxdays'];
1395
	if (!empty($_POST['groups']) && $_POST['maxdays'] > 0)
1396
	{
1397
		checkSession();
1398
		validateToken('admin-maint');
1399
1400
		$groups = array();
1401
		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...
1402
			$groups[] = (int) $id;
1403
		$time_limit = (time() - ($_POST['maxdays'] * 24 * 3600));
1404
		$where_vars = array(
1405
			'time_limit' => $time_limit,
1406
		);
1407
		if ($_POST['del_type'] == 'activated')
1408
		{
1409
			$where = 'mem.date_registered < {int:time_limit} AND mem.is_activated = {int:is_activated}';
1410
			$where_vars['is_activated'] = 0;
1411
		}
1412
		else
1413
			$where = 'mem.last_login < {int:time_limit} AND (mem.last_login != 0 OR mem.date_registered < {int:time_limit})';
1414
1415
		// Need to get *all* groups then work out which (if any) we avoid.
1416
		$request = $smcFunc['db_query']('', '
1417
			SELECT id_group, group_name, min_posts
1418
			FROM {db_prefix}membergroups',
1419
			array(
1420
			)
1421
		);
1422
		while ($row = $smcFunc['db_fetch_assoc']($request))
1423
		{
1424
			// Avoid this one?
1425
			if (!in_array($row['id_group'], $groups))
1426
			{
1427
				// Post group?
1428
				if ($row['min_posts'] != -1)
1429
				{
1430
					$where .= ' AND mem.id_post_group != {int:id_post_group_' . $row['id_group'] . '}';
1431
					$where_vars['id_post_group_' . $row['id_group']] = $row['id_group'];
1432
				}
1433
				else
1434
				{
1435
					$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';
1436
					$where_vars['id_group_' . $row['id_group']] = $row['id_group'];
1437
				}
1438
			}
1439
		}
1440
		$smcFunc['db_free_result']($request);
1441
1442
		// If we have ungrouped unselected we need to avoid those guys.
1443
		if (!in_array(0, $groups))
1444
		{
1445
			$where .= ' AND (mem.id_group != 0 OR mem.additional_groups != {string:blank_add_groups})';
1446
			$where_vars['blank_add_groups'] = '';
1447
		}
1448
1449
		// Select all the members we're about to murder/remove...
1450
		$request = $smcFunc['db_query']('', '
1451
			SELECT mem.id_member, COALESCE(m.id_member, 0) AS is_mod
1452
			FROM {db_prefix}members AS mem
1453
				LEFT JOIN {db_prefix}moderators AS m ON (m.id_member = mem.id_member)
1454
			WHERE ' . $where,
1455
			$where_vars
1456
		);
1457
		$members = array();
1458
		while ($row = $smcFunc['db_fetch_assoc']($request))
1459
		{
1460
			if (!$row['is_mod'] || !in_array(3, $groups))
1461
				$members[] = $row['id_member'];
1462
		}
1463
		$smcFunc['db_free_result']($request);
1464
1465
		require_once($sourcedir . '/Subs-Members.php');
1466
		deleteMembers($members);
1467
	}
1468
1469
	$context['maintenance_finished'] = $txt['maintain_members'];
1470
	createToken('admin-maint');
1471
}
1472
1473
/**
1474
 * Removing old posts doesn't take much as we really pass through.
1475
 */
1476
function MaintainRemoveOldPosts()
1477
{
1478
	global $sourcedir;
1479
1480
	validateToken('admin-maint');
1481
1482
	// Actually do what we're told!
1483
	require_once($sourcedir . '/RemoveTopic.php');
1484
	RemoveOldTopics2();
1485
}
1486
1487
/**
1488
 * Removing old drafts
1489
 */
1490
function MaintainRemoveOldDrafts()
1491
{
1492
	global $sourcedir, $smcFunc;
1493
1494
	validateToken('admin-maint');
1495
1496
	$drafts = array();
1497
1498
	// Find all of the old drafts
1499
	$request = $smcFunc['db_query']('', '
1500
		SELECT id_draft
1501
		FROM {db_prefix}user_drafts
1502
		WHERE poster_time <= {int:poster_time_old}',
1503
		array(
1504
			'poster_time_old' => time() - (86400 * $_POST['draftdays']),
1505
		)
1506
	);
1507
1508
	while ($row = $smcFunc['db_fetch_row']($request))
1509
		$drafts[] = (int) $row[0];
1510
	$smcFunc['db_free_result']($request);
1511
1512
	// If we have old drafts, remove them
1513
	if (count($drafts) > 0)
1514
	{
1515
		require_once($sourcedir . '/Drafts.php');
1516
		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...
1517
	}
1518
}
1519
1520
/**
1521
 * Moves topics from one board to another.
1522
 *
1523
 * @uses not_done template to pause the process.
1524
 */
1525
function MaintainMassMoveTopics()
1526
{
1527
	global $smcFunc, $sourcedir, $context, $txt;
1528
1529
	// Only admins.
1530
	isAllowedTo('admin_forum');
1531
1532
	checkSession('request');
1533
	validateToken('admin-maint');
1534
1535
	// Set up to the context.
1536
	$context['page_title'] = $txt['not_done_title'];
1537
	$context['continue_countdown'] = 3;
1538
	$context['continue_post_data'] = '';
1539
	$context['continue_get_data'] = '';
1540
	$context['sub_template'] = 'not_done';
1541
	$context['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
1542
	$context['start_time'] = time();
1543
1544
	// First time we do this?
1545
	$id_board_from = isset($_REQUEST['id_board_from']) ? (int) $_REQUEST['id_board_from'] : 0;
1546
	$id_board_to = isset($_REQUEST['id_board_to']) ? (int) $_REQUEST['id_board_to'] : 0;
1547
	$max_days = isset($_REQUEST['maxdays']) ? (int) $_REQUEST['maxdays'] : 0;
1548
	$locked = isset($_POST['move_type_locked']) || isset($_GET['locked']);
1549
	$sticky = isset($_POST['move_type_sticky']) || isset($_GET['sticky']);
1550
1551
	// No boards then this is your stop.
1552
	if (empty($id_board_from) || empty($id_board_to))
1553
		return;
1554
1555
	// The big WHERE clause
1556
	$conditions = 'WHERE t.id_board = {int:id_board_from}
1557
		AND m.icon != {string:moved}';
1558
1559
	// DB parameters
1560
	$params = array(
1561
		'id_board_from' => $id_board_from,
1562
		'moved' => 'moved',
1563
	);
1564
1565
	// Only moving topics not posted in for x days?
1566
	if (!empty($max_days))
1567
	{
1568
		$conditions .= '
1569
			AND m.poster_time < {int:poster_time}';
1570
		$params['poster_time'] = time() - 3600 * 24 * $max_days;
1571
	}
1572
1573
	// Moving locked topics?
1574
	if ($locked)
1575
	{
1576
		$conditions .= '
1577
			AND t.locked = {int:locked}';
1578
		$params['locked'] = 1;
1579
	}
1580
1581
	// What about sticky topics?
1582
	if ($sticky)
1583
	{
1584
		$conditions .= '
1585
			AND t.is_sticky = {int:sticky}';
1586
		$params['sticky'] = 1;
1587
	}
1588
1589
	// How many topics are we converting?
1590
	if (!isset($_REQUEST['totaltopics']))
1591
	{
1592
		$request = $smcFunc['db_query']('', '
1593
			SELECT COUNT(*)
1594
			FROM {db_prefix}topics AS t
1595
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_last_msg)' .
1596
			$conditions,
1597
			$params
1598
		);
1599
		list ($total_topics) = $smcFunc['db_fetch_row']($request);
1600
		$smcFunc['db_free_result']($request);
1601
	}
1602
	else
1603
		$total_topics = (int) $_REQUEST['totaltopics'];
1604
1605
	// Seems like we need this here.
1606
	$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;
1607
1608
	if ($locked)
1609
		$context['continue_get_data'] .= ';locked';
1610
1611
	if ($sticky)
1612
		$context['continue_get_data'] .= ';sticky';
1613
1614
	$context['continue_get_data'] .= ';start=' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1615
1616
	// We have topics to move so start the process.
1617
	if (!empty($total_topics))
1618
	{
1619
		while ($context['start'] <= $total_topics)
1620
		{
1621
			// Lets get the topics.
1622
			$request = $smcFunc['db_query']('', '
1623
				SELECT t.id_topic
1624
				FROM {db_prefix}topics AS t
1625
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_last_msg)
1626
				' . $conditions . '
1627
				LIMIT 10',
1628
				$params
1629
			);
1630
1631
			// Get the ids.
1632
			$topics = array();
1633
			while ($row = $smcFunc['db_fetch_assoc']($request))
1634
				$topics[] = $row['id_topic'];
1635
1636
			// Just return if we don't have any topics left to move.
1637
			if (empty($topics))
1638
			{
1639
				cache_put_data('board-' . $id_board_from, null, 120);
1640
				cache_put_data('board-' . $id_board_to, null, 120);
1641
				redirectexit('action=admin;area=maintain;sa=topics;done=massmove');
1642
			}
1643
1644
			// Lets move them.
1645
			require_once($sourcedir . '/MoveTopic.php');
1646
			moveTopics($topics, $id_board_to);
1647
1648
			// We've done at least ten more topics.
1649
			$context['start'] += 10;
1650
1651
			// Lets wait a while.
1652
			if (time() - $context['start_time'] > 3)
1653
			{
1654
				// What's the percent?
1655
				$context['continue_percent'] = round(100 * ($context['start'] / $total_topics), 1);
1656
				$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'];
1657
1658
				// Let the template system do it's thang.
1659
				return;
1660
			}
1661
		}
1662
	}
1663
1664
	// Don't confuse admins by having an out of date cache.
1665
	cache_put_data('board-' . $id_board_from, null, 120);
1666
	cache_put_data('board-' . $id_board_to, null, 120);
1667
1668
	redirectexit('action=admin;area=maintain;sa=topics;done=massmove');
1669
}
1670
1671
/**
1672
 * Recalculate all members post counts
1673
 * it requires the admin_forum permission.
1674
 *
1675
 * - recounts all posts for members found in the message table
1676
 * - updates the members post count record in the members table
1677
 * - honors the boards post count flag
1678
 * - does not count posts in the recycle bin
1679
 * - zeros post counts for all members with no posts in the message table
1680
 * - runs as a delayed loop to avoid server overload
1681
 * - uses the not_done template in Admin.template
1682
 *
1683
 * The function redirects back to action=admin;area=maintain;sa=members when complete.
1684
 * It is accessed via ?action=admin;area=maintain;sa=members;activity=recountposts
1685
 */
1686
function MaintainRecountPosts()
1687
{
1688
	global $txt, $context, $modSettings, $smcFunc;
1689
1690
	// You have to be allowed in here
1691
	isAllowedTo('admin_forum');
1692
	checkSession('request');
1693
1694
	// Set up to the context.
1695
	$context['page_title'] = $txt['not_done_title'];
1696
	$context['continue_countdown'] = 3;
1697
	$context['continue_get_data'] = '';
1698
	$context['sub_template'] = 'not_done';
1699
1700
	// init
1701
	$increment = 200;
1702
	$_REQUEST['start'] = !isset($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
1703
1704
	// Ask for some extra time, on big boards this may take a bit
1705
	@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...
1706
1707
	// Only run this query if we don't have the total number of members that have posted
1708
	if (!isset($_SESSION['total_members']))
1709
	{
1710
		validateToken('admin-maint');
1711
1712
		$request = $smcFunc['db_query']('', '
1713
			SELECT COUNT(DISTINCT m.id_member)
1714
			FROM {db_prefix}messages AS m
1715
			JOIN {db_prefix}boards AS b on m.id_board = b.id_board
1716
			WHERE m.id_member != 0
1717
				AND b.count_posts = 0',
1718
			array(
1719
			)
1720
		);
1721
1722
		// save it so we don't do this again for this task
1723
		list ($_SESSION['total_members']) = $smcFunc['db_fetch_row']($request);
1724
		$smcFunc['db_free_result']($request);
1725
	}
1726
	else
1727
		validateToken('admin-recountposts');
1728
1729
	// Lets get a group of members and determine their post count (from the boards that have post count enabled of course).
1730
	$request = $smcFunc['db_query']('', '
1731
		SELECT /*!40001 SQL_NO_CACHE */ m.id_member, COUNT(m.id_member) AS posts
1732
		FROM ({db_prefix}messages AS m, {db_prefix}boards AS b)
1733
		WHERE m.id_member != {int:zero}
1734
			AND b.count_posts = {int:zero}
1735
			AND m.id_board = b.id_board ' . (!empty($modSettings['recycle_enable']) ? '
1736
			AND b.id_board != {int:recycle}' : '') . '
1737
		GROUP BY m.id_member
1738
		LIMIT {int:start}, {int:number}',
1739
		array(
1740
			'start' => $_REQUEST['start'],
1741
			'number' => $increment,
1742
			'recycle' => $modSettings['recycle_board'],
1743
			'zero' => 0,
1744
		)
1745
	);
1746
	$total_rows = $smcFunc['db_num_rows']($request);
1747
1748
	// Update the post count for this group
1749
	while ($row = $smcFunc['db_fetch_assoc']($request))
1750
	{
1751
		$smcFunc['db_query']('', '
1752
			UPDATE {db_prefix}members
1753
			SET posts = {int:posts}
1754
			WHERE id_member = {int:row}',
1755
			array(
1756
				'row' => $row['id_member'],
1757
				'posts' => $row['posts'],
1758
			)
1759
		);
1760
	}
1761
	$smcFunc['db_free_result']($request);
1762
1763
	// Continue?
1764
	if ($total_rows == $increment)
1765
	{
1766
		$_REQUEST['start'] += $increment;
1767
		$context['continue_get_data'] = '?action=admin;area=maintain;sa=members;activity=recountposts;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1768
		$context['continue_percent'] = round(100 * $_REQUEST['start'] / $_SESSION['total_members']);
1769
1770
		createToken('admin-recountposts');
1771
		$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-recountposts_token_var'] . '" value="' . $context['admin-recountposts_token'] . '">';
1772
1773
		if (function_exists('apache_reset_timeout'))
1774
			apache_reset_timeout();
1775
		return;
1776
	}
1777
1778
	// final steps ... made more difficult since we don't yet support sub-selects on joins
1779
	// place all members who have posts in the message table in a temp table
1780
	$createTemporary = $smcFunc['db_query']('', '
1781
		CREATE TEMPORARY TABLE {db_prefix}tmp_maint_recountposts (
1782
			id_member mediumint(8) unsigned NOT NULL default {string:string_zero},
1783
			PRIMARY KEY (id_member)
1784
		)
1785
		SELECT m.id_member
1786
		FROM ({db_prefix}messages AS m,{db_prefix}boards AS b)
1787
		WHERE m.id_member != {int:zero}
1788
			AND b.count_posts = {int:zero}
1789
			AND m.id_board = b.id_board ' . (!empty($modSettings['recycle_enable']) ? '
1790
			AND b.id_board != {int:recycle}' : '') . '
1791
		GROUP BY m.id_member',
1792
		array(
1793
			'zero' => 0,
1794
			'string_zero' => '0',
1795
			'db_error_skip' => true,
1796
			'recycle' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
1797
		)
1798
	) !== false;
1799
1800 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...
1801
	{
1802
		// outer join the members table on the temporary table finding the members that have a post count but no posts in the message table
1803
		$request = $smcFunc['db_query']('', '
1804
			SELECT mem.id_member, mem.posts
1805
			FROM {db_prefix}members AS mem
1806
			LEFT OUTER JOIN {db_prefix}tmp_maint_recountposts AS res
1807
			ON res.id_member = mem.id_member
1808
			WHERE res.id_member IS null
1809
				AND mem.posts != {int:zero}',
1810
			array(
1811
				'zero' => 0,
1812
			)
1813
		);
1814
1815
		// set the post count to zero for any delinquents we may have found
1816
		while ($row = $smcFunc['db_fetch_assoc']($request))
1817
		{
1818
			$smcFunc['db_query']('', '
1819
				UPDATE {db_prefix}members
1820
				SET posts = {int:zero}
1821
				WHERE id_member = {int:row}',
1822
				array(
1823
					'row' => $row['id_member'],
1824
					'zero' => 0,
1825
				)
1826
			);
1827
		}
1828
		$smcFunc['db_free_result']($request);
1829
	}
1830
1831
	// all done
1832
	unset($_SESSION['total_members']);
1833
	$context['maintenance_finished'] = $txt['maintain_recountposts'];
1834
	redirectexit('action=admin;area=maintain;sa=members;done=recountposts');
1835
}
1836
1837
/**
1838
 * Generates a list of integration hooks for display
1839
 * Accessed through ?action=admin;area=maintain;sa=hooks;
1840
 * Allows for removal or disabling of selected hooks
1841
 */
1842
function list_integration_hooks()
1843
{
1844
	global $sourcedir, $scripturl, $context, $txt;
1845
1846
	$context['filter_url'] = '';
1847
	$context['current_filter'] = '';
1848
	$currentHooks = get_integration_hooks();
1849
	if (isset($_GET['filter']) && in_array($_GET['filter'], array_keys($currentHooks)))
1850
	{
1851
		$context['filter_url'] = ';filter=' . $_GET['filter'];
1852
		$context['current_filter'] = $_GET['filter'];
1853
	}
1854
1855
	if (!empty($_REQUEST['do']) && isset($_REQUEST['hook']) && isset($_REQUEST['function']))
1856
	{
1857
		checkSession('request');
1858
		validateToken('admin-hook', 'request');
1859
1860
		if ($_REQUEST['do'] == 'remove')
1861
			remove_integration_function($_REQUEST['hook'], urldecode($_REQUEST['function']));
1862
1863
		else
1864
		{
1865
			$function_remove = urldecode($_REQUEST['function']) . (($_REQUEST['do'] == 'disable') ? '' : '!');
1866
			$function_add = urldecode($_REQUEST['function']) . (($_REQUEST['do'] == 'disable') ? '!' : '');
1867
1868
			remove_integration_function($_REQUEST['hook'], $function_remove);
1869
			add_integration_function($_REQUEST['hook'], $function_add);
1870
1871
			redirectexit('action=admin;area=maintain;sa=hooks' . $context['filter_url']);
1872
		}
1873
	}
1874
1875
	createToken('admin-hook', 'request');
1876
1877
	$list_options = array(
1878
		'id' => 'list_integration_hooks',
1879
		'title' => $txt['hooks_title_list'],
1880
		'items_per_page' => 20,
1881
		'base_href' => $scripturl . '?action=admin;area=maintain;sa=hooks' . $context['filter_url'] . ';' . $context['session_var'] . '=' . $context['session_id'],
1882
		'default_sort_col' => 'hook_name',
1883
		'get_items' => array(
1884
			'function' => 'get_integration_hooks_data',
1885
		),
1886
		'get_count' => array(
1887
			'function' => 'get_integration_hooks_count',
1888
		),
1889
		'no_items_label' => $txt['hooks_no_hooks'],
1890
		'columns' => array(
1891
			'hook_name' => array(
1892
				'header' => array(
1893
					'value' => $txt['hooks_field_hook_name'],
1894
				),
1895
				'data' => array(
1896
					'db' => 'hook_name',
1897
				),
1898
				'sort' =>  array(
1899
					'default' => 'hook_name',
1900
					'reverse' => 'hook_name DESC',
1901
				),
1902
			),
1903
			'function_name' => array(
1904
				'header' => array(
1905
					'value' => $txt['hooks_field_function_name'],
1906
				),
1907
				'data' => array(
1908
					'function' => function ($data) use ($txt)
1909
					{
1910
						// Show a nice icon to indicate this is an instance.
1911
						$instance = (!empty($data['instance']) ? '<span class="generic_icons news" title="'. $txt['hooks_field_function_method'] .'"></span> ' : '');
1912
1913
						if (!empty($data['included_file']))
1914
							return $instance . $txt['hooks_field_function'] . ': ' . $data['real_function'] . '<br>' . $txt['hooks_field_included_file'] . ': ' . $data['included_file'];
1915
1916
						else
1917
							return $instance . $data['real_function'];
1918
					},
1919
				),
1920
				'sort' =>  array(
1921
					'default' => 'function_name',
1922
					'reverse' => 'function_name DESC',
1923
				),
1924
			),
1925
			'file_name' => array(
1926
				'header' => array(
1927
					'value' => $txt['hooks_field_file_name'],
1928
				),
1929
				'data' => array(
1930
					'db' => 'file_name',
1931
				),
1932
				'sort' =>  array(
1933
					'default' => 'file_name',
1934
					'reverse' => 'file_name DESC',
1935
				),
1936
			),
1937
			'status' => array(
1938
				'header' => array(
1939
					'value' => $txt['hooks_field_hook_exists'],
1940
					'style' => 'width:3%;',
1941
				),
1942
				'data' => array(
1943
					'function' => function ($data) use ($txt, $scripturl, $context)
1944
					{
1945
						$change_status = array('before' => '', 'after' => '');
1946
1947
							$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">';
1948
							$change_status['after'] = '</a>';
1949
1950
						return $change_status['before'] . '<span class="generic_icons post_moderation_' . $data['status'] . '" title="' . $data['img_text'] . '"></span>';
1951
					},
1952
					'class' => 'centertext',
1953
				),
1954
				'sort' =>  array(
1955
					'default' => 'status',
1956
					'reverse' => 'status DESC',
1957
				),
1958
			),
1959
		),
1960
		'additional_rows' => array(
1961
			array(
1962
				'position' => 'after_title',
1963
				'value' => $txt['hooks_disable_instructions'] . '<br>
1964
					' . $txt['hooks_disable_legend'] . ':
1965
				<ul style="list-style: none;">
1966
					<li><span class="generic_icons post_moderation_allow"></span> ' . $txt['hooks_disable_legend_exists'] . '</li>
1967
					<li><span class="generic_icons post_moderation_moderate"></span> ' . $txt['hooks_disable_legend_disabled'] . '</li>
1968
					<li><span class="generic_icons post_moderation_deny"></span> ' . $txt['hooks_disable_legend_missing'] . '</li>
1969
				</ul>'
1970
			),
1971
		),
1972
	);
1973
1974
	$list_options['columns']['remove'] = array(
1975
		'header' => array(
1976
			'value' => $txt['hooks_button_remove'],
1977
			'style' => 'width:3%',
1978
		),
1979
		'data' => array(
1980
			'function' => function ($data) use ($txt, $scripturl, $context)
1981
			{
1982
				if (!$data['hook_exists'])
1983
					return '
1984
					<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">
1985
						<span class="generic_icons delete" title="' . $txt['hooks_button_remove'] . '"></span>
1986
					</a>';
1987
			},
1988
			'class' => 'centertext',
1989
		),
1990
	);
1991
	$list_options['form'] = array(
1992
		'href' => $scripturl . '?action=admin;area=maintain;sa=hooks' . $context['filter_url'] . ';' . $context['session_var'] . '=' . $context['session_id'],
1993
		'name' => 'list_integration_hooks',
1994
	);
1995
1996
1997
	require_once($sourcedir . '/Subs-List.php');
1998
	createList($list_options);
1999
2000
	$context['page_title'] = $txt['hooks_title_list'];
2001
	$context['sub_template'] = 'show_list';
2002
	$context['default_list'] = 'list_integration_hooks';
2003
}
2004
2005
/**
2006
 * Gets all of the files in a directory and its children directories
2007
 *
2008
 * @param string $dir_path The path to the directory
2009
 * @return array An array containing information about the files found in the specified directory and its children
2010
 */
2011
function get_files_recursive($dir_path)
2012
{
2013
	$files = array();
2014
2015
	if ($dh = opendir($dir_path))
2016
	{
2017
		while (($file = readdir($dh)) !== false)
2018
		{
2019
			if ($file != '.' && $file != '..')
2020
			{
2021
				if (is_dir($dir_path . '/' . $file))
2022
					$files = array_merge($files, get_files_recursive($dir_path . '/' . $file));
2023
				else
2024
					$files[] = array('dir' => $dir_path, 'name' => $file);
2025
			}
2026
		}
2027
	}
2028
	closedir($dh);
2029
2030
	return $files;
2031
}
2032
2033
/**
2034
 * Callback function for the integration hooks list (list_integration_hooks)
2035
 * Gets all of the hooks in the system and their status
2036
 *
2037
 * @param int $start The item to start with (for pagination purposes)
2038
 * @param int $per_page How many items to display on each page
2039
 * @param string $sort A string indicating how to sort things
2040
 * @return array An array of information about the integration hooks
2041
 */
2042
function get_integration_hooks_data($start, $per_page, $sort)
2043
{
2044
	global $boarddir, $sourcedir, $settings, $txt, $context, $scripturl;
2045
2046
	$hooks = $temp_hooks = get_integration_hooks();
2047
	$hooks_data = $temp_data = $hook_status = array();
2048
2049
	$files = get_files_recursive($sourcedir);
2050
	if (!empty($files))
2051
	{
2052
		foreach ($files as $file)
2053
		{
2054
			if (is_file($file['dir'] . '/' . $file['name']) && substr($file['name'], -4) === '.php')
2055
			{
2056
				$fp = fopen($file['dir'] . '/' . $file['name'], 'rb');
2057
				$fc = fread($fp, filesize($file['dir'] . '/' . $file['name']));
2058
				fclose($fp);
2059
2060
				foreach ($temp_hooks as $hook => $allFunctions)
2061
				{
2062
					foreach ($allFunctions as $rawFunc)
2063
					{
2064
						// Get the hook info.
2065
						$hookParsedData = get_hook_info_from_raw($rawFunc);
2066
2067
						if (substr($hook, -8) === '_include')
2068
						{
2069
							$hook_status[$hook][$hookParsedData['pureFunc']]['exists'] = file_exists(strtr(trim($rawFunc), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])));
2070
							// I need to know if there is at least one function called in this file.
2071
							$temp_data['include'][$hookParsedData['pureFunc']] = array('hook' => $hook, 'function' => $hookParsedData['pureFunc']);
2072
							unset($temp_hooks[$hook][$rawFunc]);
2073
						}
2074
						elseif (strpos(str_replace(' (', '(', $fc), 'function ' . trim($hookParsedData['pureFunc']) . '(') !== false)
2075
						{
2076
							$hook_status[$hook][$hookParsedData['pureFunc']] = $hookParsedData;
2077
							$hook_status[$hook][$hookParsedData['pureFunc']]['exists'] = true;
2078
							$hook_status[$hook][$hookParsedData['pureFunc']]['in_file'] = (!empty($file['name']) ? $file['name'] : (!empty($hookParsedData['hookFile']) ? $hookParsedData['hookFile'] : ''));
2079
2080
							// Does the hook has its own file?
2081
							if (!empty($hookParsedData['hookFile']))
2082
								$temp_data['include'][$hookParsedData['pureFunc']] = array('hook' => $hook, 'function' => $hookParsedData['pureFunc']);
2083
2084
							// 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)
2085
							$temp_data['function'][$file['name']][$hookParsedData['pureFunc']] = $hookParsedData['enabled'];
2086
							unset($temp_hooks[$hook][$rawFunc]);
2087
						}
2088
					}
2089
				}
2090
			}
2091
		}
2092
	}
2093
2094
	$sort_types = array(
2095
		'hook_name' => array('hook', SORT_ASC),
2096
		'hook_name DESC' => array('hook', SORT_DESC),
2097
		'function_name' => array('function', SORT_ASC),
2098
		'function_name DESC' => array('function', SORT_DESC),
2099
		'file_name' => array('file_name', SORT_ASC),
2100
		'file_name DESC' => array('file_name', SORT_DESC),
2101
		'status' => array('status', SORT_ASC),
2102
		'status DESC' => array('status', SORT_DESC),
2103
	);
2104
2105
	$sort_options = $sort_types[$sort];
2106
	$sort = array();
2107
	$hooks_filters = array();
2108
2109
	foreach ($hooks as $hook => $functions)
2110
		$hooks_filters[] = '<option' . ($context['current_filter'] == $hook ? ' selected ' : '') . ' value="' . $hook . '">' . $hook . '</option>';
2111
2112
	if (!empty($hooks_filters))
2113
		$context['insert_after_template'] .= '
2114
		<script>
2115
			var hook_name_header = document.getElementById(\'header_list_integration_hooks_hook_name\');
2116
			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>'). ';
2117
		</script>';
2118
2119
	$temp_data = array();
2120
	$id = 0;
2121
2122
	foreach ($hooks as $hook => $functions)
2123
	{
2124
		if (empty($context['filter']) || (!empty($context['filter']) && $context['filter'] == $hook))
2125
		{
2126
			foreach ($functions as $rawFunc)
2127
			{
2128
				// Get the hook info.
2129
				$hookParsedData = get_hook_info_from_raw($rawFunc);
2130
2131
				$hook_exists = !empty($hook_status[$hook][$hookParsedData['pureFunc']]['exists']);
2132
				$file_name = isset($hook_status[$hook][$hookParsedData['pureFunc']]['in_file']) ? $hook_status[$hook][$hookParsedData['pureFunc']]['in_file'] : ((substr($hook, -8) === '_include') ? basename($rawFunc) : $hookParsedData['hookFile']);
0 ignored issues
show
Unused Code introduced by
$file_name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2133
				$sort[] = $sort_options[0];
2134
2135
				$temp_data[] = array(
2136
					'id' => 'hookid_' . $id++,
2137
					'hook_name' => $hook,
2138
					'function_name' => $hookParsedData['rawData'],
2139
					'real_function' => $hookParsedData['pureFunc'],
2140
					'included_file' => !empty($hookParsedData['absPath']) ? $hookParsedData['absPath'] : '',
2141
					'file_name' => (isset($hook_status[$hook][$hookParsedData['pureFunc']]['in_file']) ? $hook_status[$hook][$hookParsedData['pureFunc']]['in_file'] : (!empty($hookParsedData['hookFile']) ? $hookParsedData['hookFile'] : '')),
2142
					'instance' => $hookParsedData['object'],
2143
					'hook_exists' => $hook_exists,
2144
					'status' => $hook_exists ? ($hookParsedData['enabled'] ? 'allow' : 'moderate') : 'deny',
2145
					'img_text' => $txt['hooks_' . ($hook_exists ? ($hookParsedData['enabled'] ? 'active' : 'disabled') : 'missing')],
2146
					'enabled' => $hookParsedData['enabled'],
2147
					'can_be_disabled' => !isset($hook_status[$hook][$hookParsedData['pureFunc']]['enabled']),
2148
				);
2149
			}
2150
		}
2151
	}
2152
2153
	array_multisort($sort, $sort_options[1], $temp_data);
2154
2155
	$counter = 0;
2156
	$start++;
2157
2158
	foreach ($temp_data as $data)
2159
	{
2160
		if (++$counter < $start)
2161
			continue;
2162
		elseif ($counter == $start + $per_page)
2163
			break;
2164
2165
		$hooks_data[] = $data;
2166
	}
2167
2168
	return $hooks_data;
2169
}
2170
2171
/**
2172
 * Simply returns the total count of integration hooks
2173
 * Used by the integration hooks list function (list_integration_hooks)
2174
 *
2175
 * @return int The number of hooks currently in use
2176
 */
2177
function get_integration_hooks_count()
2178
{
2179
	global $context;
2180
2181
	$hooks = get_integration_hooks();
2182
	$hooks_count = 0;
2183
2184
	$context['filter'] = false;
2185
	if (isset($_GET['filter']))
2186
		$context['filter'] = $_GET['filter'];
2187
2188
	foreach ($hooks as $hook => $functions)
2189
	{
2190
		if (empty($context['filter']) || (!empty($context['filter']) && $context['filter'] == $hook))
2191
			$hooks_count += count($functions);
2192
	}
2193
2194
	return $hooks_count;
2195
}
2196
2197
/**
2198
 * Parses modSettings to create integration hook array
2199
 *
2200
 * @return array An array of information about the integration hooks
2201
 */
2202
function get_integration_hooks()
2203
{
2204
	global $modSettings;
2205
	static $integration_hooks;
2206
2207
	if (!isset($integration_hooks))
2208
	{
2209
		$integration_hooks = array();
2210
		foreach ($modSettings as $key => $value)
2211
		{
2212
			if (!empty($value) && substr($key, 0, 10) === 'integrate_')
2213
				$integration_hooks[$key] = explode(',', $value);
2214
		}
2215
	}
2216
2217
	return $integration_hooks;
2218
}
2219
2220
/**
2221
 * Parses each hook data and returns an array.
2222
 *
2223
 * @param string $rawData A string as it was saved to the DB.
2224
 * @return array everything found in the string itself
2225
 */
2226
function get_hook_info_from_raw($rawData)
2227
{
2228
	global $boarddir, $settings, $sourcedir;
2229
2230
	// A single string can hold tons of info!
2231
	$hookData = array(
2232
		'object' => false,
2233
		'enabled' => true,
2234
		'fileExists' => false,
2235
		'absPath' => '',
2236
		'hookFile' => '',
2237
		'pureFunc' => '',
2238
		'method' => '',
2239
		'class' => '',
2240
		'rawData' => $rawData,
2241
	);
2242
2243
	// Meh...
2244
	if (empty($rawData))
2245
		return $hookData;
2246
2247
	// For convenience purposes only!
2248
	$modFunc = $rawData;
2249
2250
	// Any files?
2251
	if (strpos($modFunc, '|') !== false)
2252
	{
2253
		list ($hookData['hookFile'], $modFunc) = explode('|', $modFunc);
2254
2255
		// Does the file exists? who knows!
2256
		if (empty($settings['theme_dir']))
2257
			$hookData['absPath'] = strtr(trim($hookData['hookFile']), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
2258
2259
		else
2260
			$hookData['absPath'] = strtr(trim($hookData['hookFile']), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2261
2262
		$hookData['fileExists'] = file_exists($hookData['absPath']);
2263
		$hookData['hookFile'] = basename($hookData['hookFile']);
2264
	}
2265
2266
	// Hook is an instance.
2267 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...
2268
	{
2269
		$modFunc = str_replace('#', '', $modFunc);
2270
		$hookData['object'] = true;
2271
	}
2272
2273
	// Hook is "disabled"
2274 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...
2275
	{
2276
		$modFunc = str_replace('!', '', $modFunc);
2277
		$hookData['enabled'] = false;
2278
	}
2279
2280
	// Handling methods?
2281
	if (strpos($modFunc, '::') !== false)
2282
	{
2283
		list ($hookData['class'], $hookData['method']) = explode('::', $modFunc);
2284
		$hookData['pureFunc'] = $hookData['method'];
2285
	}
2286
2287
	else
2288
		$hookData['pureFunc'] = $modFunc;
2289
2290
	return $hookData;
2291
}
2292
2293
?>
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...