Passed
Push — myDevel ( 8d5f03...2f2dcc )
by Spuds
03:48
created

bindArticleAttachments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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