createArticleAttachment()   F
last analyzed

Complexity

Conditions 25
Paths 288

Size

Total Lines 138
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 25
eloc 66
nc 288
nop 1
dl 0
loc 138
rs 2.2333
c 1
b 0
f 1

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
 * @package SimplePortal ElkArte
5
 *
6
 * @author SimplePortal Team
7
 * @copyright 2015-2021 SimplePortal Team
8
 * @license BSD 3-clause
9
 * @version 1.0.0 Beta 1
10
 */
11
12
use BBC\ParserWrapper;
13
14
/**
15
 * Returns the number of views and comments for a given article
16
 *
17
 * @param int $id the id of the article
18
 *
19
 * @return array
20
 */
21
function sportal_get_article_views_comments($id)
22
{
23
	$db = database();
24
25
	if (empty($id))
26
	{
27
		return array(0, 0);
28
	}
29
30
	// Make the request
31
	$request = $db->query('', '
32
		SELECT
33
			views, comments
34
		FROM {db_prefix}sp_articles
35
		WHERE id_article = {int:article_id}
36
		LIMIT {int:limit}',
37
		array(
38
			'article_id' => $id,
39
			'limit' => 1,
40
		)
41
	);
42
	$result = array(0, 0);
43
	while ($row = $db->fetch_assoc($request))
44
	{
45
		$result[0] = $row['views'];
46
		$result[1] = $row['comments'];
47
	}
48
	$db->free_result($request);
49
50
	return $result;
51
}
52
53
/**
54
 * Loads an article by id, or articles by namespace
55
 *
56
 * @param int|string|null $article_id id of an article or string for the articles in a namespace
57
 * @param boolean $active true to only return items that are active
58
 * @param boolean $allowed true to check permission access to the item
59
 * @param string $sort string passed to the order by parameter
60
 * @param int|null $category_id id of the category
61
 * @param int|null $limit limit the number of results
62
 * @param int|null $start start number for pages
63
 *
64
 * @return array
65
 */
66
function sportal_get_articles($article_id = null, $active = false, $allowed = false, $sort = 'spa.title', $category_id = null, $limit = null, $start = null)
67
{
68
	global $scripturl, $context, $color_profile;
69
70
	$db = database();
71
72
	$query = array();
73
	$parameters = array('sort' => $sort);
74
75
	// Load up an article or namespace
76
	if (!empty($article_id) && is_int($article_id))
77
	{
78
		$query[] = 'spa.id_article = {int:article_id}';
79
		$parameters['article_id'] = (int) $article_id;
80
	}
81
	elseif (!empty($article_id))
82
	{
83
		$query[] = 'spa.namespace = {string:namespace}';
84
		$parameters['namespace'] = $article_id;
85
	}
86
87
	// Want articles only from a specific category
88
	if (!empty($category_id))
89
	{
90
		$query[] = 'spa.id_category = {int:category_id}';
91
		$parameters['category_id'] = (int) $category_id;
92
	}
93
94
	// Checking access?
95
	if (!empty($allowed))
96
	{
97
		$query[] = sprintf($context['SPortal']['permissions']['query'], 'spa.permissions');
98
		$query[] = sprintf($context['SPortal']['permissions']['query'], 'spc.permissions');
99
	}
100
101
	// Its an active article and category?
102
	if (!empty($active))
103
	{
104
		$query[] = 'spa.status = {int:article_status}';
105
		$parameters['article_status'] = 1;
106
		$query[] = 'spc.status = {int:category_status}';
107
		$parameters['category_status'] = 1;
108
	}
109
110
	// Limits?
111
	if (!empty($limit))
112
	{
113
		$parameters['limit'] = $limit;
114
		$parameters['start'] = !empty($start) ? $start : 0;
115
	}
116
117
	// Make the request
118
	$request = $db->query('', '
119
		SELECT
120
			spa.id_article, spa.id_category, spa.namespace AS article_namespace, spa.title, spa.body,
121
			spa.type, spa.date, spa.status, spa.permissions AS article_permissions, spa.views, spa.comments, spa.styles,
122
			spc.permissions AS category_permissions, spc.name, spc.namespace AS category_namespace,
123
			m.avatar, IFNULL(m.id_member, 0) AS id_author, IFNULL(m.real_name, spa.member_name) AS author_name, m.email_address,
124
			a.id_attach, a.attachment_type, a.filename
125
		FROM {db_prefix}sp_articles AS spa
126
			INNER JOIN {db_prefix}sp_categories AS spc ON (spc.id_category = spa.id_category)
127
			LEFT JOIN {db_prefix}members AS m ON (m.id_member = spa.id_member)
128
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = m.id_member)' . (!empty($query) ? '
129
		WHERE ' . implode(' AND ', $query) : '') . '
130
		ORDER BY {raw:sort}' . (!empty($limit) ? '
131
		LIMIT {int:start}, {int:limit}' : ''), $parameters
132
	);
133
	$return = array();
134
	$member_ids = array();
135
	while ($row = $db->fetch_assoc($request))
136
	{
137
		if (!empty($row['id_author']))
138
		{
139
			$member_ids[$row['id_author']] = $row['id_author'];
140
		}
141
142
		$return[$row['id_article']] = array(
143
			'id' => $row['id_article'],
144
			'category' => array(
145
				'id' => $row['id_category'],
146
				'category_id' => $row['category_namespace'],
147
				'name' => $row['name'],
148
				'href' => $scripturl . '?category=' . $row['category_namespace'],
149
				'link' => '<a class="sp_cat_link" href="' . $scripturl . '?category=' . $row['category_namespace'] . '">' . $row['name'] . '</a>',
150
				'permissions' => $row['category_permissions'],
151
			),
152
			'author' => array(
153
				'id' => $row['id_author'],
154
				'name' => $row['author_name'],
155
				'href' => $scripturl . '?action=profile;u=' . $row['id_author'],
156
				'link' => $row['id_author']
157
					? ('<a href="' . $scripturl . '?action=profile;u=' . $row['id_author'] . '">' . $row['author_name'] . '</a>')
158
					: $row['author_name'],
159
				'avatar' => determineAvatar(array(
160
					'avatar' => $row['avatar'],
161
					'filename' => $row['filename'],
162
					'id_attach' => $row['id_attach'],
163
					'email_address' => $row['email_address'],
164
					'attachment_type' => $row['attachment_type'],
165
				)),
166
			),
167
			'article_id' => $row['article_namespace'],
168
			'title' => $row['title'],
169
			'href' => $scripturl . '?article=' . $row['article_namespace'],
170
			'link' => '<a href="' . $scripturl . '?article=' . $row['article_namespace'] . '">' . $row['title'] . '</a>',
171
			'body' => $row['body'],
172
			'type' => $row['type'],
173
			'date' => $row['date'],
174
			'permissions' => $row['article_permissions'],
175
			'styles' => $row['styles'],
176
			'view_count' => $row['views'],
177
			'comment_count' => $row['comments'],
178
			'status' => $row['status'],
179
			'has_attachments' => false
180
		);
181
	}
182
	$db->free_result($request);
183
184
	// No results, nothing else to do
185
	if (empty($return))
186
	{
187
		return array();
188
	}
189
190
	// Flag which ones have attachments
191
	$id_articles = array_keys($return);
192
	$has_attachments = sportal_article_has_attachments($id_articles);
193
	foreach ($has_attachments as $id_article => $article)
194
	{
195
		$return[$id_article]['has_attachments'] = $article;
196
	}
197
198
	// Use color profiles?
199
	if (!empty($member_ids) && sp_loadColors($member_ids) !== false)
200
	{
201
		foreach ($return as $key => $value)
202
		{
203
			if (!empty($color_profile[$value['author']['id']]['link']))
204
			{
205
				$return[$key]['author']['link'] = $color_profile[$value['author']['id']]['link'];
206
			}
207
		}
208
	}
209
210
	// Return one or all
211
	return !empty($article_id) ? current($return) : $return;
212
}
213
214
/**
215
 * Returns the count of articles in a given category
216
 *
217
 * @param int $catid category identifier
218
 *
219
 * @return int
220
 */
221
function sportal_get_articles_in_cat_count($catid)
222
{
223
	global $context;
224
225
	$db = database();
226
227
	$request = $db->query('', '
228
		SELECT 
229
			COUNT(*)
230
		FROM {db_prefix}sp_articles
231
		WHERE status = {int:article_status}
232
			AND {raw:article_permissions}
233
			AND id_category = {int:current_category}
234
		LIMIT {int:limit}',
235
		array(
236
			'article_status' => 1,
237
			'article_permissions' => sprintf($context['SPortal']['permissions']['query'], 'permissions'),
238
			'current_category' => $catid,
239
			'limit' => 1,
240
		)
241
	);
242
	list ($total_articles) = $db->fetch_row($request);
243
	$db->free_result($request);
244
245
	return $total_articles;
246
}
247
248
/**
249
 * Checks if a member has access to an article
250
 *
251
 * Checks permissions and that the article and category are active
252
 *
253
 * @param int $id_article article to check access
254
 * @return bool
255
 */
256
function sportal_article_access($id_article)
257
{
258
	global $context;
259
260
	if (empty($id_article))
261
	{
262
		return false;
263
	}
264
265
	$db = database();
266
267
	$request = $db->query('', '
268
		SELECT 
269
			spa.id_article
270
		FROM {db_prefix}sp_articles AS spa
271
			INNER JOIN {db_prefix}sp_categories AS spc ON (spc.id_category = spa.id_category)
272
		WHERE spa.id_article = {int:id_article}
273
		 	AND spa.status = {int:article_status}
274
			AND spc.status = {int:category_status}
275
			AND {raw:article_permissions}
276
			AND {raw:category_permissions}
277
		LIMIT {int:limit}',
278
		array(
279
			'id_article' => $id_article,
280
			'article_status' => 1,
281
			'category_status' => 1,
282
			'article_permissions' => sprintf($context['SPortal']['permissions']['query'], 'spa.permissions'),
283
			'category_permissions' => sprintf($context['SPortal']['permissions']['query'], 'spc.permissions'),
284
			'limit' => 1,
285
		)
286
	);
287
	list ($total_articles) = $db->fetch_row($request);
288
	$db->free_result($request);
289
290
	return !empty($total_articles);
291
}
292
293
/**
294
 * Returns the number of articles in the system that a user can see
295
 *
296
 * @return int
297
 */
298
function sportal_get_articles_count()
299
{
300
	global $context;
301
302
	$db = database();
303
304
	$request = $db->query('', '
305
		SELECT 
306
			COUNT(*)
307
		FROM {db_prefix}sp_articles AS spa
308
			INNER JOIN {db_prefix}sp_categories AS spc ON (spc.id_category = spa.id_category)
309
		WHERE spa.status = {int:article_status}
310
			AND spc.status = {int:category_status}
311
			AND {raw:article_permissions}
312
			AND {raw:category_permissions}
313
		LIMIT {int:limit}',
314
		array(
315
			'article_status' => 1,
316
			'category_status' => 1,
317
			'article_permissions' => sprintf($context['SPortal']['permissions']['query'], 'spa.permissions'),
318
			'category_permissions' => sprintf($context['SPortal']['permissions']['query'], 'spc.permissions'),
319
			'limit' => 1,
320
		)
321
	);
322
	list ($total_articles) = $db->fetch_row($request);
323
	$db->free_result($request);
324
325
	return $total_articles;
326
}
327
328
/**
329
 * Fetches the number of comments a given article has received
330
 *
331
 * @param int $id article id
332
 *
333
 * @return int
334
 */
335
function sportal_get_article_comment_count($id)
336
{
337
	$db = database();
338
339
	$request = $db->query('', '
340
		SELECT 
341
			COUNT(*)
342
		FROM {db_prefix}sp_comments
343
		WHERE id_article = {int:current_article}
344
		LIMIT {int:limit}',
345
		array(
346
			'current_article' => $id,
347
			'limit' => 1,
348
		)
349
	);
350
	list ($total_comments) = $db->fetch_row($request);
351
	$db->free_result($request);
352
353
	return $total_comments;
354
}
355
356
/**
357
 * Given an articles comment ID, fetches the comment and comment author
358
 *
359
 * @param int $id
360
 *
361
 * @return array
362
 */
363
function sportal_fetch_article_comment($id)
364
{
365
	$db = database();
366
367
	$request = $db->query('', '
368
		SELECT
369
			id_comment, id_member, body
370
		FROM {db_prefix}sp_comments
371
		WHERE id_comment = {int:comment_id}
372
		LIMIT {int:limit}',
373
		array(
374
			'comment_id' => $id,
375
			'limit' => 1,
376
		)
377
	);
378
	list ($comment_id, $author_id, $body) = $db->fetch_row($request);
379
	$db->free_result($request);
380
381
	return array($comment_id, $author_id, $body);
382
}
383
384
/**
385
 * Loads all the comments that an article has generated
386
 *
387
 * @param int|null $article_id
388
 * @param int|null $limit limit the number of results
389
 * @param int|null $start start number for pages
390
 *
391
 * @return array
392
 */
393
function sportal_get_comments($article_id = null, $limit = null, $start = null)
394
{
395
	global $scripturl, $user_info, $color_profile;
396
397
	$db = database();
398
399
	$request = $db->query('', '
400
		SELECT
401
			spc.id_comment, IFNULL(spc.id_member, 0) AS id_author,
402
			IFNULL(m.real_name, spc.member_name) AS author_name,
403
			spc.body, spc.log_time,
404
			m.avatar, m.email_address,
405
			a.id_attach, a.attachment_type, a.filename
406
		FROM {db_prefix}sp_comments AS spc
407
			LEFT JOIN {db_prefix}members AS m ON (m.id_member = spc.id_member)
408
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = m.id_member)
409
		WHERE spc.id_article = {int:article_id}
410
		ORDER BY spc.id_comment' . (!empty($limit) ? '
411
		LIMIT {int:start}, {int:limit}' : ''),
412
		array(
413
			'article_id' => (int) $article_id,
414
			'limit' => (int) $limit,
415
			'start' => (int) $start,
416
		)
417
	);
418
	$return = array();
419
	$member_ids = array();
420
	$parser = ParserWrapper::instance();
421
	while ($row = $db->fetch_assoc($request))
422
	{
423
		if (!empty($row['id_author']))
424
		{
425
			$member_ids[$row['id_author']] = $row['id_author'];
426
		}
427
428
		$return[$row['id_comment']] = array(
429
			'id' => $row['id_comment'],
430
			'body' => censor($parser->parseMessage($row['body'], true)),
431
			'time' => htmlTime($row['log_time']),
432
			'author' => array(
433
				'id' => $row['id_author'],
434
				'name' => $row['author_name'],
435
				'href' => $scripturl . '?action=profile;u=' . $row['id_author'],
436
				'link' => $row['id_author']
437
					? ('<a href="' . $scripturl . '?action=profile;u=' . $row['id_author'] . '">' . $row['author_name'] . '</a>')
438
					: $row['author_name'],
439
				'avatar' => determineAvatar(array(
440
					'avatar' => $row['avatar'],
441
					'filename' => $row['filename'],
442
					'id_attach' => $row['id_attach'],
443
					'email_address' => $row['email_address'],
444
					'attachment_type' => $row['attachment_type'],
445
				)),
446
			),
447
			'can_moderate' => allowedTo('sp_admin') || allowedTo('sp_manage_articles') || (!$user_info['is_guest'] && $user_info['id'] == $row['id_author']),
448
		);
449
	}
450
	$db->free_result($request);
451
452
	// Colorization
453
	if (!empty($member_ids) && sp_loadColors($member_ids) !== false)
454
	{
455
		foreach ($return as $key => $value)
456
		{
457
			if (!empty($color_profile[$value['author']['id']]['link']))
458
			{
459
				$return[$key]['author']['link'] = $color_profile[$value['author']['id']]['link'];
460
			}
461
		}
462
	}
463
464
	return $return;
465
}
466
467
/**
468
 * Creates a new comment response to a published article
469
 *
470
 * @param int $article_id id of the article commented on
471
 * @param string $body text of the comment
472
 *
473
 * @return null
474
 */
475
function sportal_create_article_comment($article_id, $body)
476
{
477
	global $user_info;
478
479
	$db = database();
480
481
	$db->insert('',
482
		'{db_prefix}sp_comments',
483
		array(
484
			'id_article' => 'int',
485
			'id_member' => 'int',
486
			'member_name' => 'string',
487
			'log_time' => 'int',
488
			'body' => 'string',
489
		),
490
		array(
491
			$article_id,
492
			$user_info['id'],
493
			$user_info['name'],
494
			time(),
495
			$body,
496
		),
497
		array('id_comment')
498
	);
499
500
	// Increase the comment count
501
	sportal_recount_comments($article_id);
502
}
503
504
/**
505
 * Edits an article comment that has already been made
506
 *
507
 * @param int $comment_id comment id to edit
508
 * @param string $body replacement text
509
 */
510
function sportal_modify_article_comment($comment_id, $body)
511
{
512
	$db = database();
513
514
	$db->query('', '
515
		UPDATE {db_prefix}sp_comments
516
		SET body = {text:body}
517
		WHERE id_comment = {int:comment_id}',
518
		array(
519
			'comment_id' => $comment_id,
520
			'body' => $body,
521
		)
522
	);
523
}
524
525
/**
526
 * Removes an comment from an article
527
 *
528
 * @param int $comment_id comment it
529
 *
530
 * @return boolean
531
 */
532
function sportal_delete_article_comment($comment_id)
533
{
534
	global $context, $user_info;
535
536
	$db = database();
537
538
	$request = $db->query('', '
539
		SELECT 
540
			id_comment, id_article, id_member
541
		FROM {db_prefix}sp_comments
542
		WHERE id_comment = {int:comment_id}
543
		LIMIT {int:limit}',
544
		array(
545
			'comment_id' => $comment_id,
546
			'limit' => 1,
547
		)
548
	);
549
	list ($comment_id, $article_id, $author_id) = $db->fetch_row($request);
550
	$db->free_result($request);
551
552
	// You do have a right to remove the comment?
553
	if (empty($comment_id) || (!$context['article']['can_moderate'] && $user_info['id'] != $author_id))
554
	{
555
		return false;
556
	}
557
558
	// Poof ... gone
559
	$db->query('', '
560
		DELETE FROM {db_prefix}sp_comments
561
		WHERE id_comment = {int:comment_id}',
562
		array(
563
			'comment_id' => $comment_id,
564
		)
565
	);
566
567
	// One less comment
568
	sportal_recount_comments($article_id);
569
570
	return true;
571
}
572
573
/**
574
 * Recounts all comments made on an article and updates the DB with the new value
575
 *
576
 * @param int $article_id
577
 */
578
function sportal_recount_comments($article_id)
579
{
580
	$db = database();
581
582
	// How many comments were made for this article
583
	$request = $db->query('', '
584
		SELECT 
585
			COUNT(*)
586
		FROM {db_prefix}sp_comments
587
		WHERE id_article = {int:article_id}
588
		LIMIT {int:limit}',
589
		array(
590
			'article_id' => $article_id,
591
			'limit' => 1,
592
		)
593
	);
594
	list ($comments) = $db->fetch_row($request);
595
	$db->free_result($request);
596
597
	// Update the article comment count
598
	$db->query('', '
599
		UPDATE {db_prefix}sp_articles
600
		SET comments = {int:comments}
601
		WHERE id_article = {int:article_id}',
602
		array(
603
			'article_id' => $article_id,
604
			'comments' => $comments,
605
		)
606
	);
607
}
608
609
/**
610
 * Removes attachments associated with an article
611
 *
612
 * - It does no permissions check.
613
 *
614
 * @param array $attachmentQuery
615
 */
616
function removeArticleAttachments($attachmentQuery)
617
{
618
	$db = database();
619
620
	$aid = $attachmentQuery['id_article'];
621
	$restriction = !empty($attachmentQuery['not_id_attach']) ? $attachmentQuery['not_id_attach'] : array();
622
623
	// Get all the attachments
624
	$request = $db->query('', '
625
		SELECT
626
			a.filename, a.file_hash, a.attachment_type, a.id_attach, a.id_member, a.id_article,
627
			IFNULL(thumb.id_attach, 0) AS id_thumb, thumb.filename AS thumb_filename, thumb.file_hash AS thumb_file_hash
628
		FROM {db_prefix}sp_attachments AS a
629
			LEFT JOIN {db_prefix}sp_attachments AS thumb ON (thumb.id_attach = a.id_thumb)
630
		WHERE 
631
			a.attachment_type = {int:attachment_type}
632
			AND a.id_article = {int:aid}' . (!empty($restriction) ? ' AND a.id_attach NOT IN ({array_int:restriction})' : ''),
633
		array(
634
			'attachment_type' => 0,
635
			'aid' => $aid,
636
			'restriction' => $restriction,
637
		)
638
	);
639
	$attach = array();
640
	while ($row = $db->fetch_assoc($request))
641
	{
642
		$filename = $attachmentQuery['id_folder'] . '/' . $row['id_attach'] . '_' . $row['file_hash'] . '.elk';
643
		@unlink($filename);
644
645
		// If this attachments has a thumb, remove it as well.
646
		if (!empty($row['id_thumb']))
647
		{
648
			$thumb_filename = $attachmentQuery['id_folder'] . '/' . $row['id_thumb'] . '_' . $row['thumb_file_hash'] . '.elk';
649
			@unlink($thumb_filename);
650
			$attach[] = $row['id_thumb'];
651
		}
652
653
		$attach[] = $row['id_attach'];
654
	}
655
	$db->free_result($request);
656
657
	if (!empty($attach))
658
	{
659
		$db->query('', '
660
			DELETE FROM {db_prefix}sp_attachments
661
			WHERE id_attach IN ({array_int:attachment_list})',
662
			array(
663
				'attachment_list' => $attach,
664
			)
665
		);
666
	}
667
}
668
669
/**
670
 * Compute and return the total size of attachments for a single article.
671
 *
672
 * @param int $id_article
673
 * @param bool $include_count = true if true, it also returns the attachments count
674
 */
675
function attachmentsSizeForArticle($id_article, $include_count = true)
676
{
677
	$db = database();
678
679
	if (empty($id_article))
680
	{
681
		return $include_count ? array(0, 0) : array(0);
682
	}
683
684
	if ($include_count)
685
	{
686
		$request = $db->query('', '
687
			SELECT 
688
				COUNT(*), SUM(size)
689
			FROM {db_prefix}sp_attachments
690
			WHERE id_article = {int:id_article}
691
				AND attachment_type = {int:attachment_type}',
692
			array(
693
				'id_article' => $id_article,
694
				'attachment_type' => 0,
695
			)
696
		);
697
	}
698
	else
699
	{
700
		$request = $db->query('', '
701
			SELECT 
702
				COUNT(*)
703
			FROM {db_prefix}sp_attachments
704
			WHERE id_article = {int:id_article}
705
				AND attachment_type = {int:attachment_type}',
706
			array(
707
				'id_article' => $id_article,
708
				'attachment_type' => 0,
709
			)
710
		);
711
	}
712
	$size = $db->fetch_row($request);
713
	$db->free_result($request);
714
715
	return $size;
716
}
717
718
/**
719
 * Create an article attachment, with the given array of parameters.
720
 *
721
 * What it does:
722
 * - Adds any additional or missing parameters to $attachmentOptions.
723
 * - Renames the temporary file.
724
 * - Creates a thumbnail if the file is an image and the option enabled.
725
 *
726
 * @param array $attachmentOptions associative array of options
727
 * @return bool
728
 */
729
function createArticleAttachment(&$attachmentOptions)
730
{
731
	global $modSettings;
732
733
	$db = database();
734
735
	// Going to need some help
736
	require_once(SUBSDIR . '/Attachments.subs.php');
737
	require_once(SUBSDIR . '/Graphics.subs.php');
738
739
	// If this is an image we need to set a few additional parameters.
740
	$size = elk_getimagesize($attachmentOptions['tmp_name']);
741
	list ($attachmentOptions['width'], $attachmentOptions['height']) = $size;
742
743
	// If it's an image get the mime type right.
744
	if (empty($attachmentOptions['mime_type']) && $attachmentOptions['width'])
745
	{
746
		// Got a proper mime type?
747
		if (!empty($size['mime']))
748
		{
749
			$attachmentOptions['mime_type'] = $size['mime'];
750
		}
751
		// Otherwise a valid one?
752
		else
753
		{
754
			$attachmentOptions['mime_type'] = getValidMimeImageType($size[2]);
755
		}
756
	}
757
758
	// Validate the mime is for an image
759
	if (!empty($attachmentOptions['mime_type']) && strpos($attachmentOptions['mime_type'], 'image/') !== 0)
760
	{
761
		$attachmentOptions['width'] = 0;
762
		$attachmentOptions['height'] = 0;
763
	}
764
765
	// Get the hash if no hash has been given yet.
766
	if (empty($attachmentOptions['file_hash']))
767
	{
768
		$attachmentOptions['file_hash'] = getAttachmentFilename($attachmentOptions['name'], 0, null, true);
769
	}
770
771
	// Assuming no-one set the extension let's take a look at it.
772
	if (empty($attachmentOptions['fileext']))
773
	{
774
		$attachmentOptions['fileext'] = strtolower(strrpos($attachmentOptions['name'], '.') !== false ? substr($attachmentOptions['name'], strrpos($attachmentOptions['name'], '.') + 1) : '');
775
		if (strlen($attachmentOptions['fileext']) > 8 || '.' . $attachmentOptions['fileext'] == $attachmentOptions['name'])
776
		{
777
			$attachmentOptions['fileext'] = '';
778
		}
779
	}
780
781
	$db->insert('',
782
		'{db_prefix}sp_attachments',
783
		array(
784
			'id_article' => 'int', 'id_member' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40',
785
			'fileext' => 'string-8', 'size' => 'int', 'width' => 'int', 'height' => 'int', 	'mime_type' => 'string-20'
786
		),
787
		array(
788
			(int) $attachmentOptions['article'], (int) $attachmentOptions['poster'], $attachmentOptions['name'], $attachmentOptions['file_hash'],
789
			$attachmentOptions['fileext'], (int) $attachmentOptions['size'], (empty($attachmentOptions['width']) ? 0 : (int) $attachmentOptions['width']), (empty($attachmentOptions['height']) ? '0' : (int) $attachmentOptions['height']),
790
			(!empty($attachmentOptions['mime_type']) ? $attachmentOptions['mime_type'] : ''),
791
		),
792
		array('id_attach')
793
	);
794
	$attachmentOptions['id'] = $db->insert_id('{db_prefix}sp_attachments', 'id_attach');
795
796
	// @todo Add an error here maybe?
797
	if (empty($attachmentOptions['id']))
798
	{
799
		return false;
800
	}
801
802
	// Now that we have the attach id, let's rename this and finish up.
803
	$attachmentOptions['destination'] = $attachmentOptions['id_folder'] . '/' . $attachmentOptions['id'] . '_' . $attachmentOptions['file_hash'] . '.elk';
804
	rename($attachmentOptions['tmp_name'], $attachmentOptions['destination']);
805
806
	if (empty($modSettings['attachmentThumbnails']) || (empty($attachmentOptions['width']) && empty($attachmentOptions['height'])))
807
	{
808
		return true;
809
	}
810
811
	// Like thumbnails, do we?
812
	if (!empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachmentOptions['width'] > $modSettings['attachmentThumbWidth'] || $attachmentOptions['height'] > $modSettings['attachmentThumbHeight']))
813
	{
814
		if (createThumbnail($attachmentOptions['destination'], $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
815
		{
816
			// Figure out how big we actually made it.
817
			$size = elk_getimagesize($attachmentOptions['destination'] . '_thumb');
818
			list ($thumb_width, $thumb_height) = $size;
819
820
			if (!empty($size['mime']))
821
			{
822
				$thumb_mime = $size['mime'];
823
			}
824
			else
825
			{
826
				$thumb_mime = getValidMimeImageType($size[2]);
827
			}
828
829
			$thumb_filename = $attachmentOptions['name'] . '_thumb';
830
			$thumb_size = filesize($attachmentOptions['destination'] . '_thumb');
831
			$thumb_file_hash = sha1(md5($thumb_filename . time()) . mt_rand());
832
			$thumb_path = $attachmentOptions['destination'] . '_thumb';
833
834
			// To the database we go!
835
			$db->insert('',
836
				'{db_prefix}sp_attachments',
837
				array(
838
					'id_article' => 'int', 'id_member' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
839
					'size' => 'int', 'width' => 'int', 'height' => 'int', 'mime_type' => 'string-20',
840
				),
841
				array(
842
					(int) $attachmentOptions['article'], (int) $attachmentOptions['poster'], 3, $thumb_filename, $thumb_file_hash, $attachmentOptions['fileext'],
843
					$thumb_size, $thumb_width, $thumb_height, $thumb_mime,
844
				),
845
				array('id_attach')
846
			);
847
			$attachmentOptions['thumb'] = $db->insert_id('{db_prefix}attachments', 'id_attach');
848
849
			if (!empty($attachmentOptions['thumb']))
850
			{
851
				$db->query('', '
852
					UPDATE {db_prefix}sp_attachments
853
					SET id_thumb = {int:id_thumb}
854
					WHERE id_attach = {int:id_attach}',
855
					array(
856
						'id_thumb' => $attachmentOptions['thumb'],
857
						'id_attach' => $attachmentOptions['id'],
858
					)
859
				);
860
861
				rename($thumb_path,$attachmentOptions['id_folder'] . '/' . $attachmentOptions['thumb'] . '_' . $thumb_file_hash . '.elk');
862
			}
863
		}
864
	}
865
866
	return true;
867
}
868
869
/**
870
 * Binds a set of attachments to a specific article.
871
 *
872
 * @param int $id_article
873
 * @param int[] $attachment_ids
874
 */
875
function bindArticleAttachments($id_article, $attachment_ids)
876
{
877
	$db = database();
878
879
	$db->query('', '
880
		UPDATE {db_prefix}sp_attachments
881
		SET id_article = {int:id_article}
882
		WHERE id_attach IN ({array_int:attachment_list})',
883
		array(
884
			'attachment_list' => $attachment_ids,
885
			'id_article' => $id_article,
886
		)
887
	);
888
}
889
890
/**
891
 * Get all attachments associated with an article or set of articles.
892
 *
893
 * What it does:
894
 *  - This does *not* check permissions.
895
 *
896
 * @param int|int[] $articles array of article ids
897
 * @param bool $template if to load some data into context
898
 * @return array
899
 */
900
function sportal_get_articles_attachments($articles, $template = false)
901
{
902
	global $modSettings;
903
904
	$db = database();
905
906
	if (empty($articles))
907
	{
908
		return array();
909
	}
910
911
	if (!is_array($articles))
912
	{
913
		$articles = array($articles);
914
	}
915
916
	$attachments = array();
917
	$request = $db->query('', '
918
		SELECT
919
			a.id_attach, a.id_article, a.filename, a.file_hash, IFNULL(a.size, 0) AS filesize, a.width, a.height,
920
			IFNULL(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height
921
		FROM {db_prefix}sp_attachments AS a
922
			LEFT JOIN {db_prefix}sp_attachments AS thumb ON (thumb.id_attach = a.id_thumb)
923
		WHERE a.id_article IN ({array_int:article_list})
924
			AND a.attachment_type = {int:attachment_type}',
925
		array(
926
			'article_list' => $articles,
927
			'attachment_type' => 0,
928
		)
929
	);
930
	$temp = array();
931
	while ($row = $db->fetch_assoc($request))
932
	{
933
		$temp[$row['id_attach']] = $row;
934
935
		if (!isset($attachments[$row['id_article']]))
936
		{
937
			$attachments[$row['id_article']] = array();
938
		}
939
	}
940
	$db->free_result($request);
941
942
	// This is better than sorting it with the query...
943
	ksort($temp);
944
	foreach ($temp as $id => $row)
945
	{
946
		$attachments[$row['id_article']][$id] = $row;
947
	}
948
949
	if ($template)
950
	{
951
		$return = array();
952
		foreach ($attachments as $aid => $attachment)
953
		{
954
			foreach ($attachment as $current)
955
			{
956
				$return[$aid][] = array(
957
					'name' => htmlspecialchars($current['filename'], ENT_COMPAT),
958
					'size' => $current['filesize'],
959
					'id' => $current['id_attach'],
960
					'approved' => true,
961
					'id_folder' => $modSettings['sp_articles_attachment_dir']
962
				);
963
			}
964
		}
965
966
		return $return;
967
	}
968
969
	return $attachments;
970
}
971
972
/**
973
 * This loads an attachment's contextual data including, most importantly, its size if it is an image.
974
 *
975
 * What it does:
976
 * - It requires the view_attachments permission to calculate image size.
977
 * - It attempts to keep the "aspect ratio" of the posted image in line, even if it has to be resized by
978
 * the max_image_width and max_image_height settings.
979
 *
980
 * @param int $id_article article number to load attachments for
981
 * @return array of attachments
982
 */
983
function sportal_load_attachment_context($id_article)
984
{
985
	global $modSettings, $txt, $scripturl;
986
987
	// Set up the attachment info
988
	$attachments = sportal_get_articles_attachments($id_article);
989
990
	$attachmentData = array();
991
	if (isset($attachments[$id_article]) && !empty($modSettings['attachmentEnable']))
992
	{
993
		foreach ($attachments[$id_article] as $i => $attachment)
994
		{
995
			$attachmentData[$i] = array(
996
				'id' => $attachment['id_attach'],
997
				'name' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($attachment['filename'], ENT_COMPAT)),
998
				'size' => ($attachment['filesize'] < 1024000) ? round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'] : round($attachment['filesize'] / 1024 / 1024, 2) . ' ' . $txt['megabyte'],
999
				'byte_size' => $attachment['filesize'],
1000
				'href' => $scripturl . '?action=portal;sa=spattach;article=' . $id_article . ';attach=' . $attachment['id_attach'],
1001
				'link' => '<a href="' . $scripturl . '?action=portal;sa=spattach;article=' . $id_article . ';attach=' . $attachment['id_attach'] . '">' . htmlspecialchars($attachment['filename'], ENT_COMPAT) . '</a>',
1002
				'is_image' => !empty($attachment['width']) && !empty($attachment['height']) && !empty($modSettings['attachmentShowImages']),
1003
				'file_hash' => $attachment['file_hash'],
1004
				'id_folder' => $modSettings['sp_articles_attachment_dir'],
1005
			);
1006
1007
			if (!$attachmentData[$i]['is_image'])
1008
			{
1009
				continue;
1010
			}
1011
1012
			$attachmentData[$i]['real_width'] = $attachment['width'];
1013
			$attachmentData[$i]['width'] = $attachment['width'];
1014
			$attachmentData[$i]['real_height'] = $attachment['height'];
1015
			$attachmentData[$i]['height'] = $attachment['height'];
1016
1017
			// Let's see, do we want thumbs?
1018
			if (!empty($modSettings['attachmentThumbnails']) && !empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachment['width'] > $modSettings['attachmentThumbWidth'] || $attachment['height'] > $modSettings['attachmentThumbHeight']) && strlen($attachment['filename']) < 249)
1019
			{
1020
				// Only adjust dimensions on successful thumbnail creation.
1021
				if (!empty($attachment['thumb_width']) && !empty($attachment['thumb_height']))
1022
				{
1023
					$attachmentData[$i]['width'] = $attachment['thumb_width'];
1024
					$attachmentData[$i]['height'] = $attachment['thumb_height'];
1025
				}
1026
			}
1027
1028
			if (!empty($attachment['id_thumb']))
1029
			{
1030
				$attachmentData[$i]['thumbnail'] = array(
1031
					'id' => $attachment['id_thumb'],
1032
					'href' => $scripturl . '?action=portal;sa=spattach;article=' . $id_article . ';attach=' . $attachment['id_thumb'] . ';image',
1033
				);
1034
			}
1035
			$attachmentData[$i]['thumbnail']['has_thumb'] = !empty($attachment['id_thumb']);
1036
1037
			// If thumbnails are disabled, check the maximum size of the image.
1038
			if (!$attachmentData[$i]['thumbnail']['has_thumb'] && ((!empty($modSettings['max_image_width']) && $attachment['width'] > $modSettings['max_image_width']) || (!empty($modSettings['max_image_height']) && $attachment['height'] > $modSettings['max_image_height'])))
1039
			{
1040
				if (!empty($modSettings['max_image_width']) && (empty($modSettings['max_image_height']) || $attachment['height'] * $modSettings['max_image_width'] / $attachment['width'] <= $modSettings['max_image_height']))
1041
				{
1042
					$attachmentData[$i]['width'] = $modSettings['max_image_width'];
1043
					$attachmentData[$i]['height'] = floor($attachment['height'] * $modSettings['max_image_width'] / $attachment['width']);
1044
				}
1045
				elseif (!empty($modSettings['max_image_width']))
1046
				{
1047
					$attachmentData[$i]['width'] = floor($attachment['width'] * $modSettings['max_image_height'] / $attachment['height']);
1048
					$attachmentData[$i]['height'] = $modSettings['max_image_height'];
1049
				}
1050
			}
1051
			elseif ($attachmentData[$i]['thumbnail']['has_thumb'])
1052
			{
1053
				// Data attributes for use in expandThumb
1054
				$attachmentData[$i]['thumbnail']['lightbox'] = 'data-lightboxmessage="' . $id_article . '" data-lightboximage="' . $attachment['id_attach'] . '"';
1055
1056
				// If the image is too large to show inline, make it a popup.
1057
				$attachmentData[$i]['thumbnail']['javascript'] = 'return expandThumb(' . $attachment['id_attach'] . ');';
1058
			}
1059
		}
1060
	}
1061
1062
	return $attachmentData;
1063
}
1064
1065
/**
1066
 * Get an attachment associated with an article, part of spattach.
1067
 *
1068
 * @param int $article the article id
1069
 * @param int $attach the attachment id
1070
 * @return array
1071
 */
1072
function sportal_get_attachment_from_article($article, $attach)
1073
{
1074
	$db = database();
1075
1076
	// Make sure this attachment is part of this article
1077
	$request = $db->query('', '
1078
		SELECT 
1079
			a.filename, a.file_hash, a.fileext, a.id_attach, a.attachment_type, a.mime_type, a.width, a.height
1080
		FROM {db_prefix}sp_attachments AS a
1081
		WHERE a.id_attach = {int:attach}
1082
			AND a.id_article = {int:article}
1083
		LIMIT 1',
1084
		array(
1085
			'attach' => $attach,
1086
			'article' => $article,
1087
		)
1088
	);
1089
	$attachmentData = array();
1090
	if ($db->num_rows($request) != 0)
1091
	{
1092
		$attachmentData = $db->fetch_row($request);
1093
	}
1094
	$db->free_result($request);
1095
1096
	return $attachmentData;
1097
}
1098
1099
function sportal_get_attachment_thumb_from_article($article, $attach)
1100
{
1101
	$db = database();
1102
1103
	require_once(SUBSDIR . '/Attachments.subs.php');
1104
1105
	// Make sure this attachment is with this article.
1106
	$request = $db->query('', '
1107
		SELECT 
1108
			th.filename, th.file_hash, th.fileext, th.id_attach, th.id_thumb, th.attachment_type, th.mime_type, th.width, th.height,
1109
			a.filename AS attach_filename, a.file_hash AS attach_file_hash, a.fileext AS attach_fileext,
1110
			a.id_attach AS attach_id_attach, a.id_thumb AS attach_id_thumb, a.attachment_type AS attach_attachment_type, 
1111
			a.mime_type AS attach_mime_type, a.width AS attach_width, a.height AS attach_height
1112
		FROM {db_prefix}sp_attachments AS a
1113
			LEFT JOIN {db_prefix}sp_attachments AS th ON (th.id_attach = a.id_thumb)
1114
		WHERE a.id_attach = {int:attach}',
1115
		array(
1116
			'attach' => $attach,
1117
			'article' => $article,
1118
		)
1119
	);
1120
	$attachmentData = array_fill(0, 8, '');
1121
	if ($db->num_rows($request) != 0)
1122
	{
1123
		$fetch = $db->fetch_assoc($request);
1124
1125
		// If there is a hash then the thumbnail exists
1126
		if (!empty($fetch['file_hash']))
1127
		{
1128
			$attachmentData = array(
1129
				$fetch['filename'],
1130
				$fetch['file_hash'],
1131
				$fetch['fileext'],
1132
				$fetch['id_attach'],
1133
				$fetch['attachment_type'],
1134
				$fetch['mime_type'],
1135
				$fetch['width'],
1136
				$fetch['height'],
1137
			);
1138
		}
1139
		// otherwise $modSettings['attachmentThumbnails'] may be (or was) off, so original file
1140
		elseif (getValidMimeImageType($fetch['attach_mime_type']) !== '')
1141
		{
1142
			$attachmentData = array(
1143
				$fetch['attach_filename'],
1144
				$fetch['attach_file_hash'],
1145
				$fetch['attach_fileext'],
1146
				$fetch['attach_id_attach'],
1147
				$fetch['attach_attachment_type'],
1148
				$fetch['attach_mime_type'],
1149
				$fetch['attach_width'],
1150
				$fetch['attach_height'],
1151
			);
1152
		}
1153
	}
1154
	$db->free_result($request);
1155
1156
	return $attachmentData;
1157
}
1158
1159
/**
1160
 * Returns if the given attachment ID is an image file or not
1161
 *
1162
 * What it does:
1163
 *
1164
 * - Given an attachment id, checks that it exists as an attachment
1165
 * - Verifies the message its associated is on a board the user can see
1166
 * - Sets 'is_image' if the attachment is an image file
1167
 * - Returns basic attachment values
1168
 *
1169
 * @package Attachments
1170
 * @param int $id_attach
1171
 *
1172
 * @returns array|boolean
1173
 */
1174
function isArticleAttachmentImage($id_attach)
1175
{
1176
	$db = database();
1177
1178
	// Make sure this attachment is on this board.
1179
	$request = $db->query('', '
1180
		SELECT
1181
			a.filename, a.fileext, a.id_attach, a.attachment_type, a.mime_type, a.size, a.width, a.height
1182
		FROM {db_prefix}sp_attachments as a
1183
		WHERE id_attach = {int:attach}
1184
			AND attachment_type = {int:type}
1185
		LIMIT 1',
1186
		array(
1187
			'attach' => $id_attach,
1188
			'type' => 0,
1189
		)
1190
	);
1191
	$attachmentData = array();
1192
	if ($db->num_rows($request) != 0)
1193
	{
1194
		$attachmentData = $db->fetch_assoc($request);
1195
		$attachmentData['is_image'] = substr($attachmentData['mime_type'], 0, 5) === 'image';
1196
		$attachmentData['size'] = byte_format($attachmentData['size']);
1197
	}
1198
	$db->free_result($request);
1199
1200
	return !empty($attachmentData) ? $attachmentData : false;
1201
}
1202
1203
/**
1204
 * Get attachment count for a article or group of articles
1205
 *
1206
 * @param int|int[] $articles the article id
1207
 * @return array
1208
 */
1209
function sportal_article_has_attachments($articles)
1210
{
1211
	$db = database();
1212
1213
	if (empty($articles))
1214
	{
1215
		return array();
1216
	}
1217
1218
	if (!is_array($articles))
1219
	{
1220
		$articles = (array) $articles;
1221
	}
1222
1223
	// Make sure this attachment is part of this article
1224
	$request = $db->query('', '
1225
		SELECT 
1226
			id_article, COUNT(id_attach) AS number
1227
		FROM {db_prefix}sp_attachments
1228
		WHERE id_article IN ({array_int:articles})
1229
		GROUP BY id_article',
1230
		array(
1231
			'articles' => $articles,
1232
		)
1233
	);
1234
	$attachments = array();
1235
	while ($row = $db->fetch_assoc($request))
1236
	{
1237
		$attachments[$row['id_article']] = $row['number'];
1238
	}
1239
	$db->free_result($request);
1240
1241
	return $attachments;
1242
}
1243
1244
/**
1245
 * Load the first available attachment in an article (if any) for a group of articles
1246
 *
1247
 * - Does not check permission, assumes article access has been vetted
1248
 *
1249
 * @param array $articles array as loaded by sportal_get_articles();
1250
 */
1251
function getBlogAttachments($articles)
1252
{
1253
	// Just ones with attachments
1254
	$getAttachments = array();
1255
	foreach ($articles as $id_article => $article)
1256
	{
1257
		if (!empty($article['has_attachments']))
1258
		{
1259
			$getAttachments[] = $id_article;
1260
		}
1261
	}
1262
1263
	// For each article, grab the first *image* attachment
1264
	$attachments = sportal_get_articles_attachments($getAttachments);
1265
	foreach ($attachments as $id_article => $attach)
1266
	{
1267
		if (!isset($articles[$id_article]['attachments']))
1268
		{
1269
			foreach ($attach as $val)
1270
			{
1271
				$is_image = !empty($val['width']) && !empty($val['height']);
1272
				if ($is_image)
1273
				{
1274
					$articles[$id_article]['attachments'] = $val;
1275
					break;
1276
				}
1277
			}
1278
		}
1279
	}
1280
1281
	return $articles;
1282
}
1283
1284
/**
1285
 * Load the blog level attachment details
1286
 *
1287
 * - If the article was found to have attachments (via getBlogAttachments) then
1288
 * it will load that attachment data for use in a template
1289
 * - If the message did not have attachments, it is then searched for the first
1290
 * bbc IMG tag, and that image is used.
1291
 *
1292
 * @param array $articles as setup by getBlogAttachments();
1293
 */
1294
function setBlogAttachments($articles)
1295
{
1296
	global $scripturl;
1297
1298
	foreach ($articles as $id_article => $article)
1299
	{
1300
		if (!empty($article['attachments']))
1301
		{
1302
			$attachment = $article['attachments'];
1303
			$articles[$id_article]['attachments'] += array(
1304
				'id' => $attachment['id_attach'],
1305
				'href' => $scripturl . '?action=portal;sa=spattach;article=' . $id_article . ';attach=' . $attachment['id_attach'],
1306
				'link' => '<a href="' . $scripturl . '?action=portal;sa=spattach;article=' . $id_article . ';attach=' . $attachment['id_attach'] . '">' . htmlspecialchars($attachment['filename'], ENT_COMPAT, 'UTF-8') . '</a>',
1307
				'name' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($attachment['filename'], ENT_COMPAT, 'UTF-8')),
1308
			);
1309
		}
1310
		// No attachments, perhaps an IMG tag then?
1311
		else
1312
		{
1313
			$body = $article['body'];
1314
			$pos = strpos($body, '[img');
1315
			if ($pos !== false)
1316
			{
1317
				$img_tag = substr($body, $pos, strpos($body, '[/img]', $pos) + 6);
1318
				$parser = ParserWrapper::instance();
1319
				$img_html = $parser->parseMessage($img_tag, true);
1320
				$articles[$id_article]['body'] = str_replace($img_tag, '<div class="sp_attachment_thumb">' . $img_html . '</div>', $body);
1321
			}
1322
		}
1323
	}
1324
1325
	return $articles;
1326
}