Issues (1686)

sources/ElkArte/Controller/Stats.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * Provide a display for forum statistics
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 dev
14
 *
15
 */
16
17
namespace ElkArte\Controller;
18
19
use ElkArte\AbstractController;
20
use ElkArte\Exceptions\Exception;
21
use ElkArte\Helper\Util;
22
use ElkArte\Languages\Txt;
23
24
/**
25
 * Handles the calculation of forum statistics
26
 */
27
class Stats extends AbstractController
28
{
29
	/**
30
	 * Entry point for this class.
31
	 *
32
	 * @see AbstractController::action_index
33
	 */
34
	public function action_index()
35
	{
36
		// Call the right method... wait, we only know how to do
37
		// one thing (and do it well! :P)
38
		$this->action_stats();
39
	}
40
41
	/**
42
	 * Display some useful/interesting board statistics.
43
	 *
44
	 * What it does:
45
	 *
46
	 * - Gets all the statistics in order and puts them in.
47
	 * - Uses the Stats template and language file. (and main sub template.)
48
	 * - Requires the view_stats permission.
49
	 * - Accessed from ?action=stats.
50
	 *
51
	 * @uses Stats language file
52
	 * @uses template_stats() sub template in Stats.template
53
	 */
54
	public function action_stats()
55
	{
56
		global $txt, $modSettings, $context;
57
58
		// You have to be able to see these
59
		isAllowedTo('view_stats');
60
61
		// Page disabled - redirect them out
62
		if (empty($modSettings['trackStats']))
63
		{
64
			throw new Exception('feature_disabled', true);
65
		}
66
67
		// Expanding out the history summary
68
		[$year, $month] = $this->_expandedStats();
69
70
		// Just a lil' help from our friend :P
71
		require_once(SUBSDIR . '/Stats.subs.php');
72
73
		// Handle the Ajax request.
74
		if ($this->getApi() === 'xml')
75
		{
76
			if (empty($year) || empty($month))
77
			{
78
				redirectexit('action=stats');
79
			}
80
81
			// Collapsing stats only needs adjustments of the session variables.
82
			if (!empty($this->_req->query->collapse))
83
			{
84
				obExit(false);
85
			}
86
87
			$template_layers = theme()->getLayers();
88
			$template_layers->removeAll();
89
			$context['sub_template'] = 'stats';
90
91
			getDailyStats('YEAR(date) = {int:year} AND MONTH(date) = {int:month}', ['year' => $year, 'month' => $month]);
92
93
			$context['yearly'][$year]['months'][$month]['date'] = [
94
				'month' => sprintf('%02d', $month),
95
				'year' => $year,
96
			];
97
98
			return true;
99
		}
100
101
		// Stats it is
102
		Txt::load('Stats');
103
		theme()->getTemplates()->load('Stats');
104
		loadJavascriptFile(['stats.js', 'ext/chart.min.js', 'elk_chart.js']);
105
106
		// Build the link tree......
107
		$context['breadcrumbs'][] = [
108
			'url' => getUrl('action', ['action' => 'stats']),
109
			'name' => $txt['stats_center']
110
		];
111
112
		// Prepare some things for the template page
113
		$context['page_title'] = $context['forum_name'] . ' - ' . $txt['stats_center'];
114
		$context['sub_template'] = 'statistics';
115
116
		// These are the templates that will be used to render the statistics
117
		$context['statistics_callbacks'] = [
118
			'general_statistics',
119
			'top_statistics',
120
		];
121
122
		// Call each area of statics to load our friend $context
123
		$this->loadGeneralStatistics();
124
		$this->loadTopStatistics();
125
		$this->loadMonthlyActivity();
126
127
		// Custom stats (just add a template_layer or another callback to add it to the page!)
128
		call_integration_hook('integrate_forum_stats');
129
	}
130
131
	/**
132
	 * Sanitize and validate the year / month for expand / collapse stats
133
	 *
134
	 * @return array of year and month from expand / collapse link
135
	 */
136
	private function _expandedStats()
137
	{
138
		global $context;
139
140
		$year = '';
141
		$month = '';
142
		$expand = $this->_req->getQuery('expand', 'trim');
143
		$collapse = $this->_req->getQuery('collapse', 'trim');
144
		$calData = $expand ?? $collapse;
145
146
		// No data, nothing to do
147
		if (!isset($calData))
148
		{
149
			return [$year, $month];
150
		}
151
152
		// Extract the values
153
		$month = (int) substr($calData, 4);
154
		$year = (int) substr($calData, 0, 4);
155
		$context['robot_no_index'] = true;
156
157
		if (!empty($expand) && $year > 1900 && $year < 2200 && $month >= 1 && $month <= 12)
158
		{
159
			$_SESSION['expanded_stats'][$year][] = $month;
160
		}
161
162
		// Done looking at the details and want to fold it back up
163
		if (!empty($collapse) && !empty($_SESSION['expanded_stats'][$year]))
164
		{
165
			$_SESSION['expanded_stats'][$year] = array_diff($_SESSION['expanded_stats'][$year], [$month]);
166
		}
167
168
		return [$year, $month];
169
	}
170
171
	/**
172
	 * Load some general statistics of the forum
173
	 */
174
	public function loadGeneralStatistics()
175
	{
176
		global $modSettings, $context;
177
178
		require_once(SUBSDIR . '/Boards.subs.php');
179
180
		// Get averages...
181
		$averages = getAverages();
182
183
		// This would be the amount of time the forum has been up... in days...
184
		$total_days_up = ceil((time() - strtotime($averages['date'])) / (60 * 60 * 24));
185
		$date = Util::strftime('%Y-%m-%d', forum_time(false));
186
187
		// General forum stats
188
		$context['general_statistics']['left'] = [
189
			'total_members' => allowedTo('view_mlist') ? '<a href="' . getUrl('action', ['action' => 'memberlist']) . '">' . comma_format($modSettings['totalMembers']) . '</a>' : comma_format($modSettings['totalMembers']),
190
			'total_posts' => comma_format($modSettings['totalMessages']),
191
			'total_topics' => comma_format($modSettings['totalTopics']),
192
			'total_cats' => comma_format(numCategories()),
193
			// How many users are online now.
194
			'users_online' => comma_format(onlineCount()),
195
			'most_online' => [
196
				'number' => comma_format($modSettings['mostOnline']),
197
				'date' => standardTime($modSettings['mostDate'])
198
			],
199
			// Members online so far today.
200
			'users_online_today' => comma_format(mostOnline($date)),
0 ignored issues
show
$date of type string is incompatible with the type integer expected by parameter $date of mostOnline(). ( Ignorable by Annotation )

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

200
			'users_online_today' => comma_format(mostOnline(/** @scrutinizer ignore-type */ $date)),
Loading history...
201
		];
202
203
		if (!empty($modSettings['hitStats']))
204
		{
205
			$context['general_statistics']['left'] += [
206
				'num_hits' => comma_format($averages['hits'], 0)
207
			];
208
		}
209
210
		$context['general_statistics']['right'] = [
211
			'average_members' => comma_format(round($averages['registers'] / $total_days_up, 2)),
212
			'average_posts' => comma_format(round($averages['posts'] / $total_days_up, 2)),
213
			'average_topics' => comma_format(round($averages['topics'] / $total_days_up, 2)),
214
			// Statistics such as number of boards, categories, etc.
215
			'total_boards' => comma_format(countBoards('all', ['include_redirects' => false])),
216
			'latest_member' => &$context['common_stats']['latest_member'],
217
			'average_online' => comma_format(round($averages['most_on'] / $total_days_up, 2)),
218
			'emails_sent' => comma_format(round($averages['email'] / $total_days_up, 2))
219
		];
220
221
		if (!empty($modSettings['hitStats']))
222
		{
223
			$context['general_statistics']['right'] += [
224
				'average_hits' => comma_format(round($averages['hits'] / $total_days_up, 2)),
225
			];
226
		}
227
	}
228
229
	/**
230
	 * Loads in the "top" statistics
231
	 *
232
	 * What it does:
233
	 *
234
	 * - Calls support topXXXX functions to load stats
235
	 * - Places results in to context
236
	 * - Uses Top posters, topBoards, topTopicReplies, topTopicViews, topTopicStarter, topTimeOnline
237
	 */
238
	public function loadTopStatistics()
239
	{
240
		global $context;
241
242
		// Poster top 10.
243
		$context['top']['posters'] = topPosters();
244
245
		// Board top 10.
246
		$context['top']['boards'] = topBoards();
247
248
		// Topic replies top 10.
249
		$context['top']['topics_replies'] = topTopicReplies();
250
251
		// Topic views top 10.
252
		$context['top']['topics_views'] = topTopicViews();
253
254
		// Topic poster top 10.
255
		$context['top']['starters'] = topTopicStarter();
256
257
		// Time online top 10.
258
		$context['top']['time_online'] = topTimeOnline();
259
	}
260
261
	/**
262
	 * Load the huge table of activity by month
263
	 */
264
	public function loadMonthlyActivity()
265
	{
266
		global $context;
267
268
		// Activity by month.
269
		monthlyActivity();
270
271
		$context['collapsed_years'] = [];
272
		foreach ($context['yearly'] as $year => $data)
273
		{
274
			// This gets rid of the filesort on the query ;).
275
			krsort($context['yearly'][$year]['months']);
276
277
			// Yearly stats, topics, posts, members, etc
278
			$context['yearly'][$year]['new_topics'] = comma_format($data['new_topics']);
279
			$context['yearly'][$year]['new_posts'] = comma_format($data['new_posts']);
280
			$context['yearly'][$year]['new_members'] = comma_format($data['new_members']);
281
			$context['yearly'][$year]['most_members_online'] = comma_format($data['most_members_online']);
282
			$context['yearly'][$year]['hits'] = comma_format($data['hits']);
283
284
			// Keep a list of collapsed years.
285
			if (!$data['expanded'] && !$data['current_year'])
286
			{
287
				$context['collapsed_years'][] = $year;
288
			}
289
		}
290
291
		// Want to expand out the yearly stats
292
		if (empty($_SESSION['expanded_stats']))
293
		{
294
			return false;
295
		}
296
297
		$condition_text = [];
298
		$condition_params = [];
299
		foreach ($_SESSION['expanded_stats'] as $year => $months)
300
		{
301
			if (!empty($months))
302
			{
303
				$condition_text[] = 'YEAR(date) = {int:year_' . $year . '} AND MONTH(date) IN ({array_int:months_' . $year . '})';
304
				$condition_params['year_' . $year] = $year;
305
				$condition_params['months_' . $year] = $months;
306
			}
307
		}
308
309
		// No daily stats to even look at?
310
		if (empty($condition_text))
311
		{
312
			return false;
313
		}
314
315
		getDailyStats(implode(' OR ', $condition_text), $condition_params);
316
317
		return true;
318
	}
319
}
320