PortalArticles_Controller   F
last analyzed

Complexity

Total Complexity 67

Size/Duplication

Total Lines 412
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 177
c 2
b 0
f 0
dl 0
loc 412
rs 3.04
wmc 67

5 Methods

Rating   Name   Duplication   Size   Complexity  
A pre_dispatch() 0 4 1
A action_index() 0 17 1
F action_sportal_attach() 0 160 28
F action_sportal_article() 0 156 31
B action_sportal_articles() 0 43 6

How to fix   Complexity   

Complex Class

Complex classes like PortalArticles_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.

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 PortalArticles_Controller, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @package SimplePortal ElkArte
5
 *
6
 * @author SimplePortal Team
7
 * @copyright 2015-2021 SimplePortal Team
8
 * @license BSD 3-clause
9
 * @version 1.0.0
10
 */
11
12
use BBC\ParserWrapper;
13
use BBC\PreparseCode;
14
15
/**
16
 * Article controller.
17
 *
18
 * - This class handles requests for Article Functionality
19
 */
20
class PortalArticles_Controller extends Action_Controller
21
{
22
	/**
23
	 * This method is executed before any action handler.
24
	 * Loads common things for all methods
25
	 */
26
	public function pre_dispatch()
27
	{
28
		loadTemplate('PortalArticles');
29
		require_once(SUBSDIR . '/PortalArticle.subs.php');
30
	}
31
32
	/**
33
	 * Default method
34
	 */
35
	public function action_index()
36
	{
37
		require_once(SUBSDIR . '/Action.class.php');
38
39
		// add an subaction array to act accordingly
40
		$subActions = array(
41
			'article' => array($this, 'action_sportal_article'),
42
			'articles' => array($this, 'action_sportal_articles'),
43
			'spattach' => array($this, 'action_sportal_attach'),
44
		);
45
46
		// Setup the action handler
47
		$action = new Action();
48
		$subAction = $action->initialize($subActions, 'article');
49
50
		// Call the action
51
		$action->dispatch($subAction);
52
	}
53
54
	/**
55
	 * Load all articles for selection, not used just yet
56
	 */
57
	public function action_sportal_articles()
58
	{
59
		global $context, $scripturl, $txt, $modSettings;
60
61
		// Set up for pagination
62
		$total_articles = sportal_get_articles_count();
63
		$per_page = min($total_articles, !empty($modSettings['sp_articles_per_page']) ? $modSettings['sp_articles_per_page'] : 10);
64
		$start = !empty($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
65
66
		if ($total_articles > $per_page)
67
		{
68
			$context['page_index'] = constructPageIndex($scripturl . '?action=portal;sa=articles;start=%1$d', $start, $total_articles, $per_page, true);
69
		}
70
71
		// Fetch the article page
72
		$context['articles'] = sportal_get_articles(0, true, true, 'spa.id_article DESC', 0, $per_page, $start);
73
		foreach ($context['articles'] as $article)
74
		{
75
			$context['articles'][$article['id']]['preview'] = censor($article['body']);
76
			$context['articles'][$article['id']]['date'] = htmlTime($article['date']);
77
			$context['articles'][$article['id']]['time'] = $article['date'];
78
79
			// Parse and cut as needed
80
			$context['articles'][$article['id']]['cut'] = sportal_parse_cutoff_content($context['articles'][$article['id']]['preview'], $article['type'], $modSettings['sp_articles_length'], $context['articles'][$article['id']]['article_id']);
81
		}
82
83
		// Auto video embedding enabled?
84
		if (!empty($modSettings['enableVideoEmbeding']))
85
		{
86
			addInlineJavascript('
87
				$(document).ready(function() {
88
					$().linkifyvideo(oEmbedtext);
89
				});', true
90
			);
91
		}
92
93
		$context['linktree'][] = array(
94
			'url' => $scripturl . '?action=portal;sa=articles',
95
			'name' => $txt['sp-articles'],
96
		);
97
98
		$context['page_title'] = $txt['sp-articles'];
99
		$context['sub_template'] = 'view_articles';
100
	}
101
102
	/**
103
	 * Display a chosen article, called from frontpage hook
104
	 *
105
	 * - Update the stats, like #views etc
106
	 */
107
	public function action_sportal_article()
108
	{
109
		global $context, $scripturl, $user_info, $modSettings;
110
111
		$article_id = !empty($_REQUEST['article']) ? $_REQUEST['article'] : 0;
112
113
		if (!is_int($article_id))
114
		{
115
			$article_id = Util::htmlspecialchars($article_id, ENT_QUOTES);
116
		}
117
118
		// Fetch and render the article
119
		$context['article'] = sportal_get_articles($article_id, true, true);
120
		if (empty($context['article']['id']))
121
		{
122
			throw new Elk_Exception('error_sp_article_not_found', false);
123
		}
124
125
		$context['article']['style'] = sportal_select_style($context['article']['styles']);
126
		$context['article']['body'] = censor($context['article']['body']);
127
		$context['article']['body'] = sportal_parse_content($context['article']['body'], $context['article']['type'], 'return');
128
129
		// Fetch attachments, if there are any
130
		if (!empty($modSettings['attachmentEnable']) && !empty($context['article']['has_attachments']))
131
		{
132
			loadJavascriptFile('topic.js');
133
			$context['article']['attachment'] = sportal_load_attachment_context($context['article']['id']);
134
		}
135
136
		// Set up for the comment pagination
137
		$total_comments = sportal_get_article_comment_count($context['article']['id']);
138
		$per_page = min($total_comments, !empty($modSettings['sp_articles_comments_per_page']) ? $modSettings['sp_articles_comments_per_page'] : 20);
139
		$start = !empty($_REQUEST['comments']) ? (int) $_REQUEST['comments'] : 0;
140
141
		if ($total_comments > $per_page)
142
		{
143
			$context['page_index'] = constructPageIndex($scripturl . '?article=' . $context['article']['article_id'] . ';comments=%1$d', $start, $total_comments, $per_page, true);
144
		}
145
146
		// Load in all the comments for the article
147
		$context['article']['comments'] = sportal_get_comments($context['article']['id'], $per_page, $start);
148
149
		// Prepare the final template details
150
		$context['article']['time'] = $context['article']['date'];
151
		$context['article']['date'] = htmlTime($context['article']['date']);
152
		$context['article']['can_comment'] = $context['user']['is_logged'];
153
		$context['article']['can_moderate'] = allowedTo('sp_admin') || allowedTo('sp_manage_articles');
154
155
		// Commenting, new or an update perhaps
156
		if ($context['article']['can_comment'] && !empty($_POST['body']))
157
		{
158
			checkSession();
159
			sp_prevent_flood('spacp', false);
160
161
			require_once(SUBSDIR . '/Post.subs.php');
162
163
			// Prep the body / comment
164
			$body = Util::htmlspecialchars(trim($_POST['body']));
165
			$preparse = PreparseCode::instance();
166
			$preparse->preparsecode($body, false);
167
168
			// Update or add a new comment
169
			$parser = ParserWrapper::instance();
170
			if (!empty($body) && trim(strip_tags($parser->parseMessage($body, false), '<img>')) !== '')
171
			{
172
				if (!empty($_POST['comment']))
173
				{
174
					list ($comment_id, $author_id,) = sportal_fetch_article_comment((int) $_POST['comment']);
175
					if (empty($comment_id) || (!$context['article']['can_moderate'] && $user_info['id'] != $author_id))
176
					{
177
						throw new Elk_Exception('error_sp_cannot_comment_modify', false);
178
					}
179
180
					sportal_modify_article_comment($comment_id, $body);
181
				}
182
				else
183
				{
184
					sportal_create_article_comment($context['article']['id'], $body);
185
				}
186
			}
187
188
			// Set a anchor
189
			$anchor = '#comment' . (!empty($comment_id) ? $comment_id : ($total_comments > 0 ? $total_comments - 1 : 1));
190
			redirectexit('article=' . $context['article']['article_id'] . $anchor);
191
		}
192
193
		// Prepare to edit an existing comment
194
		if ($context['article']['can_comment'] && !empty($_GET['modify']))
195
		{
196
			checkSession('get');
197
198
			list ($comment_id, $author_id, $body) = sportal_fetch_article_comment((int) $_GET['modify']);
199
			if (empty($comment_id) || (!$context['article']['can_moderate'] && $user_info['id'] != $author_id))
200
			{
201
				throw new Elk_Exception('error_sp_cannot_comment_modify', false);
202
			}
203
204
			require_once(SUBSDIR . '/Post.subs.php');
205
206
			$context['article']['comment'] = array(
207
				'id' => $comment_id,
208
				'body' => str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), un_preparsecode($body)),
209
			);
210
		}
211
212
		// Want to delete a comment?
213
		if ($context['article']['can_comment'] && !empty($_GET['delete']))
214
		{
215
			checkSession('get');
216
217
			if (sportal_delete_article_comment((int) $_GET['delete']) === false)
218
			{
219
				throw new Elk_Exception('error_sp_cannot_comment_delete', false);
220
			}
221
222
			redirectexit('article=' . $context['article']['article_id']);
223
		}
224
225
		// Increase the article view counter
226
		if (empty($_SESSION['last_viewed_article']) || $_SESSION['last_viewed_article'] != $context['article']['id'])
227
		{
228
			sportal_increase_viewcount('article', $context['article']['id']);
229
			$_SESSION['last_viewed_article'] = $context['article']['id'];
230
		}
231
232
		// Build the breadcrumbs
233
		$context['linktree'] = array_merge($context['linktree'], array(
234
			array(
235
				'url' => $scripturl . '?category=' . $context['article']['category']['category_id'],
236
				'name' => $context['article']['category']['name'],
237
			),
238
			array(
239
				'url' => $scripturl . '?article=' . $context['article']['article_id'],
240
				'name' => $context['article']['title'],
241
			)
242
		));
243
244
		// Auto video embedding enabled?
245
		if (!empty($modSettings['enableVideoEmbeding']))
246
		{
247
			addInlineJavascript('
248
				$(document).ready(function() {
249
					$().linkifyvideo(oEmbedtext);
250
				});', true
251
			);
252
		}
253
254
		// Needed for basic Lightbox functionality
255
		loadJavascriptFile('topic.js', ['defer' => false]);
256
257
		$context['description'] = trim(preg_replace('~<[^>]+>~', ' ', $context['article']['body']));
258
		$context['description'] = Util::shorten_text(preg_replace('~\s\s+|&nbsp;|&quot;|&#039;~', ' ', $context['description']), 384, true);
259
260
		// Off to the template we go
261
		$context['page_title'] = $context['article']['title'];
262
		$context['sub_template'] = 'view_article';
263
	}
264
265
	/**
266
	 * Downloads / shows an article attachment
267
	 *
268
	 * It requires the view_attachments permission.
269
	 * It disables the session parser, and clears any previous output.
270
	 * It is accessed via the query string ?action=portal;sa=spattach.
271
	 */
272
	public function action_sportal_attach()
273
	{
274
		global $txt, $modSettings, $context;
275
276
		// Some defaults that we need.
277
		$context['no_last_modified'] = true;
278
279
		// Make sure some attachment was requested and they can view them
280
		if (!isset($_GET['article'], $_GET['attach']))
281
		{
282
			throw new Elk_Exception('no_access', false);
283
		}
284
285
		// No funny business, you need to have access to the article to see its attachments
286
		if (sportal_article_access($_GET['article']) === false)
287
		{
288
			throw new Elk_Exception('no_access', false);
289
		}
290
291
		// We need to do some work on attachments.
292
		$id_article = (int) $_GET['article'];
293
		$id_attach = (int) $_GET['attach'];
294
295
		if (isset($_GET['thumb']))
296
			$attachment = sportal_get_attachment_thumb_from_article($id_article, $id_attach);
297
		else
298
			$attachment = sportal_get_attachment_from_article($id_article, $id_attach);
299
300
		if (empty($attachment))
301
		{
302
			throw new Elk_Exception('no_access', false);
303
		}
304
305
		list ($real_filename, $file_hash, $file_ext, $id_attach, $attachment_type, $mime_type, $width, $height) = $attachment;
306
		$filename = $modSettings['sp_articles_attachment_dir'] . '/' . $id_attach . '_' . $file_hash . '.elk';
307
308
		// This is done to clear any output that was made before now.
309
		while (ob_get_level() > 0)
310
		{
311
			@ob_end_clean();
312
		}
313
314
		ob_start();
315
		header('Content-Encoding: none');
316
317
		// No point in a nicer message, because this is supposed to be an attachment anyway...
318
		if (!file_exists($filename))
319
		{
320
			loadLanguage('Errors');
321
322
			header((preg_match('~HTTP/1\.[01]~i', $_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0') . ' 404 Not Found');
323
			header('Content-Type: text/plain; charset=UTF-8');
324
325
			// We need to die like this *before* we send any anti-caching headers as below.
326
			die('404 - ' . $txt['attachment_not_found']);
327
		}
328
329
		// If it hasn't been modified since the last time this attachment was retrieved,
330
		// there's no need to display it again.
331
		if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']))
332
		{
333
			list ($modified_since) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE']);
334
			if (strtotime($modified_since) >= filemtime($filename))
335
			{
336
				@ob_end_clean();
337
338
				// Answer the question - no, it hasn't been modified ;).
339
				header('HTTP/1.1 304 Not Modified');
340
			}
341
			exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
342
		}
343
344
		// Check whether the ETag was sent back, and cache based on that...
345
		$eTag = '"' . substr($id_attach . $real_filename . filemtime($filename), 0, 64) . '"';
346
		if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) && strpos($_SERVER['HTTP_IF_NONE_MATCH'], $eTag) !== false)
347
		{
348
			@ob_end_clean();
349
350
			header('HTTP/1.1 304 Not Modified');
351
			exit(0);
352
		}
353
354
		// Send the attachment headers.
355
		header('Content-Transfer-Encoding: binary');
356
		header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT');
357
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($filename)) . ' GMT');
358
		header('Accept-Ranges: bytes');
359
		header('Connection: close');
360
		header('ETag: ' . $eTag);
361
362
		// Make sure the mime type warrants an inline display.
363
		if (isset($_GET['image']) && !empty($mime_type) && strpos($mime_type, 'image/') !== 0)
364
		{
365
			unset($_GET['image']);
366
		}
367
		// Does this have a mime type?
368
		elseif (!empty($mime_type) && (isset($_GET['image']) || !in_array($file_ext, array('jpg', 'gif', 'jpeg', 'x-ms-bmp', 'png', 'psd', 'tiff', 'iff'))))
369
		{
370
			header('Content-Type: ' . strtr($mime_type, array('image/bmp' => 'image/x-ms-bmp')));
371
		}
372
		else
373
		{
374
			header('Content-Type: application/octet-stream');
375
		}
376
377
		$disposition = !isset($_GET['image']) ? 'attachment' : 'inline';
378
		$fileName = str_replace('"', '',  $filename);
379
380
		// Send as UTF-8 if the name requires that
381
		$altName = '';
382
		if (preg_match('~[\x80-\xFF]~', $fileName))
383
		{
384
			$altName = "; filename*=UTF-8''" . rawurlencode($fileName);
385
		}
386
		header('Content-Disposition: ' . $disposition . '; filename="' . $fileName . '"' . $altName);
387
388
		// If this has an "image extension" - but isn't actually an image - then ensure it isn't cached cause of silly IE.
389
		if (!isset($_GET['image']) && in_array($file_ext, array('gif', 'jpg', 'bmp', 'png', 'jpeg', 'tiff')))
390
		{
391
			header('Pragma: no-cache');
392
			header('Cache-Control: no-cache');
393
		}
394
		else
395
		{
396
			header('Cache-Control: max-age=' . (525600 * 60) . ', private');
397
		}
398
399
		if (empty($modSettings['enableCompressedOutput']) || filesize($filename) > 4194304)
400
		{
401
			header('Content-Length: ' . filesize($filename));
402
		}
403
404
		// Try to buy some time...
405
		@set_time_limit(600);
406
407
		// Since we don't do output compression for files this large...
408
		if (filesize($filename) > 4194304)
409
		{
410
			// Forcibly end any output buffering going on.
411
			while (ob_get_level() > 0)
412
			{
413
				@ob_end_clean();
414
			}
415
416
			$fp = fopen($filename, 'rb');
417
			while (!feof($fp))
418
			{
419
				echo fread($fp, 8192);
420
421
				flush();
422
			}
423
			fclose($fp);
424
		}
425
		// On some of the less-bright hosts, readfile() is disabled.  It's just a faster, more byte safe, version of what's in the if.
426
		elseif (@readfile($filename) === null)
427
		{
428
			echo file_get_contents($filename);
429
		}
430
431
		obExit(false);
432
	}
433
}
434