Completed
Pull Request — patch_1-1-4 (#3210)
by Emanuele
12:56
created

ManageErrors_Controller   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 305
Duplicated Lines 9.51 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 29
loc 305
rs 6.96
c 0
b 0
f 0
ccs 0
cts 176
cp 0
wmc 53
lcom 1
cbo 6

6 Methods

Rating   Name   Duplication   Size   Complexity  
A action_index() 0 18 3
F action_log() 2 92 21
B _applyFilter() 12 31 7
A _loadMemData() 15 21 5
B _setupFiltering() 0 40 6
C action_viewfile() 0 52 11

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ManageErrors_Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ManageErrors_Controller, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * The main purpose of this file is to show a list of all errors that were
5
 * logged on the forum, and allow filtering and deleting them.
6
 *
7
 * @name      ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
10
 *
11
 * This file contains code covered by:
12
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
13
 * license:		BSD, See included LICENSE.TXT for terms and conditions.
14
 *
15
 * @version 1.1
16
 *
17
 */
18
19
/**
20
 * ManageErrors controller, administration of error log.
21
 */
22
class ManageErrors_Controller extends Action_Controller
23
{
24
	/** @var ElkArte\Errors\Log */
25
	private $errorLog;
26
27
	/**
28
	 * Calls the right handler.
29
	 * Requires admin_forum permission.
30
	 *
31
	 * @see Action_Controller::action_index()
32
	 */
33
	public function action_index()
34
	{
35
		// Check for the administrative permission to do this.
36
		isAllowedTo('admin_forum');
37
38
		$this->errorLog = new ElkArte\Errors\Log(database());
39
40
		// The error log. View the list or view a file?
41
		$activity = $this->_req->getQuery('activity', 'strval');
42
43
		// Some code redundancy... and we only take this!
44
		if (isset($activity) && $activity == 'file')
45
			// View the file with the error
46
			$this->action_viewfile();
47
		else
48
			// View error log
49
			$this->action_log();
50
	}
51
52
	/**
53
	 * View the forum's error log.
54
	 *
55
	 * What it does:
56
	 *
57
	 * - This method sets all the context up to show the error log for maintenance.
58
	 * - It requires the admin_forum permission.
59
	 * - It is accessed from ?action=admin;area=logs;sa=errorlog.
60
	 *
61
	 * @uses the Errors template and error_log sub template.
62
	 */
63
	protected function action_log()
64
	{
65
		global $scripturl, $txt, $context, $modSettings, $filter;
66
67
		// Templates, etc...
68
		loadLanguage('Maintenance');
69
		loadTemplate('Errors');
70
71
		// Set up any filters chosen
72
		$filter = $this->_setupFiltering();
73
74
		// Deleting, are we?
75
		$type = isset($this->_req->post->delall) ? 'delall' : (isset($this->_req->post->delete) ? 'delete' : false);
76
		if ($type !== false)
77
		{
78
			// Make sure the session exists and is correct; otherwise, might be a hacker.
79
			checkSession();
80
			validateToken('admin-el');
81
82
			$error_list = $this->_req->getPost('delete');
83
			$this->errorLog->deleteErrors($type, $filter, $error_list);
84
85
			// Go back to where we were.
86
			if ($type == 'delete')
87
				redirectexit('action=admin;area=logs;sa=errorlog' . (isset($this->_req->query->desc) ? ';desc' : '') . ';start=' . $this->_req->query->start . (!empty($filter) ? ';filter=' . $this->_req->query->filter . ';value=' . $this->_req->query->value : ''));
88
89
			redirectexit('action=admin;area=logs;sa=errorlog' . (isset($this->_req->query->desc) ? ';desc' : ''));
90
		}
91
92
		$num_errors = $this->errorLog->numErrors($filter);
93
		$members = array();
94
95
		// If this filter is empty...
96 View Code Duplication
		if ($num_errors == 0 && !empty($filter))
97
			redirectexit('action=admin;area=logs;sa=errorlog' . (isset($this->_req->query->desc) ? ';desc' : ''));
98
99
		// Clean up start.
100
		if (!isset($this->_req->query->start) || $this->_req->query->start < 0)
101
			$this->_req->query->start = 0;
102
103
		// Do we want to reverse error listing?
104
		$context['sort_direction'] = isset($this->_req->query->desc) ? 'down' : 'up';
105
106
		// Set the page listing up.
107
		$context['page_index'] = constructPageIndex($scripturl . '?action=admin;area=logs;sa=errorlog' . ($context['sort_direction'] == 'down' ? ';desc' : '') . (isset($filter['href']) ? $filter['href'] : ''), $this->_req->query->start, $num_errors, $modSettings['defaultMaxMessages']);
108
		$context['start'] = $this->_req->query->start;
109
		$context['errors'] = array();
110
111
		$logdata = $this->errorLog->getErrorLogData($this->_req->query->start, $context['sort_direction'], $filter);
112
		if (!empty($logdata))
113
		{
114
			$context['errors'] = $logdata['errors'];
115
			$members = $logdata['members'];
116
		}
117
118
		// Load the member data.
119
		$this->_loadMemData($members);
120
121
		// Filtering anything?
122
		$this->_applyFilter($filter);
123
124
		$sort = ($context['sort_direction'] == 'down') ? ';desc' : '';
125
126
		// What type of errors do we have and how many do we have?
127
		$context['error_types'] = array();
128
		$context['error_types'] = $this->errorLog->fetchErrorsByType($filter, $sort);
129
		$tmp = array_keys($context['error_types']);
130
		$sum = (int) end($tmp);
131
132
		$context['error_types']['all'] = array(
133
			'label' => $txt['errortype_all'],
134
			'description' => isset($txt['errortype_all_desc']) ? $txt['errortype_all_desc'] : '',
135
			'url' => $scripturl . '?action=admin;area=logs;sa=errorlog' . ($context['sort_direction'] == 'down' ? ';desc' : ''),
136
			'is_selected' => empty($filter),
137
		);
138
139
		// Update the all errors tab with the total number of errors
140
		$context['error_types']['all']['label'] .= ' (' . $sum . ')';
141
142
		// Finally, work out what is the last tab!
143
		if (isset($context['error_types'][$sum]))
144
			$context['error_types'][$sum]['is_last'] = true;
145
		else
146
			$context['error_types']['all']['is_last'] = true;
147
148
		// And this is pretty basic ;).
149
		$context['page_title'] = $txt['errlog'];
150
		$context['has_filter'] = !empty($filter);
151
		$context['sub_template'] = 'error_log';
152
153
		createToken('admin-el');
154
	}
155
156
	/**
157
	 * Applys the filter to the template
158
	 *
159
	 * @param array $filter
160
	 */
161
	private function _applyFilter($filter)
162
	{
163
		global $context, $scripturl, $user_profile;
164
165
		if (isset($filter['variable']))
166
		{
167
			$context['filter'] = &$filter;
168
169
			// Set the filtering context.
170
			switch ($filter['variable'])
171
			{
172 View Code Duplication
				case 'id_member':
173
					$id = $filter['value']['sql'];
174
					loadMemberData($id, false, 'minimal');
175
					$context['filter']['value']['html'] = '<a href="' . $scripturl . '?action=profile;u=' . $id . '">' . $user_profile[$id]['real_name'] . '</a>';
176
					break;
177 View Code Duplication
				case 'url':
178
					$context['filter']['value']['html'] = '\'' . strtr(htmlspecialchars((substr($filter['value']['sql'], 0, 1) == '?' ? $scripturl : '') . $filter['value']['sql'], ENT_COMPAT, 'UTF-8'), array('\_' => '_')) . '\'';
179
					break;
180 View Code Duplication
				case 'message':
181
					$context['filter']['value']['html'] = '\'' . strtr(htmlspecialchars($filter['value']['sql'], ENT_COMPAT, 'UTF-8'), array("\n" => '<br />', '&lt;br /&gt;' => '<br />', "\t" => '&nbsp;&nbsp;&nbsp;', '\_' => '_', '\\%' => '%', '\\\\' => '\\')) . '\'';
182
					$context['filter']['value']['html'] = preg_replace('~&amp;lt;span class=&amp;quot;remove&amp;quot;&amp;gt;(.+?)&amp;lt;/span&amp;gt;~', '$1', $context['filter']['value']['html']);
183
					break;
184
				case 'error_type':
185
					$context['filter']['value']['html'] = '\'' . strtr(htmlspecialchars($filter['value']['sql'], ENT_COMPAT, 'UTF-8'), array("\n" => '<br />', '&lt;br /&gt;' => '<br />', "\t" => '&nbsp;&nbsp;&nbsp;', '\_' => '_', '\\%' => '%', '\\\\' => '\\')) . '\'';
186
					break;
187
				default:
188
					$context['filter']['value']['html'] = &$filter['value']['sql'];
189
			}
190
		}
191
	}
192
193
	/**
194
	 * Load basic member information for log viewing
195
	 *
196
	 * @param int[] $members
197
	 */
198
	private function _loadMemData($members)
199
	{
200
		global $context, $txt, $scripturl;
201
202
		// Load the member data.
203 View Code Duplication
		if (!empty($members))
204
		{
205
			require_once(SUBSDIR . '/Members.subs.php');
206
			$members = getBasicMemberData($members, array('add_guest' => true));
207
208
			// Go through each error and tack the data on.
209
			foreach ($context['errors'] as $id => $dummy)
210
			{
211
				$memID = $context['errors'][$id]['member']['id'];
212
				$context['errors'][$id]['member']['username'] = $members[$memID]['member_name'];
213
				$context['errors'][$id]['member']['name'] = $members[$memID]['real_name'];
214
				$context['errors'][$id]['member']['href'] = empty($memID) ? '' : $scripturl . '?action=profile;u=' . $memID;
215
				$context['errors'][$id]['member']['link'] = empty($memID) ? $txt['guest_title'] : '<a href="' . $scripturl . '?action=profile;u=' . $memID . '">' . $context['errors'][$id]['member']['name'] . '</a>';
216
			}
217
		}
218
	}
219
220
	/**
221
	 * Setup any filtering the user may have selected
222
	 */
223
	private function _setupFiltering()
224
	{
225
		global $txt;
226
227
		// We'll escape some strings...
228
		$db = database();
229
230
		// You can filter by any of the following columns:
231
		$filters = array(
232
			'id_member' => $txt['username'],
233
			'ip' => $txt['ip_address'],
234
			'session' => $txt['session'],
235
			'url' => $txt['error_url'],
236
			'message' => $txt['error_message'],
237
			'error_type' => $txt['error_type'],
238
			'file' => $txt['file'],
239
			'line' => $txt['line'],
240
		);
241
242
		$filter = array();
243
244
		// Set up the filtering...
245
		if (isset($this->_req->query->value, $this->_req->query->filter) && isset($filters[$this->_req->query->filter]))
246
		{
247
			$filter = array(
248
				'variable' => $this->_req->query->filter,
249
				'value' => array(
250
					'sql' => in_array($this->_req->query->filter, array('message', 'url', 'file'))
251
						? base64_decode(strtr($this->_req->query->value, array(' ' => '+')))
252
						: $db->escape_wildcard_string($this->_req->query->value),
253
				),
254
				'href' => ';filter=' . $this->_req->query->filter . ';value=' . $this->_req->query->value,
255
				'entity' => $filters[$this->_req->query->filter]
256
			);
257
		}
258
		elseif (isset($this->_req->query->filter) || isset($this->_req->query->value))
259
			unset($this->_req->query->filter, $this->_req->query->value);
260
261
		return $filter;
262
	}
263
264
	/**
265
	 * View a file specified in $_REQUEST['file'], with php highlighting on it
266
	 *
267
	 * Preconditions:
268
	 *  - file must be readable,
269
	 *  - full file path must be base64 encoded,
270
	 *
271
	 * - The line number number is specified by $_REQUEST['line']...
272
	 * - The function will try to get the 20 lines before and after the specified line.
273
	 */
274
	protected function action_viewfile()
275
	{
276
		global $context;
277
278
		// We can't help you if you don't spell it out loud :P
279
		if (!isset($this->_req->query->file))
280
			redirectexit();
281
282
		// Decode the file and get the line
283
		$filename = base64_decode($this->_req->query->file);
284
		$file = realpath($filename);
285
		$line = $this->_req->getQuery('line', 'intval', 0);
286
287
		// Make sure things are normalized
288
		$real_board = realpath(BOARDDIR);
289
		$real_source = realpath(SOURCEDIR);
290
		$real_cache = realpath(CACHEDIR);
291
292
		// Make sure the file requested is one they are allowed to look at
293
		$excluded = array('settings.php', 'settings_bak.php');
294
		$basename = strtolower(basename($file));
295
		$ext = strrchr($basename, '.');
296
297
		// Not like we can look at just any old file
298
		if ($ext !== '.php' || (strpos($file, $real_board) === false && strpos($file, $real_source) === false) || strpos($file, $real_cache) !== false || in_array($basename, $excluded) || !is_readable($file))
299
			throw new Elk_Exception('error_bad_file', true, array(htmlspecialchars($filename, ENT_COMPAT, 'UTF-8')));
300
301
		// Get the min and max lines
302
		$min = $line - 16 <= 0 ? 1 : $line - 16;
303
		$max = $line + 21; // One additional line to make everything work out correctly
304
305
		if ($max <= 0 || $min >= $max)
306
			throw new Elk_Exception('error_bad_line');
307
308
		$file_data = explode('<br />', highlight_php_code(htmlspecialchars(implode('', file($file)), ENT_COMPAT, 'UTF-8')));
309
310
		// We don't want to slice off too many so lets make sure we stop at the last one
311
		$max = min($max, max(array_keys($file_data)));
312
313
		$file_data = array_slice($file_data, $min - 1, $max - $min);
314
315
		$context['file_data'] = array(
316
			'contents' => $file_data,
317
			'min' => $min,
318
			'target' => $line,
319
			'file' => strtr($file, array('"' => '\\"')),
320
		);
321
322
		loadTemplate('Errors');
323
		Template_Layers::instance()->removeAll();
324
		$context['sub_template'] = 'show_file';
325
	}
326
}
327