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

Stats.php ➔ DisplayStats()   F

Complexity

Conditions 82
Paths > 20000

Size

Total Lines 681
Code Lines 390

Duplication

Lines 116
Ratio 17.03 %

Importance

Changes 0
Metric Value
cc 82
eloc 390
nc 429496.7295
nop 0
dl 116
loc 681
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Provide a display for forum statistics
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Display some useful/interesting board statistics.
21
 *
22
 * gets all the statistics in order and puts them in.
23
 * uses the Stats template and language file. (and main sub template.)
24
 * requires the view_stats permission.
25
 * accessed from ?action=stats.
26
 */
27
function DisplayStats()
28
{
29
	global $txt, $scripturl, $modSettings, $context, $smcFunc;
30
31
	isAllowedTo('view_stats');
32
	// Page disabled - redirect them out
33
	if (empty($modSettings['trackStats']))
34
		fatal_lang_error('feature_disabled', true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string|false.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
35
36
	if (!empty($_REQUEST['expand']))
37
	{
38
		$context['robot_no_index'] = true;
39
40
		$month = (int) substr($_REQUEST['expand'], 4);
41
		$year = (int) substr($_REQUEST['expand'], 0, 4);
42
		if ($year > 1900 && $year < 2200 && $month >= 1 && $month <= 12)
43
			$_SESSION['expanded_stats'][$year][] = $month;
44
	}
45
	elseif (!empty($_REQUEST['collapse']))
46
	{
47
		$context['robot_no_index'] = true;
48
49
		$month = (int) substr($_REQUEST['collapse'], 4);
50
		$year = (int) substr($_REQUEST['collapse'], 0, 4);
51
		if (!empty($_SESSION['expanded_stats'][$year]))
52
			$_SESSION['expanded_stats'][$year] = array_diff($_SESSION['expanded_stats'][$year], array($month));
53
	}
54
55
	// Handle the XMLHttpRequest.
56
	if (isset($_REQUEST['xml']))
57
	{
58
		// Collapsing stats only needs adjustments of the session variables.
59
		if (!empty($_REQUEST['collapse']))
60
			obExit(false);
61
62
		$context['sub_template'] = 'stats';
63
		$context['yearly'] = array();
64
65
		if (empty($month) || empty($year))
66
			return;
67
68
		getDailyStats('YEAR(date) = {int:year} AND MONTH(date) = {int:month}', array('year' => $year, 'month' => $month));
69
		$context['yearly'][$year]['months'][$month]['date'] = array(
70
			'month' => sprintf('%02d', $month),
71
			'year' => $year,
72
		);
73
		return;
74
	}
75
76
	loadLanguage('Stats');
77
	loadTemplate('Stats');
78
	loadJavaScriptFile('stats.js', array('default_theme' => true, 'defer' => false), 'smf_stats');
79
80
	// Build the link tree......
81
	$context['linktree'][] = array(
82
		'url' => $scripturl . '?action=stats',
83
		'name' => $txt['stats_center']
84
	);
85
	$context['page_title'] = $context['forum_name'] . ' - ' . $txt['stats_center'];
86
87
	$context['show_member_list'] = allowedTo('view_mlist');
88
89
	// Get averages...
90
	$result = $smcFunc['db_query']('', '
91
		SELECT
92
			SUM(posts) AS posts, SUM(topics) AS topics, SUM(registers) AS registers,
93
			SUM(most_on) AS most_on, MIN(date) AS date, SUM(hits) AS hits
94
		FROM {db_prefix}log_activity',
95
		array(
96
		)
97
	);
98
	$row = $smcFunc['db_fetch_assoc']($result);
99
	$smcFunc['db_free_result']($result);
100
101
	// This would be the amount of time the forum has been up... in days...
102
	$total_days_up = ceil((time() - strtotime($row['date'])) / (60 * 60 * 24));
103
104
	$context['average_posts'] = comma_format(round($row['posts'] / $total_days_up, 2));
105
	$context['average_topics'] = comma_format(round($row['topics'] / $total_days_up, 2));
106
	$context['average_members'] = comma_format(round($row['registers'] / $total_days_up, 2));
107
	$context['average_online'] = comma_format(round($row['most_on'] / $total_days_up, 2));
108
	$context['average_hits'] = comma_format(round($row['hits'] / $total_days_up, 2));
109
110
	$context['num_hits'] = comma_format($row['hits'], 0);
111
112
	// How many users are online now.
113
	$result = $smcFunc['db_query']('', '
114
		SELECT COUNT(*)
115
		FROM {db_prefix}log_online',
116
		array(
117
		)
118
	);
119
	list ($context['users_online']) = $smcFunc['db_fetch_row']($result);
120
	$smcFunc['db_free_result']($result);
121
122
	// Statistics such as number of boards, categories, etc.
123
	$result = $smcFunc['db_query']('', '
124
		SELECT COUNT(*)
125
		FROM {db_prefix}boards AS b
126
		WHERE b.redirect = {string:blank_redirect}',
127
		array(
128
			'blank_redirect' => '',
129
		)
130
	);
131
	list ($context['num_boards']) = $smcFunc['db_fetch_row']($result);
132
	$smcFunc['db_free_result']($result);
133
134
	$result = $smcFunc['db_query']('', '
135
		SELECT COUNT(*)
136
		FROM {db_prefix}categories AS c',
137
		array(
138
		)
139
	);
140
	list ($context['num_categories']) = $smcFunc['db_fetch_row']($result);
141
	$smcFunc['db_free_result']($result);
142
143
	// Format the numbers nicely.
144
	$context['users_online'] = comma_format($context['users_online']);
145
	$context['num_boards'] = comma_format($context['num_boards']);
146
	$context['num_categories'] = comma_format($context['num_categories']);
147
148
	$context['num_members'] = comma_format($modSettings['totalMembers']);
149
	$context['num_posts'] = comma_format($modSettings['totalMessages']);
150
	$context['num_topics'] = comma_format($modSettings['totalTopics']);
151
	$context['most_members_online'] = array(
152
		'number' => comma_format($modSettings['mostOnline']),
153
		'date' => timeformat($modSettings['mostDate'])
154
	);
155
	$context['latest_member'] = &$context['common_stats']['latest_member'];
156
157
	// Let's calculate gender stats only every four minutes.
158
	$disabled_fields = isset($modSettings['disabled_profile_fields']) ? explode(',', $modSettings['disabled_profile_fields']) : array();
159
	if (!in_array('gender', $disabled_fields))
160
	{
161
		if (($context['gender'] = cache_get_data('stats_gender', 240)) == null)
162
		{
163
			$result = $smcFunc['db_query']('', '
164
				SELECT COUNT(id_member) AS total_members, value AS gender
165
				FROM {db_prefix}themes
166
				WHERE variable = {string:gender_var} AND id_theme = {int:default_theme}
167
				GROUP BY value',
168
				array(
169
					'gender_var' => 'cust_gender',
170
					'default_theme' => 1,
171
				)
172
			);
173
			$context['gender'] = array();
174
			while ($row = $smcFunc['db_fetch_assoc']($result))
175
			{
176
				$context['gender'][$row['gender']] = $row['total_members'];
177
			}
178
			$smcFunc['db_free_result']($result);
179
180
			cache_put_data('stats_gender', $context['gender'], 240);
181
		}
182
	}
183
184
	$date = strftime('%Y-%m-%d', forum_time(false));
185
186
	// Members online so far today.
187
	$result = $smcFunc['db_query']('', '
188
		SELECT most_on
189
		FROM {db_prefix}log_activity
190
		WHERE date = {date:today_date}
191
		LIMIT 1',
192
		array(
193
			'today_date' => $date,
194
		)
195
	);
196
	list ($context['online_today']) = $smcFunc['db_fetch_row']($result);
197
	$smcFunc['db_free_result']($result);
198
199
	$context['online_today'] = comma_format((int) $context['online_today']);
200
201
	// Poster top 10.
202
	$members_result = $smcFunc['db_query']('', '
203
		SELECT id_member, real_name, posts
204
		FROM {db_prefix}members
205
		WHERE posts > {int:no_posts}
206
		ORDER BY posts DESC
207
		LIMIT 10',
208
		array(
209
			'no_posts' => 0,
210
		)
211
	);
212
	$context['stats_blocks']['posters'] = array();
213
	$max_num_posts = 1;
214 View Code Duplication
	while ($row_members = $smcFunc['db_fetch_assoc']($members_result))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
215
	{
216
		$context['stats_blocks']['posters'][] = array(
217
			'name' => $row_members['real_name'],
218
			'id' => $row_members['id_member'],
219
			'num' => $row_members['posts'],
220
			'href' => $scripturl . '?action=profile;u=' . $row_members['id_member'],
221
			'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row_members['id_member'] . '">' . $row_members['real_name'] . '</a>'
222
		);
223
224
		if ($max_num_posts < $row_members['posts'])
225
			$max_num_posts = $row_members['posts'];
226
	}
227
	$smcFunc['db_free_result']($members_result);
228
229 View Code Duplication
	foreach ($context['stats_blocks']['posters'] as $i => $poster)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
230
	{
231
		$context['stats_blocks']['posters'][$i]['percent'] = round(($poster['num'] * 100) / $max_num_posts);
232
		$context['stats_blocks']['posters'][$i]['num'] = comma_format($context['stats_blocks']['posters'][$i]['num']);
233
	}
234
235
	// Board top 10.
236
	$boards_result = $smcFunc['db_query']('', '
237
		SELECT id_board, name, num_posts
238
		FROM {db_prefix}boards AS b
239
		WHERE {query_see_board}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
240
			AND b.id_board != {int:recycle_board}' : '') . '
241
			AND b.redirect = {string:blank_redirect}
242
		ORDER BY num_posts DESC
243
		LIMIT 10',
244
		array(
245
			'recycle_board' => $modSettings['recycle_board'],
246
			'blank_redirect' => '',
247
		)
248
	);
249
	$context['stats_blocks']['boards'] = array();
250
	$max_num_posts = 1;
251
	while ($row_board = $smcFunc['db_fetch_assoc']($boards_result))
252
	{
253
		$context['stats_blocks']['boards'][] = array(
254
			'id' => $row_board['id_board'],
255
			'name' => $row_board['name'],
256
			'num' => $row_board['num_posts'],
257
			'href' => $scripturl . '?board=' . $row_board['id_board'] . '.0',
258
			'link' => '<a href="' . $scripturl . '?board=' . $row_board['id_board'] . '.0">' . $row_board['name'] . '</a>'
259
		);
260
261
		if ($max_num_posts < $row_board['num_posts'])
262
			$max_num_posts = $row_board['num_posts'];
263
	}
264
	$smcFunc['db_free_result']($boards_result);
265
266 View Code Duplication
	foreach ($context['stats_blocks']['boards'] as $i => $board)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
267
	{
268
		$context['stats_blocks']['boards'][$i]['percent'] = round(($board['num'] * 100) / $max_num_posts);
269
		$context['stats_blocks']['boards'][$i]['num'] = comma_format($context['stats_blocks']['boards'][$i]['num']);
270
	}
271
272
	// Are you on a larger forum?  If so, let's try to limit the number of topics we search through.
273
	if ($modSettings['totalMessages'] > 100000)
274
	{
275
		$request = $smcFunc['db_query']('', '
276
			SELECT id_topic
277
			FROM {db_prefix}topics
278
			WHERE num_replies != {int:no_replies}' . ($modSettings['postmod_active'] ? '
279
				AND approved = {int:is_approved}' : '') . '
280
			ORDER BY num_replies DESC
281
			LIMIT 100',
282
			array(
283
				'no_replies' => 0,
284
				'is_approved' => 1,
285
			)
286
		);
287
		$topic_ids = array();
288
		while ($row = $smcFunc['db_fetch_assoc']($request))
289
			$topic_ids[] = $row['id_topic'];
290
		$smcFunc['db_free_result']($request);
291
	}
292
	else
293
		$topic_ids = array();
294
295
	// Topic replies top 10.
296
	$topic_reply_result = $smcFunc['db_query']('', '
297
		SELECT m.subject, t.num_replies, t.id_board, t.id_topic, b.name
298
		FROM {db_prefix}topics AS t
299
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
300
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
301
			AND b.id_board != {int:recycle_board}' : '') . ')
302
		WHERE {query_see_board}' . (!empty($topic_ids) ? '
303
			AND t.id_topic IN ({array_int:topic_list})' : ($modSettings['postmod_active'] ? '
304
			AND t.approved = {int:is_approved}' : '')) . '
305
		ORDER BY t.num_replies DESC
306
		LIMIT 10',
307
		array(
308
			'topic_list' => $topic_ids,
309
			'recycle_board' => $modSettings['recycle_board'],
310
			'is_approved' => 1,
311
		)
312
	);
313
	$context['stats_blocks']['topics_replies'] = array();
314
	$max_num_replies = 1;
315 View Code Duplication
	while ($row_topic_reply = $smcFunc['db_fetch_assoc']($topic_reply_result))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
316
	{
317
		censorText($row_topic_reply['subject']);
318
319
		$context['stats_blocks']['topics_replies'][] = array(
320
			'id' => $row_topic_reply['id_topic'],
321
			'board' => array(
322
				'id' => $row_topic_reply['id_board'],
323
				'name' => $row_topic_reply['name'],
324
				'href' => $scripturl . '?board=' . $row_topic_reply['id_board'] . '.0',
325
				'link' => '<a href="' . $scripturl . '?board=' . $row_topic_reply['id_board'] . '.0">' . $row_topic_reply['name'] . '</a>'
326
			),
327
			'subject' => $row_topic_reply['subject'],
328
			'num' => $row_topic_reply['num_replies'],
329
			'href' => $scripturl . '?topic=' . $row_topic_reply['id_topic'] . '.0',
330
			'link' => '<a href="' . $scripturl . '?topic=' . $row_topic_reply['id_topic'] . '.0">' . $row_topic_reply['subject'] . '</a>'
331
		);
332
333
		if ($max_num_replies < $row_topic_reply['num_replies'])
334
			$max_num_replies = $row_topic_reply['num_replies'];
335
	}
336
	$smcFunc['db_free_result']($topic_reply_result);
337
338 View Code Duplication
	foreach ($context['stats_blocks']['topics_replies'] as $i => $topic)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
339
	{
340
		$context['stats_blocks']['topics_replies'][$i]['percent'] = round(($topic['num'] * 100) / $max_num_replies);
341
		$context['stats_blocks']['topics_replies'][$i]['num'] = comma_format($context['stats_blocks']['topics_replies'][$i]['num']);
342
	}
343
344
	// Large forums may need a bit more prodding...
345 View Code Duplication
	if ($modSettings['totalMessages'] > 100000)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
346
	{
347
		$request = $smcFunc['db_query']('', '
348
			SELECT id_topic
349
			FROM {db_prefix}topics
350
			WHERE num_views != {int:no_views}
351
			ORDER BY num_views DESC
352
			LIMIT 100',
353
			array(
354
				'no_views' => 0,
355
			)
356
		);
357
		$topic_ids = array();
358
		while ($row = $smcFunc['db_fetch_assoc']($request))
359
			$topic_ids[] = $row['id_topic'];
360
		$smcFunc['db_free_result']($request);
361
	}
362
	else
363
		$topic_ids = array();
364
365
	// Topic views top 10.
366
	$topic_view_result = $smcFunc['db_query']('', '
367
		SELECT m.subject, t.num_views, t.id_board, t.id_topic, b.name
368
		FROM {db_prefix}topics AS t
369
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
370
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
371
			AND b.id_board != {int:recycle_board}' : '') . ')
372
		WHERE {query_see_board}' . (!empty($topic_ids) ? '
373
			AND t.id_topic IN ({array_int:topic_list})' : ($modSettings['postmod_active'] ? '
374
			AND t.approved = {int:is_approved}' : '')) . '
375
		ORDER BY t.num_views DESC
376
		LIMIT 10',
377
		array(
378
			'topic_list' => $topic_ids,
379
			'recycle_board' => $modSettings['recycle_board'],
380
			'is_approved' => 1,
381
		)
382
	);
383
	$context['stats_blocks']['topics_views'] = array();
384
	$max_num = 1;
385 View Code Duplication
	while ($row_topic_views = $smcFunc['db_fetch_assoc']($topic_view_result))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
386
	{
387
		censorText($row_topic_views['subject']);
388
389
		$context['stats_blocks']['topics_views'][] = array(
390
			'id' => $row_topic_views['id_topic'],
391
			'board' => array(
392
				'id' => $row_topic_views['id_board'],
393
				'name' => $row_topic_views['name'],
394
				'href' => $scripturl . '?board=' . $row_topic_views['id_board'] . '.0',
395
				'link' => '<a href="' . $scripturl . '?board=' . $row_topic_views['id_board'] . '.0">' . $row_topic_views['name'] . '</a>'
396
			),
397
			'subject' => $row_topic_views['subject'],
398
			'num' => $row_topic_views['num_views'],
399
			'href' => $scripturl . '?topic=' . $row_topic_views['id_topic'] . '.0',
400
			'link' => '<a href="' . $scripturl . '?topic=' . $row_topic_views['id_topic'] . '.0">' . $row_topic_views['subject'] . '</a>'
401
		);
402
403
		if ($max_num < $row_topic_views['num_views'])
404
			$max_num = $row_topic_views['num_views'];
405
	}
406
	$smcFunc['db_free_result']($topic_view_result);
407
408 View Code Duplication
	foreach ($context['stats_blocks']['topics_views'] as $i => $topic)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
409
	{
410
		$context['stats_blocks']['topics_views'][$i]['percent'] = round(($topic['num'] * 100) / $max_num);
411
		$context['stats_blocks']['topics_views'][$i]['num'] = comma_format($context['stats_blocks']['topics_views'][$i]['num']);
412
	}
413
414
	// Try to cache this when possible, because it's a little unavoidably slow.
415
	if (($members = cache_get_data('stats_top_starters', 360)) == null)
416
	{
417
		$request = $smcFunc['db_query']('', '
418
			SELECT id_member_started, COUNT(*) AS hits
419
			FROM {db_prefix}topics' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
420
			WHERE id_board != {int:recycle_board}' : '') . '
421
			GROUP BY id_member_started
422
			ORDER BY hits DESC
423
			LIMIT 20',
424
			array(
425
				'recycle_board' => $modSettings['recycle_board'],
426
			)
427
		);
428
		$members = array();
429
		while ($row = $smcFunc['db_fetch_assoc']($request))
430
			$members[$row['id_member_started']] = $row['hits'];
431
		$smcFunc['db_free_result']($request);
432
433
		cache_put_data('stats_top_starters', $members, 360);
434
	}
435
436
	if (empty($members))
437
		$members = array(0 => 0);
438
439
	// Topic poster top 10.
440
	$members_result = $smcFunc['db_query']('', '
441
		SELECT id_member, real_name
442
		FROM {db_prefix}members
443
		WHERE id_member IN ({array_int:member_list})
444
		LIMIT 10',
445
		array(
446
			'member_list' => array_keys($members),
447
		)
448
	);
449
	$context['stats_blocks']['starters'] = array();
450
	$max_num = 1;
451
	while ($row_members = $smcFunc['db_fetch_assoc']($members_result))
452
	{
453
		$i = array_search($row_members['id_member'], array_keys($members));
454
		$context['stats_blocks']['starters'][$i] = array(
455
			'name' => $row_members['real_name'],
456
			'id' => $row_members['id_member'],
457
			'num' => $members[$row_members['id_member']],
458
			'href' => $scripturl . '?action=profile;u=' . $row_members['id_member'],
459
			'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row_members['id_member'] . '">' . $row_members['real_name'] . '</a>'
460
		);
461
462
		if ($max_num < $members[$row_members['id_member']])
463
			$max_num = $members[$row_members['id_member']];
464
	}
465
	ksort($context['stats_blocks']['starters']);
466
	$smcFunc['db_free_result']($members_result);
467
468 View Code Duplication
	foreach ($context['stats_blocks']['starters'] as $i => $topic)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
469
	{
470
		$context['stats_blocks']['starters'][$i]['percent'] = round(($topic['num'] * 100) / $max_num);
471
		$context['stats_blocks']['starters'][$i]['num'] = comma_format($context['stats_blocks']['starters'][$i]['num']);
472
	}
473
474
	// Time online top 10.
475
	$temp = cache_get_data('stats_total_time_members', 600);
476
	$members_result = $smcFunc['db_query']('', '
477
		SELECT id_member, real_name, total_time_logged_in
478
		FROM {db_prefix}members' . (!empty($temp) ? '
479
		WHERE id_member IN ({array_int:member_list_cached})' : '') . '
480
		ORDER BY total_time_logged_in DESC
481
		LIMIT 20',
482
		array(
483
			'member_list_cached' => $temp,
484
		)
485
	);
486
	$context['stats_blocks']['time_online'] = array();
487
	$temp2 = array();
488
	$max_time_online = 1;
489
	while ($row_members = $smcFunc['db_fetch_assoc']($members_result))
490
	{
491
		$temp2[] = (int) $row_members['id_member'];
492
		if (count($context['stats_blocks']['time_online']) >= 10)
493
			continue;
494
495
		// Figure out the days, hours and minutes.
496
		$timeDays = floor($row_members['total_time_logged_in'] / 86400);
497
		$timeHours = floor(($row_members['total_time_logged_in'] % 86400) / 3600);
498
499
		// Figure out which things to show... (days, hours, minutes, etc.)
500
		$timelogged = '';
501
		if ($timeDays > 0)
502
			$timelogged .= $timeDays . $txt['totalTimeLogged5'];
503
		if ($timeHours > 0)
504
			$timelogged .= $timeHours . $txt['totalTimeLogged6'];
505
		$timelogged .= floor(($row_members['total_time_logged_in'] % 3600) / 60) . $txt['totalTimeLogged7'];
506
507
		$context['stats_blocks']['time_online'][] = array(
508
			'id' => $row_members['id_member'],
509
			'name' => $row_members['real_name'],
510
			'num' => $timelogged,
511
			'seconds_online' => $row_members['total_time_logged_in'],
512
			'href' => $scripturl . '?action=profile;u=' . $row_members['id_member'],
513
			'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row_members['id_member'] . '">' . $row_members['real_name'] . '</a>'
514
		);
515
516
		if ($max_time_online < $row_members['total_time_logged_in'])
517
			$max_time_online = $row_members['total_time_logged_in'];
518
	}
519
	$smcFunc['db_free_result']($members_result);
520
521
	foreach ($context['stats_blocks']['time_online'] as $i => $member)
522
		$context['stats_blocks']['time_online'][$i]['percent'] = round(($member['seconds_online'] * 100) / $max_time_online);
523
524
	// Cache the ones we found for a bit, just so we don't have to look again.
525
	if ($temp !== $temp2)
526
		cache_put_data('stats_total_time_members', $temp2, 480);
527
528
	// Likes.
529
	if (!empty($modSettings['enable_likes']))
530
	{
531
		// Liked messages top 10.
532
		$context['stats_blocks']['liked_messages'] = array();
533
		$max_liked_message = 1;
534
		$liked_messages = $smcFunc['db_query']('', '
535
			SELECT m.id_msg, m.subject, m.likes, m.id_board, m.id_topic, t.approved
536
			FROM {db_prefix}messages as m
537
				INNER JOIN {db_prefix}topics AS t ON (m.id_topic = t.id_topic)
538
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
539
			AND b.id_board != {int:recycle_board}' : '') . ')
540
			WHERE {query_see_board}' . ($modSettings['postmod_active'] ? '
541
				AND t.approved = {int:is_approved}' : '') . '
542
			ORDER BY m.likes DESC
543
			LIMIT 10',
544
			array(
545
				'recycle_board' => $modSettings['recycle_board'],
546
				'is_approved' => 1,
547
			)
548
		);
549
550
		while ($row_liked_message = $smcFunc['db_fetch_assoc']($liked_messages))
551
		{
552
			censorText($row_liked_message['subject']);
553
554
			$context['stats_blocks']['liked_messages'][] = array(
555
				'id' => $row_liked_message['id_topic'],
556
				'subject' => $row_liked_message['subject'],
557
				'num' => $row_liked_message['likes'],
558
				'href' => $scripturl . '?msg=' . $row_liked_message['id_msg'],
559
				'link' => '<a href="' . $scripturl . '?msg=' . $row_liked_message['id_msg'] .'">' . $row_liked_message['subject'] . '</a>'
560
			);
561
562
			if ($max_liked_message < $row_liked_message['likes'])
563
				$max_liked_message = $row_liked_message['likes'];
564
		}
565
		$smcFunc['db_free_result']($liked_messages);
566
567 View Code Duplication
		foreach ($context['stats_blocks']['liked_messages'] as $i => $liked_messages)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
568
			$context['stats_blocks']['liked_messages'][$i]['percent'] = round(($liked_messages['num'] * 100) / $max_liked_message);
569
570
		// Liked users top 10.
571
		$context['stats_blocks']['liked_users'] = array();
572
		$max_liked_users = 1;
573
		$liked_users = $smcFunc['db_query']('', '
574
			SELECT m.id_member AS liked_user, COUNT(l.content_id) AS count, mem.real_name
575
			FROM {db_prefix}user_likes AS l
576
				INNER JOIN {db_prefix}messages AS m ON (l.content_id = m.id_msg)
577
				INNER JOIN {db_prefix}members AS mem ON (m.id_member = mem.id_member)
578
			WHERE content_type = {literal:msg}
579
				AND m.id_member > {int:zero}
580
			GROUP BY m.id_member, mem.real_name
581
			ORDER BY count DESC
582
			LIMIT 10',
583
			array(
584
				'no_posts' => 0,
585
				'zero' => 0,
586
			)
587
		);
588
589 View Code Duplication
		while ($row_liked_users = $smcFunc['db_fetch_assoc']($liked_users))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
590
		{
591
			$context['stats_blocks']['liked_users'][] = array(
592
				'id' => $row_liked_users['liked_user'],
593
				'num' => $row_liked_users['count'],
594
				'href' => $scripturl . '?action=profile;u=' . $row_liked_users['liked_user'],
595
				'name' => $row_liked_users['real_name'],
596
				'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row_liked_users['liked_user'] . '">' . $row_liked_users['real_name'] . '</a>',
597
			);
598
599
			if ($max_liked_users < $row_liked_users['count'])
600
				$max_liked_users = $row_liked_users['count'];
601
		}
602
603
		$smcFunc['db_free_result']($liked_users);
604
605 View Code Duplication
		foreach ($context['stats_blocks']['liked_users'] as $i => $liked_users)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
606
			$context['stats_blocks']['liked_users'][$i]['percent'] = round(($liked_users['num'] * 100) / $max_liked_users);
607
	}
608
609
	// Activity by month.
610
	$months_result = $smcFunc['db_query']('', '
611
		SELECT
612
			YEAR(date) AS stats_year, MONTH(date) AS stats_month, SUM(hits) AS hits, SUM(registers) AS registers, SUM(topics) AS topics, SUM(posts) AS posts, MAX(most_on) AS most_on, COUNT(*) AS num_days
613
		FROM {db_prefix}log_activity
614
		GROUP BY stats_year, stats_month',
615
		array()
616
	);
617
618
	$context['yearly'] = array();
619
	while ($row_months = $smcFunc['db_fetch_assoc']($months_result))
620
	{
621
		$ID_MONTH = $row_months['stats_year'] . sprintf('%02d', $row_months['stats_month']);
622
		$expanded = !empty($_SESSION['expanded_stats'][$row_months['stats_year']]) && in_array($row_months['stats_month'], $_SESSION['expanded_stats'][$row_months['stats_year']]);
623
624
		if (!isset($context['yearly'][$row_months['stats_year']]))
625
			$context['yearly'][$row_months['stats_year']] = array(
626
				'year' => $row_months['stats_year'],
627
				'new_topics' => 0,
628
				'new_posts' => 0,
629
				'new_members' => 0,
630
				'most_members_online' => 0,
631
				'hits' => 0,
632
				'num_months' => 0,
633
				'months' => array(),
634
				'expanded' => false,
635
				'current_year' => $row_months['stats_year'] == date('Y'),
636
			);
637
638
		$context['yearly'][$row_months['stats_year']]['months'][(int) $row_months['stats_month']] = array(
639
			'id' => $ID_MONTH,
640
			'date' => array(
641
				'month' => sprintf('%02d', $row_months['stats_month']),
642
				'year' => $row_months['stats_year']
643
			),
644
			'href' => $scripturl . '?action=stats;' . ($expanded ? 'collapse' : 'expand') . '=' . $ID_MONTH . '#m' . $ID_MONTH,
645
			'link' => '<a href="' . $scripturl . '?action=stats;' . ($expanded ? 'collapse' : 'expand') . '=' . $ID_MONTH . '#m' . $ID_MONTH . '">' . $txt['months'][(int) $row_months['stats_month']] . ' ' . $row_months['stats_year'] . '</a>',
646
			'month' => $txt['months'][(int) $row_months['stats_month']],
647
			'year' => $row_months['stats_year'],
648
			'new_topics' => comma_format($row_months['topics']),
649
			'new_posts' => comma_format($row_months['posts']),
650
			'new_members' => comma_format($row_months['registers']),
651
			'most_members_online' => comma_format($row_months['most_on']),
652
			'hits' => comma_format($row_months['hits']),
653
			'num_days' => $row_months['num_days'],
654
			'days' => array(),
655
			'expanded' => $expanded
656
		);
657
658
		$context['yearly'][$row_months['stats_year']]['new_topics'] += $row_months['topics'];
659
		$context['yearly'][$row_months['stats_year']]['new_posts'] += $row_months['posts'];
660
		$context['yearly'][$row_months['stats_year']]['new_members'] += $row_months['registers'];
661
		$context['yearly'][$row_months['stats_year']]['hits'] += $row_months['hits'];
662
		$context['yearly'][$row_months['stats_year']]['num_months']++;
663
		$context['yearly'][$row_months['stats_year']]['expanded'] |= $expanded;
664
		$context['yearly'][$row_months['stats_year']]['most_members_online'] = max($context['yearly'][$row_months['stats_year']]['most_members_online'], $row_months['most_on']);
665
	}
666
667
	krsort($context['yearly']);
668
669
	$context['collapsed_years'] = array();
670
	foreach ($context['yearly'] as $year => $data)
671
	{
672
		// This gets rid of the filesort on the query ;).
673
		krsort($context['yearly'][$year]['months']);
674
675
		$context['yearly'][$year]['new_topics'] = comma_format($data['new_topics']);
676
		$context['yearly'][$year]['new_posts'] = comma_format($data['new_posts']);
677
		$context['yearly'][$year]['new_members'] = comma_format($data['new_members']);
678
		$context['yearly'][$year]['most_members_online'] = comma_format($data['most_members_online']);
679
		$context['yearly'][$year]['hits'] = comma_format($data['hits']);
680
681
		// Keep a list of collapsed years.
682
		if (!$data['expanded'] && !$data['current_year'])
683
			$context['collapsed_years'][] = $year;
684
	}
685
686
	if (empty($_SESSION['expanded_stats']))
687
		return;
688
689
	$condition_text = array();
690
	$condition_params = array();
691
	foreach ($_SESSION['expanded_stats'] as $year => $months)
692
		if (!empty($months))
693
		{
694
			$condition_text[] = 'YEAR(date) = {int:year_' . $year . '} AND MONTH(date) IN ({array_int:months_' . $year . '})';
695
			$condition_params['year_' . $year] = $year;
696
			$condition_params['months_' . $year] = $months;
697
		}
698
699
	// No daily stats to even look at?
700
	if (empty($condition_text))
701
		return;
702
703
	getDailyStats(implode(' OR ', $condition_text), $condition_params);
704
705
	// Custom stats (just add a template_layer to add it to the template!)
706
 	call_integration_hook('integrate_forum_stats');
707
}
708
709
/**
710
 * Loads the statistics on a daily basis in $context.
711
 * called by DisplayStats().
712
 * @param string $condition_string An SQL condition string
713
 * @param array $condition_parameters Parameters for $condition_string
714
 */
715
function getDailyStats($condition_string, $condition_parameters = array())
716
{
717
	global $context, $smcFunc;
718
719
	// Activity by day.
720
	$days_result = $smcFunc['db_query']('', '
721
		SELECT YEAR(date) AS stats_year, MONTH(date) AS stats_month, DAYOFMONTH(date) AS stats_day, topics, posts, registers, most_on, hits
722
		FROM {db_prefix}log_activity
723
		WHERE ' . $condition_string . '
724
		ORDER BY stats_day ASC',
725
		$condition_parameters
726
	);
727
	while ($row_days = $smcFunc['db_fetch_assoc']($days_result))
728
		$context['yearly'][$row_days['stats_year']]['months'][(int) $row_days['stats_month']]['days'][] = array(
729
			'day' => sprintf('%02d', $row_days['stats_day']),
730
			'month' => sprintf('%02d', $row_days['stats_month']),
731
			'year' => $row_days['stats_year'],
732
			'new_topics' => comma_format($row_days['topics']),
733
			'new_posts' => comma_format($row_days['posts']),
734
			'new_members' => comma_format($row_days['registers']),
735
			'most_members_online' => comma_format($row_days['most_on']),
736
			'hits' => comma_format($row_days['hits'])
737
		);
738
	$smcFunc['db_free_result']($days_result);
739
}
740
741
/**
742
 * This is the function which returns stats to simplemachines.org IF enabled!
743
 * called by simplemachines.org.
744
 * only returns anything if stats was enabled during installation.
745
 * can also be accessed by the admin, to show what stats sm.org collects.
746
 * does not return any data directly to sm.org, instead starts a new request for security.
747
 *
748
 * @link https://www.simplemachines.org/about/stats.php for more info.
749
 */
750
function SMStats()
751
{
752
	global $modSettings, $user_info, $forum_version, $sourcedir;
753
754
	// First, is it disabled?
755
	if (empty($modSettings['enable_sm_stats']) || empty($modSettings['sm_stats_key']))
756
		die();
757
758
	// Are we saying who we are, and are we right? (OR an admin)
759
	if (!$user_info['is_admin'] && (!isset($_GET['sid']) || $_GET['sid'] != $modSettings['sm_stats_key']))
760
		die();
761
762
	// Verify the referer...
763
	if (!$user_info['is_admin'] && (!isset($_SERVER['HTTP_REFERER']) || md5($_SERVER['HTTP_REFERER']) != '746cb59a1a0d5cf4bd240e5a67c73085'))
764
		die();
765
766
	// Get some server versions.
767
	require_once($sourcedir . '/Subs-Admin.php');
768
	$checkFor = array(
769
		'php',
770
		'db_server',
771
	);
772
	$serverVersions = getServerVersions($checkFor);
773
774
	// Get the actual stats.
775
	$stats_to_send = array(
776
		'UID' => $modSettings['sm_stats_key'],
777
		'time_added' => time(),
778
		'members' => $modSettings['totalMembers'],
779
		'messages' => $modSettings['totalMessages'],
780
		'topics' => $modSettings['totalTopics'],
781
		'boards' => 0,
782
		'php_version' => $serverVersions['php']['version'],
783
		'database_type' => strtolower($serverVersions['db_engine']['version']),
784
		'database_version' => $serverVersions['db_server']['version'],
785
		'smf_version' => $forum_version,
786
		'smfd_version' => $modSettings['smfVersion'],
787
	);
788
789
	// Encode all the data, for security.
790
	foreach ($stats_to_send as $k => $v)
791
		$stats_to_send[$k] = urlencode($k) . '=' . urlencode($v);
792
793
	// Turn this into the query string!
794
	$stats_to_send = implode('&', $stats_to_send);
795
796
	// If we're an admin, just plonk them out.
797
	if ($user_info['is_admin'])
798
		echo $stats_to_send;
799
	else
800
	{
801
		// Connect to the collection script.
802
		$fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr);
803
		if ($fp)
804
		{
805
			$length = strlen($stats_to_send);
806
807
			$out = 'POST /smf/stats/collect_stats.php HTTP/1.1' . "\r\n";
808
			$out .= 'Host: www.simplemachines.org' . "\r\n";
809
			$out .= 'Content-Type: application/x-www-form-urlencoded' . "\r\n";
810
			$out .= 'Connection: Close' . "\r\n";
811
			$out .= 'Content-Length: ' . $length . "\r\n\r\n";
812
			$out .= $stats_to_send . "\r\n";
813
			fwrite($fp, $out);
814
			fclose($fp);
815
		}
816
	}
817
818
	// Die.
819
	die('OK');
820
}
821
822
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

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

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

Loading history...