Issues (1686)

sources/subs/Smileys.subs.php (4 issues)

1
<?php
2
3
/**
4
 * Database and support functions for adding, moving, saving smileys
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 dev
14
 *
15
 */
16
17
use ElkArte\Emoji;
18
use ElkArte\Helper\FileFunctions;
19
20
/**
21
 * Validates, if a smiley already exists
22
 *
23
 * @param string[] $smileys
24
 * @return array
25
 */
26
function smileyExists($smileys)
27
{
28
	$db = database();
29
30
	$found = [];
31
32
	if (empty($smileys))
33
	{
34
		return $found;
35
	}
36
37
	$db->fetchQuery('
38
		SELECT 
39
			filename
40
		FROM {db_prefix}smileys
41
		WHERE filename IN ({array_string:smiley_list})',
42
		[
43
			'smiley_list' => $smileys,
44
		]
45
	)->fetch_callback(
46
		function ($row) use (&$found) {
47
			$found[] = $row['filename'];
48
		}
49
	);
50
51
	return $found;
52
}
53
54
/**
55
 * Validates duplicate smileys
56
 *
57
 * @param string $code
58
 * @param string|null $current
59
 * @return bool
60
 */
61
function validateDuplicateSmiley($code, $current = null)
62
{
63
	$db = database();
64
65
	return $db->fetchQuery('
66
		SELECT 
67
			id_smiley
68
		FROM {db_prefix}smileys
69
		WHERE code = {string_case_sensitive:smiley_code}' . (!isset($current) ? '' : '
70
			AND id_smiley != {int:current_smiley}'),
71
		[
72
			'current_smiley' => $current,
73
			'smiley_code' => $code,
74
		]
75
	)->num_rows() > 0;
76
}
77
78
/**
79
 * Request the next location for a new smiley
80
 *
81
 * @param string $location
82
 *
83
 * @return int
84
 */
85
function nextSmileyLocation($location)
86
{
87
	$db = database();
88
89
	$request = $db->fetchQuery('
90
		SELECT 
91
			MAX(smiley_order) + 1
92
		FROM {db_prefix}smileys
93
		WHERE hidden = {int:smiley_location}
94
			AND smiley_row = {int:first_row}',
95
		[
96
			'smiley_location' => $location,
97
			'first_row' => 0,
98
		]
99
	);
100
	list ($smiley_order) = $request->fetch_row();
101
	$request->free_result();
102
103
	return $smiley_order;
104
}
105
106
/**
107
 * Adds a smiley to the database
108
 *
109
 * @param array $param associative array to use in the insert
110
 */
111
function addSmiley($param)
112
{
113
	$db = database();
114
115
	$db->insert('',
116
		'{db_prefix}smileys',
117
		[
118
			'code' => 'string-30', 'filename' => 'string-48', 'description' => 'string-80',
119
			'hidden' => 'int', 'smiley_order' => 'int',
120
		],
121
		$param,
122
		['id_smiley']
123
	);
124
}
125
126
/**
127
 * Deletes smileys.
128
 *
129
 * @param int[] $smileys
130
 */
131
function deleteSmileys($smileys)
132
{
133
	$db = database();
134
135
	$db->query('', '
136
		DELETE FROM {db_prefix}smileys
137
		WHERE id_smiley IN ({array_int:checked_smileys})',
138
		[
139
			'checked_smileys' => $smileys,
140
		]
141
	);
142
}
143
144
/**
145
 * Changes the display type of given smileys.
146
 *
147
 * @param int[] $smileys
148
 * @param int $display_type
149
 */
150
function updateSmileyDisplayType($smileys, $display_type)
151
{
152
	$db = database();
153
154
	$db->query('', '
155
		UPDATE {db_prefix}smileys
156
		SET 
157
			hidden = {int:display_type}
158
		WHERE id_smiley IN ({array_int:checked_smileys})',
159
		[
160
			'checked_smileys' => $smileys,
161
			'display_type' => $display_type,
162
		]
163
	);
164
}
165
166
/**
167
 * Updates a smiley.
168
 *
169
 * @param array $param
170
 */
171
function updateSmiley($param)
172
{
173
	$db = database();
174
175
	$db->query('', '
176
		UPDATE {db_prefix}smileys
177
		SET
178
			code = {string:smiley_code},
179
			filename = {string:smiley_filename},
180
			description = {string:smiley_description},
181
			hidden = {int:smiley_location}
182
		WHERE id_smiley = {int:current_smiley}',
183
		[
184
			'smiley_location' => $param['smiley_location'],
185
			'current_smiley' => $param['smiley'],
186
			'smiley_code' => $param['smiley_code'],
187
			'smiley_filename' => $param['smiley_filename'],
188
			'smiley_description' => $param['smiley_description'],
189
		]
190
	);
191
}
192
193
/**
194
 * Get detailed smiley information
195
 *
196
 * @param int $id
197
 *
198
 * @return array
199
 * @throws \ElkArte\Exceptions\Exception smiley_not_found
200
 */
201
function getSmiley($id)
202
{
203
	$db = database();
204
205
	$current_smiley = [];
206
	$db->fetchQuery('
207
		SELECT 
208
			id_smiley AS id, code, filename, description, hidden AS location, 0 AS is_new, smiley_row
209
		FROM {db_prefix}smileys
210
		WHERE id_smiley = {int:current_smiley}',
211
		[
212
			'current_smiley' => $id,
213
		]
214
	)->fetch_callback(
215
		function ($row) use (&$current_smiley)
216
		{
217
			$current_smiley = [
218
				'id' => $row['id'],
219
				'code' => $row['code'],
220
				'filename' => pathinfo($row['filename'], PATHINFO_FILENAME),
221
				'description' => $row['description'],
222
				'row' => $row['smiley_row'],
223
				'is_new' => 0,
224
				'location' => $row['location']
225
			];
226
		}
227
	);
228
229
	if (empty($current_smiley))
230
	{
231
		throw new \ElkArte\Exceptions\Exception('smiley_not_found');
232
	}
233
234
	return $current_smiley;
235
}
236
237
/**
238
 * Get the position from a given smiley
239
 *
240
 * @param int $location
241
 * @param int $id
242
 *
243
 * @return array
244
 */
245
function getSmileyPosition($location, $id)
246
{
247
	$db = database();
248
249
	$smiley = [];
250
251
	$request = $db->query('', '
252
		SELECT 
253
			smiley_row, smiley_order, hidden
254
		FROM {db_prefix}smileys
255
		WHERE hidden = {int:location}
256
			AND id_smiley = {int:id_smiley}',
257
		[
258
			'location' => $location,
259
			'id_smiley' => $id,
260
		]
261
	);
262
	list ($smiley['row'], $smiley['order'], $smiley['location']) = $request->fetch_row();
263
	$request->free_result();
264
265
	return $smiley;
266
}
267
268
/**
269
 * Move a smiley to their new position.
270
 *
271
 * @param int[] $smiley
272
 * @param int $source
273
 */
274
function moveSmileyPosition($smiley, $source)
275
{
276
	$db = database();
277
278
	$db->query('', '
279
		UPDATE {db_prefix}smileys
280
		SET 
281
			smiley_order = smiley_order + 1
282
		WHERE hidden = {int:new_location}
283
			AND smiley_row = {int:smiley_row}
284
			AND smiley_order > {int:smiley_order}',
285
		[
286
			'new_location' => $smiley['location'],
287
			'smiley_row' => $smiley['row'],
288
			'smiley_order' => $smiley['order'],
289
		]
290
	);
291
292
	$db->query('', '
293
		UPDATE {db_prefix}smileys
294
		SET
295
			smiley_order = {int:smiley_order} + 1,
296
			smiley_row = {int:smiley_row},
297
			hidden = {int:new_location}
298
		WHERE id_smiley = {int:current_smiley}',
299
		[
300
			'smiley_order' => $smiley['order'],
301
			'smiley_row' => $smiley['row'],
302
			'new_location' => $smiley['location'],
303
			'current_smiley' => $source,
304
		]
305
	);
306
}
307
308
/**
309
 * Change the row of a given smiley.
310
 *
311
 * @param int $id
312
 * @param int $row
313
 * @param string $location
314
 */
315
function updateSmileyRow($id, $row, $location)
316
{
317
	$db = database();
318
319
	$db->query('', '
320
		UPDATE {db_prefix}smileys
321
		SET 
322
			smiley_row = {int:new_row}
323
		WHERE smiley_row = {int:current_row}
324
			AND hidden = {int:location}',
325
		[
326
			'new_row' => $id,
327
			'current_row' => $row,
328
			'location' => $location === 'postform' ? '0' : '2',
329
		]
330
	);
331
}
332
333
/**
334
 * Set an new order for the given smiley.
335
 *
336
 * @param int $id
337
 * @param int $order
338
 */
339
function updateSmileyOrder($id, $order)
340
{
341
	$db = database();
342
343
	$db->query('', '
344
		UPDATE {db_prefix}smileys
345
		SET 
346
			smiley_order = {int:new_order}
347
		WHERE id_smiley = {int:current_smiley}',
348
		[
349
			'new_order' => $order,
350
			'current_smiley' => $id,
351
		]
352
	);
353
}
354
355
/**
356
 * Get a list of all visible smileys.
357
 *
358
 * hidden = 0 is post form, 1 is hidden, 2 is popup,
359
 */
360
function getSmileys()
361
{
362
	$db = database();
363
364
	$smileys = [
365
		'postform' => [
366
			'rows' => [],
367
		],
368
		'popup' => [
369
			'rows' => [],
370
		],
371
	];
372
373
	$db->fetchQuery('
374
		SELECT 
375
			id_smiley, code, filename, description, smiley_row, smiley_order, hidden
376
		FROM {db_prefix}smileys
377
		WHERE hidden != {int:hidden}
378
		ORDER BY smiley_order, smiley_row',
379
		[
380
			'hidden' => 1,
381
		]
382
	)->fetch_callback(
383
		function ($row) use (&$smileys) {
384
			global $context;
385
386
			$location = empty($row['hidden']) ? 'postform' : 'popup';
387
			$filename = pathinfo($row['filename'], PATHINFO_FILENAME) . '.' . $context['smiley_extension'];
0 ignored issues
show
Are you sure pathinfo($row['filename'], PATHINFO_FILENAME) of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

387
			$filename = /** @scrutinizer ignore-type */ pathinfo($row['filename'], PATHINFO_FILENAME) . '.' . $context['smiley_extension'];
Loading history...
388
			if (possibleSmileEmoji($row))
389
			{
390
				$filename = $row['emoji'] . '.svg';
391
			}
392
393
			$smileys[$location]['rows'][$row['smiley_row']][] = [
394
				'id' => $row['id_smiley'],
395
				'code' => htmlspecialchars($row['code'], ENT_COMPAT, 'UTF-8'),
396
				'filename' => htmlspecialchars($filename, ENT_COMPAT, 'UTF-8'),
397
				'description' => htmlspecialchars($row['description'], ENT_COMPAT, 'UTF-8'),
398
				'row' => $row['smiley_row'],
399
				'order' => $row['smiley_order'],
400
				'selected' => !empty($_REQUEST['move']) && $_REQUEST['move'] == $row['id_smiley'],
401
				'emoji' => $row['emoji'] ?? null,
402
			];
403
		}
404
	);
405
406
	return $smileys;
407
}
408
409
/**
410
 * Validates, if a smiley set was properly installed.
411
 *
412
 * @param string $set name of smiley set to check
413
 * @return bool
414
 */
415
function isSmileySetInstalled($set)
416
{
417
	$db = database();
418
419
	return $db->fetchQuery('
420
		SELECT 
421
			version, themes_installed, db_changes
422
		FROM {db_prefix}log_packages
423
		WHERE package_id = {string:current_package}
424
			AND install_state != {int:not_installed}
425
		ORDER BY time_installed DESC
426
		LIMIT 1',
427
		[
428
			'not_installed' => 0,
429
			'current_package' => $set,
430
		]
431
	)->num_rows() <= 0;
432
}
433
434
/**
435
 * Logs the installation of a new smiley set.
436
 *
437
 * @param array $param
438
 */
439
function logPackageInstall($param)
440
{
441
	$db = database();
442
443
	$db->insert('',
444
		'{db_prefix}log_packages',
445
		[
446
			'filename' => 'string', 'name' => 'string', 'package_id' => 'string', 'version' => 'string',
447
			'id_member_installed' => 'int', 'member_installed' => 'string', 'time_installed' => 'int',
448
			'install_state' => 'int', 'failed_steps' => 'string', 'themes_installed' => 'string',
449
			'member_removed' => 'int', 'db_changes' => 'string', 'credits' => 'string',
450
		],
451
		[
452
			$param['filename'], $param['name'], $param['package_id'], $param['version'],
453
			$param['id_member'], $param['member_name'], time(),
454
			1, '', '',
455
			0, '', $param['credits_tag'],
456
		],
457
		['id_install']
458
	);
459
}
460
461
/**
462
 * Get the last smiley_order from the first smileys row.
463
 *
464
 * @return string
465
 */
466
function getMaxSmileyOrder()
467
{
468
	$db = database();
469
470
	$request = $db->query('', '
471
		SELECT 
472
			MAX(smiley_order)
473
		FROM {db_prefix}smileys
474
		WHERE hidden = {int:postform}
475
			AND smiley_row = {int:first_row}',
476
		[
477
			'postform' => 0,
478
			'first_row' => 0,
479
		]
480
	);
481
	list ($smiley_order) = $request->fetch_row();
482
	$request->free_result();
483
484
	return $smiley_order;
485
}
486
487
/**
488
 * Callback function for createList().
489
 * Lists all smiley sets.
490
 *
491
 * @param int $start The item to start with (for pagination purposes)
492
 * @param int $items_per_page The number of items to show per page
493
 * @param string $sort A string indicating how to sort the results
494
 *
495
 * @return array
496
 */
497
function list_getSmileySets($start, $items_per_page, $sort)
0 ignored issues
show
The parameter $items_per_page is not used and could be removed. ( Ignorable by Annotation )

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

497
function list_getSmileySets($start, /** @scrutinizer ignore-unused */ $items_per_page, $sort)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $start is not used and could be removed. ( Ignorable by Annotation )

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

497
function list_getSmileySets(/** @scrutinizer ignore-unused */ $start, $items_per_page, $sort)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
498
{
499
	global $modSettings;
500
501
	$known_sets = explode(',', $modSettings['smiley_sets_known']);
502
	$set_names = explode("\n", $modSettings['smiley_sets_names']);
503
	$set_exts = explode(',', $modSettings['smiley_sets_extensions']);
504
505
	$cols = [
506
		'id' => [],
507
		'selected' => [],
508
		'path' => [],
509
		'name' => [],
510
	];
511
512
	foreach ($known_sets as $i => $set)
513
	{
514
		$cols['id'][] = $i;
515
		$cols['selected'][] = $i;
516
		$cols['path'][] = $set;
517
		$cols['name'][] = stripslashes($set_names[$i]);
518
		$cols['ext'][] = $set_exts[$i];
519
	}
520
521
	$sort_flag = strpos($sort, 'DESC') === false ? SORT_ASC : SORT_DESC;
522
523
	if (strpos($sort, 'name') === 0)
524
	{
525
		array_multisort($cols['name'], $sort_flag, SORT_REGULAR, $cols['path'], $cols['selected'], $cols['id'], $cols['ext']);
0 ignored issues
show
SORT_REGULAR cannot be passed to array_multisort() as the parameter $rest expects a reference. ( Ignorable by Annotation )

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

525
		array_multisort($cols['name'], $sort_flag, /** @scrutinizer ignore-type */ SORT_REGULAR, $cols['path'], $cols['selected'], $cols['id'], $cols['ext']);
Loading history...
526
	}
527
	elseif (strpos($sort, 'ext') === 0)
528
	{
529
		array_multisort($cols['ext'], $sort_flag, SORT_REGULAR, $cols['name'], $cols['selected'], $cols['id'], $cols['path']);
530
	}
531
	elseif (strpos($sort, 'path') === 0)
532
	{
533
		array_multisort($cols['path'], $sort_flag, SORT_REGULAR, $cols['name'], $cols['selected'], $cols['id'], $cols['ext']);
534
	}
535
	else
536
	{
537
		array_multisort($cols['selected'], $sort_flag, SORT_REGULAR, $cols['path'], $cols['name'], $cols['id'], $cols['ext']);
538
	}
539
540
	$smiley_sets = [];
541
	foreach ($cols['id'] as $i => $id)
542
	{
543
		$smiley_sets[] = [
544
			'id' => $id,
545
			'path' => $cols['path'][$i],
546
			'name' => $cols['name'][$i],
547
			'ext' => $cols['ext'][$i],
548
			'selected' => $cols['path'][$i] === $modSettings['smiley_sets_default']
549
		];
550
	}
551
552
	return $smiley_sets;
553
}
554
555
/**
556
 * Callback function for createList().
557
 */
558
function list_getNumSmileySets()
559
{
560
	global $modSettings;
561
562
	return count(explode(',', $modSettings['smiley_sets_known']));
563
}
564
565
/**
566
 * Callback function for createList().
567
 *
568
 * @param int $start The item to start with (for pagination purposes)
569
 * @param int $items_per_page The number of items to show per page
570
 * @param string $sort A string indicating how to sort the results
571
 *
572
 * @return array
573
 */
574
function list_getSmileys($start, $items_per_page, $sort)
575
{
576
	$db = database();
577
578
	$result = [];
579
	$db->fetchQuery('
580
		SELECT 
581
			id_smiley, code, filename, description, smiley_row, smiley_order, hidden
582
		FROM {db_prefix}smileys
583
		ORDER BY ' . $sort . '
584
		LIMIT ' . $items_per_page . '  OFFSET ' . $start,
585
		[]
586
	)->fetch_callback(
587
		function($row) use(&$result) {
588
			$result[] = [
589
				'id_smiley' => $row['id_smiley'],
590
				'code' => $row['code'],
591
				'filename' => pathinfo($row['filename'], PATHINFO_FILENAME),
592
				'description' => $row['description'],
593
				'smiley_row' => $row['smiley_row'],
594
				'smiley_order' => $row['smiley_order'],
595
				'hidden' => $row['hidden'],
596
			];
597
		}
598
	);
599
600
	return $result;
601
}
602
603
/**
604
 * Callback function for createList().
605
 */
606
function list_getNumSmileys()
607
{
608
	$db = database();
609
610
	$request = $db->query('', '
611
		SELECT 
612
			COUNT(*)
613
		FROM {db_prefix}smileys',
614
		[]
615
	);
616
	list ($numSmileys) = $request->fetch_row();
617
	$request->free_result();
618
619
	return $numSmileys;
620
}
621
622
/**
623
 * Reads all smiley directories, and sets the image type for the set(s).  Saves this information
624
 * in modSettings.
625
 *
626
 * @return string a csv string in the same order as smiley_sets_known
627
 */
628
function setSmileyExtensionArray()
629
{
630
	global $modSettings;
631
632
	$smiley_types =  ['jpg', 'gif', 'jpeg', 'png', 'webp', 'svg'];
633
	$smileys_dir = empty($modSettings['smileys_dir']) ? BOARDDIR . '/smileys' : $modSettings['smileys_dir'];
634
	$fileFunc = FileFunctions::instance();
635
	$extensionTypes = [];
636
637
	$smiley_sets_known = explode(',', $modSettings['smiley_sets_known']);
638
	foreach ($smiley_sets_known as $set)
639
	{
640
		$smiles = $fileFunc->listTree($smileys_dir . '/' . $set);
641
642
		// What type of set is this, svg, gif, png
643
		foreach ($smiles as $smile)
644
		{
645
			$temp = pathinfo($smile['filename'], PATHINFO_EXTENSION);
646
			if (in_array($temp, $smiley_types, true))
647
			{
648
				$extensionTypes[] = $temp;
649
				break;
650
			}
651
		}
652
	}
653
654
	$extensionTypes = implode(',', $extensionTypes);
655
	updateSettings(['smiley_sets_extensions' => $extensionTypes]);
656
657
	return $extensionTypes;
658
}
659
660
/**
661
 * Fetch and prepare the smileys for use in the post editor
662
 *
663
 * What it does:
664
 * - Old smiles as :) are processed as normal, requiring an image file in its smile set directory
665
 * - Emoji :smile:
666
 *   - first checked if the image file exists in the smile set directory
667
 *   - if not found it will use the emoji class to check if the code exists in the emoji code list and if found
668
 * the image will be set appropriately and a flag set to indicate an emoji, not smile, image
669
 *   - if not found treated as a missing image
670
 *   - Does not process any defined with hidden = 1 (hidden / custom)
671
 *
672
 * @return array composed of smiley location and row in that location
673
 */
674
function getEditorSmileys()
675
{
676
	global $context;
677
678
	$db = database();
679
680
	$smileys = [];
681
682
	$db->fetchQuery('
683
		SELECT 
684
			code, filename, description, smiley_row, hidden
685
		FROM {db_prefix}smileys
686
		WHERE hidden IN (0, 2)
687
		ORDER BY smiley_row, smiley_order',
688
		[]
689
	)->fetch_callback(
690
		function ($row) use (&$smileys, $context) {
691
			$filename = $row['filename'] . '.' . $context['smiley_extension'];
692
			if (possibleSmileEmoji($row))
693
			{
694
				$filename = $row['emoji'] . '.svg';
695
			}
696
697
			$row['description'] = htmlspecialchars($row['description'], ENT_COMPAT, 'UTF-8');
698
			$row['filename'] = htmlspecialchars($filename, ENT_COMPAT, 'UTF-8');
699
700
			$smileys[empty($row['hidden']) ? 'postform' : 'popup'][$row['smiley_row']]['smileys'][] = $row;
701
		}
702
	);
703
704
	return $smileys;
705
}
706
707
/**
708
 * Checks if a defined smiley code as :smile: exists
709
 *
710
 * What it does:
711
 * - Looks in the smile directory for example, smile.png.
712
 * - If not found, checks if :smile: is a legitimate emoji short code.
713
 * - If so, sets an ['emoji'] row to the proper utf8 value.
714
 *
715
 * @param array $row
716
 * @param string $path
717
 * @param string $ext
718
 * @return bool if the code is a legitimate emoji short code and no image exists in the smile/smile_set directory
719
 */
720
function possibleSmileEmoji(&$row, $path = null, $ext = null)
721
{
722
	global $context;
723
724
	$ext = $ext ?? $context['smiley_extension'];
725
	$path = $path ?? $context['smiley_dir'];
726
	$path = rtrim($path, '/\\') . DIRECTORY_SEPARATOR;
727
728
	// At least 4 characters long, starts and ends with :  -- Marginally faster than preg_match
729
	$possibleEmoji = isset($row['code'][3]) && $row['code'][0] === ':' && substr($row['code'], -1, 1) === ':';
730
731
	// If this is possibly an emoji and the image does not exist in the smile set
732
	if ($possibleEmoji && !FileFunctions::instance()->fileExists($path . $row['filename'] . '.' . $ext))
733
	{
734
		$emoji = Emoji::instance();
735
736
		// Check if we have an emoji image for this smiley code
737
		$test = preg_replace_callback('~(:([-+\w]+):)~u', [$emoji, 'emojiToImage'], $row['code']);
738
		if ($test !== $row['filename'] && preg_match('~data-emoji-code=["\'](.*?)["\']~', $test, $result))
739
		{
740
			// Valid emoji, set the filename to the proper emoji file and type
741
			$row['emoji'] =  $result[1];
742
			return true;
743
		}
744
	}
745
746
	return false;
747
}
748