Completed
Pull Request — master (#3325)
by Emanuele
11:19
created

action_convertmsgbody_display()   F

Complexity

Conditions 23
Paths 391

Size

Total Lines 106
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 552

Importance

Changes 0
Metric Value
cc 23
eloc 64
dl 0
loc 106
rs 0.9458
c 0
b 0
f 0
nc 391
nop 0
ccs 0
cts 80
cp 0
crap 552

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
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This file contains code covered by:
11
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
12
 * license:		BSD, See included LICENSE.TXT for terms and conditions.
13
 *
14
 * @version 1.1.6
15
 *
16
 */
17
18
/**
19
 * Entry point class for all of the maintenance ,routine, members, database,
20
 * attachments, topics and hooks
21
 *
22
 * @package Maintenance
23
 */
24
class Maintenance_Controller extends Action_Controller
25
{
26
	/**
27
	 * Maximum topic counter
28
	 * @var int
29
	 */
30
	public $max_topics;
31
32
	/**
33
	 * How many actions to take for a maintenance actions
34
	 * @var int
35
	 */
36
	public $increment;
37
38
	/**
39
	 * Total steps for a given maintenance action
40
	 * @var int
41
	 */
42
	public $total_steps;
43
44
	/**
45
	 * reStart pointer for paused maintenance actions
46
	 * @var int
47
	 */
48
	public $start;
49
50
	/**
51
	 * Loop counter for paused maintenance actions
52
	 * @var int
53
	 */
54
	public $step;
55
56
	/**
57
	 * Main dispatcher, the maintenance access point.
58
	 *
59
	 * What it does:
60
	 *
61
	 * - This, as usual, checks permissions, loads language files,
62
	 * and forwards to the actual workers.
63
	 *
64
	 * @see Action_Controller::action_index()
65
	 */
66
	public function action_index()
67
	{
68
		global $txt, $context;
69
70
		// You absolutely must be an admin by here!
71
		isAllowedTo('admin_forum');
72
73
		// Need something to talk about?
74
		loadLanguage('Maintenance');
75
		loadTemplate('Maintenance');
76
77
		// This uses admin tabs - as it should!
78
		$context[$context['admin_menu_name']]['tab_data'] = array(
79
			'title' => $txt['maintain_title'],
80
			'class' => 'database',
81
			'description' => $txt['maintain_info'],
82
			'tabs' => array(
83
				'routine' => array(),
84
				'database' => array(),
85
				'members' => array(),
86
				'topics' => array(),
87
				'hooks' => array(),
88
				'attachments' => array('label' => $txt['maintain_sub_attachments']),
89
			),
90
		);
91
92
		// So many things you can do - but frankly I won't let you - just these!
93
		$subActions = array(
94
			'routine' => array(
95
				'controller' => $this,
96
				'function' => 'action_routine',
97
				'activities' => array(
98
					'version' => 'action_version_display',
99
					'repair' => 'action_repair_display',
100
					'recount' => 'action_recount_display',
101
					'logs' => 'action_logs_display',
102
					'cleancache' => 'action_cleancache_display',
103
				),
104
			),
105
			'database' => array(
106
				'controller' => $this,
107
				'function' => 'action_database',
108
				'activities' => array(
109
					'optimize' => 'action_optimize_display',
110
					'backup' => 'action_backup_display',
111
					'convertmsgbody' => 'action_convertmsgbody_display',
112
				),
113
			),
114
			'members' => array(
115
				'controller' => $this,
116
				'function' => 'action_members',
117
				'activities' => array(
118
					'reattribute' => 'action_reattribute_display',
119
					'purgeinactive' => 'action_purgeinactive_display',
120
					'recountposts' => 'action_recountposts_display',
121
				),
122
			),
123
			'topics' => array(
124
				'controller' => $this,
125
				'function' => 'action_topics',
126
				'activities' => array(
127
					'massmove' => 'action_massmove_display',
128
					'pruneold' => 'action_pruneold_display',
129
				),
130
			),
131
			'hooks' => array(
132
				'controller' => $this,
133
				'function' => 'action_hooks',
134
			),
135
			'attachments' => array(
136
				'controller' => 'ManageAttachments_Controller',
137
				'function' => 'action_maintenance',
138
			),
139
		);
140
141
		// Set up the action handler
142
		$action = new Action('manage_maintenance');
143
144
		// Yep, sub-action time and call integrate_sa_manage_maintenance as well
145
		$subAction = $action->initialize($subActions, 'routine');
146
147
		// Doing something special, does it exist?
148
		if (isset($this->_req->query->activity, $subActions[$subAction]['activities'][$this->_req->query->activity]))
149
			$activity = $this->_req->query->activity;
150
151
		// Set a few things.
152
		$context[$context['admin_menu_name']]['current_subsection'] = $subAction;
153
		$context['page_title'] = $txt['maintain_title'];
154
		$context['sub_action'] = $subAction;
155
156
		// Finally fall through to what we are doing.
157
		$action->dispatch($subAction);
158
159
		// Any special activity defined, then go to it.
160
		if (isset($activity))
161
		{
162
			if (is_string($subActions[$subAction]['activities'][$activity]) && method_exists($this, $subActions[$subAction]['activities'][$activity]))
163
				$this->{$subActions[$subAction]['activities'][$activity]}();
164
			elseif (is_string($subActions[$subAction]['activities'][$activity]))
165
				$subActions[$subAction]['activities'][$activity]();
166
			else
167
			{
168
				if (is_array($subActions[$subAction]['activities'][$activity]))
169
				{
170
					$activity_obj = new $subActions[$subAction]['activities'][$activity]['class']();
171
					$activity_obj->{$subActions[$subAction]['activities'][$activity]['method']}();
172
				}
173
				else
174
				{
175
					$subActions[$subAction]['activities'][$activity]();
176
				}
177
			}
178
		}
179
180
		// Create a maintenance token.  Kinda hard to do it any other way.
181
		createToken('admin-maint');
182
	}
183
184
	/**
185
	 * Supporting function for the database maintenance area.
186
	 */
187
	public function action_database()
188
	{
189
		global $context, $modSettings, $maintenance, $iknowitmaybeunsafe;
190
191
		// We need this, really..
192
		require_once(SUBSDIR . '/Maintenance.subs.php');
193
194
		// Set up the sub-template
195
		$context['sub_template'] = 'maintain_database';
196
197
		if (DB_TYPE === 'MySQL')
0 ignored issues
show
introduced by
The condition DB_TYPE === 'MySQL' is always true.
Loading history...
198
		{
199
			$body_type = fetchBodyType();
200
201
			$context['convert_to'] = $body_type == 'text' ? 'mediumtext' : 'text';
202
			$context['convert_to_suggest'] = ($body_type != 'text' && !empty($modSettings['max_messageLength']) && $modSettings['max_messageLength'] < 65536);
203
		}
204
205
		// Check few things to give advices before make a backup
206
		// If safe mod is enable the external tool is *always* the best (and probably the only) solution
207
		$context['safe_mode_enable'] = false;
208
		if (version_compare(PHP_VERSION, '5.4.0', '<'))
209
		{
210
			$context['safe_mode_enable'] = @ini_get('safe_mode');
211
		}
212
213
		// This is just a...guess
214
		$messages = countMessages();
215
216
		// 256 is what we use in the backup script
217
		detectServer()->setMemoryLimit('256M');
218
		$memory_limit = memoryReturnBytes(ini_get('memory_limit')) / (1024 * 1024);
219
220
		// Zip limit is set to more or less 1/4th the size of the available memory * 1500
221
		// 1500 is an estimate of the number of messages that generates a database of 1 MB (yeah I know IT'S AN ESTIMATION!!!)
222
		// Why that? Because the only reliable zip package is the one sent out the first time,
223
		// so when the backup takes 1/5th (just to stay on the safe side) of the memory available
224
		$zip_limit = $memory_limit * 1500 / 5;
225
226
		// Here is more tricky: it depends on many factors, but the main idea is that
227
		// if it takes "too long" the backup is not reliable. So, I know that on my computer it take
228
		// 20 minutes to backup 2.5 GB, of course my computer is not representative, so I'll multiply by 4 the time.
229
		// I would consider "too long" 5 minutes (I know it can be a long time, but let's start with that):
230
		// 80 minutes for a 2.5 GB and a 5 minutes limit means 160 MB approx
231
		$plain_limit = 240000;
232
233
		// Last thing: are we able to gain time?
234
		$current_time_limit = ini_get('max_execution_time');
235
		@set_time_limit(159); //something strange just to be sure
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

235
		/** @scrutinizer ignore-unhandled */ @set_time_limit(159); //something strange just to be sure

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...
236
		$new_time_limit = ini_get('max_execution_time');
237
		@set_time_limit($current_time_limit);
0 ignored issues
show
Bug introduced by
$current_time_limit of type string is incompatible with the type integer expected by parameter $seconds of set_time_limit(). ( Ignorable by Annotation )

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

237
		@set_time_limit(/** @scrutinizer ignore-type */ $current_time_limit);
Loading history...
238
239
		$context['use_maintenance'] = 0;
240
241
		// External tool if:
242
		//  * safe_mode enable OR
243
		//  * cannot change the execution time OR
244
		//  * cannot reset timeout
245
		if ($context['safe_mode_enable'] || empty($new_time_limit) || ($current_time_limit == $new_time_limit && !function_exists('apache_reset_timeout')))
246
			$context['suggested_method'] = 'use_external_tool';
247
		elseif ($zip_limit < $plain_limit && $messages < $zip_limit)
248
			$context['suggested_method'] = 'zipped_file';
249
		elseif ($zip_limit > $plain_limit || ($zip_limit < $plain_limit && $plain_limit < $messages))
250
		{
251
			$context['suggested_method'] = 'use_external_tool';
252
			$context['use_maintenance'] = empty($maintenance) ? 2 : 0;
253
		}
254
		else
255
		{
256
			$context['use_maintenance'] = 1;
257
			$context['suggested_method'] = 'plain_text';
258
		}
259
260
		loadTemplate('Packages');
261
		loadLanguage('Packages');
262
263
		// $context['package_ftp'] may be set action_backup_display when an error occur
264
		if (!isset($context['package_ftp']))
265
		{
266
			$context['package_ftp'] = array(
267
				'form_elements_only' => true,
268
				'server' => '',
269
				'port' => '',
270
				'username' => '',
271
				'path' => '',
272
				'error' => '',
273
			);
274
		}
275
		$context['skip_security'] = !empty($iknowitmaybeunsafe);
276
	}
277
278
	/**
279
	 * Supporting function for the routine maintenance area.
280
	 *
281
	 * @event integrate_routine_maintenance, passed $context['routine_actions'] array to allow
282
	 * addons to add more options
283
	 * @uses Template Maintenance, sub template maintain_routine
284
	 */
285
	public function action_routine()
286
	{
287
		global $context, $txt, $scripturl;
288
289
		if ($this->_req->getQuery('done', 'trim|strval') === 'recount')
290
			$context['maintenance_finished'] = $txt['maintain_recount'];
291
292
		// set up the sub-template
293
		$context['sub_template'] = 'maintain_routine';
294
		$context['routine_actions'] = array(
295
			'version' => array(
296
				'url' => $scripturl . '?action=admin;area=maintain;sa=routine;activity=version',
297
				'title' => $txt['maintain_version'],
298
				'description' => $txt['maintain_version_info'],
299
				'submit' => $txt['maintain_run_now'],
300
				'hidden' => array(
301
					'session_var' => 'session_id',
302
				)
303
			),
304
			'repair' => array(
305
				'url' => $scripturl . '?action=admin;area=repairboards',
306
				'title' => $txt['maintain_errors'],
307
				'description' => $txt['maintain_errors_info'],
308
				'submit' => $txt['maintain_run_now'],
309
				'hidden' => array(
310
					'session_var' => 'session_id',
311
					'admin-maint_token_var' => 'admin-maint_token',
312
				)
313
			),
314
			'recount' => array(
315
				'url' => $scripturl . '?action=admin;area=maintain;sa=routine;activity=recount',
316
				'title' => $txt['maintain_recount'],
317
				'description' => $txt['maintain_recount_info'],
318
				'submit' => $txt['maintain_run_now'],
319
				'hidden' => array(
320
					'session_var' => 'session_id',
321
					'admin-maint_token_var' => 'admin-maint_token',
322
				)
323
			),
324
			'logs' => array(
325
				'url' => $scripturl . '?action=admin;area=maintain;sa=routine;activity=logs',
326
				'title' => $txt['maintain_logs'],
327
				'description' => $txt['maintain_logs_info'],
328
				'submit' => $txt['maintain_run_now'],
329
				'hidden' => array(
330
					'session_var' => 'session_id',
331
					'admin-maint_token_var' => 'admin-maint_token',
332
				)
333
			),
334
			'cleancache' => array(
335
				'url' => $scripturl . '?action=admin;area=maintain;sa=routine;activity=cleancache',
336
				'title' => $txt['maintain_cache'],
337
				'description' => $txt['maintain_cache_info'],
338
				'submit' => $txt['maintain_run_now'],
339
				'hidden' => array(
340
					'session_var' => 'session_id',
341
					'admin-maint_token_var' => 'admin-maint_token',
342
				)
343
			),
344
		);
345
346
		call_integration_hook('integrate_routine_maintenance', array(&$context['routine_actions']));
347
	}
348
349
	/**
350
	 * Supporting function for the members maintenance area.
351
	 */
352
	public function action_members()
353
	{
354
		global $context, $txt;
355
356
		require_once(SUBSDIR . '/Membergroups.subs.php');
357
358
		// Get all membergroups - for deleting members and the like.
359
		$context['membergroups'] = getBasicMembergroupData(array('all'));
360
361
		// Show that we completed this action
362
		if ($this->_req->getQuery('done', 'strval') === 'recountposts')
363
			$context['maintenance_finished'] = array(
364
				'errors' => array(sprintf($txt['maintain_done'], $txt['maintain_recountposts'])),
365
			);
366
367
		loadJavascriptFile('suggest.js');
368
369
		// Set up the sub-template
370
		$context['sub_template'] = 'maintain_members';
371
	}
372
373
	/**
374
	 * Supporting function for the topics maintenance area.
375
	 *
376
	 * @event integrate_topics_maintenance, passed $context['topics_actions'] to allow addons
377
	 * to add additonal topic maintance functions
378
	 * @uses GenericBoards template, sub template maintain_topics
379
	 */
380
	public function action_topics()
381
	{
382
		global $context, $txt, $scripturl;
383
384
		require_once(SUBSDIR . '/Boards.subs.php');
385
386
		// Let's load up the boards in case they are useful.
387
		$context += getBoardList(array('not_redirection' => true));
388
389
		// Include a list of boards per category for easy toggling.
390
		foreach ($context['categories'] as $cat => &$category)
391
		{
392
			$context['boards_in_category'][$cat] = count($category['boards']);
393
			$category['child_ids'] = array_keys($category['boards']);
394
		}
395
396
		// @todo Hacky!
397
		$txt['choose_board'] = $txt['maintain_old_all'];
398
		$context['boards_check_all'] = true;
399
		loadTemplate('GenericBoards');
400
401
		$context['topics_actions'] = array(
402
			'pruneold' => array(
403
				'url' => $scripturl . '?action=admin;area=maintain;sa=topics;activity=pruneold',
404
				'title' => $txt['maintain_old'],
405
				'submit' => $txt['maintain_old_remove'],
406
				'confirm' => $txt['maintain_old_confirm'],
407
				'hidden' => array(
408
					'session_var' => 'session_id',
409
					'admin-maint_token_var' => 'admin-maint_token',
410
				)
411
			),
412
			'massmove' => array(
413
				'url' => $scripturl . '?action=admin;area=maintain;sa=topics;activity=massmove',
414
				'title' => $txt['move_topics_maintenance'],
415
				'submit' => $txt['move_topics_now'],
416
				'confirm' => $txt['move_topics_confirm'],
417
				'hidden' => array(
418
					'session_var' => 'session_id',
419
					'admin-maint_token_var' => 'admin-maint_token',
420
				)
421
			),
422
		);
423
424
		call_integration_hook('integrate_topics_maintenance', array(&$context['topics_actions']));
425
426
		if ($this->_req->getQuery('done', 'strval') === 'purgeold')
427
			$context['maintenance_finished'] = array(
428
				'errors' => array(sprintf($txt['maintain_done'], $txt['maintain_old'])),
429
			);
430
		elseif ($this->_req->getQuery('done', 'strval') === 'massmove')
431
			$context['maintenance_finished'] = array(
432
				'errors' => array(sprintf($txt['maintain_done'], $txt['move_topics_maintenance'])),
433
			);
434
435
		// Set up the sub-template
436
		$context['sub_template'] = 'maintain_topics';
437
	}
438
439
	/**
440
	 * Find and try to fix all errors on the forum.
441
	 *
442
	 * - Forwards to repair boards controller.
443
	 */
444
	public function action_repair_display()
445
	{
446
		// Honestly, this should be done in the sub function.
447
		validateToken('admin-maint');
448
449
		$controller = new RepairBoards_Controller(new Event_manager());
450
		$controller->pre_dispatch();
451
		$controller->action_repairboards();
452
	}
453
454
	/**
455
	 * Wipes the current cache entries as best it can.
456
	 *
457
	 * - This only applies to our own cache entries, opcache and data.
458
	 * - This action, like other maintenance tasks, may be called automatically
459
	 * by the task scheduler or manually by the admin in Maintenance area.
460
	 */
461
	public function action_cleancache_display()
462
	{
463
		global $context, $txt;
464
465
		checkSession();
466
		validateToken('admin-maint');
467
468
		// Just wipe the whole cache directory!
469
		Cache::instance()->clean();
470
471
		$context['maintenance_finished'] = $txt['maintain_cache'];
472
	}
473
474
	/**
475
	 * Empties all unimportant logs.
476
	 *
477
	 * - This action may be called periodically, by the tasks scheduler,
478
	 * or manually by the admin in Maintenance area.
479
	 */
480
	public function action_logs_display()
481
	{
482
		global $context, $txt;
483
484
		require_once(SUBSDIR . '/Maintenance.subs.php');
485
486
		checkSession();
487
		validateToken('admin-maint');
488
489
		// Maintenance time was scheduled!
490
		// When there is no intelligent life on this planet.
491
		// Apart from me, I mean.
492
		flushLogTables();
493
494
		updateSettings(array('search_pointer' => 0));
495
496
		$context['maintenance_finished'] = $txt['maintain_logs'];
497
	}
498
499
	/**
500
	 * Convert the column "body" of the table {db_prefix}messages from TEXT to
501
	 * MEDIUMTEXT and vice versa.
502
	 *
503
	 * What it does:
504
	 *
505
	 * - It requires the admin_forum permission.
506
	 * - This is needed only for MySQL.
507
	 * - During the conversion from MEDIUMTEXT to TEXT it check if any of the
508
	 * posts exceed the TEXT length and if so it aborts.
509
	 * - This action is linked from the maintenance screen (if it's applicable).
510
	 * - Accessed by ?action=admin;area=maintain;sa=database;activity=convertmsgbody.
511
	 *
512
	 * @uses the convert_msgbody sub template of the Admin template.
513
	 */
514
	public function action_convertmsgbody_display()
515
	{
516
		global $context, $txt, $modSettings, $time_start;
517
518
		// Show me your badge!
519
		isAllowedTo('admin_forum');
520
521
		if (DB_TYPE !== 'MySQL')
0 ignored issues
show
introduced by
The condition DB_TYPE !== 'MySQL' is always false.
Loading history...
522
			return;
523
524
		$body_type = '';
525
526
		// Find the body column "type" from the message table
527
		$colData = getMessageTableColumns();
528
		foreach ($colData as $column)
529
		{
530
			if ($column['name'] === 'body')
531
			{
532
				$body_type = $column['type'];
533
				break;
534
			}
535
		}
536
537
		$context['convert_to'] = $body_type == 'text' ? 'mediumtext' : 'text';
538
539
		if ($body_type === 'text' || ($body_type !== 'text' && isset($this->_req->post->do_conversion)))
540
		{
541
			checkSession();
542
			validateToken('admin-maint');
543
544
			// Make it longer so we can do their limit.
545
			if ($body_type === 'text')
546
				resizeMessageTableBody('mediumtext');
547
			// Shorten the column so we can have a bit (literally per record) less space occupied
548
			else
549
				resizeMessageTableBody('text');
550
551
			$colData = getMessageTableColumns();
552
			foreach ($colData as $column)
553
				if ($column['name'] === 'body')
554
					$body_type = $column['type'];
555
556
			$context['maintenance_finished'] = $txt[$context['convert_to'] . '_title'];
557
			$context['convert_to'] = $body_type === 'text' ? 'mediumtext' : 'text';
558
			$context['convert_to_suggest'] = ($body_type !== 'text' && !empty($modSettings['max_messageLength']) && $modSettings['max_messageLength'] < 65536);
559
560
			return;
561
		}
562
		elseif ($body_type !== 'text' && (!isset($this->_req->post->do_conversion) || isset($this->_req->post->cont)))
563
		{
564
			checkSession();
565
566
			if (empty($this->_req->query->start))
567
				validateToken('admin-maint');
568
			else
569
				validateToken('admin-convertMsg');
570
571
			$context['page_title'] = $txt['not_done_title'];
572
			$context['continue_post_data'] = '';
573
			$context['continue_countdown'] = 3;
574
			$context['sub_template'] = 'not_done';
575
			$increment = 500;
576
			$id_msg_exceeding = isset($this->_req->post->id_msg_exceeding) ? explode(',', $this->_req->post->id_msg_exceeding) : array();
577
578
			$max_msgs = countMessages();
579
			$start = $this->_req->query->start;
580
581
			// Try for as much time as possible.
582
			detectServer()->setTimeLimit(600);
583
584
			while ($start < $max_msgs)
585
			{
586
				$id_msg_exceeding = detectExceedingMessages($start, $increment);
587
588
				$start += $increment;
589
590
				if (microtime(true) - $time_start > 3)
591
				{
592
					createToken('admin-convertMsg');
593
					$context['continue_post_data'] = '
594
						<input type="hidden" name="' . $context['admin-convertMsg_token_var'] . '" value="' . $context['admin-convertMsg_token'] . '" />
595
						<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" />
596
						<input type="hidden" name="id_msg_exceeding" value="' . implode(',', $id_msg_exceeding) . '" />';
597
					$context['continue_get_data'] = '?action=admin;area=maintain;sa=database;activity=convertmsgbody;start=' . $start;
598
					$context['continue_percent'] = round(100 * $start / $max_msgs);
599
					$context['not_done_title'] = $txt['not_done_title'] . ' (' . $context['continue_percent'] . '%)';
600
601
					return;
602
				}
603
			}
604
605
			createToken('admin-maint');
606
			$context['page_title'] = $txt[$context['convert_to'] . '_title'];
607
			$context['sub_template'] = 'convert_msgbody';
608
609
			if (!empty($id_msg_exceeding))
610
			{
611
				if (count($id_msg_exceeding) > 100)
612
				{
613
					$query_msg = array_slice($id_msg_exceeding, 0, 100);
614
					$context['exceeding_messages_morethan'] = sprintf($txt['exceeding_messages_morethan'], count($id_msg_exceeding));
615
				}
616
				else
617
					$query_msg = $id_msg_exceeding;
618
619
				$context['exceeding_messages'] = getExceedingMessages($query_msg);
620
			}
621
		}
622
	}
623
624
	/**
625
	 * Optimizes all tables in the database and lists how much was saved.
626
	 *
627
	 * What it does:
628
	 *
629
	 * - It requires the admin_forum permission.
630
	 * - It shows as the maintain_forum admin area.
631
	 * - It is accessed from ?action=admin;area=maintain;sa=database;activity=optimize.
632
	 * - It also updates the optimize scheduled task such that the tables are not automatically optimized again too soon.
633
	 */
634
	public function action_optimize_display()
635
	{
636
		global $txt, $context;
637
638
		isAllowedTo('admin_forum');
639
640
		// Some validation
641
		checkSession('post');
642
		validateToken('admin-maint');
643
644
		ignore_user_abort(true);
645
646
		require_once(SUBSDIR . '/Maintenance.subs.php');
647
648
		$context['page_title'] = $txt['database_optimize'];
649
		$context['sub_template'] = 'optimize';
650
651
		$tables = getElkTables();
652
653
		// If there aren't any tables then I believe that would mean the world has exploded...
654
		$context['num_tables'] = count($tables);
655
		if ($context['num_tables'] == 0)
656
			throw new Elk_Exception('You appear to be running ElkArte in a flat file mode... fantastic!', false);
657
658
		// For each table....
659
		$context['optimized_tables'] = array();
660
		foreach ($tables as $table)
661
		{
662
			// Optimize the table!  We use backticks here because it might be a custom table.
663
			$data_freed = optimizeTable($table['table_name']);
664
665
			if ($data_freed > 0)
666
				$context['optimized_tables'][] = array(
667
					'name' => $table['table_name'],
668
					'data_freed' => $data_freed,
669
				);
670
		}
671
672
		// Number of tables, etc....
673
		$txt['database_numb_tables'] = sprintf($txt['database_numb_tables'], $context['num_tables']);
674
		$context['num_tables_optimized'] = count($context['optimized_tables']);
675
676
		// Check that we don't auto optimise again too soon!
677
		require_once(SUBSDIR . '/ScheduledTasks.subs.php');
678
		calculateNextTrigger('auto_optimize', true);
679
	}
680
681
	/**
682
	 * Recount many forum totals that can be recounted automatically without harm.
683
	 *
684
	 * What it does:
685
	 *
686
	 * - it requires the admin_forum permission.
687
	 * - It shows the maintain_forum admin area.
688
	 * - The function redirects back to ?action=admin;area=maintain when complete.
689
	 * - It is accessed via ?action=admin;area=maintain;sa=database;activity=recount.
690
	 *
691
	 * Totals recounted:
692
	 * - fixes for topics with wrong num_replies.
693
	 * - updates for num_posts and num_topics of all boards.
694
	 * - recounts personal_messages but not unread_messages.
695
	 * - repairs messages pointing to boards with topics pointing to other boards.
696
	 * - updates the last message posted in boards and children.
697
	 * - updates member count, latest member, topic count, and message count.
698
	 */
699
	public function action_recount_display()
700
	{
701
		global $txt, $context, $modSettings, $time_start;
702
703
		isAllowedTo('admin_forum');
704
		checkSession();
705
706
		// Functions
707
		require_once(SUBSDIR . '/Maintenance.subs.php');
708
		require_once(SUBSDIR . '/Topic.subs.php');
709
710
		// Validate the request or the loop
711
		if (!isset($this->_req->query->step))
712
			validateToken('admin-maint');
713
		else
714
			validateToken('admin-boardrecount');
715
716
		// For the loop template
717
		$context['page_title'] = $txt['not_done_title'];
718
		$context['continue_post_data'] = '';
719
		$context['continue_countdown'] = 3;
720
		$context['sub_template'] = 'not_done';
721
722
		// Try for as much time as possible.
723
		detectServer()->setTimeLimit(600);
724
725
		// Step the number of topics at a time so things don't time out...
726
		$this->max_topics = getMaxTopicID();
727
		$this->increment = (int) min(max(50, ceil($this->max_topics / 4)), 2000);
728
729
		// An 8 step process, should be 12 for the admin
730
		$this->total_steps = 8;
731
		$this->start = $this->_req->getQuery('start', 'inval', 0);
732
		$this->step = $this->_req->getQuery('step', 'intval', 0);
733
734
		// Get each topic with a wrong reply count and fix it
735
		if (empty($this->step))
736
		{
737
			// let's just do some at a time, though.
738
			while ($this->start < $this->max_topics)
739
			{
740
				recountApprovedMessages($this->start, $this->increment);
741
				recountUnapprovedMessages($this->start, $this->increment);
742
				$this->start += $this->increment;
743
744
				if (microtime(true) - $time_start > 3)
745
				{
746
					$percent = round((100 * $this->start / $this->max_topics) / $this->total_steps);
747
					$this->_buildContinue($percent, 0);
748
					return;
749
				}
750
			}
751
752
			// Done with step 0, reset start for the next one
753
			$this->start = 0;
754
		}
755
756
		// Update the post count of each board.
757
		if ($this->step <= 1)
758
		{
759
			if (empty($this->start))
760
				resetBoardsCounter('num_posts');
761
762
			while ($this->start < $this->max_topics)
763
			{
764
				// Recount the posts
765
				updateBoardsCounter('posts', $this->start, $this->increment);
766
				$this->start += $this->increment;
767
768
				if (microtime(true) - $time_start > 3)
769
				{
770
					$percent = round((200 + 100 * $this->start / $this->max_topics) / $this->total_steps);
771
					$this->_buildContinue($percent, 1);
772
					return;
773
				}
774
			}
775
776
			// Done with step 1, reset start for the next one
777
			$this->start = 0;
778
		}
779
780
		// Update the topic count of each board.
781
		if ($this->step <= 2)
782
		{
783
			if (empty($this->start))
784
				resetBoardsCounter('num_topics');
785
786
			while ($this->start < $this->max_topics)
787
			{
788
				updateBoardsCounter('topics', $this->start, $this->increment);
789
				$this->start += $this->increment;
790
791
				if (microtime(true) - $time_start > 3)
792
				{
793
					$percent = round((300 + 100 * $this->start / $this->max_topics) / $this->total_steps);
794
					$this->_buildContinue($percent, 2);
795
					return;
796
				}
797
			}
798
799
			// Done with step 2, reset start for the next one
800
			$this->start = 0;
801
		}
802
803
		// Update the unapproved post count of each board.
804
		if ($this->step <= 3)
805
		{
806
			if (empty($this->start))
807
				resetBoardsCounter('unapproved_posts');
808
809
			while ($this->start < $this->max_topics)
810
			{
811
				updateBoardsCounter('unapproved_posts', $this->start, $this->increment);
812
				$this->start += $this->increment;
813
814
				if (microtime(true) - $time_start > 3)
815
				{
816
					$percent = round((400 + 100 * $this->start / $this->max_topics) / $this->total_steps);
817
					$this->_buildContinue($percent, 3);
818
					return;
819
				}
820
			}
821
822
			// Done with step 3, reset start for the next one
823
			$this->start = 0;
824
		}
825
826
		// Update the unapproved topic count of each board.
827
		if ($this->step <= 4)
828
		{
829
			if (empty($this->start))
830
				resetBoardsCounter('unapproved_topics');
831
832
			while ($this->start < $this->max_topics)
833
			{
834
				updateBoardsCounter('unapproved_topics', $this->start, $this->increment);
835
				$this->start += $this->increment;
836
837
				if (microtime(true) - $time_start > 3)
838
				{
839
					$percent = round((500 + 100 * $this->start / $this->max_topics) / $this->total_steps);
840
					$this->_buildContinue($percent, 4);
841
					return;
842
				}
843
			}
844
845
			// Done with step 4, reset start for the next one
846
			$this->start = 0;
847
		}
848
849
		// Get all members with wrong number of personal messages.
850
		if ($this->step <= 5)
851
		{
852
			updatePersonalMessagesCounter();
853
854
			if (microtime(true) - $time_start > 3)
855
			{
856
				$this->start = 0;
857
				$percent = round(700 / $this->total_steps);
858
				$this->_buildContinue($percent, 6);
859
				return;
860
			}
861
862
			// Done with step 5, reset start for the next one
863
			$this->start = 0;
864
		}
865
866
		// Any messages pointing to the wrong board?
867
		if ($this->step <= 6)
868
		{
869
			while ($this->start < $modSettings['maxMsgID'])
870
			{
871
				updateMessagesBoardID($this->_req->query->start, $this->increment);
872
				$this->start += $this->increment;
873
874
				if (microtime(true) - $time_start > 3)
875
				{
876
					$percent = round((700 + 100 * $this->start / $modSettings['maxMsgID']) / $this->total_steps);
877
					$this->_buildContinue($percent, 6);
878
					return;
879
				}
880
			}
881
882
			// Done with step 6, reset start for the next one
883
			$this->start = 0;
884
		}
885
886
		updateBoardsLastMessage();
887
888
		// Update all the basic statistics.
889
		require_once(SUBSDIR . '/Members.subs.php');
890
		updateMemberStats();
891
		require_once(SUBSDIR . '/Messages.subs.php');
892
		updateMessageStats();
893
		require_once(SUBSDIR . '/Topic.subs.php');
894
		updateTopicStats();
895
896
		// Finally, update the latest event times.
897
		require_once(SUBSDIR . '/ScheduledTasks.subs.php');
898
		calculateNextTrigger();
899
900
		// Ta-da
901
		redirectexit('action=admin;area=maintain;sa=routine;done=recount');
902
	}
903
904
	/**
905
	 * Helper function for teh recount process, build the continue values for
906
	 * the template
907
	 *
908
	 * @param int $percent percent done
909
	 * @param int $step step we are on
910
	 */
911
	private function _buildContinue($percent, $step)
912
	{
913
		global $context, $txt;
914
915
		createToken('admin-boardrecount');
916
917
		$context['continue_post_data'] = '
918
			<input type="hidden" name="' . $context['admin-boardrecount_token_var'] . '" value="' . $context['admin-boardrecount_token'] . '" />
919
			<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" />';
920
		$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=' . $step . ';start=' . $this->start;
921
		$context['continue_percent'] = $percent;
922
		$context['not_done_title'] = $txt['not_done_title'] . ' (' . $context['continue_percent'] . '%)';
923
	}
924
925
	/**
926
	 * Perform a detailed version check.  A very good thing ;).
927
	 *
928
	 * What it does
929
	 * - The function parses the comment headers in all files for their version information,
930
	 * and outputs that for some javascript to check with simplemachines.org.
931
	 * - It does not connect directly with elkarte.net, but rather expects the client to.
932
	 * - It requires the admin_forum permission.
933
	 * - Uses the view_versions admin area.
934
	 * - Accessed through ?action=admin;area=maintain;sa=routine;activity=version.
935
	 *
936
	 * @uses Admin template, view_versions sub-template.
937
	 */
938
	public function action_version_display()
939
	{
940
		global $txt, $context, $modSettings;
941
942
		isAllowedTo('admin_forum');
943
944
		// Call the function that'll get all the version info we need.
945
		require_once(SUBSDIR . '/Admin.subs.php');
946
		$versionOptions = array(
947
			'include_ssi' => true,
948
			'include_subscriptions' => true,
949
			'sort_results' => true,
950
		);
951
		$version_info = getFileVersions($versionOptions);
952
953
		// Add the new info to the template context.
954
		$context += array(
955
			'file_versions' => $version_info['file_versions'],
956
			'file_versions_admin' => $version_info['file_versions_admin'],
957
			'file_versions_controllers' => $version_info['file_versions_controllers'],
958
			'file_versions_database' => $version_info['file_versions_database'],
959
			'file_versions_subs' => $version_info['file_versions_subs'],
960
			'default_template_versions' => $version_info['default_template_versions'],
961
			'template_versions' => $version_info['template_versions'],
962
			'default_language_versions' => $version_info['default_language_versions'],
963
			'default_known_languages' => array_keys($version_info['default_language_versions']),
964
		);
965
966
		// Make it easier to manage for the template.
967
		$context['forum_version'] = FORUM_VERSION;
968
969
		$context['sub_template'] = 'view_versions';
970
		$context['page_title'] = $txt['admin_version_check'];
971
		$context['detailed_version_url'] = $modSettings['detailed-version.js'];
972
	}
973
974
	/**
975
	 * Re-attribute posts to the user sent from the maintenance page.
976
	 */
977
	public function action_reattribute_display()
978
	{
979
		global $context, $txt;
980
981
		checkSession();
982
983
		$validator = new Data_Validator();
984
		$validator->sanitation_rules(array('posts' => 'empty', 'type' => 'trim', 'from_email' => 'trim', 'from_name' => 'trim', 'to' => 'trim'));
985
		$validator->validation_rules(array('from_email' => 'valid_email', 'from_name' => 'required', 'to' => 'required', 'type' => 'contains[name,email]'));
986
		$validator->validate($this->_req->post);
987
988
		// Fetch the Mr. Clean values
989
		$our_post = array_replace((array) $this->_req->post, $validator->validation_data());
990
991
		// Do we have a valid set of options to continue?
992
		if (($our_post['type'] === 'name' && !empty($our_post['from_name'])) || ($our_post['type'] === 'email' && !$validator->validation_errors('from_email')))
993
		{
994
			// Find the member.
995
			require_once(SUBSDIR . '/Auth.subs.php');
996
			$members = findMembers($our_post['to']);
997
998
			// No members, no further
999
			if (empty($members))
1000
				throw new Elk_Exception('reattribute_cannot_find_member');
1001
1002
			$memID = array_shift($members);
1003
			$memID = $memID['id'];
1004
1005
			$email = $our_post['type'] == 'email' ? $our_post['from_email'] : '';
1006
			$membername = $our_post['type'] == 'name' ? $our_post['from_name'] : '';
1007
1008
			// Now call the reattribute function.
1009
			require_once(SUBSDIR . '/Members.subs.php');
1010
			reattributePosts($memID, $email, $membername, !$our_post['posts']);
1011
1012
			$context['maintenance_finished'] = array(
1013
				'errors' => array(sprintf($txt['maintain_done'], $txt['maintain_reattribute_posts'])),
1014
			);
1015
		}
1016
		else
1017
		{
1018
			// Show them the correct error
1019
			if ($our_post['type'] === 'name' && empty($our_post['from_name']))
1020
				$error = $validator->validation_errors(array('from_name', 'to'));
1021
			else
1022
				$error = $validator->validation_errors(array('from_email', 'to'));
1023
1024
			$context['maintenance_finished'] = array(
1025
				'errors' => $error,
1026
				'type' => 'minor',
1027
			);
1028
		}
1029
	}
1030
1031
	/**
1032
	 * Handling function for the backup stuff.
1033
	 *
1034
	 * - It requires an administrator and the session hash by post.
1035
	 * - This method simply forwards to DumpDatabase2().
1036
	 */
1037
	public function action_backup_display()
1038
	{
1039
		global $user_info, $iknowitmaybeunsafe;
1040
1041
		validateToken('admin-maint');
1042
1043
		// Administrators only!
1044
		if (!allowedTo('admin_forum'))
1045
			throw new Elk_Exception('no_dump_database', 'critical');
1046
1047
		checkSession('post');
1048
1049
		// Validate access
1050
		if (empty($iknowitmaybeunsafe) && !$this->_validate_access())
1051
			return $this->action_database();
1052
		else
1053
		{
1054
			require_once(SUBSDIR . '/Admin.subs.php');
1055
1056
			emailAdmins('admin_backup_database', array(
1057
				'BAK_REALNAME' => $user_info['name']
1058
			));
1059
			logAction('database_backup', array('member' => $user_info['id']), 'admin');
1060
1061
			require_once(SOURCEDIR . '/DumpDatabase.php');
1062
			DumpDatabase2();
1063
1064
			// Should not get here as DumpDatabase2 exits
1065
			return true;
1066
		}
1067
	}
1068
1069
	/**
1070
	 * Validates the user can make an FTP connection with the supplied uid/pass
1071
	 *
1072
	 * - Used as an extra layer of security when performing backups
1073
	 */
1074
	private function _validate_access()
1075
	{
1076
		global $context, $txt;
1077
1078
		require_once(SUBSDIR . '/FtpConnection.class.php');
1079
1080
		$ftp = new Ftp_Connection($this->_req->post->ftp_server, $this->_req->post->ftp_port, $this->_req->post->ftp_username, $this->_req->post->ftp_password);
1081
1082
		// No errors on the connection, id/pass are good
1083
		if ($ftp->error === false)
1084
		{
1085
			// I know, I know... but a lot of people want to type /home/xyz/... which is wrong, but logical.
1086
			if (!$ftp->chdir($this->_req->post->ftp_path))
1087
				$ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $this->_req->post->ftp_path));
1088
		}
1089
1090
		// If we had an error...
1091
		if ($ftp->error !== false)
1092
		{
1093
			loadLanguage('Packages');
1094
			$ftp_error = $ftp->last_message === null ? (isset($txt['package_ftp_' . $ftp->error]) ? $txt['package_ftp_' . $ftp->error] : '') : $ftp->last_message;
0 ignored issues
show
Bug introduced by
Are you sure $ftp->error of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

1094
			$ftp_error = $ftp->last_message === null ? (isset($txt['package_ftp_' . /** @scrutinizer ignore-type */ $ftp->error]) ? $txt['package_ftp_' . $ftp->error] : '') : $ftp->last_message;
Loading history...
1095
1096
			// Fill the boxes for a FTP connection with data from the previous attempt
1097
			$context['package_ftp'] = array(
1098
				'form_elements_only' => 1,
1099
				'server' => $this->_req->post->ftp_server,
1100
				'port' => $this->_req->post->ftp_port,
1101
				'username' => $this->_req->post->ftp_username,
1102
				'path' => $this->_req->post->ftp_path,
1103
				'error' => empty($ftp_error) ? null : $ftp_error,
1104
			);
1105
1106
			return false;
1107
		}
1108
1109
		return true;
1110
	}
1111
1112
	/**
1113
	 * Removing old and inactive members.
1114
	 */
1115
	public function action_purgeinactive_display()
1116
	{
1117
		global $context, $txt;
1118
1119
		checkSession();
1120
		validateToken('admin-maint');
1121
1122
		// Start with checking and cleaning what was sent
1123
		$validator = new Data_Validator();
1124
		$validator->sanitation_rules(array('maxdays' => 'intval'));
1125
		$validator->validation_rules(array('maxdays' => 'required', 'groups' => 'isarray', 'del_type' => 'required'));
1126
1127
		// Validator says, you can pass or not
1128
		if ($validator->validate($this->_req->post))
1129
		{
1130
			// Get the clean data
1131
			$our_post = array_replace((array) $this->_req->post, $validator->validation_data());
1132
1133
			require_once(SUBSDIR . '/Maintenance.subs.php');
1134
			require_once(SUBSDIR . '/Members.subs.php');
1135
1136
			$groups = array();
1137
			foreach ($our_post['groups'] as $id => $dummy)
1138
				$groups[] = (int) $id;
1139
1140
			$time_limit = (time() - ($our_post['maxdays'] * 24 * 3600));
1141
			$members = purgeMembers($our_post['del_type'], $groups, $time_limit);
1142
			deleteMembers($members);
1143
1144
			$context['maintenance_finished'] = array(
1145
				'errors' => array(sprintf($txt['maintain_done'], $txt['maintain_members'])),
1146
			);
1147
		}
1148
		else
1149
		{
1150
			$context['maintenance_finished'] = array(
1151
				'errors' => $validator->validation_errors(),
1152
				'type' => 'minor',
1153
			);
1154
		}
1155
	}
1156
1157
	/**
1158
	 * This method takes care of removal of old posts.
1159
	 * They're very very old, perhaps even older.
1160
	 */
1161
	public function action_pruneold_display()
1162
	{
1163
		validateToken('admin-maint');
1164
1165
		isAllowedTo('admin_forum');
1166
		checkSession('post', 'admin');
1167
1168
		// No boards at all?  Forget it then :/.
1169
		if (empty($this->_req->post->boards))
1170
			redirectexit('action=admin;area=maintain;sa=topics');
1171
1172
		$boards = array_keys($this->_req->post->boards);
1173
1174
		if (!isset($this->_req->post->delete_type) || !in_array($this->_req->post->delete_type, array('moved', 'nothing', 'locked')))
1175
			$delete_type = 'nothing';
1176
		else
1177
			$delete_type = $this->_req->post->delete_type;
1178
1179
		$exclude_stickies = isset($this->_req->post->delete_old_not_sticky);
1180
1181
		// @todo what is the minimum for maxdays? Maybe throw an error?
1182
		$older_than = time() - 3600 * 24 * max($this->_req->getPost('maxdays', 'intval', 0), 1);
1183
1184
		require_once(SUBSDIR . '/Topic.subs.php');
1185
		removeOldTopics($boards, $delete_type, $exclude_stickies, $older_than);
1186
1187
		// Log an action into the moderation log.
1188
		logAction('pruned', array('days' => max($this->_req->getPost('maxdays', 'intval', 0), 1)));
1189
1190
		redirectexit('action=admin;area=maintain;sa=topics;done=purgeold');
1191
	}
1192
1193
	/**
1194
	 * Moves topics from one board to another.
1195
	 *
1196
	 * @uses not_done template to pause the process.
1197
	 */
1198
	public function action_massmove_display()
1199
	{
1200
		global $context, $txt, $time_start;
1201
1202
		// Only admins.
1203
		isAllowedTo('admin_forum');
1204
1205
		// And valid requests
1206
		checkSession();
1207
1208
		// Set up to the context.
1209
		$context['page_title'] = $txt['not_done_title'];
1210
		$context['continue_countdown'] = 3;
1211
		$context['continue_post_data'] = '';
1212
		$context['continue_get_data'] = '';
1213
		$context['sub_template'] = 'not_done';
1214
		$context['start'] = $this->_req->getQuery('start', 'intval', 0);
1215
1216
		// First time we do this?
1217
		$id_board_from = $this->_req->getPost('id_board_from', 'intval', (int) $this->_req->query->id_board_from);
1218
		$id_board_to = $this->_req->getPost('id_board_to', 'intval', (int) $this->_req->query->id_board_to);
1219
1220
		// No boards then this is your stop.
1221
		if (empty($id_board_from) || empty($id_board_to))
1222
			return;
1223
1224
		// These will be needed
1225
		require_once(SUBSDIR . '/Maintenance.subs.php');
1226
		require_once(SUBSDIR . '/Topic.subs.php');
1227
1228
		// How many topics are we moving?
1229
		if (!isset($this->_req->query->totaltopics))
1230
			$total_topics = countTopicsFromBoard($id_board_from);
1231
		else
1232
		{
1233
			$total_topics = (int) $this->_req->query->totaltopics;
1234
			validateToken('admin_movetopics');
1235
		}
1236
1237
		// We have topics to move so start the process.
1238
		if (!empty($total_topics))
1239
		{
1240
			while ($context['start'] <= $total_topics)
1241
			{
1242
				// Lets get the next 10 topics.
1243
				$topics = getTopicsToMove($id_board_from);
1244
1245
				// Just return if we don't have any topics left to move.
1246
				if (empty($topics))
1247
					break;
1248
1249
				// Lets move them.
1250
				moveTopics($topics, $id_board_to);
1251
1252
				// Increase the counter
1253
				$context['start'] += 10;
1254
1255
				// If this is really taking some time, show the pause screen
1256
				if (microtime(true) - $time_start > 3)
1257
				{
1258
					createToken('admin_movetopics');
1259
1260
					// What's the percent?
1261
					$context['continue_percent'] = round(100 * ($context['start'] / $total_topics), 1);
1262
1263
					// Set up for the form
1264
					$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'];
1265
					$context['continue_post_data'] = '
1266
						<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" />
1267
						<input type="hidden" name="' . $context['admin_movetopics_token_var'] . '" value="' . $context['admin_movetopics_token'] . '" />';
1268
1269
					// Let the template system do it's thang.
1270
					return;
1271
				}
1272
			}
1273
		}
1274
1275
		// Don't confuse admins by having an out of date cache.
1276
		Cache::instance()->remove('board-' . $id_board_from);
1277
		Cache::instance()->remove('board-' . $id_board_to);
1278
1279
		redirectexit('action=admin;area=maintain;sa=topics;done=massmove');
1280
	}
1281
1282
	/**
1283
	 * Generates a list of integration hooks for display
1284
	 *
1285
	 * - Accessed through ?action=admin;area=maintain;sa=hooks;
1286
	 * - Allows for removal or disabling of selected hooks
1287
	 */
1288
	public function action_hooks()
1289
	{
1290
		global $scripturl, $context, $txt;
1291
1292
		require_once(SUBSDIR . '/AddonSettings.subs.php');
1293
1294
		$context['filter_url'] = '';
1295
		$context['current_filter'] = '';
1296
1297
		// Get the list of the current system hooks, filter them if needed
1298
		$currentHooks = get_integration_hooks();
1299
		if (isset($this->_req->query->filter) && in_array($this->_req->query->filter, array_keys($currentHooks)))
1300
		{
1301
			$context['filter_url'] = ';filter=' . $this->_req->query->filter;
1302
			$context['current_filter'] = $this->_req->query->filter;
1303
		}
1304
1305
		$list_options = array(
1306
			'id' => 'list_integration_hooks',
1307
			'title' => $txt['maintain_sub_hooks_list'],
1308
			'items_per_page' => 20,
1309
			'base_href' => $scripturl . '?action=admin;area=maintain;sa=hooks' . $context['filter_url'] . ';' . $context['session_var'] . '=' . $context['session_id'],
1310
			'default_sort_col' => 'hook_name',
1311
			'get_items' => array(
1312
				'function' => array($this, 'list_getIntegrationHooks'),
1313
			),
1314
			'get_count' => array(
1315
				'function' => array($this, 'list_getIntegrationHooksCount'),
1316
			),
1317
			'no_items_label' => $txt['hooks_no_hooks'],
1318
			'columns' => array(
1319
				'hook_name' => array(
1320
					'header' => array(
1321
						'value' => $txt['hooks_field_hook_name'],
1322
					),
1323
					'data' => array(
1324
						'db' => 'hook_name',
1325
					),
1326
					'sort' => array(
1327
						'default' => 'hook_name',
1328
						'reverse' => 'hook_name DESC',
1329
					),
1330
				),
1331
				'function_name' => array(
1332
					'header' => array(
1333
						'value' => $txt['hooks_field_function_name'],
1334
					),
1335
					'data' => array(
1336
						'function' => function ($data) {
1337
							global $txt;
1338
1339
							if (!empty($data['included_file']))
1340
								return $txt['hooks_field_function'] . ': ' . $data['real_function'] . '<br />' . $txt['hooks_field_included_file'] . ': ' . $data['included_file'];
1341
							else
1342
								return $data['real_function'];
1343
						},
1344
					),
1345
					'sort' => array(
1346
						'default' => 'function_name',
1347
						'reverse' => 'function_name DESC',
1348
					),
1349
				),
1350
				'file_name' => array(
1351
					'header' => array(
1352
						'value' => $txt['hooks_field_file_name'],
1353
					),
1354
					'data' => array(
1355
						'db' => 'file_name',
1356
					),
1357
					'sort' => array(
1358
						'default' => 'file_name',
1359
						'reverse' => 'file_name DESC',
1360
					),
1361
				),
1362
				'status' => array(
1363
					'header' => array(
1364
						'value' => $txt['hooks_field_hook_exists'],
1365
						'class' => 'nowrap',
1366
					),
1367
					'data' => array(
1368
						'function' => function ($data) {
1369
							return '<i class="icon i-post_moderation_' . $data['status'] . '" title="' . $data['img_text'] . '"></i>';
1370
						},
1371
						'class' => 'centertext',
1372
					),
1373
					'sort' => array(
1374
						'default' => 'status',
1375
						'reverse' => 'status DESC',
1376
					),
1377
				),
1378
			),
1379
			'additional_rows' => array(
1380
				array(
1381
					'position' => 'after_title',
1382
					'value' => $txt['hooks_disable_legend'] . ':
1383
					<ul>
1384
						<li>
1385
							<i class="icon i-post_moderation_allow" title="' . $txt['hooks_active'] . '"></i>' . $txt['hooks_disable_legend_exists'] . '
1386
						</li>
1387
						<li>
1388
							<i class="icon i-post_moderation_moderate" title="' . $txt['hooks_disabled'] . '"></i>' . $txt['hooks_disable_legend_disabled'] . '
1389
						</li>
1390
						<li>
1391
							<i class="icon i-post_moderation_deny" title="' . $txt['hooks_missing'] . '"></i>' . $txt['hooks_disable_legend_missing'] . '
1392
						</li>
1393
					</ul>'
1394
				),
1395
			),
1396
		);
1397
1398
		createList($list_options);
1399
1400
		$context['page_title'] = $txt['maintain_sub_hooks_list'];
1401
		$context['sub_template'] = 'show_list';
1402
		$context['default_list'] = 'list_integration_hooks';
1403
	}
1404
1405
	/**
1406
	 * Recalculate all members post counts
1407
	 *
1408
	 * What it does:
1409
	 *
1410
	 * - It requires the admin_forum permission.
1411
	 * - Recounts all posts for members found in the message table
1412
	 * - Updates the members post count record in the members table
1413
	 * - Honors the boards post count flag
1414
	 * - Does not count posts in the recycle bin
1415
	 * - Zeros post counts for all members with no posts in the message table
1416
	 * - Runs as a delayed loop to avoid server overload
1417
	 * - Uses the not_done template in Admin.template
1418
	 * - Redirects back to action=admin;area=maintain;sa=members when complete.
1419
	 * - Accessed via ?action=admin;area=maintain;sa=members;activity=recountposts
1420
	 */
1421
	public function action_recountposts_display()
1422
	{
1423
		global $txt, $context;
1424
1425
		// Check the session
1426
		checkSession();
1427
1428
		// Set up to the context for the pause screen
1429
		$context['page_title'] = $txt['not_done_title'];
1430
		$context['continue_countdown'] = 3;
1431
		$context['continue_get_data'] = '';
1432
		$context['sub_template'] = 'not_done';
1433
1434
		// Init, do 200 members in a bunch
1435
		$increment = 200;
1436
		$start = $this->_req->getQuery('start', 'intval', 0);
1437
1438
		// Ask for some extra time, on big boards this may take a bit
1439
		detectServer()->setTimeLimit(600);
1440
		Debug::instance()->off();
1441
1442
		// The functions here will come in handy
1443
		require_once(SUBSDIR . '/Maintenance.subs.php');
1444
1445
		// Only run this query if we don't have the total number of members that have posted
1446
		if (!isset($this->_req->session->total_members) || $start === 0)
1447
		{
1448
			validateToken('admin-maint');
1449
			$total_members = countContributors();
1450
			$_SESSION['total_member'] = $total_members;
1451
		}
1452
		else
1453
		{
1454
			validateToken('admin-recountposts');
1455
			$total_members = $this->_req->session->total_members;
1456
		}
1457
1458
		// Lets get the next group of members and determine their post count
1459
		// (from the boards that have post count enabled of course).
1460
		$total_rows = updateMembersPostCount($start, $increment);
1461
1462
		// Continue?
1463
		if ($total_rows == $increment)
1464
		{
1465
			createToken('admin-recountposts');
1466
1467
			$start += $increment;
1468
			$context['continue_get_data'] = '?action=admin;area=maintain;sa=members;activity=recountposts;start=' . $start;
1469
			$context['continue_percent'] = round(100 * $start / $total_members);
1470
			$context['not_done_title'] = $txt['not_done_title'] . ' (' . $context['continue_percent'] . '%)';
1471
			$context['continue_post_data'] = '<input type="hidden" name="' . $context['admin-recountposts_token_var'] . '" value="' . $context['admin-recountposts_token'] . '" />
1472
				<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" />';
1473
1474
			Debug::instance()->on();
1475
			return;
1476
		}
1477
1478
		// No countable posts? set posts counter to 0
1479
		updateZeroPostMembers();
1480
1481
		Debug::instance()->on();
1482
		// All done, clean up and go back to maintenance
1483
		unset($_SESSION['total_members']);
1484
		redirectexit('action=admin;area=maintain;sa=members;done=recountposts');
1485
	}
1486
1487
	/**
1488
	 * Simply returns the total count of integration hooks
1489
	 * Callback for createList().
1490
	 *
1491
	 * @return int
1492
	 */
1493
	public function list_getIntegrationHooksCount()
1494
	{
1495
		global $context;
1496
1497
		$context['filter'] = false;
1498
		if (isset($this->_req->query->filter))
1499
			$context['filter'] = $this->_req->query->filter;
1500
1501
		return integration_hooks_count($context['filter']);
1502
	}
1503
1504
	/**
1505
	 * Callback for createList(). Called by action_hooks
1506
	 *
1507
	 * @param int $start The item to start with (for pagination purposes)
1508
	 * @param int $items_per_page The number of items to show per page
1509
	 * @param string $sort A string indicating how to sort the results
1510
	 */
1511
	public function list_getIntegrationHooks($start, $items_per_page, $sort)
1512
	{
1513
		return list_integration_hooks_data($start, $items_per_page, $sort);
1514
	}
1515
}
1516