Passed
Push — release-2.1 ( 0c2197...207d2d )
by Jeremy
05:47
created

ConvertEntities()   F

Complexity

Conditions 28
Paths 12304

Size

Total Lines 218
Code Lines 122

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 28
eloc 122
c 1
b 0
f 0
nop 0
dl 0
loc 218
rs 0
nc 12304

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 2018 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Main dispatcher, the maintenance access point.
21
 * This, as usual, checks permissions, loads language files, and forwards to the actual workers.
22
 */
23
function ManageMaintenance()
24
{
25
	global $txt, $context;
26
27
	// You absolutely must be an admin by here!
28
	isAllowedTo('admin_forum');
29
30
	// Need something to talk about?
31
	loadLanguage('ManageMaintenance');
32
	loadTemplate('ManageMaintenance');
33
34
	// This uses admin tabs - as it should!
35
	$context[$context['admin_menu_name']]['tab_data'] = array(
36
		'title' => $txt['maintain_title'],
37
		'description' => $txt['maintain_info'],
38
		'tabs' => array(
39
			'routine' => array(),
40
			'database' => array(),
41
			'members' => array(),
42
			'topics' => array(),
43
		),
44
	);
45
46
	// So many things you can do - but frankly I won't let you - just these!
47
	$subActions = array(
48
		'routine' => array(
49
			'function' => 'MaintainRoutine',
50
			'template' => 'maintain_routine',
51
			'activities' => array(
52
				'version' => 'VersionDetail',
53
				'repair' => 'MaintainFindFixErrors',
54
				'recount' => 'AdminBoardRecount',
55
				'logs' => 'MaintainEmptyUnimportantLogs',
56
				'cleancache' => 'MaintainCleanCache',
57
			),
58
		),
59
		'database' => array(
60
			'function' => 'MaintainDatabase',
61
			'template' => 'maintain_database',
62
			'activities' => array(
63
				'optimize' => 'OptimizeTables',
64
				'convertentities' => 'ConvertEntities',
65
				'convertmsgbody' => 'ConvertMsgBody',
66
			),
67
		),
68
		'members' => array(
69
			'function' => 'MaintainMembers',
70
			'template' => 'maintain_members',
71
			'activities' => array(
72
				'reattribute' => 'MaintainReattributePosts',
73
				'purgeinactive' => 'MaintainPurgeInactiveMembers',
74
				'recountposts' => 'MaintainRecountPosts',
75
			),
76
		),
77
		'topics' => array(
78
			'function' => 'MaintainTopics',
79
			'template' => 'maintain_topics',
80
			'activities' => array(
81
				'massmove' => 'MaintainMassMoveTopics',
82
				'pruneold' => 'MaintainRemoveOldPosts',
83
				'olddrafts' => 'MaintainRemoveOldDrafts',
84
			),
85
		),
86
		'hooks' => array(
87
			'function' => 'list_integration_hooks',
88
		),
89
		'destroy' => array(
90
			'function' => 'Destroy',
91
			'activities' => array(),
92
		),
93
	);
94
95
	call_integration_hook('integrate_manage_maintenance', array(&$subActions));
96
97
	// Yep, sub-action time!
98
	if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]))
99
		$subAction = $_REQUEST['sa'];
100
	else
101
		$subAction = 'routine';
102
103
	// Doing something special?
104
	if (isset($_REQUEST['activity']) && isset($subActions[$subAction]['activities'][$_REQUEST['activity']]))
105
		$activity = $_REQUEST['activity'];
106
107
	// Set a few things.
108
	$context['page_title'] = $txt['maintain_title'];
109
	$context['sub_action'] = $subAction;
110
	$context['sub_template'] = !empty($subActions[$subAction]['template']) ? $subActions[$subAction]['template'] : '';
111
112
	// Finally fall through to what we are doing.
113
	call_helper($subActions[$subAction]['function']);
114
115
	// Any special activity?
116
	if (isset($activity))
117
		call_helper($subActions[$subAction]['activities'][$activity]);
118
119
	// Create a maintenance token.  Kinda hard to do it any other way.
120
	createToken('admin-maint');
121
}
122
123
/**
124
 * Supporting function for the database maintenance area.
125
 */
126
function MaintainDatabase()
127
{
128
	global $context, $db_type, $db_character_set, $modSettings, $smcFunc, $txt;
129
130
	// Show some conversion options?
131
	$context['convert_entities'] = isset($modSettings['global_character_set']) && $modSettings['global_character_set'] === 'UTF-8';
132
133
	if ($db_type == 'mysql')
134
	{
135
		db_extend('packages');
136
137
		$colData = $smcFunc['db_list_columns']('{db_prefix}messages', true);
138
		foreach ($colData as $column)
139
			if ($column['name'] == 'body')
140
				$body_type = $column['type'];
141
142
		$context['convert_to'] = $body_type == 'text' ? 'mediumtext' : 'text';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $body_type does not seem to be defined for all execution paths leading up to this point.
Loading history...
143
		$context['convert_to_suggest'] = ($body_type != 'text' && !empty($modSettings['max_messageLength']) && $modSettings['max_messageLength'] < 65536);
144
	}
145
146
	if (isset($_GET['done']) && $_GET['done'] == 'convertentities')
147
		$context['maintenance_finished'] = $txt['entity_convert_title'];
148
}
149
150
/**
151
 * Supporting function for the routine maintenance area.
152
 */
153
function MaintainRoutine()
154
{
155
	global $context, $txt;
156
157
	if (isset($_GET['done']) && $_GET['done'] == 'recount')
158
		$context['maintenance_finished'] = $txt['maintain_recount'];
159
}
160
161
/**
162
 * Supporting function for the members maintenance area.
163
 */
164
function MaintainMembers()
165
{
166
	global $context, $smcFunc, $txt;
167
168
	// Get membergroups - for deleting members and the like.
169
	$result = $smcFunc['db_query']('', '
170
		SELECT id_group, group_name
171
		FROM {db_prefix}membergroups',
172
		array(
173
		)
174
	);
175
	$context['membergroups'] = array(
176
		array(
177
			'id' => 0,
178
			'name' => $txt['maintain_members_ungrouped']
179
		),
180
	);
181
	while ($row = $smcFunc['db_fetch_assoc']($result))
182
	{
183
		$context['membergroups'][] = array(
184
			'id' => $row['id_group'],
185
			'name' => $row['group_name']
186
		);
187
	}
188
	$smcFunc['db_free_result']($result);
189
190
	if (isset($_GET['done']) && $_GET['done'] == 'recountposts')
191
		$context['maintenance_finished'] = $txt['maintain_recountposts'];
192
193
	loadJavaScriptFile('suggest.js', array('defer' => false, 'minimize' => true), 'smf_suggest');
194
}
195
196
/**
197
 * Supporting function for the topics maintenance area.
198
 */
199
function MaintainTopics()
200
{
201
	global $context, $smcFunc, $txt, $sourcedir;
202
203
	// Let's load up the boards in case they are useful.
204
	$result = $smcFunc['db_query']('order_by_board_order', '
205
		SELECT b.id_board, b.name, b.child_level, c.name AS cat_name, c.id_cat
206
		FROM {db_prefix}boards AS b
207
			LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
208
		WHERE {query_see_board}
209
			AND redirect = {string:blank_redirect}',
210
		array(
211
			'blank_redirect' => '',
212
		)
213
	);
214
	$context['categories'] = array();
215
	while ($row = $smcFunc['db_fetch_assoc']($result))
216
	{
217
		if (!isset($context['categories'][$row['id_cat']]))
218
			$context['categories'][$row['id_cat']] = array(
219
				'name' => $row['cat_name'],
220
				'boards' => array()
221
			);
222
223
		$context['categories'][$row['id_cat']]['boards'][$row['id_board']] = array(
224
			'id' => $row['id_board'],
225
			'name' => $row['name'],
226
			'child_level' => $row['child_level']
227
		);
228
	}
229
	$smcFunc['db_free_result']($result);
230
231
	require_once($sourcedir . '/Subs-Boards.php');
232
	sortCategories($context['categories']);
233
234
	if (isset($_GET['done']) && $_GET['done'] == 'purgeold')
235
		$context['maintenance_finished'] = $txt['maintain_old'];
236
	elseif (isset($_GET['done']) && $_GET['done'] == 'massmove')
237
		$context['maintenance_finished'] = $txt['move_topics_maintenance'];
238
}
239
240
/**
241
 * Find and fix all errors on the forum.
242
 */
243
function MaintainFindFixErrors()
244
{
245
	global $sourcedir;
246
247
	// Honestly, this should be done in the sub function.
248
	validateToken('admin-maint');
249
250
	require_once($sourcedir . '/RepairBoards.php');
251
	RepairBoards();
252
}
253
254
/**
255
 * Wipes the whole cache directory.
256
 * This only applies to SMF's own cache directory, though.
257
 */
258
function MaintainCleanCache()
259
{
260
	global $context, $txt;
261
262
	checkSession();
263
	validateToken('admin-maint');
264
265
	// Just wipe the whole cache directory!
266
	clean_cache();
267
268
	$context['maintenance_finished'] = $txt['maintain_cache'];
269
}
270
271
/**
272
 * Empties all uninmportant logs
273
 */
274
function MaintainEmptyUnimportantLogs()
275
{
276
	global $context, $smcFunc, $txt;
277
278
	checkSession();
279
	validateToken('admin-maint');
280
281
	// No one's online now.... MUHAHAHAHA :P.
282
	$smcFunc['db_query']('', '
283
		DELETE FROM {db_prefix}log_online');
284
285
	// Dump the banning logs.
286
	$smcFunc['db_query']('', '
287
		DELETE FROM {db_prefix}log_banned');
288
289
	// Start id_error back at 0 and dump the error log.
290
	$smcFunc['db_query']('truncate_table', '
291
		TRUNCATE {db_prefix}log_errors');
292
293
	// Clear out the spam log.
294
	$smcFunc['db_query']('', '
295
		DELETE FROM {db_prefix}log_floodcontrol');
296
297
	// Last but not least, the search logs!
298
	$smcFunc['db_query']('truncate_table', '
299
		TRUNCATE {db_prefix}log_search_topics');
300
301
	$smcFunc['db_query']('truncate_table', '
302
		TRUNCATE {db_prefix}log_search_messages');
303
304
	$smcFunc['db_query']('truncate_table', '
305
		TRUNCATE {db_prefix}log_search_results');
306
307
	updateSettings(array('search_pointer' => 0));
308
309
	$context['maintenance_finished'] = $txt['maintain_logs'];
310
}
311
312
/**
313
 * Oh noes! I'd document this but that would give it away
314
 */
315
function Destroy()
316
{
317
	global $context;
318
319
	echo '<!DOCTYPE html>
320
		<html', $context['right_to_left'] ? ' dir="rtl"' : '', '><head><title>', $context['forum_name_html_safe'], ' deleted!</title></head>
321
		<body style="background-color: orange; font-family: arial, sans-serif; text-align: center;">
322
		<div style="margin-top: 8%; font-size: 400%; color: black;">Oh my, you killed ', $context['forum_name_html_safe'], '!</div>
323
		<div style="margin-top: 7%; font-size: 500%; color: red;"><strong>You lazy bum!</strong></div>
324
		</body></html>';
325
	obExit(false);
326
}
327
328
/**
329
 * Convert the column "body" of the table {db_prefix}messages from TEXT to MEDIUMTEXT and vice versa.
330
 * It requires the admin_forum permission.
331
 * This is needed only for MySQL.
332
 * During the conversion from MEDIUMTEXT to TEXT it check if any of the posts exceed the TEXT length and if so it aborts.
333
 * This action is linked from the maintenance screen (if it's applicable).
334
 * Accessed by ?action=admin;area=maintain;sa=database;activity=convertmsgbody.
335
 *
336
 * @uses the convert_msgbody sub template of the Admin template.
337
 */
338
function ConvertMsgBody()
339
{
340
	global $scripturl, $context, $txt, $db_type;
341
	global $modSettings, $smcFunc, $time_start;
342
343
	// Show me your badge!
344
	isAllowedTo('admin_forum');
345
346
	if ($db_type != 'mysql')
347
		return;
348
349
	db_extend('packages');
350
351
	$colData = $smcFunc['db_list_columns']('{db_prefix}messages', true);
352
	foreach ($colData as $column)
353
		if ($column['name'] == 'body')
354
			$body_type = $column['type'];
355
356
	$context['convert_to'] = $body_type == 'text' ? 'mediumtext' : 'text';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $body_type does not seem to be defined for all execution paths leading up to this point.
Loading history...
357
358
	if ($body_type == 'text' || ($body_type != 'text' && isset($_POST['do_conversion'])))
359
	{
360
		checkSession();
361
		validateToken('admin-maint');
362
363
		// Make it longer so we can do their limit.
364
		if ($body_type == 'text')
365
			$smcFunc['db_change_column']('{db_prefix}messages', 'body', array('type' => 'mediumtext'));
366
		// Shorten the column so we can have a bit (literally per record) less space occupied
367
		else
368
			$smcFunc['db_change_column']('{db_prefix}messages', 'body', array('type' => 'text'));
369
370
		// 3rd party integrations may be interested in knowning about this.
371
		call_integration_hook('integrate_convert_msgbody', array($body_type));
372
373
		$colData = $smcFunc['db_list_columns']('{db_prefix}messages', true);
374
		foreach ($colData as $column)
375
			if ($column['name'] == 'body')
376
				$body_type = $column['type'];
377
378
		$context['maintenance_finished'] = $txt[$context['convert_to'] . '_title'];
379
		$context['convert_to'] = $body_type == 'text' ? 'mediumtext' : 'text';
380
		$context['convert_to_suggest'] = ($body_type != 'text' && !empty($modSettings['max_messageLength']) && $modSettings['max_messageLength'] < 65536);
381
382
		return;
383
	}
384
	elseif ($body_type != 'text' && (!isset($_POST['do_conversion']) || isset($_POST['cont'])))
385
	{
386
		checkSession();
387
		if (empty($_REQUEST['start']))
388
			validateToken('admin-maint');
389
		else
390
			validateToken('admin-convertMsg');
391
392
		$context['page_title'] = $txt['not_done_title'];
393
		$context['continue_post_data'] = '';
394
		$context['continue_countdown'] = 3;
395
		$context['sub_template'] = 'not_done';
396
		$increment = 500;
397
		$id_msg_exceeding = isset($_POST['id_msg_exceeding']) ? explode(',', $_POST['id_msg_exceeding']) : array();
398
399
		$request = $smcFunc['db_query']('', '
400
			SELECT COUNT(*) as count
401
			FROM {db_prefix}messages',
402
			array()
403
		);
404
		list($max_msgs) = $smcFunc['db_fetch_row']($request);
405
		$smcFunc['db_free_result']($request);
406
407
		// Try for as much time as possible.
408
		@set_time_limit(600);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for set_time_limit(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

408
		/** @scrutinizer ignore-unhandled */ @set_time_limit(600);

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

555
			/** @scrutinizer ignore-unhandled */ @apache_reset_timeout();

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

750
	/** @scrutinizer ignore-unhandled */ @set_time_limit(100);

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

830
	/** @scrutinizer ignore-unhandled */ @set_time_limit(600);

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...
831
832
	// Step the number of topics at a time so things don't time out...
833
	$request = $smcFunc['db_query']('', '
834
		SELECT MAX(id_topic)
835
		FROM {db_prefix}topics',
836
		array(
837
		)
838
	);
839
	list ($max_topics) = $smcFunc['db_fetch_row']($request);
840
	$smcFunc['db_free_result']($request);
841
842
	$increment = min(max(50, ceil($max_topics / 4)), 2000);
843
	if (empty($_REQUEST['start']))
844
		$_REQUEST['start'] = 0;
845
846
	$total_steps = 8;
847
848
	// Get each topic with a wrong reply count and fix it - let's just do some at a time, though.
849
	if (empty($_REQUEST['step']))
850
	{
851
		$_REQUEST['step'] = 0;
852
853
		while ($_REQUEST['start'] < $max_topics)
854
		{
855
			// Recount approved messages
856
			$request = $smcFunc['db_query']('', '
857
				SELECT /*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.num_replies) AS num_replies,
858
					CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END AS real_num_replies
859
				FROM {db_prefix}topics AS t
860
					LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = {int:is_approved})
861
				WHERE t.id_topic > {int:start}
862
					AND t.id_topic <= {int:max_id}
863
				GROUP BY t.id_topic
864
				HAVING CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END != MAX(t.num_replies)',
865
				array(
866
					'is_approved' => 1,
867
					'start' => $_REQUEST['start'],
868
					'max_id' => $_REQUEST['start'] + $increment,
869
				)
870
			);
871
			while ($row = $smcFunc['db_fetch_assoc']($request))
872
				$smcFunc['db_query']('', '
873
					UPDATE {db_prefix}topics
874
					SET num_replies = {int:num_replies}
875
					WHERE id_topic = {int:id_topic}',
876
					array(
877
						'num_replies' => $row['real_num_replies'],
878
						'id_topic' => $row['id_topic'],
879
					)
880
				);
881
			$smcFunc['db_free_result']($request);
882
883
			// Recount unapproved messages
884
			$request = $smcFunc['db_query']('', '
885
				SELECT /*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.unapproved_posts) AS unapproved_posts,
886
					COUNT(mu.id_msg) AS real_unapproved_posts
887
				FROM {db_prefix}topics AS t
888
					LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = {int:not_approved})
889
				WHERE t.id_topic > {int:start}
890
					AND t.id_topic <= {int:max_id}
891
				GROUP BY t.id_topic
892
				HAVING COUNT(mu.id_msg) != MAX(t.unapproved_posts)',
893
				array(
894
					'not_approved' => 0,
895
					'start' => $_REQUEST['start'],
896
					'max_id' => $_REQUEST['start'] + $increment,
897
				)
898
			);
899
			while ($row = $smcFunc['db_fetch_assoc']($request))
900
				$smcFunc['db_query']('', '
901
					UPDATE {db_prefix}topics
902
					SET unapproved_posts = {int:unapproved_posts}
903
					WHERE id_topic = {int:id_topic}',
904
					array(
905
						'unapproved_posts' => $row['real_unapproved_posts'],
906
						'id_topic' => $row['id_topic'],
907
					)
908
				);
909
			$smcFunc['db_free_result']($request);
910
911
			$_REQUEST['start'] += $increment;
912
913
			if (microtime(true) - $time_start > 3)
914
			{
915
				createToken('admin-boardrecount');
916
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
917
918
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=0;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
919
				$context['continue_percent'] = round((100 * $_REQUEST['start'] / $max_topics) / $total_steps);
920
921
				return;
922
			}
923
		}
924
925
		$_REQUEST['start'] = 0;
926
	}
927
928
	// Update the post count of each board.
929
	if ($_REQUEST['step'] <= 1)
930
	{
931
		if (empty($_REQUEST['start']))
932
			$smcFunc['db_query']('', '
933
				UPDATE {db_prefix}boards
934
				SET num_posts = {int:num_posts}
935
				WHERE redirect = {string:redirect}',
936
				array(
937
					'num_posts' => 0,
938
					'redirect' => '',
939
				)
940
			);
941
942
		while ($_REQUEST['start'] < $max_topics)
943
		{
944
			$request = $smcFunc['db_query']('', '
945
				SELECT /*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_num_posts
946
				FROM {db_prefix}messages AS m
947
				WHERE m.id_topic > {int:id_topic_min}
948
					AND m.id_topic <= {int:id_topic_max}
949
					AND m.approved = {int:is_approved}
950
				GROUP BY m.id_board',
951
				array(
952
					'id_topic_min' => $_REQUEST['start'],
953
					'id_topic_max' => $_REQUEST['start'] + $increment,
954
					'is_approved' => 1,
955
				)
956
			);
957
			while ($row = $smcFunc['db_fetch_assoc']($request))
958
				$smcFunc['db_query']('', '
959
					UPDATE {db_prefix}boards
960
					SET num_posts = num_posts + {int:real_num_posts}
961
					WHERE id_board = {int:id_board}',
962
					array(
963
						'id_board' => $row['id_board'],
964
						'real_num_posts' => $row['real_num_posts'],
965
					)
966
				);
967
			$smcFunc['db_free_result']($request);
968
969
			$_REQUEST['start'] += $increment;
970
971
			if (microtime(true) - $time_start > 3)
972
			{
973
				createToken('admin-boardrecount');
974
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
975
976
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=1;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
977
				$context['continue_percent'] = round((200 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
978
979
				return;
980
			}
981
		}
982
983
		$_REQUEST['start'] = 0;
984
	}
985
986
	// Update the topic count of each board.
987
	if ($_REQUEST['step'] <= 2)
988
	{
989
		if (empty($_REQUEST['start']))
990
			$smcFunc['db_query']('', '
991
				UPDATE {db_prefix}boards
992
				SET num_topics = {int:num_topics}',
993
				array(
994
					'num_topics' => 0,
995
				)
996
			);
997
998
		while ($_REQUEST['start'] < $max_topics)
999
		{
1000
			$request = $smcFunc['db_query']('', '
1001
				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_num_topics
1002
				FROM {db_prefix}topics AS t
1003
				WHERE t.approved = {int:is_approved}
1004
					AND t.id_topic > {int:id_topic_min}
1005
					AND t.id_topic <= {int:id_topic_max}
1006
				GROUP BY t.id_board',
1007
				array(
1008
					'is_approved' => 1,
1009
					'id_topic_min' => $_REQUEST['start'],
1010
					'id_topic_max' => $_REQUEST['start'] + $increment,
1011
				)
1012
			);
1013
			while ($row = $smcFunc['db_fetch_assoc']($request))
1014
				$smcFunc['db_query']('', '
1015
					UPDATE {db_prefix}boards
1016
					SET num_topics = num_topics + {int:real_num_topics}
1017
					WHERE id_board = {int:id_board}',
1018
					array(
1019
						'id_board' => $row['id_board'],
1020
						'real_num_topics' => $row['real_num_topics'],
1021
					)
1022
				);
1023
			$smcFunc['db_free_result']($request);
1024
1025
			$_REQUEST['start'] += $increment;
1026
1027
			if (microtime(true) - $time_start > 3)
1028
			{
1029
				createToken('admin-boardrecount');
1030
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1031
1032
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=2;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1033
				$context['continue_percent'] = round((300 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1034
1035
				return;
1036
			}
1037
		}
1038
1039
		$_REQUEST['start'] = 0;
1040
	}
1041
1042
	// Update the unapproved post count of each board.
1043
	if ($_REQUEST['step'] <= 3)
1044
	{
1045
		if (empty($_REQUEST['start']))
1046
			$smcFunc['db_query']('', '
1047
				UPDATE {db_prefix}boards
1048
				SET unapproved_posts = {int:unapproved_posts}',
1049
				array(
1050
					'unapproved_posts' => 0,
1051
				)
1052
			);
1053
1054
		while ($_REQUEST['start'] < $max_topics)
1055
		{
1056
			$request = $smcFunc['db_query']('', '
1057
				SELECT /*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_unapproved_posts
1058
				FROM {db_prefix}messages AS m
1059
				WHERE m.id_topic > {int:id_topic_min}
1060
					AND m.id_topic <= {int:id_topic_max}
1061
					AND m.approved = {int:is_approved}
1062
				GROUP BY m.id_board',
1063
				array(
1064
					'id_topic_min' => $_REQUEST['start'],
1065
					'id_topic_max' => $_REQUEST['start'] + $increment,
1066
					'is_approved' => 0,
1067
				)
1068
			);
1069
			while ($row = $smcFunc['db_fetch_assoc']($request))
1070
				$smcFunc['db_query']('', '
1071
					UPDATE {db_prefix}boards
1072
					SET unapproved_posts = unapproved_posts + {int:unapproved_posts}
1073
					WHERE id_board = {int:id_board}',
1074
					array(
1075
						'id_board' => $row['id_board'],
1076
						'unapproved_posts' => $row['real_unapproved_posts'],
1077
					)
1078
				);
1079
			$smcFunc['db_free_result']($request);
1080
1081
			$_REQUEST['start'] += $increment;
1082
1083
			if (microtime(true) - $time_start > 3)
1084
			{
1085
				createToken('admin-boardrecount');
1086
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1087
1088
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=3;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1089
				$context['continue_percent'] = round((400 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1090
1091
				return;
1092
			}
1093
		}
1094
1095
		$_REQUEST['start'] = 0;
1096
	}
1097
1098
	// Update the unapproved topic count of each board.
1099
	if ($_REQUEST['step'] <= 4)
1100
	{
1101
		if (empty($_REQUEST['start']))
1102
			$smcFunc['db_query']('', '
1103
				UPDATE {db_prefix}boards
1104
				SET unapproved_topics = {int:unapproved_topics}',
1105
				array(
1106
					'unapproved_topics' => 0,
1107
				)
1108
			);
1109
1110
		while ($_REQUEST['start'] < $max_topics)
1111
		{
1112
			$request = $smcFunc['db_query']('', '
1113
				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_unapproved_topics
1114
				FROM {db_prefix}topics AS t
1115
				WHERE t.approved = {int:is_approved}
1116
					AND t.id_topic > {int:id_topic_min}
1117
					AND t.id_topic <= {int:id_topic_max}
1118
				GROUP BY t.id_board',
1119
				array(
1120
					'is_approved' => 0,
1121
					'id_topic_min' => $_REQUEST['start'],
1122
					'id_topic_max' => $_REQUEST['start'] + $increment,
1123
				)
1124
			);
1125
			while ($row = $smcFunc['db_fetch_assoc']($request))
1126
				$smcFunc['db_query']('', '
1127
					UPDATE {db_prefix}boards
1128
					SET unapproved_topics = unapproved_topics + {int:real_unapproved_topics}
1129
					WHERE id_board = {int:id_board}',
1130
					array(
1131
						'id_board' => $row['id_board'],
1132
						'real_unapproved_topics' => $row['real_unapproved_topics'],
1133
					)
1134
				);
1135
			$smcFunc['db_free_result']($request);
1136
1137
			$_REQUEST['start'] += $increment;
1138
1139
			if (microtime(true) - $time_start > 3)
1140
			{
1141
				createToken('admin-boardrecount');
1142
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1143
1144
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=4;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1145
				$context['continue_percent'] = round((500 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1146
1147
				return;
1148
			}
1149
		}
1150
1151
		$_REQUEST['start'] = 0;
1152
	}
1153
1154
	// Get all members with wrong number of personal messages.
1155
	if ($_REQUEST['step'] <= 5)
1156
	{
1157
		$request = $smcFunc['db_query']('', '
1158
			SELECT /*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num,
1159
				MAX(mem.instant_messages) AS instant_messages
1160
			FROM {db_prefix}members AS mem
1161
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (mem.id_member = pmr.id_member AND pmr.deleted = {int:is_not_deleted})
1162
			GROUP BY mem.id_member
1163
			HAVING COUNT(pmr.id_pm) != MAX(mem.instant_messages)',
1164
			array(
1165
				'is_not_deleted' => 0,
1166
			)
1167
		);
1168
		while ($row = $smcFunc['db_fetch_assoc']($request))
1169
			updateMemberData($row['id_member'], array('instant_messages' => $row['real_num']));
1170
		$smcFunc['db_free_result']($request);
1171
1172
		$request = $smcFunc['db_query']('', '
1173
			SELECT /*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num,
1174
				MAX(mem.unread_messages) AS unread_messages
1175
			FROM {db_prefix}members AS mem
1176
				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})
1177
			GROUP BY mem.id_member
1178
			HAVING COUNT(pmr.id_pm) != MAX(mem.unread_messages)',
1179
			array(
1180
				'is_not_deleted' => 0,
1181
				'is_not_read' => 0,
1182
			)
1183
		);
1184
		while ($row = $smcFunc['db_fetch_assoc']($request))
1185
			updateMemberData($row['id_member'], array('unread_messages' => $row['real_num']));
1186
		$smcFunc['db_free_result']($request);
1187
1188
		if (microtime(true) - $time_start > 3)
1189
		{
1190
			createToken('admin-boardrecount');
1191
			$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1192
1193
			$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=6;start=0;' . $context['session_var'] . '=' . $context['session_id'];
1194
			$context['continue_percent'] = round(700 / $total_steps);
1195
1196
			return;
1197
		}
1198
	}
1199
1200
	// Any messages pointing to the wrong board?
1201
	if ($_REQUEST['step'] <= 6)
1202
	{
1203
		while ($_REQUEST['start'] < $modSettings['maxMsgID'])
1204
		{
1205
			$request = $smcFunc['db_query']('', '
1206
				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, m.id_msg
1207
				FROM {db_prefix}messages AS m
1208
					INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic AND t.id_board != m.id_board)
1209
				WHERE m.id_msg > {int:id_msg_min}
1210
					AND m.id_msg <= {int:id_msg_max}',
1211
				array(
1212
					'id_msg_min' => $_REQUEST['start'],
1213
					'id_msg_max' => $_REQUEST['start'] + $increment,
1214
				)
1215
			);
1216
			$boards = array();
1217
			while ($row = $smcFunc['db_fetch_assoc']($request))
1218
				$boards[$row['id_board']][] = $row['id_msg'];
1219
			$smcFunc['db_free_result']($request);
1220
1221
			foreach ($boards as $board_id => $messages)
1222
				$smcFunc['db_query']('', '
1223
					UPDATE {db_prefix}messages
1224
					SET id_board = {int:id_board}
1225
					WHERE id_msg IN ({array_int:id_msg_array})',
1226
					array(
1227
						'id_msg_array' => $messages,
1228
						'id_board' => $board_id,
1229
					)
1230
				);
1231
1232
			$_REQUEST['start'] += $increment;
1233
1234
			if (microtime(true) - $time_start > 3)
1235
			{
1236
				createToken('admin-boardrecount');
1237
				$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '">';
1238
1239
				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=6;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1240
				$context['continue_percent'] = round((700 + 100 * $_REQUEST['start'] / $modSettings['maxMsgID']) / $total_steps);
1241
1242
				return;
1243
			}
1244
		}
1245
1246
		$_REQUEST['start'] = 0;
1247
	}
1248
1249
	// Update the latest message of each board.
1250
	$request = $smcFunc['db_query']('', '
1251
		SELECT m.id_board, MAX(m.id_msg) AS local_last_msg
1252
		FROM {db_prefix}messages AS m
1253
		WHERE m.approved = {int:is_approved}
1254
		GROUP BY m.id_board',
1255
		array(
1256
			'is_approved' => 1,
1257
		)
1258
	);
1259
	$realBoardCounts = array();
1260
	while ($row = $smcFunc['db_fetch_assoc']($request))
1261
		$realBoardCounts[$row['id_board']] = $row['local_last_msg'];
1262
	$smcFunc['db_free_result']($request);
1263
1264
	$request = $smcFunc['db_query']('', '
1265
		SELECT /*!40001 SQL_NO_CACHE */ id_board, id_parent, id_last_msg, child_level, id_msg_updated
1266
		FROM {db_prefix}boards',
1267
		array(
1268
		)
1269
	);
1270
	$resort_me = array();
1271
	while ($row = $smcFunc['db_fetch_assoc']($request))
1272
	{
1273
		$row['local_last_msg'] = isset($realBoardCounts[$row['id_board']]) ? $realBoardCounts[$row['id_board']] : 0;
1274
		$resort_me[$row['child_level']][] = $row;
1275
	}
1276
	$smcFunc['db_free_result']($request);
1277
1278
	krsort($resort_me);
1279
1280
	$lastModifiedMsg = array();
1281
	foreach ($resort_me as $rows)
1282
		foreach ($rows as $row)
1283
		{
1284
			// The latest message is the latest of the current board and its children.
1285
			if (isset($lastModifiedMsg[$row['id_board']]))
1286
				$curLastModifiedMsg = max($row['local_last_msg'], $lastModifiedMsg[$row['id_board']]);
1287
			else
1288
				$curLastModifiedMsg = $row['local_last_msg'];
1289
1290
			// If what is and what should be the latest message differ, an update is necessary.
1291
			if ($row['local_last_msg'] != $row['id_last_msg'] || $curLastModifiedMsg != $row['id_msg_updated'])
1292
				$smcFunc['db_query']('', '
1293
					UPDATE {db_prefix}boards
1294
					SET id_last_msg = {int:id_last_msg}, id_msg_updated = {int:id_msg_updated}
1295
					WHERE id_board = {int:id_board}',
1296
					array(
1297
						'id_last_msg' => $row['local_last_msg'],
1298
						'id_msg_updated' => $curLastModifiedMsg,
1299
						'id_board' => $row['id_board'],
1300
					)
1301
				);
1302
1303
			// Parent boards inherit the latest modified message of their children.
1304
			if (isset($lastModifiedMsg[$row['id_parent']]))
1305
				$lastModifiedMsg[$row['id_parent']] = max($row['local_last_msg'], $lastModifiedMsg[$row['id_parent']]);
1306
			else
1307
				$lastModifiedMsg[$row['id_parent']] = $row['local_last_msg'];
1308
		}
1309
1310
	// Update all the basic statistics.
1311
	updateStats('member');
1312
	updateStats('message');
1313
	updateStats('topic');
1314
1315
	// Finally, update the latest event times.
1316
	require_once($sourcedir . '/ScheduledTasks.php');
1317
	CalculateNextTrigger();
1318
1319
	redirectexit('action=admin;area=maintain;sa=routine;done=recount');
1320
}
1321
1322
/**
1323
 * Perform a detailed version check.  A very good thing ;).
1324
 * The function parses the comment headers in all files for their version information,
1325
 * and outputs that for some javascript to check with simplemachines.org.
1326
 * It does not connect directly with simplemachines.org, but rather expects the client to.
1327
 *
1328
 * It requires the admin_forum permission.
1329
 * Uses the view_versions admin area.
1330
 * Accessed through ?action=admin;area=maintain;sa=routine;activity=version.
1331
 * @uses Admin template, view_versions sub-template.
1332
 */
1333
function VersionDetail()
1334
{
1335
	global $forum_version, $txt, $sourcedir, $context;
1336
1337
	isAllowedTo('admin_forum');
1338
1339
	// Call the function that'll get all the version info we need.
1340
	require_once($sourcedir . '/Subs-Admin.php');
1341
	$versionOptions = array(
1342
		'include_ssi' => true,
1343
		'include_subscriptions' => true,
1344
		'include_tasks' => true,
1345
		'sort_results' => true,
1346
	);
1347
	$version_info = getFileVersions($versionOptions);
1348
1349
	// Add the new info to the template context.
1350
	$context += array(
1351
		'file_versions' => $version_info['file_versions'],
1352
		'default_template_versions' => $version_info['default_template_versions'],
1353
		'template_versions' => $version_info['template_versions'],
1354
		'default_language_versions' => $version_info['default_language_versions'],
1355
		'default_known_languages' => array_keys($version_info['default_language_versions']),
1356
		'tasks_versions' => $version_info['tasks_versions'],
1357
	);
1358
1359
	// Make it easier to manage for the template.
1360
	$context['forum_version'] = $forum_version;
1361
1362
	$context['sub_template'] = 'view_versions';
1363
	$context['page_title'] = $txt['admin_version_check'];
1364
}
1365
1366
/**
1367
 * Re-attribute posts.
1368
 */
1369
function MaintainReattributePosts()
1370
{
1371
	global $sourcedir, $context, $txt;
1372
1373
	checkSession();
1374
1375
	// Find the member.
1376
	require_once($sourcedir . '/Subs-Auth.php');
1377
	$members = findMembers($_POST['to']);
1378
1379
	if (empty($members))
1380
		fatal_lang_error('reattribute_cannot_find_member');
1381
1382
	$memID = array_shift($members);
1383
	$memID = $memID['id'];
1384
1385
	$email = $_POST['type'] == 'email' ? $_POST['from_email'] : '';
1386
	$membername = $_POST['type'] == 'name' ? $_POST['from_name'] : '';
1387
1388
	// Now call the reattribute function.
1389
	require_once($sourcedir . '/Subs-Members.php');
1390
	reattributePosts($memID, $email, $membername, !empty($_POST['posts']));
1391
1392
	$context['maintenance_finished'] = $txt['maintain_reattribute_posts'];
1393
}
1394
1395
/**
1396
 * Removing old members. Done and out!
1397
 * @todo refactor
1398
 */
1399
function MaintainPurgeInactiveMembers()
1400
{
1401
	global $sourcedir, $context, $smcFunc, $txt;
1402
1403
	$_POST['maxdays'] = empty($_POST['maxdays']) ? 0 : (int) $_POST['maxdays'];
1404
	if (!empty($_POST['groups']) && $_POST['maxdays'] > 0)
1405
	{
1406
		checkSession();
1407
		validateToken('admin-maint');
1408
1409
		$groups = array();
1410
		foreach ($_POST['groups'] as $id => $dummy)
1411
			$groups[] = (int) $id;
1412
		$time_limit = (time() - ($_POST['maxdays'] * 24 * 3600));
1413
		$where_vars = array(
1414
			'time_limit' => $time_limit,
1415
		);
1416
		if ($_POST['del_type'] == 'activated')
1417
		{
1418
			$where = 'mem.date_registered < {int:time_limit} AND mem.is_activated = {int:is_activated}';
1419
			$where_vars['is_activated'] = 0;
1420
		}
1421
		else
1422
			$where = 'mem.last_login < {int:time_limit} AND (mem.last_login != 0 OR mem.date_registered < {int:time_limit})';
1423
1424
		// Need to get *all* groups then work out which (if any) we avoid.
1425
		$request = $smcFunc['db_query']('', '
1426
			SELECT id_group, group_name, min_posts
1427
			FROM {db_prefix}membergroups',
1428
			array(
1429
			)
1430
		);
1431
		while ($row = $smcFunc['db_fetch_assoc']($request))
1432
		{
1433
			// Avoid this one?
1434
			if (!in_array($row['id_group'], $groups))
1435
			{
1436
				// Post group?
1437
				if ($row['min_posts'] != -1)
1438
				{
1439
					$where .= ' AND mem.id_post_group != {int:id_post_group_' . $row['id_group'] . '}';
1440
					$where_vars['id_post_group_' . $row['id_group']] = $row['id_group'];
1441
				}
1442
				else
1443
				{
1444
					$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';
1445
					$where_vars['id_group_' . $row['id_group']] = $row['id_group'];
1446
				}
1447
			}
1448
		}
1449
		$smcFunc['db_free_result']($request);
1450
1451
		// If we have ungrouped unselected we need to avoid those guys.
1452
		if (!in_array(0, $groups))
1453
		{
1454
			$where .= ' AND (mem.id_group != 0 OR mem.additional_groups != {string:blank_add_groups})';
1455
			$where_vars['blank_add_groups'] = '';
1456
		}
1457
1458
		// Select all the members we're about to murder/remove...
1459
		$request = $smcFunc['db_query']('', '
1460
			SELECT mem.id_member, COALESCE(m.id_member, 0) AS is_mod
1461
			FROM {db_prefix}members AS mem
1462
				LEFT JOIN {db_prefix}moderators AS m ON (m.id_member = mem.id_member)
1463
			WHERE ' . $where,
1464
			$where_vars
1465
		);
1466
		$members = array();
1467
		while ($row = $smcFunc['db_fetch_assoc']($request))
1468
		{
1469
			if (!$row['is_mod'] || !in_array(3, $groups))
1470
				$members[] = $row['id_member'];
1471
		}
1472
		$smcFunc['db_free_result']($request);
1473
1474
		require_once($sourcedir . '/Subs-Members.php');
1475
		deleteMembers($members);
1476
	}
1477
1478
	$context['maintenance_finished'] = $txt['maintain_members'];
1479
	createToken('admin-maint');
1480
}
1481
1482
/**
1483
 * Removing old posts doesn't take much as we really pass through.
1484
 */
1485
function MaintainRemoveOldPosts()
1486
{
1487
	global $sourcedir;
1488
1489
	validateToken('admin-maint');
1490
1491
	// Actually do what we're told!
1492
	require_once($sourcedir . '/RemoveTopic.php');
1493
	RemoveOldTopics2();
1494
}
1495
1496
/**
1497
 * Removing old drafts
1498
 */
1499
function MaintainRemoveOldDrafts()
1500
{
1501
	global $sourcedir, $smcFunc;
1502
1503
	validateToken('admin-maint');
1504
1505
	$drafts = array();
1506
1507
	// Find all of the old drafts
1508
	$request = $smcFunc['db_query']('', '
1509
		SELECT id_draft
1510
		FROM {db_prefix}user_drafts
1511
		WHERE poster_time <= {int:poster_time_old}',
1512
		array(
1513
			'poster_time_old' => time() - (86400 * $_POST['draftdays']),
1514
		)
1515
	);
1516
1517
	while ($row = $smcFunc['db_fetch_row']($request))
1518
		$drafts[] = (int) $row[0];
1519
	$smcFunc['db_free_result']($request);
1520
1521
	// If we have old drafts, remove them
1522
	if (count($drafts) > 0)
1523
	{
1524
		require_once($sourcedir . '/Drafts.php');
1525
		DeleteDraft($drafts, false);
0 ignored issues
show
Bug introduced by
$drafts of type array|integer[] is incompatible with the type integer expected by parameter $id_draft of DeleteDraft(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1714
	/** @scrutinizer ignore-unhandled */ @set_time_limit(600);

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2037
	closedir(/** @scrutinizer ignore-type */ $dh);
Loading history...
2038
2039
	return $files;
2040
}
2041
2042
/**
2043
 * Callback function for the integration hooks list (list_integration_hooks)
2044
 * Gets all of the hooks in the system and their status
2045
 *
2046
 * @param int $start The item to start with (for pagination purposes)
2047
 * @param int $per_page How many items to display on each page
2048
 * @param string $sort A string indicating how to sort things
2049
 * @return array An array of information about the integration hooks
2050
 */
2051
function get_integration_hooks_data($start, $per_page, $sort)
2052
{
2053
	global $boarddir, $sourcedir, $settings, $txt, $context, $scripturl;
2054
2055
	$hooks = $temp_hooks = get_integration_hooks();
2056
	$hooks_data = $temp_data = $hook_status = array();
2057
2058
	$files = get_files_recursive($sourcedir);
2059
	if (!empty($files))
2060
	{
2061
		foreach ($files as $file)
2062
		{
2063
			if (is_file($file['dir'] . '/' . $file['name']) && substr($file['name'], -4) === '.php')
2064
			{
2065
				$fp = fopen($file['dir'] . '/' . $file['name'], 'rb');
2066
				$fc = fread($fp, filesize($file['dir'] . '/' . $file['name']));
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fread() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2066
				$fc = fread(/** @scrutinizer ignore-type */ $fp, filesize($file['dir'] . '/' . $file['name']));
Loading history...
2067
				fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2067
				fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
2068
2069
				foreach ($temp_hooks as $hook => $allFunctions)
2070
				{
2071
					foreach ($allFunctions as $rawFunc)
2072
					{
2073
						// Get the hook info.
2074
						$hookParsedData = get_hook_info_from_raw($rawFunc);
2075
2076
						if (substr($hook, -8) === '_include')
2077
						{
2078
							$hook_status[$hook][$hookParsedData['pureFunc']]['exists'] = file_exists(strtr(trim($rawFunc), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])));
2079
							// I need to know if there is at least one function called in this file.
2080
							$temp_data['include'][$hookParsedData['pureFunc']] = array('hook' => $hook, 'function' => $hookParsedData['pureFunc']);
2081
							unset($temp_hooks[$hook][$rawFunc]);
2082
						}
2083
						elseif (strpos(str_replace(' (', '(', $fc), 'function ' . trim($hookParsedData['pureFunc']) . '(') !== false)
2084
						{
2085
							$hook_status[$hook][$hookParsedData['pureFunc']] = $hookParsedData;
2086
							$hook_status[$hook][$hookParsedData['pureFunc']]['exists'] = true;
2087
							$hook_status[$hook][$hookParsedData['pureFunc']]['in_file'] = (!empty($file['name']) ? $file['name'] : (!empty($hookParsedData['hookFile']) ? $hookParsedData['hookFile'] : ''));
2088
2089
							// Does the hook has its own file?
2090
							if (!empty($hookParsedData['hookFile']))
2091
								$temp_data['include'][$hookParsedData['pureFunc']] = array('hook' => $hook, 'function' => $hookParsedData['pureFunc']);
2092
2093
							// 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)
2094
							$temp_data['function'][$file['name']][$hookParsedData['pureFunc']] = $hookParsedData['enabled'];
2095
							unset($temp_hooks[$hook][$rawFunc]);
2096
						}
2097
					}
2098
				}
2099
			}
2100
		}
2101
	}
2102
2103
	$sort_types = array(
2104
		'hook_name' => array('hook', SORT_ASC),
2105
		'hook_name DESC' => array('hook', SORT_DESC),
2106
		'function_name' => array('function', SORT_ASC),
2107
		'function_name DESC' => array('function', SORT_DESC),
2108
		'file_name' => array('file_name', SORT_ASC),
2109
		'file_name DESC' => array('file_name', SORT_DESC),
2110
		'status' => array('status', SORT_ASC),
2111
		'status DESC' => array('status', SORT_DESC),
2112
	);
2113
2114
	$sort_options = $sort_types[$sort];
2115
	$sort = array();
2116
	$hooks_filters = array();
2117
2118
	foreach ($hooks as $hook => $functions)
2119
		$hooks_filters[] = '<option' . ($context['current_filter'] == $hook ? ' selected ' : '') . ' value="' . $hook . '">' . $hook . '</option>';
2120
2121
	if (!empty($hooks_filters))
2122
		$context['insert_after_template'] .= '
2123
		<script>
2124
			var hook_name_header = document.getElementById(\'header_list_integration_hooks_hook_name\');
2125
			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>') . ';
2126
		</script>';
2127
2128
	$temp_data = array();
2129
	$id = 0;
2130
2131
	foreach ($hooks as $hook => $functions)
2132
	{
2133
		if (empty($context['filter']) || (!empty($context['filter']) && $context['filter'] == $hook))
2134
		{
2135
			foreach ($functions as $rawFunc)
2136
			{
2137
				// Get the hook info.
2138
				$hookParsedData = get_hook_info_from_raw($rawFunc);
2139
2140
				$hook_exists = !empty($hook_status[$hook][$hookParsedData['pureFunc']]['exists']);
2141
				$sort[] = $sort_options[0];
2142
2143
				$temp_data[] = array(
2144
					'id' => 'hookid_' . $id++,
2145
					'hook_name' => $hook,
2146
					'function_name' => $hookParsedData['rawData'],
2147
					'real_function' => $hookParsedData['pureFunc'],
2148
					'included_file' => !empty($hookParsedData['absPath']) ? $hookParsedData['absPath'] : '',
2149
					'file_name' => (isset($hook_status[$hook][$hookParsedData['pureFunc']]['in_file']) ? $hook_status[$hook][$hookParsedData['pureFunc']]['in_file'] : (!empty($hookParsedData['hookFile']) ? $hookParsedData['hookFile'] : '')),
2150
					'instance' => $hookParsedData['object'],
2151
					'hook_exists' => $hook_exists,
2152
					'status' => $hook_exists ? ($hookParsedData['enabled'] ? 'allow' : 'moderate') : 'deny',
2153
					'img_text' => $txt['hooks_' . ($hook_exists ? ($hookParsedData['enabled'] ? 'active' : 'disabled') : 'missing')],
2154
					'enabled' => $hookParsedData['enabled'],
2155
					'can_be_disabled' => !isset($hook_status[$hook][$hookParsedData['pureFunc']]['enabled']),
2156
				);
2157
			}
2158
		}
2159
	}
2160
2161
	array_multisort($sort, $sort_options[1], $temp_data);
2162
2163
	$counter = 0;
2164
	$start++;
2165
2166
	foreach ($temp_data as $data)
2167
	{
2168
		if (++$counter < $start)
2169
			continue;
2170
		elseif ($counter == $start + $per_page)
2171
			break;
2172
2173
		$hooks_data[] = $data;
2174
	}
2175
2176
	return $hooks_data;
2177
}
2178
2179
/**
2180
 * Simply returns the total count of integration hooks
2181
 * Used by the integration hooks list function (list_integration_hooks)
2182
 *
2183
 * @return int The number of hooks currently in use
2184
 */
2185
function get_integration_hooks_count()
2186
{
2187
	global $context;
2188
2189
	$hooks = get_integration_hooks();
2190
	$hooks_count = 0;
2191
2192
	$context['filter'] = false;
2193
	if (isset($_GET['filter']))
2194
		$context['filter'] = $_GET['filter'];
2195
2196
	foreach ($hooks as $hook => $functions)
2197
	{
2198
		if (empty($context['filter']) || (!empty($context['filter']) && $context['filter'] == $hook))
2199
			$hooks_count += count($functions);
2200
	}
2201
2202
	return $hooks_count;
2203
}
2204
2205
/**
2206
 * Parses modSettings to create integration hook array
2207
 *
2208
 * @return array An array of information about the integration hooks
2209
 */
2210
function get_integration_hooks()
2211
{
2212
	global $modSettings;
2213
	static $integration_hooks;
2214
2215
	if (!isset($integration_hooks))
2216
	{
2217
		$integration_hooks = array();
2218
		foreach ($modSettings as $key => $value)
2219
		{
2220
			if (!empty($value) && substr($key, 0, 10) === 'integrate_')
2221
				$integration_hooks[$key] = explode(',', $value);
2222
		}
2223
	}
2224
2225
	return $integration_hooks;
2226
}
2227
2228
/**
2229
 * Parses each hook data and returns an array.
2230
 *
2231
 * @param string $rawData A string as it was saved to the DB.
2232
 * @return array everything found in the string itself
2233
 */
2234
function get_hook_info_from_raw($rawData)
2235
{
2236
	global $boarddir, $settings, $sourcedir;
2237
2238
	// A single string can hold tons of info!
2239
	$hookData = array(
2240
		'object' => false,
2241
		'enabled' => true,
2242
		'fileExists' => false,
2243
		'absPath' => '',
2244
		'hookFile' => '',
2245
		'pureFunc' => '',
2246
		'method' => '',
2247
		'class' => '',
2248
		'rawData' => $rawData,
2249
	);
2250
2251
	// Meh...
2252
	if (empty($rawData))
2253
		return $hookData;
2254
2255
	// For convenience purposes only!
2256
	$modFunc = $rawData;
2257
2258
	// Any files?
2259
	if (strpos($modFunc, '|') !== false)
2260
	{
2261
		list ($hookData['hookFile'], $modFunc) = explode('|', $modFunc);
2262
2263
		// Does the file exists? who knows!
2264
		if (empty($settings['theme_dir']))
2265
			$hookData['absPath'] = strtr(trim($hookData['hookFile']), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
2266
2267
		else
2268
			$hookData['absPath'] = strtr(trim($hookData['hookFile']), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2269
2270
		$hookData['fileExists'] = file_exists($hookData['absPath']);
2271
		$hookData['hookFile'] = basename($hookData['hookFile']);
2272
	}
2273
2274
	// Hook is an instance.
2275
	if (strpos($modFunc, '#') !== false)
2276
	{
2277
		$modFunc = str_replace('#', '', $modFunc);
2278
		$hookData['object'] = true;
2279
	}
2280
2281
	// Hook is "disabled"
2282
	if (strpos($modFunc, '!') !== false)
2283
	{
2284
		$modFunc = str_replace('!', '', $modFunc);
2285
		$hookData['enabled'] = false;
2286
	}
2287
2288
	// Handling methods?
2289
	if (strpos($modFunc, '::') !== false)
2290
	{
2291
		list ($hookData['class'], $hookData['method']) = explode('::', $modFunc);
2292
		$hookData['pureFunc'] = $hookData['method'];
2293
	}
2294
2295
	else
2296
		$hookData['pureFunc'] = $modFunc;
2297
2298
	return $hookData;
2299
}
2300
2301
/**
2302
 * Converts html entities to utf8 equivalents
2303
 * special db wrapper for mysql based on the limitation of mysql/mb3
2304
 *
2305
 * Callback function for preg_replace_callback
2306
 * Uses capture group 1 in the supplied array
2307
 * Does basic checks to keep characters inside a viewable range.
2308
 *
2309
 * @param array $matches An array of matches (relevant info should be the 2nd item in the array)
2310
 * @return string The fixed string or return the old when limitation of mysql is hit
2311
 */
2312
function fixchardb__callback($matches)
2313
{
2314
	global $smcFunc;
2315
	if (!isset($matches[1]))
2316
		return '';
2317
2318
	$num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1];
0 ignored issues
show
Bug introduced by
$matches[1] of type array is incompatible with the type string expected by parameter $string of substr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2318
	$num = $matches[1][0] === 'x' ? hexdec(substr(/** @scrutinizer ignore-type */ $matches[1], 1)) : (int) $matches[1];
Loading history...
2319
	
2320
	// it's to big for mb3?
2321
	if ($num > 0xFFFF && !$smcFunc['db_mb4'])
2322
		return $matches[0];
2323
	else
2324
		return fixchar__callback($matches);
2325
}
2326
2327
?>